@applicaster/zapp-react-native-ui-components 15.0.0-rc.99 → 16.0.0-rc.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/Components/Cell/TvOSCellComponent.tsx +1 -3
- package/Components/GeneralContentScreen/GeneralContentScreen.tsx +39 -28
- package/Components/GeneralContentScreen/__tests__/GeneralContentScreen.test.tsx +104 -0
- package/Components/GeneralContentScreen/utils/__tests__/getScreenDataSource.test.ts +19 -0
- package/Components/GeneralContentScreen/utils/getScreenDataSource.ts +9 -0
- package/Components/HandlePlayable/HandlePlayable.tsx +16 -29
- package/Components/HandlePlayable/utils.ts +31 -0
- package/Components/HookRenderer/HookRenderer.tsx +40 -10
- package/Components/HookRenderer/__tests__/HookRenderer.test.tsx +60 -0
- package/Components/Layout/TV/NavBarContainer.tsx +1 -10
- package/Components/Layout/TV/__tests__/__snapshots__/NavBarContainer.test.tsx.snap +7 -12
- package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +7 -12
- package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +5 -0
- package/Components/MasterCell/CONFIG_BUILDER_TO_REACT_COMPONENT.md +144 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/model.test.ts +80 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +187 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/selectors.test.ts +45 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/style.test.ts +49 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +165 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +405 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/index.ts +1 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/model.ts +47 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +170 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/selectors.ts +26 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/style.ts +29 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/types.ts +37 -0
- package/Components/MasterCell/DefaultComponents/Button.tsx +0 -15
- package/Components/MasterCell/DefaultComponents/ButtonContainerView/components/HorizontalSeparator.tsx +8 -0
- package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tsx +15 -0
- package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tv.android.tsx +58 -0
- package/Components/MasterCell/DefaultComponents/{tv/ButtonContainerView/index.tsx → ButtonContainerView/index.tv.tsx} +3 -11
- package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.web.ts +1 -0
- package/Components/MasterCell/DefaultComponents/ButtonContainerView/types.ts +40 -0
- package/Components/MasterCell/DefaultComponents/DataProvider/index.tsx +163 -0
- package/Components/MasterCell/DefaultComponents/FocusableView/index.android.tsx +2 -23
- package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -22
- package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +3 -1
- package/Components/MasterCell/DefaultComponents/LiveImage/__tests__/prepareEntry.test.ts +352 -0
- package/Components/MasterCell/DefaultComponents/LiveImage/executePreloadHooks.ts +136 -0
- package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +33 -16
- package/Components/MasterCell/DefaultComponents/PressableView.tsx +34 -0
- package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +11 -0
- package/Components/MasterCell/DefaultComponents/Text/index.tsx +2 -6
- package/Components/MasterCell/DefaultComponents/__tests__/DataProvider.test.tsx +141 -0
- package/Components/MasterCell/DefaultComponents/index.ts +9 -3
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +135 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Asset.ts +33 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +22 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +125 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +16 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabel.ts +67 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +37 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +393 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/builders.test.ts +141 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +343 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/helpers.ts +105 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +122 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +118 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +238 -0
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Asset.ts +4 -18
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Button.ts +24 -73
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TextLabelsContainer.ts +37 -18
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TvActionButton.tsx +27 -0
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/index.test.ts +89 -0
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/renderedTree.test.tsx +231 -0
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +47 -52
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +35 -171
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +98 -145
- package/Components/MasterCell/MappingFunctions/index.js +3 -2
- package/Components/MasterCell/README.md +4 -0
- package/Components/MasterCell/__tests__/__snapshots__/dataAdapter.test.js.snap +24 -0
- package/Components/MasterCell/__tests__/configInflater.test.js +1 -0
- package/Components/MasterCell/__tests__/elementMapper.test.js +46 -0
- package/Components/MasterCell/dataAdapter.ts +4 -1
- package/Components/MasterCell/elementMapper.tsx +52 -7
- package/Components/MasterCell/utils/__tests__/cloneChildrenWithIds.test.tsx +43 -0
- package/Components/MasterCell/utils/__tests__/useFilterChildren.test.tsx +80 -0
- package/Components/MasterCell/utils/index.ts +85 -15
- package/Components/Navigator/StackNavigator.tsx +6 -0
- package/Components/PlayerContainer/PlayerContainer.tsx +2 -19
- package/Components/PreloaderWrapper/__tests__/index.test.tsx +26 -0
- package/Components/PreloaderWrapper/index.tsx +15 -0
- package/Components/River/ComponentsMap/ComponentsMap.tsx +2 -16
- package/Components/River/RefreshControl.tsx +19 -82
- package/Components/River/River.tsx +9 -82
- package/Components/River/RiverItem.tsx +26 -20
- package/Components/River/hooks/__tests__/usePullToRefresh.test.ts +132 -0
- package/Components/River/hooks/index.ts +1 -0
- package/Components/River/hooks/usePullToRefresh.ts +51 -0
- package/Components/Screen/__tests__/Screen.test.tsx +1 -0
- package/Components/Screen/hooks.ts +73 -3
- package/Components/Screen/index.tsx +7 -1
- package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +46 -0
- package/Components/ScreenFeedLoader/__tests__/ScreenFeedLoader.test.tsx +94 -0
- package/Components/ScreenFeedLoader/index.ts +1 -0
- package/Components/ScreenResolver/__tests__/screenResolver.test.js +24 -0
- package/Components/ScreenResolver/hooks/index.ts +3 -0
- package/Components/ScreenResolver/hooks/useGetComponent.ts +15 -0
- package/Components/ScreenResolver/hooks/useScreenComponentResolver.tsx +90 -0
- package/Components/ScreenResolver/index.tsx +15 -117
- package/Components/ScreenResolver/utils/__tests__/getScreenTypeProps.test.ts +45 -0
- package/Components/ScreenResolver/utils/getScreenTypeProps.ts +43 -0
- package/Components/ScreenResolver/utils/index.ts +1 -0
- package/Components/ScreenResolver/withDefaultScreenContext.tsx +16 -0
- package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +25 -0
- package/Components/ScreenResolverFeedProvider/__tests__/ScreenResolverFeedProvider.test.tsx +44 -0
- package/Components/ScreenResolverFeedProvider/index.ts +1 -0
- package/Components/ScreenRevealManager/withScreenRevealManager.tsx +4 -1
- package/Components/TopCutoffOverlay/__tests__/TopCutoffOverlay.test.tsx +201 -0
- package/Components/TopCutoffOverlay/hooks/__tests__/useMarginTop.test.ts +130 -0
- package/Components/TopCutoffOverlay/hooks/index.ts +1 -0
- package/Components/TopCutoffOverlay/hooks/useMarginTop.ts +59 -0
- package/Components/TopCutoffOverlay/index.tsx +55 -0
- package/Components/Transitioner/Scene.tsx +9 -15
- package/Components/VideoLive/LiveImageManager.ts +199 -54
- package/Components/VideoLive/PlayerLiveImageComponent.tsx +31 -33
- package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +2 -17
- package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +0 -2
- package/Components/Viewport/ViewportAware/index.tsx +16 -7
- package/Components/ZappUIComponent/index.tsx +12 -6
- package/Components/default-cell-renderer/viewTrees/mobile/index.ts +0 -3
- package/Components/index.js +1 -1
- package/Contexts/ScreenContext/__tests__/index.test.tsx +57 -0
- package/Contexts/ScreenContext/index.tsx +46 -1
- package/Contexts/ZappPipesContext/ZappPipesContextFactory.tsx +18 -7
- package/Decorators/ZappPipesDataConnector/ResolverSelector.tsx +25 -7
- package/Decorators/ZappPipesDataConnector/__tests__/ResolverSelector.test.tsx +212 -5
- package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +39 -21
- package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +18 -7
- package/package.json +5 -5
- package/Components/MasterCell/DefaultComponents/Text/utils/__tests__/withAdjustedLineHeight.test.ts +0 -46
- package/Components/MasterCell/DefaultComponents/Text/utils/index.ts +0 -21
- package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +0 -135
- package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/types.ts +0 -25
- package/Components/PlayerContainer/ErrorDisplay/ErrorDisplay.tsx +0 -57
- package/Components/PlayerContainer/ErrorDisplay/index.ts +0 -9
- package/Components/PlayerContainer/useRestrictMobilePlayback.tsx +0 -101
- /package/Components/HookRenderer/{index.tsx → index.ts} +0 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { prepareEntry } from "../index";
|
|
2
|
+
|
|
3
|
+
const makeEntry = (overrides = {}): any => ({
|
|
4
|
+
content: { src: "https://example.com/video.mp4", type: "video/mp4" },
|
|
5
|
+
extensions: {},
|
|
6
|
+
...overrides,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
describe("prepareEntry", () => {
|
|
10
|
+
it("should return null when entry is null or undefined", () => {
|
|
11
|
+
expect(prepareEntry(null)).toBeNull();
|
|
12
|
+
expect(prepareEntry(undefined)).toBeNull();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe("preview_playback_override", () => {
|
|
16
|
+
it("should deep merge override with entry, override taking precedence", () => {
|
|
17
|
+
const override = {
|
|
18
|
+
link: { href: "https://video-preload.com/id", rel: "self" },
|
|
19
|
+
extensions: {
|
|
20
|
+
brightcove: { video_id: 1234 },
|
|
21
|
+
preview: true,
|
|
22
|
+
free: true,
|
|
23
|
+
requires_authentication: false,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const entry = makeEntry({
|
|
28
|
+
extensions: {
|
|
29
|
+
preview_playback_override: override,
|
|
30
|
+
existing_field: "keep-me",
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const result = prepareEntry(entry);
|
|
35
|
+
|
|
36
|
+
expect(result.extensions.brightcove.video_id).toBe(1234);
|
|
37
|
+
expect(result.extensions.preview).toBe(true);
|
|
38
|
+
expect(result.extensions.existing_field).toBe("keep-me");
|
|
39
|
+
|
|
40
|
+
expect(result.link).toEqual({
|
|
41
|
+
href: "https://video-preload.com/id",
|
|
42
|
+
rel: "self",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(result.content).toEqual(entry.content);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should override entry fields when both have the same key", () => {
|
|
49
|
+
const override = {
|
|
50
|
+
extensions: {
|
|
51
|
+
brightcove: { video_id: 9999 },
|
|
52
|
+
},
|
|
53
|
+
content: { src: "https://override.com/video.mp4" },
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const entry = makeEntry({
|
|
57
|
+
extensions: {
|
|
58
|
+
preview_playback_override: override,
|
|
59
|
+
brightcove: { video_id: 1111, account_id: "abc" },
|
|
60
|
+
},
|
|
61
|
+
content: { src: "https://original.com/video.mp4", type: "video/mp4" },
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const result = prepareEntry(entry);
|
|
65
|
+
|
|
66
|
+
expect(result.extensions.brightcove.video_id).toBe(9999);
|
|
67
|
+
expect(result.extensions.brightcove.account_id).toBe("abc");
|
|
68
|
+
expect(result.content.src).toBe("https://override.com/video.mp4");
|
|
69
|
+
expect(result.content.type).toBe("video/mp4");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should take priority over other playback sources", () => {
|
|
73
|
+
const override = {
|
|
74
|
+
link: { href: "https://override.com" },
|
|
75
|
+
extensions: { brightcove: { video_id: 5555 } },
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const entry = makeEntry({
|
|
79
|
+
extensions: {
|
|
80
|
+
preview_playback_override: override,
|
|
81
|
+
brightcove: { preview_playback: "other-id" },
|
|
82
|
+
preview_playback: "https://other.com/video.mp4",
|
|
83
|
+
},
|
|
84
|
+
content: {
|
|
85
|
+
src: "https://example.com/video.mp4",
|
|
86
|
+
teaser: "https://teaser.com/video.mp4",
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const result = prepareEntry(entry);
|
|
91
|
+
|
|
92
|
+
expect(result.link).toEqual({ href: "https://override.com" });
|
|
93
|
+
expect(result.extensions.brightcove.video_id).toBe(5555);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("brightcove preview_playback", () => {
|
|
98
|
+
it("should return entry with brightcove video_id replaced by preview_playback", () => {
|
|
99
|
+
const entry = makeEntry({
|
|
100
|
+
extensions: {
|
|
101
|
+
brightcove: {
|
|
102
|
+
video_id: "original-id",
|
|
103
|
+
preview_playback: "preview-id",
|
|
104
|
+
},
|
|
105
|
+
some_other: "value",
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const result = prepareEntry(entry);
|
|
110
|
+
|
|
111
|
+
expect(result).toEqual({
|
|
112
|
+
...entry,
|
|
113
|
+
extensions: {
|
|
114
|
+
...entry.extensions,
|
|
115
|
+
brightcove: {
|
|
116
|
+
video_id: "preview-id",
|
|
117
|
+
preview_playback: "preview-id",
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should preserve other extensions", () => {
|
|
124
|
+
const entry = makeEntry({
|
|
125
|
+
extensions: {
|
|
126
|
+
brightcove: { preview_playback: "preview-id" },
|
|
127
|
+
custom_field: "keep-me",
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const result = prepareEntry(entry);
|
|
132
|
+
|
|
133
|
+
expect(result.extensions.custom_field).toBe("keep-me");
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe("extensions.preview_playback (non-brightcove)", () => {
|
|
138
|
+
it("should return entry with content.src set to preview_playback URL", () => {
|
|
139
|
+
const entry = makeEntry({
|
|
140
|
+
extensions: { preview_playback: "https://preview.com/video.m3u8" },
|
|
141
|
+
content: { src: "https://original.com/video.mp4" },
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const result = prepareEntry(entry);
|
|
145
|
+
|
|
146
|
+
expect(result.content.src).toBe("https://preview.com/video.m3u8");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("should preserve other content fields", () => {
|
|
150
|
+
const entry = makeEntry({
|
|
151
|
+
extensions: { preview_playback: "https://preview.com/video.m3u8" },
|
|
152
|
+
content: { src: "https://original.com/video.mp4", type: "video/mp4" },
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const result = prepareEntry(entry);
|
|
156
|
+
|
|
157
|
+
expect(result.content.type).toBe("video/mp4");
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("content.teaser", () => {
|
|
162
|
+
it("should return entry with content.src set to teaser URL", () => {
|
|
163
|
+
const entry = makeEntry({
|
|
164
|
+
extensions: {},
|
|
165
|
+
content: {
|
|
166
|
+
src: "https://original.com/video.mp4",
|
|
167
|
+
teaser: "https://teaser.com/teaser.mp4",
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const result = prepareEntry(entry);
|
|
172
|
+
|
|
173
|
+
expect(result.content.src).toBe("https://teaser.com/teaser.mp4");
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe("brightcove video_id (without preview_playback)", () => {
|
|
178
|
+
it("should return the entry as-is when brightcove video_id exists", () => {
|
|
179
|
+
const entry = makeEntry({
|
|
180
|
+
extensions: { brightcove: { video_id: "some-id" } },
|
|
181
|
+
content: { src: "" },
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const result = prepareEntry(entry);
|
|
185
|
+
|
|
186
|
+
expect(result).toBe(entry);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe("content.src fallback", () => {
|
|
191
|
+
it("should return null when content.src is missing", () => {
|
|
192
|
+
const entry = makeEntry({
|
|
193
|
+
extensions: {},
|
|
194
|
+
content: {},
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
expect(prepareEntry(entry)).toBeNull();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("should return null when content.src is empty", () => {
|
|
201
|
+
const entry = makeEntry({
|
|
202
|
+
extensions: {},
|
|
203
|
+
content: { src: "" },
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
expect(prepareEntry(entry)).toBeNull();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("should return entry when content.type starts with video", () => {
|
|
210
|
+
const entry = makeEntry({
|
|
211
|
+
extensions: {},
|
|
212
|
+
content: { src: "https://example.com/stream", type: "video/mp4" },
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
expect(prepareEntry(entry)).toBe(entry);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("should return entry when src has a known video extension", () => {
|
|
219
|
+
const extensions = ["m3u8", "mp4", "m4v", "mkv", "mov", "mpd", "ogv"];
|
|
220
|
+
|
|
221
|
+
for (const ext of extensions) {
|
|
222
|
+
const entry = makeEntry({
|
|
223
|
+
extensions: {},
|
|
224
|
+
content: { src: `https://example.com/video.${ext}` },
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
expect(prepareEntry(entry)).toBe(entry);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("should return entry for audio extensions", () => {
|
|
232
|
+
const extensions = ["mp3", "oga", "opus"];
|
|
233
|
+
|
|
234
|
+
for (const ext of extensions) {
|
|
235
|
+
const entry = makeEntry({
|
|
236
|
+
extensions: {},
|
|
237
|
+
content: { src: `https://example.com/audio.${ext}` },
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
expect(prepareEntry(entry)).toBe(entry);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("should return null for non-video/non-audio URLs", () => {
|
|
245
|
+
const entry = makeEntry({
|
|
246
|
+
extensions: {},
|
|
247
|
+
content: { src: "https://example.com/page.html" },
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
expect(prepareEntry(entry)).toBeNull();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("should return null for URLs without extensions", () => {
|
|
254
|
+
const entry = makeEntry({
|
|
255
|
+
extensions: {},
|
|
256
|
+
content: { src: "https://example.com/stream" },
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
expect(prepareEntry(entry)).toBeNull();
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
describe("priority order", () => {
|
|
264
|
+
it("should prefer brightcove.preview_playback over extensions.preview_playback", () => {
|
|
265
|
+
const entry = makeEntry({
|
|
266
|
+
extensions: {
|
|
267
|
+
brightcove: { preview_playback: "bc-preview" },
|
|
268
|
+
preview_playback: "ext-preview",
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const result = prepareEntry(entry);
|
|
273
|
+
|
|
274
|
+
expect(result.extensions.brightcove.video_id).toBe("bc-preview");
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it("should prefer extensions.preview_playback over content.teaser", () => {
|
|
278
|
+
const entry = makeEntry({
|
|
279
|
+
extensions: { preview_playback: "https://preview.com/video.mp4" },
|
|
280
|
+
content: {
|
|
281
|
+
src: "https://original.com/video.mp4",
|
|
282
|
+
teaser: "https://teaser.com/video.mp4",
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const result = prepareEntry(entry);
|
|
287
|
+
|
|
288
|
+
expect(result.content.src).toBe("https://preview.com/video.mp4");
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("should prefer preview_playback_override over all other sources", () => {
|
|
292
|
+
const override = {
|
|
293
|
+
extensions: { brightcove: { video_id: "override-id" } },
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const entry = makeEntry({
|
|
297
|
+
extensions: {
|
|
298
|
+
preview_playback_override: override,
|
|
299
|
+
brightcove: { preview_playback: "bc-preview", video_id: "original" },
|
|
300
|
+
preview_playback: "https://ext-preview.com/video.mp4",
|
|
301
|
+
},
|
|
302
|
+
content: {
|
|
303
|
+
src: "https://original.com/video.mp4",
|
|
304
|
+
teaser: "https://teaser.com/video.mp4",
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const result = prepareEntry(entry);
|
|
309
|
+
|
|
310
|
+
expect(result.extensions.brightcove.video_id).toBe("override-id");
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("should prefer content.teaser over brightcove.video_id", () => {
|
|
314
|
+
const entry = makeEntry({
|
|
315
|
+
extensions: { brightcove: { video_id: "some-id" } },
|
|
316
|
+
content: {
|
|
317
|
+
src: "https://original.com/video.mp4",
|
|
318
|
+
teaser: "https://teaser.com/teaser.mp4",
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const result = prepareEntry(entry);
|
|
323
|
+
|
|
324
|
+
expect(result.content.src).toBe("https://teaser.com/teaser.mp4");
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it("should prefer content.teaser over content.src URL check", () => {
|
|
328
|
+
const entry = makeEntry({
|
|
329
|
+
extensions: {},
|
|
330
|
+
content: {
|
|
331
|
+
src: "https://original.com/video.mp4",
|
|
332
|
+
teaser: "https://teaser.com/teaser.m3u8",
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const result = prepareEntry(entry);
|
|
337
|
+
|
|
338
|
+
expect(result.content.src).toBe("https://teaser.com/teaser.m3u8");
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it("should prefer brightcove.video_id over content.src URL check", () => {
|
|
342
|
+
const entry = makeEntry({
|
|
343
|
+
extensions: { brightcove: { video_id: "bc-id" } },
|
|
344
|
+
content: { src: "https://example.com/page.html" },
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
const result = prepareEntry(entry);
|
|
348
|
+
|
|
349
|
+
expect(result).toBe(entry);
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { findPluginByIdentifier } from "@applicaster/zapp-react-native-utils/pluginUtils";
|
|
2
|
+
import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
|
|
3
|
+
import { loggerLiveImageManager } from "../../../VideoLive/loggerHelper";
|
|
4
|
+
|
|
5
|
+
const { log_debug, log_error } = loggerLiveImageManager;
|
|
6
|
+
|
|
7
|
+
export type PreloadHookConfig = ZappPreloadPlugins & {
|
|
8
|
+
configuration: any;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function runHookWithCallback(
|
|
12
|
+
invoke: (
|
|
13
|
+
payload: any,
|
|
14
|
+
callback: (result: hookCallbackArgs) => void,
|
|
15
|
+
abortCallback: () => void
|
|
16
|
+
) => void,
|
|
17
|
+
payload: any
|
|
18
|
+
): Promise<{ payload: any; aborted: boolean }> {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const abortCallback = () => {
|
|
21
|
+
resolve({ payload, aborted: true });
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
invoke(
|
|
26
|
+
payload,
|
|
27
|
+
({
|
|
28
|
+
success,
|
|
29
|
+
error,
|
|
30
|
+
payload: resultPayload,
|
|
31
|
+
abort,
|
|
32
|
+
}: hookCallbackArgs) => {
|
|
33
|
+
if (error) {
|
|
34
|
+
reject(error);
|
|
35
|
+
} else if (!success || abort) {
|
|
36
|
+
resolve({ payload: resultPayload ?? payload, aborted: true });
|
|
37
|
+
} else {
|
|
38
|
+
resolve({ payload: resultPayload ?? payload, aborted: false });
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
abortCallback
|
|
42
|
+
);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
log_error(`Error running preload hook: ${error.message}`, { error });
|
|
45
|
+
reject(error);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// TODO: Move this function to a more generic place,
|
|
51
|
+
// as it can be used by other components that want to run preload hooks
|
|
52
|
+
// and add test coverage for it
|
|
53
|
+
export async function executePreloadHooks({
|
|
54
|
+
preloadHooks,
|
|
55
|
+
entry,
|
|
56
|
+
}: {
|
|
57
|
+
preloadHooks: PreloadHookConfig[];
|
|
58
|
+
entry: ZappEntry;
|
|
59
|
+
}): Promise<ZappEntry | null> {
|
|
60
|
+
if (!preloadHooks?.length) {
|
|
61
|
+
return entry;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const sortedHooks = [...preloadHooks].sort(
|
|
65
|
+
(a, b) => (a.weight || 0) - (b.weight || 0)
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const hookNames = sortedHooks.map((hook) => hook.identifier).join(", ");
|
|
69
|
+
|
|
70
|
+
log_debug(
|
|
71
|
+
`Preload hook sequence: ${hookNames}, entry: ${entry.id} - ${entry.title}`
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
let payload: any = entry;
|
|
75
|
+
|
|
76
|
+
let needsAbort = false;
|
|
77
|
+
const plugins = appStore.get("plugins");
|
|
78
|
+
|
|
79
|
+
for (const hookConfig of sortedHooks) {
|
|
80
|
+
const plugin = findPluginByIdentifier(hookConfig.identifier, plugins, true);
|
|
81
|
+
|
|
82
|
+
if (!plugin?.module) {
|
|
83
|
+
log_debug(
|
|
84
|
+
`Preload hook plugin not found: ${hookConfig.identifier}, skipping`
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const { module: hookModule } = plugin;
|
|
91
|
+
|
|
92
|
+
if (hookModule.skipHook?.(payload)) {
|
|
93
|
+
log_debug(`Skipping hook: ${hookConfig.identifier}`);
|
|
94
|
+
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (hookModule.runInBackground) {
|
|
99
|
+
log_debug(
|
|
100
|
+
`Running background hook: ${hookConfig.identifier}, entry: ${payload?.id} - ${payload?.title}`
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const result = await runHookWithCallback(
|
|
104
|
+
(item, callback, abortCallback) =>
|
|
105
|
+
hookModule
|
|
106
|
+
.runInBackground(item, callback, hookConfig, abortCallback)
|
|
107
|
+
?.catch?.((error: Error) => {
|
|
108
|
+
log_error(
|
|
109
|
+
`Error running background preload hook: ${hookConfig.identifier}, error: ${error.message}`,
|
|
110
|
+
{ error }
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
callback({ success: false, payload: item, error });
|
|
114
|
+
}),
|
|
115
|
+
payload
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
payload = result.payload;
|
|
119
|
+
needsAbort = result.aborted;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (needsAbort) {
|
|
123
|
+
log_debug(
|
|
124
|
+
`Preload hook requested abort: ${hookConfig.identifier}, stopping chain`
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (needsAbort) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return payload;
|
|
136
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import
|
|
2
|
+
import { merge, clone } from "@applicaster/zapp-react-native-utils/utils";
|
|
3
3
|
import { Platform, View, ViewStyle } from "react-native";
|
|
4
4
|
import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
|
|
5
5
|
import { isTV, isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
@@ -83,30 +83,22 @@ const removeAdsFromEntry = (entry) => {
|
|
|
83
83
|
return entry;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
const newEntry =
|
|
86
|
+
const newEntry = clone(entry);
|
|
87
87
|
|
|
88
88
|
delete newEntry.extensions.video_ads;
|
|
89
89
|
|
|
90
90
|
return newEntry;
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
-
const prepareEntry = (entry) => {
|
|
93
|
+
export const prepareEntry = (entry: ZappEntry) => {
|
|
94
94
|
if (!entry) {
|
|
95
95
|
return null;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
return {
|
|
100
|
-
...entry,
|
|
101
|
-
content: { ...entry?.content, src: entry?.extensions?.preview_playback },
|
|
102
|
-
};
|
|
103
|
-
}
|
|
98
|
+
const previewPlaybackOverride = entry.extensions?.preview_playback_override;
|
|
104
99
|
|
|
105
|
-
if (
|
|
106
|
-
return {
|
|
107
|
-
...entry,
|
|
108
|
-
content: { ...entry?.content, src: entry?.content?.teaser },
|
|
109
|
-
};
|
|
100
|
+
if (previewPlaybackOverride) {
|
|
101
|
+
return merge({}, entry, previewPlaybackOverride);
|
|
110
102
|
}
|
|
111
103
|
|
|
112
104
|
const previewPlayback = entry.extensions?.brightcove?.preview_playback;
|
|
@@ -124,6 +116,20 @@ const prepareEntry = (entry) => {
|
|
|
124
116
|
};
|
|
125
117
|
}
|
|
126
118
|
|
|
119
|
+
if (entry.extensions?.preview_playback) {
|
|
120
|
+
return {
|
|
121
|
+
...entry,
|
|
122
|
+
content: { ...entry?.content, src: entry?.extensions?.preview_playback },
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (entry.content?.teaser) {
|
|
127
|
+
return {
|
|
128
|
+
...entry,
|
|
129
|
+
content: { ...entry?.content, src: entry?.content?.teaser },
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
127
133
|
if (entry.extensions?.brightcove?.video_id) {
|
|
128
134
|
return entry;
|
|
129
135
|
}
|
|
@@ -167,14 +173,24 @@ function getFocusedState(
|
|
|
167
173
|
const getPlayerConfig = (player_screen_id, actionIdentifier) => {
|
|
168
174
|
if (player_screen_id) {
|
|
169
175
|
const rivers = appStore.get("rivers");
|
|
176
|
+
const plugins = appStore.get("plugins");
|
|
170
177
|
const playerScreen = rivers?.[player_screen_id] ?? null;
|
|
171
178
|
|
|
179
|
+
const preloadHooks = playerScreen?.hooks?.preload_plugins?.map((hook) => {
|
|
180
|
+
const configuration =
|
|
181
|
+
plugins.find((plugin) => plugin.identifier === hook.identifier)
|
|
182
|
+
?.configuration || {};
|
|
183
|
+
|
|
184
|
+
return { ...hook, configuration, ...rivers[hook.screen_id] };
|
|
185
|
+
});
|
|
186
|
+
|
|
172
187
|
// TODO: Check is it a player later
|
|
173
188
|
|
|
174
189
|
// TODO: Add more dict if needed from the screen component, styles, data etc
|
|
175
190
|
return {
|
|
176
191
|
playerPluginId: playerScreen?.type ?? DEFAULT_PLAYER_IDENTIFIER,
|
|
177
192
|
screenConfig: playerScreen?.general,
|
|
193
|
+
preloadHooks,
|
|
178
194
|
};
|
|
179
195
|
}
|
|
180
196
|
|
|
@@ -229,7 +245,7 @@ const LiveImageComponent = (props: LiveImageProps) => {
|
|
|
229
245
|
|
|
230
246
|
const isCurrentlyFocused = useTrackCurrentAutoScrollingElement(componentId);
|
|
231
247
|
|
|
232
|
-
const { playerPluginId, screenConfig } =
|
|
248
|
+
const { playerPluginId, screenConfig, preloadHooks } =
|
|
233
249
|
getPlayerConfig(player_screen_id, actionIdentifier) ?? {};
|
|
234
250
|
|
|
235
251
|
const playableEntry = playerPluginId ? getPlayableEntry(item) : null;
|
|
@@ -239,8 +255,8 @@ const LiveImageComponent = (props: LiveImageProps) => {
|
|
|
239
255
|
|
|
240
256
|
const isShouldRender =
|
|
241
257
|
playerPlugin &&
|
|
242
|
-
getFocusedState(state, componentType, isCurrentlyFocused) &&
|
|
243
258
|
playableEntry &&
|
|
259
|
+
getFocusedState(state, componentType, isCurrentlyFocused) &&
|
|
244
260
|
cellUUID &&
|
|
245
261
|
isSupportedTVForLiveImage() &&
|
|
246
262
|
!isScreenReaderEnabled;
|
|
@@ -259,6 +275,7 @@ const LiveImageComponent = (props: LiveImageProps) => {
|
|
|
259
275
|
imageKey={imageKey}
|
|
260
276
|
item={playableEntry}
|
|
261
277
|
audioMutedByDefault={audio_muted_by_default}
|
|
278
|
+
preloadHooks={preloadHooks}
|
|
262
279
|
/>
|
|
263
280
|
</View>
|
|
264
281
|
) : null}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { TouchableOpacity } from "react-native";
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
children?: React.ReactNode;
|
|
6
|
+
style?: Record<string, unknown>;
|
|
7
|
+
testID?: string;
|
|
8
|
+
accessibilityLabel?: string;
|
|
9
|
+
accessibilityHint?: string;
|
|
10
|
+
onPress?: () => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function PressableView({
|
|
14
|
+
children,
|
|
15
|
+
style = {},
|
|
16
|
+
testID,
|
|
17
|
+
accessibilityLabel,
|
|
18
|
+
accessibilityHint,
|
|
19
|
+
onPress,
|
|
20
|
+
}: Props) {
|
|
21
|
+
return (
|
|
22
|
+
<TouchableOpacity
|
|
23
|
+
activeOpacity={1}
|
|
24
|
+
onPress={onPress}
|
|
25
|
+
testID={testID}
|
|
26
|
+
accessibilityLabel={accessibilityLabel}
|
|
27
|
+
accessibilityHint={accessibilityHint}
|
|
28
|
+
accessible={!!(testID || accessibilityLabel)}
|
|
29
|
+
style={style}
|
|
30
|
+
>
|
|
31
|
+
{children}
|
|
32
|
+
</TouchableOpacity>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -45,6 +45,7 @@ export const useTextLabel = ({ label, entry }): string => {
|
|
|
45
45
|
const [entryStateLocal, setEntryStateLocal] =
|
|
46
46
|
React.useState(initialEntryState);
|
|
47
47
|
|
|
48
|
+
// For favourites
|
|
48
49
|
React.useEffect(() => {
|
|
49
50
|
return action?.addListeners?.(({ entryState, entry: actionEntry }) => {
|
|
50
51
|
if (entry.id === actionEntry.id) {
|
|
@@ -53,6 +54,16 @@ export const useTextLabel = ({ label, entry }): string => {
|
|
|
53
54
|
});
|
|
54
55
|
}, []);
|
|
55
56
|
|
|
57
|
+
// For rest actions
|
|
58
|
+
React.useEffect(() => {
|
|
59
|
+
// Update entryStateLocal when action state changes. Example: state change when pressing the download button.
|
|
60
|
+
if (typeof action?.addListener === "function") {
|
|
61
|
+
return action.addListener(String(entry?.id), (nextState) => {
|
|
62
|
+
setEntryStateLocal(nextState);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}, []);
|
|
66
|
+
|
|
56
67
|
if (context && name && action) {
|
|
57
68
|
return prepareHebrewText(extractLabel(entryStateLocal.label, name), isRTL);
|
|
58
69
|
}
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
} from "@applicaster/zapp-react-native-utils/cellUtils";
|
|
8
8
|
|
|
9
9
|
import { useTextLabel, withFocusedStyles } from "./hooks";
|
|
10
|
-
import { withScaledLineHeight } from "./utils";
|
|
11
10
|
import { toNumber } from "@applicaster/zapp-react-native-utils/numberUtils";
|
|
12
11
|
import { MeasurementPortalContext } from "../../../MeasurmentsPortal";
|
|
13
12
|
import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
@@ -70,12 +69,9 @@ const _Text = ({
|
|
|
70
69
|
|
|
71
70
|
return (
|
|
72
71
|
<Text
|
|
73
|
-
style={[
|
|
74
|
-
withScaledLineHeight(withFocusedStyles({ style, otherProps })),
|
|
75
|
-
{ height },
|
|
76
|
-
]}
|
|
77
|
-
allowFontScaling={false}
|
|
72
|
+
style={[withFocusedStyles({ style, otherProps }), { height }]}
|
|
78
73
|
{...withoutLabel(otherProps)}
|
|
74
|
+
allowFontScaling={false}
|
|
79
75
|
>
|
|
80
76
|
{textLabel}
|
|
81
77
|
</Text>
|