@maestro_io/maestro-web-sdk 5.1.2 → 5.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/1.maestro-web-sdk.esm.js +1 -1
- package/dist/1.maestro-web-sdk.esm.js.map +1 -1
- package/dist/10.maestro-web-sdk.esm.js +1 -1
- package/dist/10.maestro-web-sdk.esm.js.map +1 -1
- package/dist/11.maestro-web-sdk.esm.js +1 -1
- package/dist/11.maestro-web-sdk.esm.js.map +1 -1
- package/dist/12.maestro-web-sdk.esm.js +1 -1
- package/dist/12.maestro-web-sdk.esm.js.map +1 -1
- package/dist/13.maestro-web-sdk.esm.js +1 -1
- package/dist/13.maestro-web-sdk.esm.js.map +1 -1
- package/dist/14.maestro-web-sdk.esm.js +1 -1
- package/dist/14.maestro-web-sdk.esm.js.map +1 -1
- package/dist/15.maestro-web-sdk.esm.js +1 -1
- package/dist/15.maestro-web-sdk.esm.js.map +1 -1
- package/dist/16.maestro-web-sdk.esm.js +1 -1
- package/dist/16.maestro-web-sdk.esm.js.map +1 -1
- package/dist/17.maestro-web-sdk.esm.js +1 -1
- package/dist/17.maestro-web-sdk.esm.js.map +1 -1
- package/dist/18.maestro-web-sdk.esm.js +1 -1
- package/dist/18.maestro-web-sdk.esm.js.map +1 -1
- package/dist/19.maestro-web-sdk.esm.js +1 -1
- package/dist/19.maestro-web-sdk.esm.js.map +1 -1
- package/dist/2.maestro-web-sdk.esm.js +1 -1
- package/dist/20.maestro-web-sdk.esm.js +1 -1
- package/dist/20.maestro-web-sdk.esm.js.map +1 -1
- package/dist/21.maestro-web-sdk.esm.js +1 -1
- package/dist/21.maestro-web-sdk.esm.js.map +1 -1
- package/dist/22.maestro-web-sdk.esm.js +1 -1
- package/dist/22.maestro-web-sdk.esm.js.map +1 -1
- package/dist/23.maestro-web-sdk.esm.js +1 -1
- package/dist/23.maestro-web-sdk.esm.js.map +1 -1
- package/dist/24.maestro-web-sdk.esm.js +1 -1
- package/dist/24.maestro-web-sdk.esm.js.map +1 -1
- package/dist/25.maestro-web-sdk.esm.js +1 -1
- package/dist/25.maestro-web-sdk.esm.js.map +1 -1
- package/dist/26.maestro-web-sdk.esm.js +1 -1
- package/dist/26.maestro-web-sdk.esm.js.map +1 -1
- package/dist/27.maestro-web-sdk.esm.js +1 -1
- package/dist/27.maestro-web-sdk.esm.js.map +1 -1
- package/dist/28.maestro-web-sdk.esm.js +1 -1
- package/dist/28.maestro-web-sdk.esm.js.map +1 -1
- package/dist/29.maestro-web-sdk.esm.js +1 -1
- package/dist/29.maestro-web-sdk.esm.js.map +1 -1
- package/dist/3.maestro-web-sdk.esm.js +1 -1
- package/dist/3.maestro-web-sdk.esm.js.map +1 -1
- package/dist/30.maestro-web-sdk.esm.js +1 -1
- package/dist/30.maestro-web-sdk.esm.js.map +1 -1
- package/dist/31.maestro-web-sdk.esm.js +1 -1
- package/dist/31.maestro-web-sdk.esm.js.map +1 -1
- package/dist/32.maestro-web-sdk.esm.js +1 -1
- package/dist/32.maestro-web-sdk.esm.js.map +1 -1
- package/dist/33.maestro-web-sdk.esm.js +1 -1
- package/dist/33.maestro-web-sdk.esm.js.map +1 -1
- package/dist/34.maestro-web-sdk.esm.js +1 -1
- package/dist/34.maestro-web-sdk.esm.js.map +1 -1
- package/dist/35.maestro-web-sdk.esm.js +1 -1
- package/dist/35.maestro-web-sdk.esm.js.map +1 -1
- package/dist/36.maestro-web-sdk.esm.js +1 -1
- package/dist/36.maestro-web-sdk.esm.js.map +1 -1
- package/dist/37.maestro-web-sdk.esm.js +1 -1
- package/dist/37.maestro-web-sdk.esm.js.map +1 -1
- package/dist/38.maestro-web-sdk.esm.js +1 -1
- package/dist/38.maestro-web-sdk.esm.js.map +1 -1
- package/dist/39.maestro-web-sdk.esm.js +2 -0
- package/dist/39.maestro-web-sdk.esm.js.map +1 -0
- package/dist/4.maestro-web-sdk.esm.js +1 -1
- package/dist/4.maestro-web-sdk.esm.js.map +1 -1
- package/dist/40.maestro-web-sdk.esm.js +2 -0
- package/dist/40.maestro-web-sdk.esm.js.map +1 -0
- package/dist/5.maestro-web-sdk.esm.js +1 -1
- package/dist/5.maestro-web-sdk.esm.js.map +1 -1
- package/dist/6.maestro-web-sdk.esm.js +1 -1
- package/dist/6.maestro-web-sdk.esm.js.map +1 -1
- package/dist/7.maestro-web-sdk.esm.js +1 -1
- package/dist/7.maestro-web-sdk.esm.js.map +1 -1
- package/dist/8.maestro-web-sdk.esm.js +1 -1
- package/dist/8.maestro-web-sdk.esm.js.map +1 -1
- package/dist/9.maestro-web-sdk.esm.js +1 -1
- package/dist/9.maestro-web-sdk.esm.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/maestro-web-sdk.esm.js +3 -3
- package/dist/maestro-web-sdk.esm.js.map +1 -1
- package/dist/maestro-web-sdk.umd.js +3 -3
- package/dist/maestro-web-sdk.umd.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/src/components/atoms/SvgIcon/AddIcon.d.ts +3 -0
- package/dist/src/components/atoms/SvgIcon/AddIcon.js +6 -0
- package/dist/src/components/atoms/SvgIcon/Icon.js +2 -0
- package/dist/src/components/core/PanelManager/PanelManager.js +5 -3
- package/dist/src/components/molecules/NetworkDialog/NetworkDialog.d.ts +11 -0
- package/dist/src/components/molecules/NetworkDialog/NetworkDialog.js +34 -0
- package/dist/src/components/molecules/NetworkDialog/index.d.ts +2 -0
- package/dist/src/components/molecules/NetworkDialog/index.js +2 -0
- package/dist/src/components/molecules/NetworkError/NetworkError.d.ts +104 -0
- package/dist/src/components/molecules/NetworkError/NetworkError.js +26 -0
- package/dist/src/components/molecules/NetworkError/index.d.ts +2 -0
- package/dist/src/components/molecules/NetworkError/index.js +2 -0
- package/dist/src/components/molecules/PanelNavButton/PanelNavButton.d.ts +1 -0
- package/dist/src/components/molecules/PanelNavButton/PanelNavButton.js +16 -7
- package/dist/src/components/organisms/PanelNavigation/PanelNavigation.d.ts +1 -0
- package/dist/src/components/organisms/PanelNavigation/PanelNavigation.js +11 -7
- package/dist/src/development/MaestroEventDelegate.js +1 -0
- package/dist/src/helpers/site.d.ts +1 -0
- package/dist/src/helpers/site.js +6 -0
- package/dist/src/helpers/siteConfig.d.ts +1 -0
- package/dist/src/helpers/siteConfig.js +5 -0
- package/dist/src/models/IPanel.d.ts +1 -1
- package/dist/src/models/IPanel.js +1 -1
- package/dist/src/modules/espn/bets/services/BetsService.js +5 -5
- package/dist/src/modules/espn/bets/view/BetsView.js +1 -1
- package/dist/src/modules/espn/components/TabLoadError/TabLoadError.d.ts +96 -0
- package/dist/src/modules/espn/components/TabLoadError/TabLoadError.js +14 -0
- package/dist/src/modules/espn/components/TabLoadError/index.d.ts +2 -0
- package/dist/src/modules/espn/components/TabLoadError/index.js +2 -0
- package/dist/src/modules/espn/fantasy/components/TournamentOverflowQR/TournamentOverflowQR.d.ts +5 -5
- package/dist/src/modules/espn/fantasy/components/TournamentOverflowQR/TournamentOverflowQR.js +7 -73
- package/dist/src/modules/espn/fantasy/services/FantasyService.js +4 -1
- package/dist/src/modules/espn/fantasy/view/FantasyItemView.js +1 -1
- package/dist/src/modules/espn/fantasy/view/FantasyView.js +3 -2
- package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.FantasyPolling.d.ts +1 -1
- package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.FantasyPolling.js +10 -7
- package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.LeagueManagerPolling.d.ts +1 -1
- package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.LeagueManagerPolling.js +10 -7
- package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.TournamentPolling.d.ts +1 -1
- package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.TournamentPolling.js +10 -7
- package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.d.ts +1 -0
- package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.js +42 -32
- package/dist/src/modules/espn/key-plays/view/KeyPlayErrorStateView.js +1 -1
- package/dist/src/modules/espn/shop/services/ShopService.js +1 -1
- package/dist/src/modules/espn/shop/view/ShopView.js +1 -1
- package/dist/src/modules/espn/stats/services/StatsService.js +2 -2
- package/dist/src/modules/espn/stats/view/StatsView.js +1 -1
- package/dist/src/modules/fox/components/TabLoadError/TabLoadError.d.ts +96 -0
- package/dist/src/modules/fox/components/TabLoadError/TabLoadError.js +19 -0
- package/dist/src/modules/fox/components/TabLoadError/index.d.ts +2 -0
- package/dist/src/modules/fox/components/TabLoadError/index.js +2 -0
- package/dist/src/modules/fox/multi-view/interfaces/IMultiViewViewModel.d.ts +3 -0
- package/dist/src/modules/fox/multi-view/interfaces/IMultiViewViewModel.js +1 -0
- package/dist/src/modules/fox/multi-view/services/MultiViewService.d.ts +104 -0
- package/dist/src/modules/fox/multi-view/services/MultiViewService.js +97 -0
- package/dist/src/modules/fox/multi-view/types.d.ts +48 -42
- package/dist/src/modules/fox/multi-view/types.js +79 -82
- package/dist/src/modules/fox/multi-view/view/BuildYourOwn/BuildYourOwn.d.ts +7 -0
- package/dist/src/modules/fox/multi-view/view/BuildYourOwn/BuildYourOwn.js +99 -0
- package/dist/src/modules/fox/multi-view/view/BuildYourOwn/index.d.ts +2 -0
- package/dist/src/modules/fox/multi-view/view/BuildYourOwn/index.js +2 -0
- package/dist/src/modules/fox/multi-view/view/CuratedMultiview/CuratedMultiviews.d.ts +8 -0
- package/dist/src/modules/fox/multi-view/view/CuratedMultiview/CuratedMultiviews.js +92 -0
- package/dist/src/modules/fox/multi-view/view/CuratedMultiview/index.d.ts +2 -0
- package/dist/src/modules/fox/multi-view/view/CuratedMultiview/index.js +2 -0
- package/dist/src/modules/fox/multi-view/view/FoxMultiView.d.ts +1 -0
- package/dist/src/modules/fox/multi-view/view/FoxMultiView.js +65 -2
- package/dist/src/modules/fox/multi-view/view/components/BuildYourOwnTabs/BuildYourOwnTabs.d.ts +8 -0
- package/dist/src/modules/fox/multi-view/view/components/BuildYourOwnTabs/BuildYourOwnTabs.js +30 -0
- package/dist/src/modules/fox/multi-view/view/components/BuildYourOwnTabs/index.d.ts +2 -0
- package/dist/src/modules/fox/multi-view/view/components/BuildYourOwnTabs/index.js +2 -0
- package/dist/src/modules/fox/multi-view/view/components/CustomMultiviewStreamsCard/CustomMultiviewStreamsCard.d.ts +118 -0
- package/dist/src/modules/fox/multi-view/view/components/CustomMultiviewStreamsCard/CustomMultiviewStreamsCard.js +51 -0
- package/dist/src/modules/fox/multi-view/view/components/CustomMultiviewStreamsCard/index.d.ts +2 -0
- package/dist/src/modules/fox/multi-view/view/components/CustomMultiviewStreamsCard/index.js +2 -0
- package/dist/src/modules/fox/multi-view/view/components/LayoutSelector/LayoutSelector.d.ts +1 -1
- package/dist/src/modules/fox/multi-view/view/components/LayoutSelector/LayoutSelector.js +11 -14
- package/dist/src/modules/fox/multi-view/view/components/curated-multiview-card/CuratedMultiviewCard.d.ts +101 -0
- package/dist/src/modules/fox/multi-view/view/components/curated-multiview-card/CuratedMultiviewCard.js +41 -0
- package/dist/src/modules/fox/multi-view/view/components/curated-multiview-card/index.d.ts +2 -0
- package/dist/src/modules/fox/multi-view/view/components/curated-multiview-card/index.js +2 -0
- package/dist/src/modules/fox/multi-view/view-model/MultiViewViewModel.d.ts +84 -0
- package/dist/src/modules/fox/multi-view/view-model/MultiViewViewModel.js +509 -0
- package/dist/src/modules/fox/multi-view/view-model/index.d.ts +1 -0
- package/dist/src/modules/fox/multi-view/view-model/index.js +1 -0
- package/dist/src/services/NetworkManager/NetworkManager.js +1 -1
- package/dist/src/services/NetworkManager/errors.d.ts +3 -1
- package/dist/src/services/NetworkManager/errors.js +15 -1
- package/dist/src/services/StaticService/StaticService.js +1 -0
- package/dist/src/view-models/MaestroEventViewModel.d.ts +2 -0
- package/dist/src/view-models/MaestroEventViewModel.js +17 -1
- package/package.json +1 -1
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
import { ViewModel } from '@/view-models/ViewModel';
|
|
2
|
+
import { Observable } from '@/helpers/Observable';
|
|
3
|
+
import { stringHashCode } from '@/helpers/strings';
|
|
4
|
+
import { DEFAULT_LAYOUTS_BY_LISTINGS_QUANTITY, } from '../types';
|
|
5
|
+
import MultiViewService, { isProductAPIValid, } from '../services/MultiViewService';
|
|
6
|
+
import { AuthError } from '@/services/NetworkManager/errors';
|
|
7
|
+
import { captureException } from '@/sentry';
|
|
8
|
+
import SDK from '@/index';
|
|
9
|
+
const DEFAULT_POLLING_INTERVAL = 30000;
|
|
10
|
+
export class MultiViewViewModel extends ViewModel {
|
|
11
|
+
constructor(eventId, delegate, analyticsService) {
|
|
12
|
+
super();
|
|
13
|
+
Object.defineProperty(this, "_layouts", {
|
|
14
|
+
enumerable: true,
|
|
15
|
+
configurable: true,
|
|
16
|
+
writable: true,
|
|
17
|
+
value: new Observable([])
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(this, "_isPolling", {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
configurable: true,
|
|
22
|
+
writable: true,
|
|
23
|
+
value: new Observable(false)
|
|
24
|
+
});
|
|
25
|
+
Object.defineProperty(this, "service", {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
configurable: true,
|
|
28
|
+
writable: true,
|
|
29
|
+
value: void 0
|
|
30
|
+
});
|
|
31
|
+
Object.defineProperty(this, "delegate", {
|
|
32
|
+
enumerable: true,
|
|
33
|
+
configurable: true,
|
|
34
|
+
writable: true,
|
|
35
|
+
value: void 0
|
|
36
|
+
});
|
|
37
|
+
Object.defineProperty(this, "eventId", {
|
|
38
|
+
enumerable: true,
|
|
39
|
+
configurable: true,
|
|
40
|
+
writable: true,
|
|
41
|
+
value: void 0
|
|
42
|
+
});
|
|
43
|
+
Object.defineProperty(this, "analyticsService", {
|
|
44
|
+
enumerable: true,
|
|
45
|
+
configurable: true,
|
|
46
|
+
writable: true,
|
|
47
|
+
value: void 0
|
|
48
|
+
});
|
|
49
|
+
Object.defineProperty(this, "isDataLoading", {
|
|
50
|
+
enumerable: true,
|
|
51
|
+
configurable: true,
|
|
52
|
+
writable: true,
|
|
53
|
+
value: new Observable(false)
|
|
54
|
+
});
|
|
55
|
+
Object.defineProperty(this, "pollingTask", {
|
|
56
|
+
enumerable: true,
|
|
57
|
+
configurable: true,
|
|
58
|
+
writable: true,
|
|
59
|
+
value: null
|
|
60
|
+
});
|
|
61
|
+
Object.defineProperty(this, "pollingInterval", {
|
|
62
|
+
enumerable: true,
|
|
63
|
+
configurable: true,
|
|
64
|
+
writable: true,
|
|
65
|
+
value: DEFAULT_POLLING_INTERVAL
|
|
66
|
+
});
|
|
67
|
+
Object.defineProperty(this, "items", {
|
|
68
|
+
enumerable: true,
|
|
69
|
+
configurable: true,
|
|
70
|
+
writable: true,
|
|
71
|
+
value: new Observable([])
|
|
72
|
+
});
|
|
73
|
+
Object.defineProperty(this, "hasEverSuccessfullyLoaded", {
|
|
74
|
+
enumerable: true,
|
|
75
|
+
configurable: true,
|
|
76
|
+
writable: true,
|
|
77
|
+
value: new Observable(false)
|
|
78
|
+
});
|
|
79
|
+
Object.defineProperty(this, "onAuthError", {
|
|
80
|
+
enumerable: true,
|
|
81
|
+
configurable: true,
|
|
82
|
+
writable: true,
|
|
83
|
+
value: void 0
|
|
84
|
+
});
|
|
85
|
+
Object.defineProperty(this, "_selectedLayoutId", {
|
|
86
|
+
enumerable: true,
|
|
87
|
+
configurable: true,
|
|
88
|
+
writable: true,
|
|
89
|
+
value: new Observable(null)
|
|
90
|
+
});
|
|
91
|
+
Object.defineProperty(this, "lastResponseHash", {
|
|
92
|
+
enumerable: true,
|
|
93
|
+
configurable: true,
|
|
94
|
+
writable: true,
|
|
95
|
+
value: null
|
|
96
|
+
});
|
|
97
|
+
this.eventId = eventId;
|
|
98
|
+
this.delegate = delegate;
|
|
99
|
+
this.analyticsService = analyticsService;
|
|
100
|
+
this.service = new MultiViewService();
|
|
101
|
+
}
|
|
102
|
+
getLayouts() {
|
|
103
|
+
return this._layouts.value;
|
|
104
|
+
}
|
|
105
|
+
getIsPolling() {
|
|
106
|
+
return this._isPolling;
|
|
107
|
+
}
|
|
108
|
+
getIsDataLoading() {
|
|
109
|
+
return this.isDataLoading;
|
|
110
|
+
}
|
|
111
|
+
getEventId() {
|
|
112
|
+
return this.eventId;
|
|
113
|
+
}
|
|
114
|
+
updateBuildYourOwnItem(updater) {
|
|
115
|
+
this.items.value = this.items.value.map((i) => i.type === 'build-your-own' ? updater(i) : i);
|
|
116
|
+
}
|
|
117
|
+
calculateElapsedTime(startTime) {
|
|
118
|
+
const elapsedMinutes = Math.floor((Date.now() - new Date(startTime).getTime()) / 60000);
|
|
119
|
+
if (elapsedMinutes < 0)
|
|
120
|
+
return '';
|
|
121
|
+
return `${elapsedMinutes}'`;
|
|
122
|
+
}
|
|
123
|
+
buildStreamsFromItems(items) {
|
|
124
|
+
return items
|
|
125
|
+
.filter((item) => item.is_multiview_eligible === true)
|
|
126
|
+
.map((item) => {
|
|
127
|
+
const cameras = [
|
|
128
|
+
{
|
|
129
|
+
id: item.entity_id || '',
|
|
130
|
+
name: 'Main Broadcast',
|
|
131
|
+
isActive: true,
|
|
132
|
+
},
|
|
133
|
+
];
|
|
134
|
+
if (item.associated_listings &&
|
|
135
|
+
Array.isArray(item.associated_listings)) {
|
|
136
|
+
for (const al of item.associated_listings) {
|
|
137
|
+
cameras.push({
|
|
138
|
+
id: al.entity_id || '',
|
|
139
|
+
name: al.title || '',
|
|
140
|
+
isActive: false,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const location = item.location?.city && item.location?.state
|
|
145
|
+
? `${item.location.city}, ${item.location.state}`
|
|
146
|
+
: undefined;
|
|
147
|
+
const currentTime = item.start_time
|
|
148
|
+
? this.calculateElapsedTime(item.start_time)
|
|
149
|
+
: undefined;
|
|
150
|
+
return {
|
|
151
|
+
listing: {
|
|
152
|
+
id: item.entity_id || '',
|
|
153
|
+
description: item.title || '',
|
|
154
|
+
backgroundImage: item.images?.series_list || '',
|
|
155
|
+
type: item.type || 'unknown',
|
|
156
|
+
},
|
|
157
|
+
totalCameras: cameras.length,
|
|
158
|
+
activeCameraCount: cameras.filter((c) => c.isActive).length,
|
|
159
|
+
currentTime,
|
|
160
|
+
location,
|
|
161
|
+
cameras,
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
mergeStreams(existing, incoming) {
|
|
166
|
+
const existingMap = new Map(existing.map((s) => [s.listing.id, s]));
|
|
167
|
+
return incoming.map((newStream) => {
|
|
168
|
+
const prev = existingMap.get(newStream.listing.id);
|
|
169
|
+
if (!prev)
|
|
170
|
+
return newStream;
|
|
171
|
+
const prevCameraMap = new Map(prev.cameras.map((c) => [c.id, c.isActive]));
|
|
172
|
+
const cameras = newStream.cameras.map((c) => ({
|
|
173
|
+
...c,
|
|
174
|
+
isActive: prevCameraMap.has(c.id)
|
|
175
|
+
? prevCameraMap.get(c.id)
|
|
176
|
+
: c.isActive,
|
|
177
|
+
}));
|
|
178
|
+
const activeCameraCount = cameras.filter((c) => c.isActive).length;
|
|
179
|
+
return { ...newStream, cameras, activeCameraCount };
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
getLayoutsObservable() {
|
|
183
|
+
return this._layouts;
|
|
184
|
+
}
|
|
185
|
+
getSelectedLayoutId() {
|
|
186
|
+
return this._selectedLayoutId;
|
|
187
|
+
}
|
|
188
|
+
onSelectLayout(layoutId) {
|
|
189
|
+
const layout = this._layouts.value.find((l) => l.layoutId === layoutId);
|
|
190
|
+
if (!layout)
|
|
191
|
+
return;
|
|
192
|
+
this._selectedLayoutId.value = layoutId;
|
|
193
|
+
}
|
|
194
|
+
onToggleCamera(streamId, cameraId) {
|
|
195
|
+
this.updateBuildYourOwnItem((item) => ({
|
|
196
|
+
...item,
|
|
197
|
+
streams: item.streams.map((stream) => {
|
|
198
|
+
if (stream.listing.id !== streamId)
|
|
199
|
+
return stream;
|
|
200
|
+
const cameras = stream.cameras.map((c) => c.id === cameraId ? { ...c, isActive: !c.isActive } : c);
|
|
201
|
+
const activeCameraCount = cameras.filter((c) => c.isActive).length;
|
|
202
|
+
return { ...stream, cameras, activeCameraCount };
|
|
203
|
+
}),
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Notify client app about a Product API auth error.
|
|
208
|
+
* Uses configured callback first, and falls back to onPanelEvent.
|
|
209
|
+
*/
|
|
210
|
+
notifyProductAPIUnauthorized(error) {
|
|
211
|
+
if (error.statusCode !== 401) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const payload = {
|
|
215
|
+
code: error.statusCode,
|
|
216
|
+
message: error.message || 'Not authorized',
|
|
217
|
+
success: false,
|
|
218
|
+
data: error.data,
|
|
219
|
+
};
|
|
220
|
+
if (this.onAuthError) {
|
|
221
|
+
this.onAuthError(payload);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async trackPanelView() {
|
|
225
|
+
try {
|
|
226
|
+
await this.analyticsService.panelView('foxMultiView');
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
captureException(error, {
|
|
230
|
+
context: 'MultiViewViewModel.trackPanelView',
|
|
231
|
+
});
|
|
232
|
+
console.error('[MULTIVIEW] Error tracking panel view:', error);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async init() {
|
|
236
|
+
try {
|
|
237
|
+
await this.trackPanelView();
|
|
238
|
+
await this.fetchLayouts();
|
|
239
|
+
const panelConfigInterval = this.getPanelConfigInterval();
|
|
240
|
+
if (panelConfigInterval) {
|
|
241
|
+
this.pollingInterval = panelConfigInterval;
|
|
242
|
+
}
|
|
243
|
+
this.refreshPolling();
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
const errorMessage = error instanceof Error
|
|
247
|
+
? error.message
|
|
248
|
+
: 'Failed to initialize MultiView';
|
|
249
|
+
this.setError(errorMessage);
|
|
250
|
+
console.error('[MultiViewViewModel] Initialization error:', error);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async fetchLayouts() {
|
|
254
|
+
if (this.getLayouts().length) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
this.isDataLoading.value = true;
|
|
259
|
+
const layouts = await this.service.getLayouts();
|
|
260
|
+
if (layouts && layouts.length > 0) {
|
|
261
|
+
this._layouts.value = layouts;
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
this._layouts.value = Object.values(DEFAULT_LAYOUTS_BY_LISTINGS_QUANTITY);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
console.log('[MultiViewViewModel] Error fetching layouts:', error);
|
|
269
|
+
if (error instanceof AuthError) {
|
|
270
|
+
this.notifyProductAPIUnauthorized(error);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (this._layouts.value.length === 0) {
|
|
274
|
+
this._layouts.value = Object.values(DEFAULT_LAYOUTS_BY_LISTINGS_QUANTITY);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
finally {
|
|
278
|
+
this.isDataLoading.value = false;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
async setDataToPanel(data) {
|
|
282
|
+
try {
|
|
283
|
+
if (isProductAPIValid(data.productAPIRequest)) {
|
|
284
|
+
this.service.setProductAPIData(data.productAPIRequest);
|
|
285
|
+
this.onAuthError = data.productAPIRequest.callback;
|
|
286
|
+
await this.fetchLayouts();
|
|
287
|
+
this.refreshPolling();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to set panel data';
|
|
292
|
+
console.error('[MultiViewViewModel] Error setting panel data:', error);
|
|
293
|
+
if (!this.hasEverSuccessfullyLoaded.value) {
|
|
294
|
+
this.setError(errorMessage);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
async onSelectCustomMultiview(params) {
|
|
299
|
+
try {
|
|
300
|
+
this.setLoading(true);
|
|
301
|
+
this.setError(null);
|
|
302
|
+
const response = await this.service.getOrCreateCustomMultiview(params);
|
|
303
|
+
if (this.delegate) {
|
|
304
|
+
this.delegate.onPanelEvent({
|
|
305
|
+
type: 'fox-multiview:custom-multiview',
|
|
306
|
+
payload: response.multiviewId,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
const errorMessage = error instanceof Error
|
|
312
|
+
? error.message
|
|
313
|
+
: 'Failed to select custom multiview';
|
|
314
|
+
this.setError(errorMessage);
|
|
315
|
+
console.error('[MultiViewViewModel] Error selecting multiview:', error);
|
|
316
|
+
throw error;
|
|
317
|
+
}
|
|
318
|
+
finally {
|
|
319
|
+
this.setLoading(false);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Clean up resources when the ViewModel is no longer needed
|
|
324
|
+
*/
|
|
325
|
+
cleanup() {
|
|
326
|
+
this.stopPolling();
|
|
327
|
+
this.hasEverSuccessfullyLoaded.value = false;
|
|
328
|
+
this._selectedLayoutId.value = null;
|
|
329
|
+
this.lastResponseHash = null;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Called when a curated multiview is selected. Notifies the client via onPanelEvent
|
|
333
|
+
* so the app can switch the video player to the selected multiview.
|
|
334
|
+
*/
|
|
335
|
+
onSelectCuratedMultiview(item) {
|
|
336
|
+
if (item.isSelected) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const layout = DEFAULT_LAYOUTS_BY_LISTINGS_QUANTITY[item.listings.length];
|
|
340
|
+
if (!layout) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const payload = {
|
|
344
|
+
childListings: item.listings.map((l, listingIndex) => ({
|
|
345
|
+
callsign: l.type,
|
|
346
|
+
listingId: l.id,
|
|
347
|
+
position: layout.positions[layout.positionsOrder[listingIndex]],
|
|
348
|
+
})),
|
|
349
|
+
multiviewID: item.id,
|
|
350
|
+
};
|
|
351
|
+
this.delegate.onPanelEvent({
|
|
352
|
+
type: 'fox-multiview:new-multiview',
|
|
353
|
+
payload,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* @description Update polling interval from panel config.
|
|
358
|
+
* Refresh polling if necessary.
|
|
359
|
+
*/
|
|
360
|
+
updatePollingInterval() {
|
|
361
|
+
const panelConfigInterval = this.getPanelConfigInterval();
|
|
362
|
+
if (panelConfigInterval && panelConfigInterval !== this.pollingInterval) {
|
|
363
|
+
this.pollingInterval = panelConfigInterval;
|
|
364
|
+
this.refreshPolling();
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
getPanelConfigInterval() {
|
|
368
|
+
const intervalInSeconds = SDK.getPageConfig()?.panels?.find((p) => p.panel_type === 'foxMultiView')?.renderer?.block_data?.product_api_polling_interval;
|
|
369
|
+
if (intervalInSeconds == null) {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
const parsedInterval = parseInt(String(intervalInSeconds), 10);
|
|
373
|
+
if (Number.isFinite(parsedInterval) && parsedInterval > 0) {
|
|
374
|
+
return parsedInterval * 1000; // Convert seconds to milliseconds
|
|
375
|
+
}
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Stop polling for multiview data
|
|
380
|
+
*/
|
|
381
|
+
stopPolling() {
|
|
382
|
+
if (this.pollingTask) {
|
|
383
|
+
this.pollingTask.stop();
|
|
384
|
+
this.pollingTask = null;
|
|
385
|
+
}
|
|
386
|
+
if (this.isDataLoading.value) {
|
|
387
|
+
this.isDataLoading.value = false;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Refresh polling for curated multiviews
|
|
392
|
+
*/
|
|
393
|
+
refreshPolling() {
|
|
394
|
+
// First stop any existing polling
|
|
395
|
+
this.stopPolling();
|
|
396
|
+
// Start the poll loop
|
|
397
|
+
this.pollingTask = this.service.pollCuratedMultiviews(this.pollingInterval, (data) => {
|
|
398
|
+
this.updatePollingInterval();
|
|
399
|
+
this.processCuratedMultiviewData(data);
|
|
400
|
+
}, (error) => {
|
|
401
|
+
console.error('[MultiViewViewModel] Error polling data:', error);
|
|
402
|
+
if (error instanceof AuthError) {
|
|
403
|
+
this.notifyProductAPIUnauthorized(error);
|
|
404
|
+
}
|
|
405
|
+
if (!this.hasEverSuccessfullyLoaded.value) {
|
|
406
|
+
this.setError(error instanceof Error ? error.message : 'Failed to load data');
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
processCuratedMultiviewData(data) {
|
|
411
|
+
if (!data || !data.status || !data.data) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
if (!Array.isArray(data.data.items)) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
// Calculate hash to check if data changed
|
|
418
|
+
const responseHash = stringHashCode(JSON.stringify(data));
|
|
419
|
+
if (this.lastResponseHash === responseHash) {
|
|
420
|
+
console.log('[MULTIVIEW] Response hash unchanged - skipping update');
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
this.lastResponseHash = responseHash;
|
|
424
|
+
const curatedMultiviewItem = [];
|
|
425
|
+
for (const item of data.data.items) {
|
|
426
|
+
const listings = [];
|
|
427
|
+
if (!item.is_multiview) {
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
if (item.multiview_metadata && Array.isArray(item.multiview_metadata)) {
|
|
431
|
+
for (const mv of item.multiview_metadata) {
|
|
432
|
+
let description = item.title || '';
|
|
433
|
+
if (mv.is_sportingevent &&
|
|
434
|
+
mv.left_team &&
|
|
435
|
+
mv.right_team &&
|
|
436
|
+
mv.left_team.abbreviation &&
|
|
437
|
+
mv.right_team.abbreviation) {
|
|
438
|
+
description = `${mv.left_team.abbreviation} vs ${mv.right_team.abbreviation}`;
|
|
439
|
+
}
|
|
440
|
+
listings.push({
|
|
441
|
+
id: mv.entity_id || '',
|
|
442
|
+
description,
|
|
443
|
+
backgroundImage: mv.images?.series_list || '',
|
|
444
|
+
type: mv.type || 'unknown',
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
listings.push({
|
|
450
|
+
id: item.entity_id || '',
|
|
451
|
+
description: item.title || '',
|
|
452
|
+
backgroundImage: item.images?.still || '',
|
|
453
|
+
type: item.type || 'unknown',
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
curatedMultiviewItem.push({
|
|
457
|
+
id: item.entity_id || '',
|
|
458
|
+
isSelected: this.eventId === item.entity_id,
|
|
459
|
+
listings,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
const newItems = [...this.items.value];
|
|
463
|
+
const existingCuratedMultiviewIndex = newItems.findIndex((i) => i.type === 'curated-multiviews');
|
|
464
|
+
if (curatedMultiviewItem.length === 0) {
|
|
465
|
+
// No curated multiview items — remove the section if it was previously present
|
|
466
|
+
if (existingCuratedMultiviewIndex !== -1) {
|
|
467
|
+
newItems.splice(existingCuratedMultiviewIndex, 1);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
else if (existingCuratedMultiviewIndex !== -1) {
|
|
471
|
+
newItems[existingCuratedMultiviewIndex] = {
|
|
472
|
+
type: 'curated-multiviews',
|
|
473
|
+
items: curatedMultiviewItem,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
newItems.unshift({
|
|
478
|
+
type: 'curated-multiviews',
|
|
479
|
+
items: curatedMultiviewItem,
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
const newStreams = this.buildStreamsFromItems(data.data.items);
|
|
483
|
+
const existingByoIndex = newItems.findIndex((i) => i.type === 'build-your-own');
|
|
484
|
+
if (existingByoIndex !== -1) {
|
|
485
|
+
const existingByo = newItems[existingByoIndex];
|
|
486
|
+
const mergedStreams = this.mergeStreams(existingByo.streams, newStreams);
|
|
487
|
+
newItems[existingByoIndex] = {
|
|
488
|
+
type: 'build-your-own',
|
|
489
|
+
streams: mergedStreams,
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
newItems.push({ type: 'build-your-own', streams: newStreams });
|
|
494
|
+
}
|
|
495
|
+
this.items.value = newItems;
|
|
496
|
+
if (!this.hasEverSuccessfullyLoaded.value) {
|
|
497
|
+
this.hasEverSuccessfullyLoaded.value = true;
|
|
498
|
+
if (this.error.value) {
|
|
499
|
+
this.setError(null);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
async clearTabLoadFailure() {
|
|
504
|
+
this.setError(null);
|
|
505
|
+
this.hasEverSuccessfullyLoaded.value = false;
|
|
506
|
+
await this.fetchLayouts();
|
|
507
|
+
this.refreshPolling();
|
|
508
|
+
}
|
|
509
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MultiViewViewModel } from './MultiViewViewModel';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MultiViewViewModel } from './MultiViewViewModel';
|
|
@@ -120,7 +120,7 @@ class NetworkManager {
|
|
|
120
120
|
const status = error.response.status;
|
|
121
121
|
const message = error.response.data?.message || error.message;
|
|
122
122
|
if (status === 401 || status === 403) {
|
|
123
|
-
throw new AuthError(message);
|
|
123
|
+
throw new AuthError(status, message, error.response.data);
|
|
124
124
|
}
|
|
125
125
|
else if (status === 404) {
|
|
126
126
|
throw new NotFoundError(message);
|
|
@@ -2,7 +2,9 @@ export declare class NetworkError extends Error {
|
|
|
2
2
|
constructor(message: string);
|
|
3
3
|
}
|
|
4
4
|
export declare class AuthError extends Error {
|
|
5
|
-
|
|
5
|
+
statusCode: number;
|
|
6
|
+
data?: unknown;
|
|
7
|
+
constructor(statusCode: number, message: string, data?: unknown);
|
|
6
8
|
}
|
|
7
9
|
export declare class NotFoundError extends Error {
|
|
8
10
|
constructor(message: string);
|
|
@@ -6,9 +6,23 @@ export class NetworkError extends Error {
|
|
|
6
6
|
}
|
|
7
7
|
}
|
|
8
8
|
export class AuthError extends Error {
|
|
9
|
-
constructor(message) {
|
|
9
|
+
constructor(statusCode, message, data) {
|
|
10
10
|
super(message);
|
|
11
|
+
Object.defineProperty(this, "statusCode", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
writable: true,
|
|
15
|
+
value: void 0
|
|
16
|
+
});
|
|
17
|
+
Object.defineProperty(this, "data", {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
configurable: true,
|
|
20
|
+
writable: true,
|
|
21
|
+
value: void 0
|
|
22
|
+
});
|
|
11
23
|
this.name = 'AuthError';
|
|
24
|
+
this.statusCode = statusCode;
|
|
25
|
+
this.data = data;
|
|
12
26
|
}
|
|
13
27
|
}
|
|
14
28
|
export class NotFoundError extends Error {
|
|
@@ -5,6 +5,7 @@ import NetworkManager from '../NetworkManager/NetworkManager';
|
|
|
5
5
|
const SITE_ID_STATIC_BASE_URL_MAP = {
|
|
6
6
|
'664b9c57d59a7a431542d814': 'https://static-prod.us-central1-espn-prod.gcp.maestro.io',
|
|
7
7
|
'6930c214fcb9633b5484de81': 'https://static-prod.us-central1-espn-prod.gcp.maestro.io',
|
|
8
|
+
'695c05610f99e7584a1b7407': 'https://static-prod.us-central1-fox-prod.gcp.maestro.io', // fox
|
|
8
9
|
};
|
|
9
10
|
export default class StaticService {
|
|
10
11
|
constructor(siteId) {
|
|
@@ -14,6 +14,7 @@ import { IStatsViewModel } from '../modules/espn/stats/interfaces/IStatsViewMode
|
|
|
14
14
|
import { IFantasyViewModel } from '@/modules/espn/fantasy/types';
|
|
15
15
|
import { IShopViewModel } from '@/modules/espn/shop/types';
|
|
16
16
|
import { BuilderPanelViewModel } from '@/modules/maestro/builder/view-model/BuilderPanelViewModel';
|
|
17
|
+
import { IMultiViewViewModel } from '@/modules/fox/multi-view/interfaces/IMultiViewViewModel';
|
|
17
18
|
export declare class MaestroEventViewModel extends ViewModel implements IMaestroEvent {
|
|
18
19
|
private _eventId;
|
|
19
20
|
private _pageId;
|
|
@@ -29,6 +30,7 @@ export declare class MaestroEventViewModel extends ViewModel implements IMaestro
|
|
|
29
30
|
statsViewModel: IStatsViewModel | null;
|
|
30
31
|
shopViewModel: IShopViewModel | null;
|
|
31
32
|
builderPanelViewModel: BuilderPanelViewModel | null;
|
|
33
|
+
multiViewViewModel: IMultiViewViewModel | null;
|
|
32
34
|
panelManagerViewModel: PanelManagerViewModel;
|
|
33
35
|
navigation: SpatialNavigation;
|
|
34
36
|
constructor(eventId: string | undefined, pageId: string | undefined, delegate: IMaestroEventDelegate, analyticsService: AnalyticsService);
|
|
@@ -92,6 +92,12 @@ export class MaestroEventViewModel extends ViewModel {
|
|
|
92
92
|
writable: true,
|
|
93
93
|
value: null
|
|
94
94
|
});
|
|
95
|
+
Object.defineProperty(this, "multiViewViewModel", {
|
|
96
|
+
enumerable: true,
|
|
97
|
+
configurable: true,
|
|
98
|
+
writable: true,
|
|
99
|
+
value: null
|
|
100
|
+
});
|
|
95
101
|
Object.defineProperty(this, "panelManagerViewModel", {
|
|
96
102
|
enumerable: true,
|
|
97
103
|
configurable: true,
|
|
@@ -187,6 +193,15 @@ export class MaestroEventViewModel extends ViewModel {
|
|
|
187
193
|
console.warn('Failed to load ShopViewModel:', err);
|
|
188
194
|
}));
|
|
189
195
|
}
|
|
196
|
+
if (StaticService.isPanelAvailable(pageConfig, 'foxMultiView')) {
|
|
197
|
+
loadPromises.push(import('@/modules/fox/multi-view/view-model/MultiViewViewModel')
|
|
198
|
+
.then(({ MultiViewViewModel }) => {
|
|
199
|
+
this.multiViewViewModel = new MultiViewViewModel(this._eventId.value, this.delegate, this.analyticsService);
|
|
200
|
+
})
|
|
201
|
+
.catch((err) => {
|
|
202
|
+
console.warn('Failed to load MultiViewViewModel:', err);
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
190
205
|
if (StaticService.isPanelAvailable(pageConfig, 'builderBlockPanel')) {
|
|
191
206
|
const builderPanel = pageConfig?.panels?.find((p) => p.panel_type === 'builderBlockPanel');
|
|
192
207
|
if (builderPanel) {
|
|
@@ -239,7 +254,7 @@ export class MaestroEventViewModel extends ViewModel {
|
|
|
239
254
|
espnShop: this.shopViewModel,
|
|
240
255
|
builderBlockPanel: this.builderPanelViewModel,
|
|
241
256
|
foxKeyPlays: null,
|
|
242
|
-
|
|
257
|
+
foxMultiView: this.multiViewViewModel,
|
|
243
258
|
foxStats: null,
|
|
244
259
|
helloWorld: null,
|
|
245
260
|
};
|
|
@@ -266,6 +281,7 @@ export class MaestroEventViewModel extends ViewModel {
|
|
|
266
281
|
this.fantasyViewModel?.cleanup();
|
|
267
282
|
this.shopViewModel?.cleanup();
|
|
268
283
|
this.builderPanelViewModel?.cleanup();
|
|
284
|
+
this.multiViewViewModel?.cleanup();
|
|
269
285
|
this.navigation.destroy();
|
|
270
286
|
try {
|
|
271
287
|
await this.didHidePanel();
|