@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.
Files changed (177) hide show
  1. package/dist/1.maestro-web-sdk.esm.js +1 -1
  2. package/dist/1.maestro-web-sdk.esm.js.map +1 -1
  3. package/dist/10.maestro-web-sdk.esm.js +1 -1
  4. package/dist/10.maestro-web-sdk.esm.js.map +1 -1
  5. package/dist/11.maestro-web-sdk.esm.js +1 -1
  6. package/dist/11.maestro-web-sdk.esm.js.map +1 -1
  7. package/dist/12.maestro-web-sdk.esm.js +1 -1
  8. package/dist/12.maestro-web-sdk.esm.js.map +1 -1
  9. package/dist/13.maestro-web-sdk.esm.js +1 -1
  10. package/dist/13.maestro-web-sdk.esm.js.map +1 -1
  11. package/dist/14.maestro-web-sdk.esm.js +1 -1
  12. package/dist/14.maestro-web-sdk.esm.js.map +1 -1
  13. package/dist/15.maestro-web-sdk.esm.js +1 -1
  14. package/dist/15.maestro-web-sdk.esm.js.map +1 -1
  15. package/dist/16.maestro-web-sdk.esm.js +1 -1
  16. package/dist/16.maestro-web-sdk.esm.js.map +1 -1
  17. package/dist/17.maestro-web-sdk.esm.js +1 -1
  18. package/dist/17.maestro-web-sdk.esm.js.map +1 -1
  19. package/dist/18.maestro-web-sdk.esm.js +1 -1
  20. package/dist/18.maestro-web-sdk.esm.js.map +1 -1
  21. package/dist/19.maestro-web-sdk.esm.js +1 -1
  22. package/dist/19.maestro-web-sdk.esm.js.map +1 -1
  23. package/dist/2.maestro-web-sdk.esm.js +1 -1
  24. package/dist/20.maestro-web-sdk.esm.js +1 -1
  25. package/dist/20.maestro-web-sdk.esm.js.map +1 -1
  26. package/dist/21.maestro-web-sdk.esm.js +1 -1
  27. package/dist/21.maestro-web-sdk.esm.js.map +1 -1
  28. package/dist/22.maestro-web-sdk.esm.js +1 -1
  29. package/dist/22.maestro-web-sdk.esm.js.map +1 -1
  30. package/dist/23.maestro-web-sdk.esm.js +1 -1
  31. package/dist/23.maestro-web-sdk.esm.js.map +1 -1
  32. package/dist/24.maestro-web-sdk.esm.js +1 -1
  33. package/dist/24.maestro-web-sdk.esm.js.map +1 -1
  34. package/dist/25.maestro-web-sdk.esm.js +1 -1
  35. package/dist/25.maestro-web-sdk.esm.js.map +1 -1
  36. package/dist/26.maestro-web-sdk.esm.js +1 -1
  37. package/dist/26.maestro-web-sdk.esm.js.map +1 -1
  38. package/dist/27.maestro-web-sdk.esm.js +1 -1
  39. package/dist/27.maestro-web-sdk.esm.js.map +1 -1
  40. package/dist/28.maestro-web-sdk.esm.js +1 -1
  41. package/dist/28.maestro-web-sdk.esm.js.map +1 -1
  42. package/dist/29.maestro-web-sdk.esm.js +1 -1
  43. package/dist/29.maestro-web-sdk.esm.js.map +1 -1
  44. package/dist/3.maestro-web-sdk.esm.js +1 -1
  45. package/dist/3.maestro-web-sdk.esm.js.map +1 -1
  46. package/dist/30.maestro-web-sdk.esm.js +1 -1
  47. package/dist/30.maestro-web-sdk.esm.js.map +1 -1
  48. package/dist/31.maestro-web-sdk.esm.js +1 -1
  49. package/dist/31.maestro-web-sdk.esm.js.map +1 -1
  50. package/dist/32.maestro-web-sdk.esm.js +1 -1
  51. package/dist/32.maestro-web-sdk.esm.js.map +1 -1
  52. package/dist/33.maestro-web-sdk.esm.js +1 -1
  53. package/dist/33.maestro-web-sdk.esm.js.map +1 -1
  54. package/dist/34.maestro-web-sdk.esm.js +1 -1
  55. package/dist/34.maestro-web-sdk.esm.js.map +1 -1
  56. package/dist/35.maestro-web-sdk.esm.js +1 -1
  57. package/dist/35.maestro-web-sdk.esm.js.map +1 -1
  58. package/dist/36.maestro-web-sdk.esm.js +1 -1
  59. package/dist/36.maestro-web-sdk.esm.js.map +1 -1
  60. package/dist/37.maestro-web-sdk.esm.js +1 -1
  61. package/dist/37.maestro-web-sdk.esm.js.map +1 -1
  62. package/dist/38.maestro-web-sdk.esm.js +1 -1
  63. package/dist/38.maestro-web-sdk.esm.js.map +1 -1
  64. package/dist/39.maestro-web-sdk.esm.js +2 -0
  65. package/dist/39.maestro-web-sdk.esm.js.map +1 -0
  66. package/dist/4.maestro-web-sdk.esm.js +1 -1
  67. package/dist/4.maestro-web-sdk.esm.js.map +1 -1
  68. package/dist/40.maestro-web-sdk.esm.js +2 -0
  69. package/dist/40.maestro-web-sdk.esm.js.map +1 -0
  70. package/dist/5.maestro-web-sdk.esm.js +1 -1
  71. package/dist/5.maestro-web-sdk.esm.js.map +1 -1
  72. package/dist/6.maestro-web-sdk.esm.js +1 -1
  73. package/dist/6.maestro-web-sdk.esm.js.map +1 -1
  74. package/dist/7.maestro-web-sdk.esm.js +1 -1
  75. package/dist/7.maestro-web-sdk.esm.js.map +1 -1
  76. package/dist/8.maestro-web-sdk.esm.js +1 -1
  77. package/dist/8.maestro-web-sdk.esm.js.map +1 -1
  78. package/dist/9.maestro-web-sdk.esm.js +1 -1
  79. package/dist/9.maestro-web-sdk.esm.js.map +1 -1
  80. package/dist/index.d.ts +4 -1
  81. package/dist/maestro-web-sdk.esm.js +3 -3
  82. package/dist/maestro-web-sdk.esm.js.map +1 -1
  83. package/dist/maestro-web-sdk.umd.js +3 -3
  84. package/dist/maestro-web-sdk.umd.js.map +1 -1
  85. package/dist/package.json +1 -1
  86. package/dist/src/components/atoms/SvgIcon/AddIcon.d.ts +3 -0
  87. package/dist/src/components/atoms/SvgIcon/AddIcon.js +6 -0
  88. package/dist/src/components/atoms/SvgIcon/Icon.js +2 -0
  89. package/dist/src/components/core/PanelManager/PanelManager.js +5 -3
  90. package/dist/src/components/molecules/NetworkDialog/NetworkDialog.d.ts +11 -0
  91. package/dist/src/components/molecules/NetworkDialog/NetworkDialog.js +34 -0
  92. package/dist/src/components/molecules/NetworkDialog/index.d.ts +2 -0
  93. package/dist/src/components/molecules/NetworkDialog/index.js +2 -0
  94. package/dist/src/components/molecules/NetworkError/NetworkError.d.ts +104 -0
  95. package/dist/src/components/molecules/NetworkError/NetworkError.js +26 -0
  96. package/dist/src/components/molecules/NetworkError/index.d.ts +2 -0
  97. package/dist/src/components/molecules/NetworkError/index.js +2 -0
  98. package/dist/src/components/molecules/PanelNavButton/PanelNavButton.d.ts +1 -0
  99. package/dist/src/components/molecules/PanelNavButton/PanelNavButton.js +16 -7
  100. package/dist/src/components/organisms/PanelNavigation/PanelNavigation.d.ts +1 -0
  101. package/dist/src/components/organisms/PanelNavigation/PanelNavigation.js +11 -7
  102. package/dist/src/development/MaestroEventDelegate.js +1 -0
  103. package/dist/src/helpers/site.d.ts +1 -0
  104. package/dist/src/helpers/site.js +6 -0
  105. package/dist/src/helpers/siteConfig.d.ts +1 -0
  106. package/dist/src/helpers/siteConfig.js +5 -0
  107. package/dist/src/models/IPanel.d.ts +1 -1
  108. package/dist/src/models/IPanel.js +1 -1
  109. package/dist/src/modules/espn/bets/services/BetsService.js +5 -5
  110. package/dist/src/modules/espn/bets/view/BetsView.js +1 -1
  111. package/dist/src/modules/espn/components/TabLoadError/TabLoadError.d.ts +96 -0
  112. package/dist/src/modules/espn/components/TabLoadError/TabLoadError.js +14 -0
  113. package/dist/src/modules/espn/components/TabLoadError/index.d.ts +2 -0
  114. package/dist/src/modules/espn/components/TabLoadError/index.js +2 -0
  115. package/dist/src/modules/espn/fantasy/components/TournamentOverflowQR/TournamentOverflowQR.d.ts +5 -5
  116. package/dist/src/modules/espn/fantasy/components/TournamentOverflowQR/TournamentOverflowQR.js +7 -73
  117. package/dist/src/modules/espn/fantasy/services/FantasyService.js +4 -1
  118. package/dist/src/modules/espn/fantasy/view/FantasyItemView.js +1 -1
  119. package/dist/src/modules/espn/fantasy/view/FantasyView.js +3 -2
  120. package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.FantasyPolling.d.ts +1 -1
  121. package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.FantasyPolling.js +10 -7
  122. package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.LeagueManagerPolling.d.ts +1 -1
  123. package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.LeagueManagerPolling.js +10 -7
  124. package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.TournamentPolling.d.ts +1 -1
  125. package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.TournamentPolling.js +10 -7
  126. package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.d.ts +1 -0
  127. package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.js +42 -32
  128. package/dist/src/modules/espn/key-plays/view/KeyPlayErrorStateView.js +1 -1
  129. package/dist/src/modules/espn/shop/services/ShopService.js +1 -1
  130. package/dist/src/modules/espn/shop/view/ShopView.js +1 -1
  131. package/dist/src/modules/espn/stats/services/StatsService.js +2 -2
  132. package/dist/src/modules/espn/stats/view/StatsView.js +1 -1
  133. package/dist/src/modules/fox/components/TabLoadError/TabLoadError.d.ts +96 -0
  134. package/dist/src/modules/fox/components/TabLoadError/TabLoadError.js +19 -0
  135. package/dist/src/modules/fox/components/TabLoadError/index.d.ts +2 -0
  136. package/dist/src/modules/fox/components/TabLoadError/index.js +2 -0
  137. package/dist/src/modules/fox/multi-view/interfaces/IMultiViewViewModel.d.ts +3 -0
  138. package/dist/src/modules/fox/multi-view/interfaces/IMultiViewViewModel.js +1 -0
  139. package/dist/src/modules/fox/multi-view/services/MultiViewService.d.ts +104 -0
  140. package/dist/src/modules/fox/multi-view/services/MultiViewService.js +97 -0
  141. package/dist/src/modules/fox/multi-view/types.d.ts +48 -42
  142. package/dist/src/modules/fox/multi-view/types.js +79 -82
  143. package/dist/src/modules/fox/multi-view/view/BuildYourOwn/BuildYourOwn.d.ts +7 -0
  144. package/dist/src/modules/fox/multi-view/view/BuildYourOwn/BuildYourOwn.js +99 -0
  145. package/dist/src/modules/fox/multi-view/view/BuildYourOwn/index.d.ts +2 -0
  146. package/dist/src/modules/fox/multi-view/view/BuildYourOwn/index.js +2 -0
  147. package/dist/src/modules/fox/multi-view/view/CuratedMultiview/CuratedMultiviews.d.ts +8 -0
  148. package/dist/src/modules/fox/multi-view/view/CuratedMultiview/CuratedMultiviews.js +92 -0
  149. package/dist/src/modules/fox/multi-view/view/CuratedMultiview/index.d.ts +2 -0
  150. package/dist/src/modules/fox/multi-view/view/CuratedMultiview/index.js +2 -0
  151. package/dist/src/modules/fox/multi-view/view/FoxMultiView.d.ts +1 -0
  152. package/dist/src/modules/fox/multi-view/view/FoxMultiView.js +65 -2
  153. package/dist/src/modules/fox/multi-view/view/components/BuildYourOwnTabs/BuildYourOwnTabs.d.ts +8 -0
  154. package/dist/src/modules/fox/multi-view/view/components/BuildYourOwnTabs/BuildYourOwnTabs.js +30 -0
  155. package/dist/src/modules/fox/multi-view/view/components/BuildYourOwnTabs/index.d.ts +2 -0
  156. package/dist/src/modules/fox/multi-view/view/components/BuildYourOwnTabs/index.js +2 -0
  157. package/dist/src/modules/fox/multi-view/view/components/CustomMultiviewStreamsCard/CustomMultiviewStreamsCard.d.ts +118 -0
  158. package/dist/src/modules/fox/multi-view/view/components/CustomMultiviewStreamsCard/CustomMultiviewStreamsCard.js +51 -0
  159. package/dist/src/modules/fox/multi-view/view/components/CustomMultiviewStreamsCard/index.d.ts +2 -0
  160. package/dist/src/modules/fox/multi-view/view/components/CustomMultiviewStreamsCard/index.js +2 -0
  161. package/dist/src/modules/fox/multi-view/view/components/LayoutSelector/LayoutSelector.d.ts +1 -1
  162. package/dist/src/modules/fox/multi-view/view/components/LayoutSelector/LayoutSelector.js +11 -14
  163. package/dist/src/modules/fox/multi-view/view/components/curated-multiview-card/CuratedMultiviewCard.d.ts +101 -0
  164. package/dist/src/modules/fox/multi-view/view/components/curated-multiview-card/CuratedMultiviewCard.js +41 -0
  165. package/dist/src/modules/fox/multi-view/view/components/curated-multiview-card/index.d.ts +2 -0
  166. package/dist/src/modules/fox/multi-view/view/components/curated-multiview-card/index.js +2 -0
  167. package/dist/src/modules/fox/multi-view/view-model/MultiViewViewModel.d.ts +84 -0
  168. package/dist/src/modules/fox/multi-view/view-model/MultiViewViewModel.js +509 -0
  169. package/dist/src/modules/fox/multi-view/view-model/index.d.ts +1 -0
  170. package/dist/src/modules/fox/multi-view/view-model/index.js +1 -0
  171. package/dist/src/services/NetworkManager/NetworkManager.js +1 -1
  172. package/dist/src/services/NetworkManager/errors.d.ts +3 -1
  173. package/dist/src/services/NetworkManager/errors.js +15 -1
  174. package/dist/src/services/StaticService/StaticService.js +1 -0
  175. package/dist/src/view-models/MaestroEventViewModel.d.ts +2 -0
  176. package/dist/src/view-models/MaestroEventViewModel.js +17 -1
  177. 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
- constructor(message: string);
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
- foxMultiview: null,
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();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@maestro_io/maestro-web-sdk",
3
3
  "private": false,
4
- "version": "5.1.2",
4
+ "version": "5.2.1",
5
5
  "type": "commonjs",
6
6
  "license": "MIT",
7
7
  "main": "dist/maestro-web-sdk.umd.js",