@maestro_io/maestro-web-sdk 5.2.0 → 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 (102) hide show
  1. package/dist/10.maestro-web-sdk.esm.js +1 -1
  2. package/dist/10.maestro-web-sdk.esm.js.map +1 -1
  3. package/dist/11.maestro-web-sdk.esm.js +1 -1
  4. package/dist/11.maestro-web-sdk.esm.js.map +1 -1
  5. package/dist/12.maestro-web-sdk.esm.js +1 -1
  6. package/dist/12.maestro-web-sdk.esm.js.map +1 -1
  7. package/dist/13.maestro-web-sdk.esm.js +1 -1
  8. package/dist/13.maestro-web-sdk.esm.js.map +1 -1
  9. package/dist/14.maestro-web-sdk.esm.js +1 -1
  10. package/dist/14.maestro-web-sdk.esm.js.map +1 -1
  11. package/dist/15.maestro-web-sdk.esm.js +1 -1
  12. package/dist/15.maestro-web-sdk.esm.js.map +1 -1
  13. package/dist/16.maestro-web-sdk.esm.js +1 -1
  14. package/dist/16.maestro-web-sdk.esm.js.map +1 -1
  15. package/dist/17.maestro-web-sdk.esm.js +1 -1
  16. package/dist/17.maestro-web-sdk.esm.js.map +1 -1
  17. package/dist/18.maestro-web-sdk.esm.js +1 -1
  18. package/dist/18.maestro-web-sdk.esm.js.map +1 -1
  19. package/dist/19.maestro-web-sdk.esm.js +1 -1
  20. package/dist/19.maestro-web-sdk.esm.js.map +1 -1
  21. package/dist/20.maestro-web-sdk.esm.js +1 -1
  22. package/dist/20.maestro-web-sdk.esm.js.map +1 -1
  23. package/dist/21.maestro-web-sdk.esm.js +1 -1
  24. package/dist/21.maestro-web-sdk.esm.js.map +1 -1
  25. package/dist/22.maestro-web-sdk.esm.js +1 -1
  26. package/dist/22.maestro-web-sdk.esm.js.map +1 -1
  27. package/dist/23.maestro-web-sdk.esm.js +1 -1
  28. package/dist/23.maestro-web-sdk.esm.js.map +1 -1
  29. package/dist/24.maestro-web-sdk.esm.js +1 -1
  30. package/dist/24.maestro-web-sdk.esm.js.map +1 -1
  31. package/dist/25.maestro-web-sdk.esm.js +1 -1
  32. package/dist/25.maestro-web-sdk.esm.js.map +1 -1
  33. package/dist/26.maestro-web-sdk.esm.js +1 -1
  34. package/dist/26.maestro-web-sdk.esm.js.map +1 -1
  35. package/dist/27.maestro-web-sdk.esm.js +1 -1
  36. package/dist/27.maestro-web-sdk.esm.js.map +1 -1
  37. package/dist/28.maestro-web-sdk.esm.js +1 -1
  38. package/dist/28.maestro-web-sdk.esm.js.map +1 -1
  39. package/dist/29.maestro-web-sdk.esm.js +1 -1
  40. package/dist/29.maestro-web-sdk.esm.js.map +1 -1
  41. package/dist/3.maestro-web-sdk.esm.js +1 -1
  42. package/dist/3.maestro-web-sdk.esm.js.map +1 -1
  43. package/dist/30.maestro-web-sdk.esm.js +1 -1
  44. package/dist/30.maestro-web-sdk.esm.js.map +1 -1
  45. package/dist/31.maestro-web-sdk.esm.js +1 -1
  46. package/dist/31.maestro-web-sdk.esm.js.map +1 -1
  47. package/dist/32.maestro-web-sdk.esm.js +1 -1
  48. package/dist/32.maestro-web-sdk.esm.js.map +1 -1
  49. package/dist/33.maestro-web-sdk.esm.js +1 -1
  50. package/dist/33.maestro-web-sdk.esm.js.map +1 -1
  51. package/dist/34.maestro-web-sdk.esm.js +1 -1
  52. package/dist/34.maestro-web-sdk.esm.js.map +1 -1
  53. package/dist/35.maestro-web-sdk.esm.js +1 -1
  54. package/dist/35.maestro-web-sdk.esm.js.map +1 -1
  55. package/dist/36.maestro-web-sdk.esm.js +1 -1
  56. package/dist/36.maestro-web-sdk.esm.js.map +1 -1
  57. package/dist/37.maestro-web-sdk.esm.js +1 -1
  58. package/dist/37.maestro-web-sdk.esm.js.map +1 -1
  59. package/dist/38.maestro-web-sdk.esm.js +1 -1
  60. package/dist/38.maestro-web-sdk.esm.js.map +1 -1
  61. package/dist/39.maestro-web-sdk.esm.js +1 -1
  62. package/dist/39.maestro-web-sdk.esm.js.map +1 -1
  63. package/dist/6.maestro-web-sdk.esm.js +1 -1
  64. package/dist/7.maestro-web-sdk.esm.js +1 -1
  65. package/dist/8.maestro-web-sdk.esm.js +1 -1
  66. package/dist/8.maestro-web-sdk.esm.js.map +1 -1
  67. package/dist/9.maestro-web-sdk.esm.js +1 -1
  68. package/dist/maestro-web-sdk.esm.js +2 -2
  69. package/dist/maestro-web-sdk.umd.js +2 -2
  70. package/dist/maestro-web-sdk.umd.js.map +1 -1
  71. package/dist/package.json +1 -1
  72. package/dist/src/modules/espn/bets/services/BetsService.js +5 -5
  73. package/dist/src/modules/espn/fantasy/services/FantasyService.js +3 -0
  74. package/dist/src/modules/espn/fantasy/view/FantasyView.js +2 -1
  75. package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.FantasyPolling.d.ts +1 -1
  76. package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.FantasyPolling.js +10 -7
  77. package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.LeagueManagerPolling.d.ts +1 -1
  78. package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.LeagueManagerPolling.js +10 -7
  79. package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.TournamentPolling.d.ts +1 -1
  80. package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.TournamentPolling.js +10 -7
  81. package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.d.ts +1 -0
  82. package/dist/src/modules/espn/fantasy/view-model/FantasyViewModel.js +42 -32
  83. package/dist/src/modules/espn/shop/services/ShopService.js +1 -1
  84. package/dist/src/modules/espn/stats/services/StatsService.js +2 -2
  85. package/dist/src/modules/fox/multi-view/services/MultiViewService.d.ts +20 -15
  86. package/dist/src/modules/fox/multi-view/services/MultiViewService.js +6 -3
  87. package/dist/src/modules/fox/multi-view/types.d.ts +20 -0
  88. package/dist/src/modules/fox/multi-view/view/BuildYourOwn/BuildYourOwn.d.ts +7 -0
  89. package/dist/src/modules/fox/multi-view/view/BuildYourOwn/BuildYourOwn.js +99 -0
  90. package/dist/src/modules/fox/multi-view/view/BuildYourOwn/index.d.ts +2 -0
  91. package/dist/src/modules/fox/multi-view/view/BuildYourOwn/index.js +2 -0
  92. package/dist/src/modules/fox/multi-view/view/CuratedMultiview/CuratedMultiviews.js +2 -1
  93. package/dist/src/modules/fox/multi-view/view/FoxMultiView.js +3 -0
  94. package/dist/src/modules/fox/multi-view/view/components/BuildYourOwnTabs/BuildYourOwnTabs.d.ts +8 -0
  95. package/dist/src/modules/fox/multi-view/view/components/BuildYourOwnTabs/BuildYourOwnTabs.js +30 -0
  96. package/dist/src/modules/fox/multi-view/view/components/BuildYourOwnTabs/index.d.ts +2 -0
  97. package/dist/src/modules/fox/multi-view/view/components/BuildYourOwnTabs/index.js +2 -0
  98. package/dist/src/modules/fox/multi-view/view/components/CustomMultiviewStreamsCard/CustomMultiviewStreamsCard.d.ts +2 -6
  99. package/dist/src/modules/fox/multi-view/view/components/curated-multiview-card/CuratedMultiviewCard.js +27 -7
  100. package/dist/src/modules/fox/multi-view/view-model/MultiViewViewModel.d.ts +25 -4
  101. package/dist/src/modules/fox/multi-view/view-model/MultiViewViewModel.js +196 -15
  102. package/package.json +1 -1
@@ -82,7 +82,8 @@ const CuratedMultiviews = (props) => {
82
82
  props.onSelect(item);
83
83
  };
84
84
  return (React.createElement("div", { className: "curated-multiviews", style: { maxHeight }, ref: wrapperRef, "data-scrolled": scrolled },
85
- React.createElement("div", { className: "header", ref: headerRef }, "Curated Multiview"),
85
+ React.createElement("div", { className: "header", ref: headerRef },
86
+ React.createElement("div", null, "Curated Multiview")),
86
87
  props.items.map((item, index) => (React.createElement("div", { key: item.id, ref: (ref) => {
87
88
  refsArray.current[index] = ref;
88
89
  } },
@@ -52,6 +52,9 @@ const FoxMultiView = () => {
52
52
  switch (item.type) {
53
53
  case 'curated-multiviews':
54
54
  return (React.createElement(CuratedMultiviews, { key: item.type, items: item.items, onSelect: onSelectCuratedMultiview }));
55
+ // Hiding this for v5.2.1
56
+ // case 'build-your-own':
57
+ // return <BuildYourOwn key={item.type} streams={item.streams} />;
55
58
  default:
56
59
  return null;
57
60
  }
@@ -0,0 +1,8 @@
1
+ import './BuildYourOwnTabs.style.css';
2
+ import { BuildYourOwnTab } from '../../../types';
3
+ type BuildYourOwnTabsProps = {
4
+ activeTab: BuildYourOwnTab;
5
+ onSelectTab: (tab: BuildYourOwnTab) => void;
6
+ };
7
+ declare const BuildYourOwnTabs: (props: BuildYourOwnTabsProps) => JSX.Element;
8
+ export default BuildYourOwnTabs;
@@ -0,0 +1,30 @@
1
+ import React, { useCallback } from 'react';
2
+ import './BuildYourOwnTabs.style.css';
3
+ import { withFocusable, } from '@/external/spatial-navigation';
4
+ import { FocusableItem } from '@/components/core/ScrollableContainer';
5
+ import SDK from '@/index';
6
+ const TabButtonComponent = (props) => {
7
+ const { label, isActive, focused } = props;
8
+ return (React.createElement(FocusableItem, null,
9
+ React.createElement("div", { className: "byo-tab focus-ring", "data-focused": focused, "data-active": isActive },
10
+ React.createElement("span", { className: "byo-tab-label T10 FONT_BOLD" }, label))));
11
+ };
12
+ const TabButton = withFocusable()(TabButtonComponent);
13
+ const TABS = [
14
+ { tab: 'streams', label: 'Streams' },
15
+ { tab: 'layouts', label: 'Layouts' },
16
+ ];
17
+ const BuildYourOwnTabs = (props) => {
18
+ const { activeTab, onSelectTab } = props;
19
+ const onBecameFocused = useCallback((tab) => () => {
20
+ onSelectTab(tab);
21
+ }, [onSelectTab]);
22
+ const onArrowPress = useCallback((tab) => (d) => {
23
+ if (d === 'left' && tab === 'streams') {
24
+ SDK.getMaestroEventViewModel().deliverFocus();
25
+ return false;
26
+ }
27
+ }, []);
28
+ return (React.createElement("div", { className: "byo-tabs" }, TABS.map(({ tab, label }) => (React.createElement(TabButton, { key: tab, focusKey: `byo-tab-${tab}`, tab: tab, label: label, isActive: tab === activeTab, onEnterPress: () => onSelectTab(tab), onArrowPress: onArrowPress(tab), onBecameFocused: onBecameFocused(tab) })))));
29
+ };
30
+ export default BuildYourOwnTabs;
@@ -0,0 +1,2 @@
1
+ import BuildYourOwnTabs from './BuildYourOwnTabs';
2
+ export default BuildYourOwnTabs;
@@ -0,0 +1,2 @@
1
+ import BuildYourOwnTabs from './BuildYourOwnTabs';
2
+ export default BuildYourOwnTabs;
@@ -2,12 +2,8 @@
2
2
  import React from 'react';
3
3
  import './CustomMultiviewStreamsCard.style.css';
4
4
  import { WithFocusableProps } from '@/external/spatial-navigation';
5
- import { CuratedMultiviewListing } from '../../../types';
6
- export type Camera = {
7
- id: string;
8
- name: string;
9
- isActive: boolean;
10
- };
5
+ import { CuratedMultiviewListing, Camera } from '../../../types';
6
+ export type { Camera };
11
7
  export type CustomMultiviewStreamsCardProps = {
12
8
  /** Primary listing displayed on this card */
13
9
  listing: CuratedMultiviewListing;
@@ -2,19 +2,39 @@ import React from 'react';
2
2
  import './CuratedMultiviewCard.style.css';
3
3
  import { withFocusable, } from '@/external/spatial-navigation';
4
4
  import Icon from '@/components/atoms/SvgIcon';
5
- const StreamScreen = ({ listing }) => {
6
- const { backgroundImage, description } = listing;
7
- return (React.createElement("div", { className: "curated-multiview-screen", style: {
8
- backgroundImage: `url(${backgroundImage})`,
9
- } },
10
- React.createElement("span", { className: "curated-multiview-screen-title FONT_BOLD" }, description)));
5
+ function getScreenDetails(listings) {
6
+ const imageLists = [];
7
+ const descriptionLists = [];
8
+ listings.forEach((listing) => {
9
+ if (listing.backgroundImage)
10
+ imageLists.push({ id: listing.id, image: listing.backgroundImage });
11
+ if (listing.description)
12
+ descriptionLists.push({
13
+ id: listing.id,
14
+ description: listing.description,
15
+ });
16
+ });
17
+ return {
18
+ imageLists,
19
+ descriptionLists,
20
+ };
21
+ }
22
+ const StreamScreen = ({ screenDetails }) => {
23
+ const { imageLists, descriptionLists } = screenDetails;
24
+ return (React.createElement(React.Fragment, null,
25
+ React.createElement("div", { className: "curated-multiview-screen" },
26
+ React.createElement("div", null,
27
+ React.createElement("div", null, imageLists.map((imageList) => (React.createElement("img", { key: imageList.id, src: imageList.image, alt: imageList.id })))))),
28
+ React.createElement("div", { className: "curated-multiview-screen-description" }, descriptionLists.map((descriptionList) => (React.createElement("span", { key: descriptionList.id }, descriptionList.description))))));
11
29
  };
12
30
  const CuratedMultiviewCard = (props) => {
13
31
  const { listings, selected, focused } = props;
32
+ const screenDetails = getScreenDetails(listings);
14
33
  return (React.createElement("div", { className: "curated-multiview-card focus-ring", "data-focused": focused },
15
34
  React.createElement("div", { className: "curated-multiview-card-row" },
16
35
  React.createElement("div", { className: "curated-multiview-card-cell curated-multiview-card-cell-screens" },
17
- React.createElement("div", { className: "curated-multiview-screens-container" }, listings.slice(0, 2).map((listing) => (React.createElement(StreamScreen, { key: listing.id, listing: listing }))))),
36
+ React.createElement("div", { className: "curated-multiview-screens-container" },
37
+ React.createElement(StreamScreen, { screenDetails: screenDetails }))),
18
38
  React.createElement("div", { className: "curated-multiview-card-cell curated-multiview-card-cell-radio" },
19
39
  React.createElement("div", { className: "curated-multiview-radio", "data-selected": selected }, selected && React.createElement(Icon, { iconName: "checkmark", width: 22.5, height: 22 }))))));
20
40
  };
@@ -1,6 +1,6 @@
1
1
  import { ViewModel } from '@/view-models/ViewModel';
2
2
  import { Observable } from '@/helpers/Observable';
3
- import { CuratedMultiviewListing, MultiViewLayout } from '../types';
3
+ import { CuratedMultiviewListing, CustomMultiviewStream, MultiViewLayout } from '../types';
4
4
  import IMaestroEventDelegate from '@/interfaces/IMaestroEventDelegate';
5
5
  import { CuratedMultiviewResponse, MultiViewListingRequest } from '../services/MultiViewService';
6
6
  import AnalyticsService from '@/services/AnalyticsService/AnalyticsService';
@@ -13,7 +13,11 @@ export type CuratedMultiviewsItem = {
13
13
  type: 'curated-multiviews';
14
14
  items: CuratedMultiviewItem[];
15
15
  };
16
- export type MultiviewItem = CuratedMultiviewsItem;
16
+ export type BuildYourOwnItem = {
17
+ type: 'build-your-own';
18
+ streams: CustomMultiviewStream[];
19
+ };
20
+ export type MultiviewItem = CuratedMultiviewsItem | BuildYourOwnItem;
17
21
  export declare class MultiViewViewModel extends ViewModel {
18
22
  private _layouts;
19
23
  private _isPolling;
@@ -23,14 +27,25 @@ export declare class MultiViewViewModel extends ViewModel {
23
27
  private analyticsService;
24
28
  isDataLoading: Observable<boolean>;
25
29
  private pollingTask;
26
- items: Observable<CuratedMultiviewsItem[]>;
30
+ private pollingInterval;
31
+ items: Observable<MultiviewItem[]>;
27
32
  private hasEverSuccessfullyLoaded;
28
33
  private onAuthError?;
34
+ private _selectedLayoutId;
35
+ private lastResponseHash;
29
36
  constructor(eventId: string, delegate: IMaestroEventDelegate, analyticsService: AnalyticsService);
30
37
  getLayouts(): MultiViewLayout[];
31
38
  getIsPolling(): Observable<boolean>;
32
39
  getIsDataLoading(): Observable<boolean>;
33
40
  getEventId(): string;
41
+ private updateBuildYourOwnItem;
42
+ private calculateElapsedTime;
43
+ private buildStreamsFromItems;
44
+ private mergeStreams;
45
+ getLayoutsObservable(): Observable<MultiViewLayout[]>;
46
+ getSelectedLayoutId(): Observable<string | null>;
47
+ onSelectLayout(layoutId: string): void;
48
+ onToggleCamera(streamId: string, cameraId: string): void;
34
49
  /**
35
50
  * Notify client app about a Product API auth error.
36
51
  * Uses configured callback first, and falls back to onPanelEvent.
@@ -51,7 +66,13 @@ export declare class MultiViewViewModel extends ViewModel {
51
66
  */
52
67
  onSelectCuratedMultiview(item: CuratedMultiviewItem): void;
53
68
  /**
54
- * Stop polling for bet data
69
+ * @description Update polling interval from panel config.
70
+ * Refresh polling if necessary.
71
+ */
72
+ private updatePollingInterval;
73
+ private getPanelConfigInterval;
74
+ /**
75
+ * Stop polling for multiview data
55
76
  */
56
77
  stopPolling(): void;
57
78
  /**
@@ -1,9 +1,12 @@
1
1
  import { ViewModel } from '@/view-models/ViewModel';
2
2
  import { Observable } from '@/helpers/Observable';
3
+ import { stringHashCode } from '@/helpers/strings';
3
4
  import { DEFAULT_LAYOUTS_BY_LISTINGS_QUANTITY, } from '../types';
4
5
  import MultiViewService, { isProductAPIValid, } from '../services/MultiViewService';
5
6
  import { AuthError } from '@/services/NetworkManager/errors';
6
7
  import { captureException } from '@/sentry';
8
+ import SDK from '@/index';
9
+ const DEFAULT_POLLING_INTERVAL = 30000;
7
10
  export class MultiViewViewModel extends ViewModel {
8
11
  constructor(eventId, delegate, analyticsService) {
9
12
  super();
@@ -55,6 +58,12 @@ export class MultiViewViewModel extends ViewModel {
55
58
  writable: true,
56
59
  value: null
57
60
  });
61
+ Object.defineProperty(this, "pollingInterval", {
62
+ enumerable: true,
63
+ configurable: true,
64
+ writable: true,
65
+ value: DEFAULT_POLLING_INTERVAL
66
+ });
58
67
  Object.defineProperty(this, "items", {
59
68
  enumerable: true,
60
69
  configurable: true,
@@ -73,6 +82,18 @@ export class MultiViewViewModel extends ViewModel {
73
82
  writable: true,
74
83
  value: void 0
75
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
+ });
76
97
  this.eventId = eventId;
77
98
  this.delegate = delegate;
78
99
  this.analyticsService = analyticsService;
@@ -90,6 +111,98 @@ export class MultiViewViewModel extends ViewModel {
90
111
  getEventId() {
91
112
  return this.eventId;
92
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
+ }
93
206
  /**
94
207
  * Notify client app about a Product API auth error.
95
208
  * Uses configured callback first, and falls back to onPanelEvent.
@@ -123,6 +236,10 @@ export class MultiViewViewModel extends ViewModel {
123
236
  try {
124
237
  await this.trackPanelView();
125
238
  await this.fetchLayouts();
239
+ const panelConfigInterval = this.getPanelConfigInterval();
240
+ if (panelConfigInterval) {
241
+ this.pollingInterval = panelConfigInterval;
242
+ }
126
243
  this.refreshPolling();
127
244
  }
128
245
  catch (error) {
@@ -140,9 +257,12 @@ export class MultiViewViewModel extends ViewModel {
140
257
  try {
141
258
  this.isDataLoading.value = true;
142
259
  const layouts = await this.service.getLayouts();
143
- if (layouts) {
260
+ if (layouts && layouts.length > 0) {
144
261
  this._layouts.value = layouts;
145
262
  }
263
+ else {
264
+ this._layouts.value = Object.values(DEFAULT_LAYOUTS_BY_LISTINGS_QUANTITY);
265
+ }
146
266
  }
147
267
  catch (error) {
148
268
  console.log('[MultiViewViewModel] Error fetching layouts:', error);
@@ -150,9 +270,8 @@ export class MultiViewViewModel extends ViewModel {
150
270
  this.notifyProductAPIUnauthorized(error);
151
271
  return;
152
272
  }
153
- const errorMessage = error instanceof Error ? error.message : 'Failed to fetch layouts';
154
- if (!this.hasEverSuccessfullyLoaded.value) {
155
- this.setError(errorMessage);
273
+ if (this._layouts.value.length === 0) {
274
+ this._layouts.value = Object.values(DEFAULT_LAYOUTS_BY_LISTINGS_QUANTITY);
156
275
  }
157
276
  }
158
277
  finally {
@@ -161,10 +280,9 @@ export class MultiViewViewModel extends ViewModel {
161
280
  }
162
281
  async setDataToPanel(data) {
163
282
  try {
164
- const callback = typeof data.callback === 'function' ? data.callback : undefined;
165
- if (isProductAPIValid(data)) {
166
- this.onAuthError = callback;
167
- this.service.setProductAPIData(data);
283
+ if (isProductAPIValid(data.productAPIRequest)) {
284
+ this.service.setProductAPIData(data.productAPIRequest);
285
+ this.onAuthError = data.productAPIRequest.callback;
168
286
  await this.fetchLayouts();
169
287
  this.refreshPolling();
170
288
  }
@@ -207,6 +325,8 @@ export class MultiViewViewModel extends ViewModel {
207
325
  cleanup() {
208
326
  this.stopPolling();
209
327
  this.hasEverSuccessfullyLoaded.value = false;
328
+ this._selectedLayoutId.value = null;
329
+ this.lastResponseHash = null;
210
330
  }
211
331
  /**
212
332
  * Called when a curated multiview is selected. Notifies the client via onPanelEvent
@@ -234,7 +354,29 @@ export class MultiViewViewModel extends ViewModel {
234
354
  });
235
355
  }
236
356
  /**
237
- * Stop polling for bet data
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
238
380
  */
239
381
  stopPolling() {
240
382
  if (this.pollingTask) {
@@ -252,11 +394,17 @@ export class MultiViewViewModel extends ViewModel {
252
394
  // First stop any existing polling
253
395
  this.stopPolling();
254
396
  // Start the poll loop
255
- this.pollingTask = this.service.pollCuratedMultiviews(30000, this.processCuratedMultiviewData.bind(this), (error) => {
397
+ this.pollingTask = this.service.pollCuratedMultiviews(this.pollingInterval, (data) => {
398
+ this.updatePollingInterval();
399
+ this.processCuratedMultiviewData(data);
400
+ }, (error) => {
256
401
  console.error('[MultiViewViewModel] Error polling data:', error);
257
402
  if (error instanceof AuthError) {
258
403
  this.notifyProductAPIUnauthorized(error);
259
404
  }
405
+ if (!this.hasEverSuccessfullyLoaded.value) {
406
+ this.setError(error instanceof Error ? error.message : 'Failed to load data');
407
+ }
260
408
  });
261
409
  }
262
410
  processCuratedMultiviewData(data) {
@@ -266,6 +414,13 @@ export class MultiViewViewModel extends ViewModel {
266
414
  if (!Array.isArray(data.data.items)) {
267
415
  return;
268
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;
269
424
  const curatedMultiviewItem = [];
270
425
  for (const item of data.data.items) {
271
426
  const listings = [];
@@ -274,12 +429,18 @@ export class MultiViewViewModel extends ViewModel {
274
429
  }
275
430
  if (item.multiview_metadata && Array.isArray(item.multiview_metadata)) {
276
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
+ }
277
440
  listings.push({
278
441
  id: mv.entity_id || '',
279
- description: mv.is_sportingevent && mv.left_team && mv.right_team
280
- ? `${mv.left_team.abbreviation} vs ${mv.right_team.abbreviation}`
281
- : item.title || '',
282
- backgroundImage: 'https://placedog.net/640/480',
442
+ description,
443
+ backgroundImage: mv.images?.series_list || '',
283
444
  type: mv.type || 'unknown',
284
445
  });
285
446
  }
@@ -300,7 +461,13 @@ export class MultiViewViewModel extends ViewModel {
300
461
  }
301
462
  const newItems = [...this.items.value];
302
463
  const existingCuratedMultiviewIndex = newItems.findIndex((i) => i.type === 'curated-multiviews');
303
- if (existingCuratedMultiviewIndex !== -1) {
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) {
304
471
  newItems[existingCuratedMultiviewIndex] = {
305
472
  type: 'curated-multiviews',
306
473
  items: curatedMultiviewItem,
@@ -312,6 +479,19 @@ export class MultiViewViewModel extends ViewModel {
312
479
  items: curatedMultiviewItem,
313
480
  });
314
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
+ }
315
495
  this.items.value = newItems;
316
496
  if (!this.hasEverSuccessfullyLoaded.value) {
317
497
  this.hasEverSuccessfullyLoaded.value = true;
@@ -322,6 +502,7 @@ export class MultiViewViewModel extends ViewModel {
322
502
  }
323
503
  async clearTabLoadFailure() {
324
504
  this.setError(null);
505
+ this.hasEverSuccessfullyLoaded.value = false;
325
506
  await this.fetchLayouts();
326
507
  this.refreshPolling();
327
508
  }
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.2.0",
4
+ "version": "5.2.1",
5
5
  "type": "commonjs",
6
6
  "license": "MIT",
7
7
  "main": "dist/maestro-web-sdk.umd.js",