@applicaster/zapp-react-native-utils 14.0.9-alpha.2354002522 → 14.0.9-alpha.4253701132
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/configurationUtils/__tests__/imageSrcFromMediaItem.test.ts +38 -0
- package/configurationUtils/index.ts +17 -11
- package/manifestUtils/defaultManifestConfigurations/player.js +45 -0
- package/navigationUtils/index.ts +15 -5
- package/package.json +2 -2
- package/reactHooks/hookModal/hooks/useHookModalScreenData.ts +12 -8
- package/reactHooks/navigation/__tests__/index.test.tsx +41 -8
- package/reactHooks/navigation/index.ts +15 -4
- package/reactHooks/navigation/useRoute.ts +3 -1
- package/services/storageServiceSync.web.ts +1 -1
|
@@ -31,4 +31,42 @@ describe("imageSrcFromMediaItem", () => {
|
|
|
31
31
|
|
|
32
32
|
expect(imageSrcFromMediaItem(badEntry, ["image_base"])).toBeUndefined();
|
|
33
33
|
});
|
|
34
|
+
|
|
35
|
+
it("returns undefined when fallback is false and key is not found", () => {
|
|
36
|
+
const result = imageSrcFromMediaItem(entry as ZappEntry, [
|
|
37
|
+
"does_not_exist",
|
|
38
|
+
false,
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
expect(result).toBeUndefined();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("returns src when fallback is false and key is found", () => {
|
|
45
|
+
const result = imageSrcFromMediaItem(entry as ZappEntry, [
|
|
46
|
+
"logo_thumbnail",
|
|
47
|
+
false,
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
expect(result).toEqual(entry.media_group[1].media_item[0].src);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("returns image_base as fallback when fallback is explicitly true and key is not found", () => {
|
|
54
|
+
const result = imageSrcFromMediaItem(entry as ZappEntry, [
|
|
55
|
+
"does_not_exist",
|
|
56
|
+
true,
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
const fallback = entry.media_group[0].media_item[0];
|
|
60
|
+
expect(result).toEqual(fallback.src);
|
|
61
|
+
expect(fallback.key).toBe("image_base");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("returns src when fallback is explicitly true and key is found", () => {
|
|
65
|
+
const result = imageSrcFromMediaItem(entry as ZappEntry, [
|
|
66
|
+
"logo_thumbnail",
|
|
67
|
+
true,
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
expect(result).toEqual(entry.media_group[1].media_item[0].src);
|
|
71
|
+
});
|
|
34
72
|
});
|
|
@@ -155,9 +155,9 @@ export function getMediaItems(entry: ZappEntry): Option<ZappMediaItem[]> {
|
|
|
155
155
|
|
|
156
156
|
/**
|
|
157
157
|
* Retrieves the "src" value from a media item in the entry's media group,
|
|
158
|
-
* based on a provided key, with fallback logic.
|
|
158
|
+
* based on a provided key, with optional fallback logic.
|
|
159
159
|
*
|
|
160
|
-
* Fallback order:
|
|
160
|
+
* Fallback order (when enabled):
|
|
161
161
|
* 1. Attempts to find a media item with the specified key (or "image_base" if none provided).
|
|
162
162
|
* 2. If not found, attempts to find a media item with the key "image_base".
|
|
163
163
|
* 3. If still not found, falls back to the first available media item.
|
|
@@ -166,15 +166,19 @@ export function getMediaItems(entry: ZappEntry): Option<ZappMediaItem[]> {
|
|
|
166
166
|
* since empty URIs are invalid in some platforms (e.g., React Native).
|
|
167
167
|
*
|
|
168
168
|
* @param {ZappEntry} entry - The entry object containing a media group.
|
|
169
|
-
* @param {string[] | unknown} arg -
|
|
169
|
+
* @param {string[] | unknown} arg - Can be an array or any other value (treated as empty array).
|
|
170
|
+
* When an array:
|
|
171
|
+
* - First element: The key to look up. If omitted or undefined, defaults to "image_base".
|
|
172
|
+
* - Second element: Boolean to enable/disable fallback logic. If omitted or undefined, defaults to true.
|
|
170
173
|
* @returns {?string} The "src" URI from the matched media item, or undefined if not found or empty.
|
|
171
174
|
*/
|
|
172
175
|
export function imageSrcFromMediaItem(
|
|
173
176
|
entry: ZappEntry,
|
|
174
177
|
arg: string[] | unknown
|
|
175
178
|
): Option<string> {
|
|
176
|
-
const args:
|
|
179
|
+
const args: any = R.unless(Array.isArray, Array)(arg || []);
|
|
177
180
|
const imageKey: string = args?.[0] || "image_base"; // always a single key in this function
|
|
181
|
+
const fallback: boolean = args?.[1] !== false;
|
|
178
182
|
|
|
179
183
|
const mediaItems = getMediaItems(entry);
|
|
180
184
|
|
|
@@ -185,14 +189,16 @@ export function imageSrcFromMediaItem(
|
|
|
185
189
|
// Try to find the item with the given key
|
|
186
190
|
let foundItem = mediaItems.find((item) => item.key === imageKey);
|
|
187
191
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
+
if (fallback) {
|
|
193
|
+
// If not found and key was not "image_base", try to find "image_base"
|
|
194
|
+
if (!foundItem && imageKey !== "image_base") {
|
|
195
|
+
foundItem = mediaItems.find((item) => item.key === "image_base");
|
|
196
|
+
}
|
|
192
197
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
198
|
+
// If still not found, default to first item
|
|
199
|
+
if (!foundItem) {
|
|
200
|
+
foundItem = mediaItems[0];
|
|
201
|
+
}
|
|
196
202
|
}
|
|
197
203
|
|
|
198
204
|
const src = foundItem?.src;
|
|
@@ -331,6 +331,35 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
331
331
|
label_tooltip: "Hint for skip intro button accessibility",
|
|
332
332
|
type: "text_input",
|
|
333
333
|
},
|
|
334
|
+
{
|
|
335
|
+
type: "text_input",
|
|
336
|
+
label: "Text Tracks label",
|
|
337
|
+
key: "text_label_text",
|
|
338
|
+
initial_value: "Subtitles",
|
|
339
|
+
label_tooltip: "Label for the text tracks list",
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
type: "text_input",
|
|
343
|
+
label: "Audio Tracks label",
|
|
344
|
+
key: "audio_label_text",
|
|
345
|
+
initial_value: "Audio Tracks",
|
|
346
|
+
label_tooltip: "Label for the audio tracks list",
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
type: "text_input",
|
|
350
|
+
label: "Off label",
|
|
351
|
+
key: "off_label_text",
|
|
352
|
+
initial_value: "Off",
|
|
353
|
+
label_tooltip: "Label for the 'Off' option in text tracks list",
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
type: "text_input",
|
|
357
|
+
label: "Default track label",
|
|
358
|
+
key: "default_audio_label_text",
|
|
359
|
+
initial_value: "Default",
|
|
360
|
+
label_tooltip:
|
|
361
|
+
"Label for the 'Default' audio track in audio tracks list",
|
|
362
|
+
},
|
|
334
363
|
],
|
|
335
364
|
};
|
|
336
365
|
|
|
@@ -3424,6 +3453,22 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
3424
3453
|
label_tooltip:
|
|
3425
3454
|
"This field allows you to override the player configuration / initialization options. i.e. html5: { vhs: { limitRenditionByPlayerDimensions: false } } Please ensure your configuration matches the player you are targeting, and that it is a valid json. See config options here: https://videojs.com/guides/options/",
|
|
3426
3455
|
},
|
|
3456
|
+
{
|
|
3457
|
+
key: "advanced_player_styles",
|
|
3458
|
+
label: "Advanced Player Styles",
|
|
3459
|
+
type: "text_input",
|
|
3460
|
+
multiline: true,
|
|
3461
|
+
initial_value: `.vjs-text-track-cue {
|
|
3462
|
+
inset: unset !important;
|
|
3463
|
+
top: 94% !important;
|
|
3464
|
+
bottom: 0 !important;
|
|
3465
|
+
left: 0 !important;
|
|
3466
|
+
right: 0 !important;
|
|
3467
|
+
width: 100% !important;
|
|
3468
|
+
}`,
|
|
3469
|
+
label_tooltip:
|
|
3470
|
+
"This field allows you to inject custom CSS styles to override default player styles. The styles will be injected into the document head and can be used to customize the appearance of the player. Leave empty to use only default styles.",
|
|
3471
|
+
},
|
|
3427
3472
|
{
|
|
3428
3473
|
key: "use_dashjs_player",
|
|
3429
3474
|
label: "Use Enhanced Player for DASH streams",
|
package/navigationUtils/index.ts
CHANGED
|
@@ -5,7 +5,8 @@ import { layoutV2TypeMatcher } from "./layoutV2TypeMatcher";
|
|
|
5
5
|
import { HOOKS_EVENTS } from "../appUtils/HooksManager/constants";
|
|
6
6
|
import { HooksManager } from "../appUtils/HooksManager";
|
|
7
7
|
import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
import { zappHookModalStore } from "@applicaster/zapp-react-native-ui-components/Contexts/ZappHookModalContext";
|
|
9
10
|
import { logger } from "@applicaster/zapp-react-native-utils/logger";
|
|
10
11
|
import {
|
|
11
12
|
isGeneralPlugin,
|
|
@@ -15,6 +16,8 @@ import {
|
|
|
15
16
|
} from "./itemTypeMatchers";
|
|
16
17
|
import { RootState } from "@applicaster/zapp-react-native-redux/store";
|
|
17
18
|
|
|
19
|
+
import { pick } from "@applicaster/zapp-react-native-utils/utils";
|
|
20
|
+
|
|
18
21
|
type PathAttribute = {
|
|
19
22
|
screenType: string;
|
|
20
23
|
screenId: string;
|
|
@@ -403,15 +406,22 @@ export const mapContentTypesToRivers = (
|
|
|
403
406
|
};
|
|
404
407
|
|
|
405
408
|
export const runZappHooksForEntry = async (
|
|
406
|
-
entry: HookPluginProps["payload"]
|
|
407
|
-
|
|
409
|
+
entry: HookPluginProps["payload"]
|
|
410
|
+
): Promise<{ success: boolean; payload: ZappEntry }> => {
|
|
411
|
+
const {
|
|
408
412
|
setState,
|
|
409
413
|
resetState,
|
|
410
414
|
setIsHooksExecutionInProgress,
|
|
411
415
|
setIsPresentingFullScreen,
|
|
412
416
|
setIsRunningInBackground,
|
|
413
|
-
}
|
|
414
|
-
|
|
417
|
+
} = pick(zappHookModalStore.getState(), [
|
|
418
|
+
"setState",
|
|
419
|
+
"resetState",
|
|
420
|
+
"setIsHooksExecutionInProgress",
|
|
421
|
+
"setIsPresentingFullScreen",
|
|
422
|
+
"setIsRunningInBackground",
|
|
423
|
+
]);
|
|
424
|
+
|
|
415
425
|
resetState?.();
|
|
416
426
|
|
|
417
427
|
let success;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-utils",
|
|
3
|
-
"version": "14.0.9-alpha.
|
|
3
|
+
"version": "14.0.9-alpha.4253701132",
|
|
4
4
|
"description": "Applicaster Zapp React Native utilities package",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@applicaster/applicaster-types": "14.0.9-alpha.
|
|
30
|
+
"@applicaster/applicaster-types": "14.0.9-alpha.4253701132",
|
|
31
31
|
"buffer": "^5.2.1",
|
|
32
32
|
"camelize": "^1.0.0",
|
|
33
33
|
"dayjs": "^1.11.10",
|
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
import { reactHooksLogger } from "../../logger";
|
|
2
|
-
import {
|
|
2
|
+
import { useZappHookModalStore } from "@applicaster/zapp-react-native-ui-components/Contexts/ZappHookModalContext";
|
|
3
3
|
|
|
4
4
|
const logger = reactHooksLogger.addSubsystem("useHookModalScreenData");
|
|
5
5
|
|
|
6
6
|
type VariousScreenData = LegacyNavigationScreenData | ZappRiver | ZappEntry;
|
|
7
7
|
|
|
8
|
-
export const useHookModalScreenData = (
|
|
9
|
-
|
|
8
|
+
export const useHookModalScreenData = (
|
|
9
|
+
skipSubscription: boolean
|
|
10
|
+
): VariousScreenData | undefined => {
|
|
11
|
+
const hookModalState = useZappHookModalStore((state) =>
|
|
12
|
+
skipSubscription ? undefined : state.state
|
|
13
|
+
);
|
|
10
14
|
|
|
11
|
-
if (!hookModalState
|
|
15
|
+
if (!hookModalState?.screenData) {
|
|
12
16
|
return;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
try {
|
|
16
|
-
const screenData = hookModalState.
|
|
17
|
-
const hookPlugin = hookModalState.
|
|
20
|
+
const screenData = hookModalState.screenData?.payload;
|
|
21
|
+
const hookPlugin = hookModalState.screenData.hookPlugin;
|
|
18
22
|
|
|
19
23
|
if (hookPlugin?.screen_id) {
|
|
20
24
|
return {
|
|
@@ -23,7 +27,7 @@ export const useHookModalScreenData = (): VariousScreenData | undefined => {
|
|
|
23
27
|
};
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
return hookModalState.
|
|
30
|
+
return hookModalState.screenData?.payload;
|
|
27
31
|
} catch (error) {
|
|
28
32
|
logger.error({
|
|
29
33
|
message: "Hook modal screen data creation failed",
|
|
@@ -34,6 +38,6 @@ export const useHookModalScreenData = (): VariousScreenData | undefined => {
|
|
|
34
38
|
jsOnly: true,
|
|
35
39
|
});
|
|
36
40
|
|
|
37
|
-
return hookModalState.
|
|
41
|
+
return hookModalState.screenData?.payload;
|
|
38
42
|
}
|
|
39
43
|
};
|
|
@@ -5,7 +5,6 @@ import { isNavBarVisible, useRoute } from "../";
|
|
|
5
5
|
import { Provider } from "react-redux";
|
|
6
6
|
import configureMockStore from "redux-mock-store";
|
|
7
7
|
import { NavigationContext } from "@applicaster/zapp-react-native-ui-components/Contexts/NavigationContext";
|
|
8
|
-
import { ZappHookModalContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
|
|
9
8
|
import { ROUTE_TYPES } from "@applicaster/zapp-react-native-utils/navigationUtils/routeTypes";
|
|
10
9
|
import { ScreenDataContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenDataContext";
|
|
11
10
|
|
|
@@ -83,9 +82,45 @@ const hookModalContextState = {
|
|
|
83
82
|
},
|
|
84
83
|
setState: jest.fn(),
|
|
85
84
|
resetState: jest.fn(),
|
|
85
|
+
hookPresentationMode: null,
|
|
86
86
|
};
|
|
87
87
|
|
|
88
88
|
const hooksModalPathname = `${ROUTE_TYPES.HOOKS_MODAL}/${hookModalContextState.state.screenData.payload.id}`;
|
|
89
|
+
|
|
90
|
+
jest.mock(
|
|
91
|
+
"@applicaster/zapp-react-native-ui-components/Contexts/ZappHookModalContext",
|
|
92
|
+
() => {
|
|
93
|
+
// Define state inside mock factory to avoid hoisting issues
|
|
94
|
+
const mockState = {
|
|
95
|
+
isRunningInBackground: false,
|
|
96
|
+
setIsRunningInBackground: jest.fn(),
|
|
97
|
+
setIsPresentingFullScreen: jest.fn(),
|
|
98
|
+
isPresentationFullScreen: true,
|
|
99
|
+
isHooksExecutionInProgress: true,
|
|
100
|
+
setIsHooksExecutionInProgress: jest.fn(),
|
|
101
|
+
state: {
|
|
102
|
+
path: "/home",
|
|
103
|
+
screenData: { payload: { id: "hookExecutedInModal" } },
|
|
104
|
+
},
|
|
105
|
+
setState: jest.fn(),
|
|
106
|
+
resetState: jest.fn(),
|
|
107
|
+
hookPresentationMode: null,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const mockHook = jest.fn((selector) => {
|
|
111
|
+
return selector ? selector(mockState) : mockState;
|
|
112
|
+
}) as jest.MockedFunction<any> & { getState: () => typeof mockState };
|
|
113
|
+
|
|
114
|
+
// Add getState method to the mock hook (Zustand stores have this)
|
|
115
|
+
mockHook.getState = jest.fn(() => mockState);
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
useZappHookModalStore: mockHook,
|
|
119
|
+
zappHookModalStore: mockHook,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
|
|
89
124
|
const videoModalPathname = `${ROUTE_TYPES.VIDEO_MODAL}/${videoModalNavigator.videoModalState.item.id}`;
|
|
90
125
|
const mockStore = configureMockStore();
|
|
91
126
|
|
|
@@ -125,13 +160,11 @@ const videoModalWrapper = ({ children }) => (
|
|
|
125
160
|
|
|
126
161
|
const hookModalWrapper = ({ children }) => (
|
|
127
162
|
<Provider store={store}>
|
|
128
|
-
<
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
</NavigationContext.Provider>
|
|
134
|
-
</ZappHookModalContext.ReactContext.Provider>
|
|
163
|
+
<NavigationContext.Provider value={hookModalNavigator}>
|
|
164
|
+
<PathnameContext.Provider value={hooksModalPathname}>
|
|
165
|
+
{children}
|
|
166
|
+
</PathnameContext.Provider>
|
|
167
|
+
</NavigationContext.Provider>
|
|
135
168
|
</Provider>
|
|
136
169
|
);
|
|
137
170
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
2
2
|
import { BackHandler } from "react-native";
|
|
3
|
+
import { shallow } from "zustand/shallow";
|
|
3
4
|
|
|
4
5
|
import {
|
|
5
6
|
useContentTypes,
|
|
@@ -8,13 +9,13 @@ import {
|
|
|
8
9
|
import { HooksManager } from "@applicaster/zapp-react-native-utils/appUtils/HooksManager";
|
|
9
10
|
|
|
10
11
|
import { LONG_KEY_PRESS_TIMEOUT } from "@applicaster/quick-brick-core/const";
|
|
11
|
-
import {
|
|
12
|
-
import { HookModalContextT } from "@applicaster/zapp-react-native-ui-components/Contexts/ZappHookModalContext";
|
|
12
|
+
import { useZappHookModalStore } from "@applicaster/zapp-react-native-ui-components/Contexts/ZappHookModalContext";
|
|
13
13
|
import { HOOKS_EVENTS } from "../../appUtils/HooksManager/constants";
|
|
14
14
|
import { getRiverFromRoute, getTargetRoute } from "../../navigationUtils";
|
|
15
15
|
import { useConnectionInfo } from "../connection";
|
|
16
16
|
|
|
17
17
|
import { isTV, isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
18
|
+
import { pick } from "@applicaster/zapp-react-native-utils/utils";
|
|
18
19
|
import { useNavbarState } from "../screen";
|
|
19
20
|
|
|
20
21
|
export { useNavigation } from "./useNavigation";
|
|
@@ -166,7 +167,17 @@ export const useZappHooksForEntry = (
|
|
|
166
167
|
setIsHooksExecutionInProgress,
|
|
167
168
|
setIsPresentingFullScreen,
|
|
168
169
|
setIsRunningInBackground,
|
|
169
|
-
}
|
|
170
|
+
} = useZappHookModalStore(
|
|
171
|
+
(state) =>
|
|
172
|
+
pick(state, [
|
|
173
|
+
"setState",
|
|
174
|
+
"resetState",
|
|
175
|
+
"setIsHooksExecutionInProgress",
|
|
176
|
+
"setIsPresentingFullScreen",
|
|
177
|
+
"setIsRunningInBackground",
|
|
178
|
+
]),
|
|
179
|
+
shallow
|
|
180
|
+
);
|
|
170
181
|
|
|
171
182
|
const {
|
|
172
183
|
appData: { layoutVersion },
|
|
@@ -52,7 +52,9 @@ export const useRoute = (
|
|
|
52
52
|
|
|
53
53
|
const modalScreenData = modalState.screen;
|
|
54
54
|
|
|
55
|
-
const hookModalScreenData = useHookModalScreenData(
|
|
55
|
+
const hookModalScreenData = useHookModalScreenData(
|
|
56
|
+
!isHookModalPathname(pathname)
|
|
57
|
+
);
|
|
56
58
|
|
|
57
59
|
const videoModalScreenData =
|
|
58
60
|
navigator?.videoModalState?.item &&
|
|
@@ -17,7 +17,7 @@ export const resolveStorageContextKey = (storageKey: string) => {
|
|
|
17
17
|
const key = splitKey[1];
|
|
18
18
|
|
|
19
19
|
if (!key) {
|
|
20
|
-
return getFromSessionOrLocalStorage(
|
|
20
|
+
return getFromSessionOrLocalStorage(namespaceOrKey);
|
|
21
21
|
} else {
|
|
22
22
|
return getFromSessionOrLocalStorage(key, namespaceOrKey);
|
|
23
23
|
}
|