@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.
- 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/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 +1 -1
- package/dist/39.maestro-web-sdk.esm.js.map +1 -1
- package/dist/6.maestro-web-sdk.esm.js +1 -1
- package/dist/7.maestro-web-sdk.esm.js +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/maestro-web-sdk.esm.js +2 -2
- package/dist/maestro-web-sdk.umd.js +2 -2
- package/dist/maestro-web-sdk.umd.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/src/modules/espn/bets/services/BetsService.js +5 -5
- package/dist/src/modules/espn/fantasy/services/FantasyService.js +3 -0
- package/dist/src/modules/espn/fantasy/view/FantasyView.js +2 -1
- 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/shop/services/ShopService.js +1 -1
- package/dist/src/modules/espn/stats/services/StatsService.js +2 -2
- package/dist/src/modules/fox/multi-view/services/MultiViewService.d.ts +20 -15
- package/dist/src/modules/fox/multi-view/services/MultiViewService.js +6 -3
- package/dist/src/modules/fox/multi-view/types.d.ts +20 -0
- 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.js +2 -1
- package/dist/src/modules/fox/multi-view/view/FoxMultiView.js +3 -0
- 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 +2 -6
- package/dist/src/modules/fox/multi-view/view/components/curated-multiview-card/CuratedMultiviewCard.js +27 -7
- package/dist/src/modules/fox/multi-view/view-model/MultiViewViewModel.d.ts +25 -4
- package/dist/src/modules/fox/multi-view/view-model/MultiViewViewModel.js +196 -15
- 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 },
|
|
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
|
}
|
package/dist/src/modules/fox/multi-view/view/components/BuildYourOwnTabs/BuildYourOwnTabs.d.ts
ADDED
|
@@ -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;
|
|
@@ -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
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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" },
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
154
|
-
|
|
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
|
-
|
|
165
|
-
|
|
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
|
-
*
|
|
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(
|
|
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
|
|
280
|
-
|
|
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 (
|
|
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
|
}
|