@applicaster/zapp-react-native-utils 14.0.0-rc.60 → 14.0.0-rc.62
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/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +3 -1
- package/appUtils/focusManager/events.ts +2 -0
- package/appUtils/focusManager/index.ts +31 -27
- package/appUtils/focusManagerAux/utils/index.ts +2 -14
- package/focusManager/FocusManager.ts +78 -4
- package/focusManager/aux/index.ts +98 -0
- package/focusManager/utils.ts +12 -6
- package/manifestUtils/defaultManifestConfigurations/player.js +172 -0
- package/manifestUtils/index.js +4 -0
- package/manifestUtils/keys.js +12 -0
- package/manifestUtils/sharedConfiguration/screenPicker/stylesFields.js +6 -0
- package/package.json +2 -2
- package/playerUtils/getPlayerActionButtons.ts +1 -1
- package/screenPickerUtils/index.ts +6 -0
- package/utils/__tests__/endsWith.test.ts +30 -0
- package/utils/__tests__/omit.test.ts +19 -0
- package/utils/__tests__/path.test.ts +33 -0
- package/utils/__tests__/take.test.ts +40 -0
- package/utils/endsWith.ts +9 -0
- package/utils/index.ts +10 -1
- package/utils/omit.ts +5 -0
- package/utils/path.ts +5 -0
- package/utils/take.ts +5 -0
|
@@ -6,7 +6,8 @@ exports[`focusManager should be defined 1`] = `
|
|
|
6
6
|
"disableFocus": [Function],
|
|
7
7
|
"enableFocus": [Function],
|
|
8
8
|
"findPreferredFocusChild": [Function],
|
|
9
|
-
"
|
|
9
|
+
"focusOnSelectedTab": [Function],
|
|
10
|
+
"focusOnSelectedTopMenuItem": [Function],
|
|
10
11
|
"focusableTree": Tree {
|
|
11
12
|
"loadingCounter": 0,
|
|
12
13
|
"root": {
|
|
@@ -29,6 +30,7 @@ exports[`focusManager should be defined 1`] = `
|
|
|
29
30
|
"isFocusOnContent": [Function],
|
|
30
31
|
"isFocusOnMenu": [Function],
|
|
31
32
|
"isGroupItemFocused": [Function],
|
|
33
|
+
"isTabsScreenContentFocused": [Function],
|
|
32
34
|
"longPress": [Function],
|
|
33
35
|
"moveFocus": [Function],
|
|
34
36
|
"on": [Function],
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
isNilOrEmpty,
|
|
4
4
|
isNotNil,
|
|
5
5
|
} from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
6
|
+
import { getFocusableId } from "@applicaster/zapp-react-native-utils/screenPickerUtils";
|
|
6
7
|
|
|
7
8
|
import { Tree } from "./treeDataStructure/Tree";
|
|
8
9
|
import {
|
|
@@ -15,9 +16,7 @@ import { coreLogger } from "../../logger";
|
|
|
15
16
|
import { ACTION } from "./utils/enums";
|
|
16
17
|
|
|
17
18
|
import {
|
|
18
|
-
|
|
19
|
-
findSelectedMenuId,
|
|
20
|
-
isTabsScreenContentFocused,
|
|
19
|
+
isTabsScreenOnContentFocused,
|
|
21
20
|
isCurrentFocusOnContent,
|
|
22
21
|
isCurrentFocusOnMenu,
|
|
23
22
|
isCurrentFocusOn,
|
|
@@ -300,37 +299,40 @@ export const focusManager = (function () {
|
|
|
300
299
|
return isCurrentFocusOnMenu(currentFocusNode);
|
|
301
300
|
}
|
|
302
301
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
// set focus on selected menu item
|
|
308
|
-
const direction = undefined;
|
|
302
|
+
function landFocusToWithoutScrolling(id) {
|
|
303
|
+
if (id) {
|
|
304
|
+
// set focus on selected menu item
|
|
305
|
+
const direction = undefined;
|
|
309
306
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
307
|
+
const context: FocusManager.FocusContext = {
|
|
308
|
+
source: "back",
|
|
309
|
+
preserveScroll: true,
|
|
310
|
+
};
|
|
314
311
|
|
|
315
|
-
|
|
312
|
+
logger.log({ message: "landFocusTo", data: { id } });
|
|
316
313
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
314
|
+
blur(direction);
|
|
315
|
+
setFocus(id, direction, context);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
321
318
|
|
|
322
|
-
|
|
323
|
-
|
|
319
|
+
function isTabsScreenContentFocused() {
|
|
320
|
+
return isTabsScreenOnContentFocused(currentFocusNode);
|
|
321
|
+
}
|
|
324
322
|
|
|
325
|
-
|
|
326
|
-
|
|
323
|
+
function focusOnSelectedTab(item: ZappEntry): void {
|
|
324
|
+
// Move focus to appropriate top navigation tab with context
|
|
325
|
+
const selectedTabId = getFocusableId(item.id);
|
|
327
326
|
|
|
328
|
-
|
|
329
|
-
|
|
327
|
+
// Set focus with back button context to tabs-menu
|
|
328
|
+
landFocusToWithoutScrolling(selectedTabId);
|
|
329
|
+
}
|
|
330
330
|
|
|
331
|
-
|
|
331
|
+
function focusOnSelectedTopMenuItem(
|
|
332
|
+
selectedMenuItemId: Option<string>
|
|
333
|
+
): void {
|
|
332
334
|
// Set focus with back button context to top-menu
|
|
333
|
-
|
|
335
|
+
landFocusToWithoutScrolling(selectedMenuItemId);
|
|
334
336
|
}
|
|
335
337
|
|
|
336
338
|
/**
|
|
@@ -643,9 +645,11 @@ export const focusManager = (function () {
|
|
|
643
645
|
recoverFocus,
|
|
644
646
|
isCurrentFocusOnTheTopScreen,
|
|
645
647
|
findPreferredFocusChild,
|
|
646
|
-
focusTopNavigation,
|
|
647
648
|
isFocusOnContent,
|
|
648
649
|
isFocusOnMenu,
|
|
649
650
|
isFocusOn,
|
|
651
|
+
focusOnSelectedTopMenuItem,
|
|
652
|
+
focusOnSelectedTab,
|
|
653
|
+
isTabsScreenContentFocused,
|
|
650
654
|
};
|
|
651
655
|
})();
|
|
@@ -123,19 +123,7 @@ export const findSelectedTabId = (item: ZappEntry): string => {
|
|
|
123
123
|
return selectedTabId;
|
|
124
124
|
};
|
|
125
125
|
|
|
126
|
-
export const
|
|
127
|
-
// Set focus with back button context
|
|
128
|
-
const navbar = getNavbarNode(focusableTree);
|
|
129
|
-
|
|
130
|
-
const selectedMenuItemId = find(
|
|
131
|
-
(child) => child.component.props.selected,
|
|
132
|
-
navbar.children
|
|
133
|
-
)?.id;
|
|
134
|
-
|
|
135
|
-
return selectedMenuItemId;
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
export const isTabsScreenContentFocused = (node) => {
|
|
126
|
+
export const isTabsScreenOnContentFocused = (node) => {
|
|
139
127
|
if (isRoot(node)) {
|
|
140
128
|
return false;
|
|
141
129
|
}
|
|
@@ -152,7 +140,7 @@ export const isTabsScreenContentFocused = (node) => {
|
|
|
152
140
|
return true;
|
|
153
141
|
}
|
|
154
142
|
|
|
155
|
-
return
|
|
143
|
+
return isTabsScreenOnContentFocused(node.parent);
|
|
156
144
|
};
|
|
157
145
|
|
|
158
146
|
export const isCurrentFocusOnMenu = (node) => {
|
|
@@ -2,11 +2,24 @@ import { path } from "ramda";
|
|
|
2
2
|
import { isString } from "@applicaster/zapp-react-native-utils/stringUtils";
|
|
3
3
|
import * as FOCUS_EVENTS from "@applicaster/zapp-react-native-utils/appUtils/focusManager/events";
|
|
4
4
|
|
|
5
|
+
import {
|
|
6
|
+
QUICK_BRICK_CONTENT,
|
|
7
|
+
QUICK_BRICK_NAVBAR,
|
|
8
|
+
} from "@applicaster/quick-brick-core/const";
|
|
9
|
+
|
|
5
10
|
import { logger } from "./logger";
|
|
6
11
|
import { TreeNode } from "./TreeNode";
|
|
7
12
|
import { Tree } from "./Tree";
|
|
8
13
|
import { subscriber } from "../functionUtils";
|
|
9
14
|
import { getFocusableId, toFocusDirection } from "./utils";
|
|
15
|
+
import {
|
|
16
|
+
findSelectedMenuId,
|
|
17
|
+
isTabsScreenContentFocused,
|
|
18
|
+
findSelectedTabId,
|
|
19
|
+
contextWithoutScrolling,
|
|
20
|
+
} from "./aux";
|
|
21
|
+
|
|
22
|
+
export { contextWithoutScrolling } from "./aux";
|
|
10
23
|
|
|
11
24
|
export {
|
|
12
25
|
toFocusDirection,
|
|
@@ -221,7 +234,8 @@ class FocusManager {
|
|
|
221
234
|
|
|
222
235
|
private setNextFocus(
|
|
223
236
|
nextFocus: FocusManager.TouchableReactRef,
|
|
224
|
-
options?: FocusManager.Android.CallbackOptions
|
|
237
|
+
options?: FocusManager.Android.CallbackOptions,
|
|
238
|
+
context?: FocusManager.FocusContext
|
|
225
239
|
) {
|
|
226
240
|
if (nextFocus?.current?.props?.blockFocus) {
|
|
227
241
|
return;
|
|
@@ -250,7 +264,7 @@ class FocusManager {
|
|
|
250
264
|
|
|
251
265
|
FocusManager.instance.setPreviousNavigationDirection(options ?? null);
|
|
252
266
|
|
|
253
|
-
nextFocus?.current?.onFocus?.(nextFocus.current, options ?? {});
|
|
267
|
+
nextFocus?.current?.onFocus?.(nextFocus.current, options ?? {}, context);
|
|
254
268
|
}
|
|
255
269
|
}
|
|
256
270
|
|
|
@@ -291,7 +305,8 @@ class FocusManager {
|
|
|
291
305
|
|
|
292
306
|
setFocus(
|
|
293
307
|
newFocus: FocusManager.TouchableReactRef | string,
|
|
294
|
-
options?: FocusManager.Android.CallbackOptions
|
|
308
|
+
options?: FocusManager.Android.CallbackOptions,
|
|
309
|
+
context?: FocusManager.FocusContext
|
|
295
310
|
) {
|
|
296
311
|
// Checks if element is focusable
|
|
297
312
|
const { isFocusable, error } = FocusManager.isFocusable(newFocus);
|
|
@@ -316,7 +331,7 @@ class FocusManager {
|
|
|
316
331
|
}
|
|
317
332
|
|
|
318
333
|
if (newFocusRef) {
|
|
319
|
-
FocusManager.instance.setNextFocus(newFocusRef, options);
|
|
334
|
+
FocusManager.instance.setNextFocus(newFocusRef, options, context);
|
|
320
335
|
}
|
|
321
336
|
}
|
|
322
337
|
}
|
|
@@ -351,6 +366,11 @@ class FocusManager {
|
|
|
351
366
|
FocusManager.instance.focused.onBlur(FocusManager.instance.focused, {});
|
|
352
367
|
}
|
|
353
368
|
|
|
369
|
+
// send reset event to some handler to reset their internal state, before real reset happens
|
|
370
|
+
this.eventHandler?.invokeHandler?.(FOCUS_EVENTS.RESET, {
|
|
371
|
+
focusedId: FocusManager.instance.focusedId,
|
|
372
|
+
});
|
|
373
|
+
|
|
354
374
|
FocusManager.instance.setFocusLocal({ current: null });
|
|
355
375
|
}
|
|
356
376
|
|
|
@@ -417,6 +437,60 @@ class FocusManager {
|
|
|
417
437
|
throw new Error(`Group with id ${id} not found`);
|
|
418
438
|
}
|
|
419
439
|
}
|
|
440
|
+
|
|
441
|
+
isFocusOnMenu(): boolean {
|
|
442
|
+
return this.isFocusableChildOf(
|
|
443
|
+
FocusManager.instance.focusedId,
|
|
444
|
+
QUICK_BRICK_NAVBAR
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
isFocusOnContent(): boolean {
|
|
449
|
+
return this.isFocusableChildOf(
|
|
450
|
+
FocusManager.instance.focusedId,
|
|
451
|
+
QUICK_BRICK_CONTENT
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
private landFocusToWithoutScrolling = (id) => {
|
|
456
|
+
if (id) {
|
|
457
|
+
// set focus on selected menu item
|
|
458
|
+
const direction = undefined;
|
|
459
|
+
|
|
460
|
+
const context: FocusManager.FocusContext =
|
|
461
|
+
contextWithoutScrolling("back");
|
|
462
|
+
|
|
463
|
+
logger.log({ message: "landFocusToWithoutScrolling", data: { id } });
|
|
464
|
+
|
|
465
|
+
this.setFocus(id, direction, context);
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
// Move focus to appropriate top navigation tab with context
|
|
470
|
+
focusOnSelectedTab(index: number): void {
|
|
471
|
+
const selectedTabId = findSelectedTabId(this.tree, index);
|
|
472
|
+
|
|
473
|
+
// Set focus with back button context to tabs-menu
|
|
474
|
+
this.landFocusToWithoutScrolling(selectedTabId);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Move focus to appropriate top navigation tab with context
|
|
478
|
+
focusOnSelectedTopMenuItem(index: number, sectionKey: string): void {
|
|
479
|
+
const selectedMenuItemId = findSelectedMenuId(this.tree, {
|
|
480
|
+
index,
|
|
481
|
+
sectionKey,
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
// Set focus with back button context to top-menu
|
|
485
|
+
this.landFocusToWithoutScrolling(selectedMenuItemId);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
isTabsScreenContentFocused(): boolean {
|
|
489
|
+
return isTabsScreenContentFocused(
|
|
490
|
+
this.tree,
|
|
491
|
+
FocusManager.instance.focusedId
|
|
492
|
+
);
|
|
493
|
+
}
|
|
420
494
|
}
|
|
421
495
|
|
|
422
496
|
export const focusManager = FocusManager.getInstance();
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { isNil, pathOr } from "@applicaster/zapp-react-native-utils/utils";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
QUICK_BRICK_CONTENT,
|
|
5
|
+
QUICK_BRICK_NAVBAR,
|
|
6
|
+
QUICK_BRICK_NAVBAR_SECTIONS,
|
|
7
|
+
} from "@applicaster/quick-brick-core/const";
|
|
8
|
+
|
|
9
|
+
const isNavBar = (node) => QUICK_BRICK_NAVBAR === node?.id;
|
|
10
|
+
const isContent = (node) => QUICK_BRICK_CONTENT === node?.id;
|
|
11
|
+
|
|
12
|
+
// SCREEN_PICKER_SELECTOR_CONTAINER(we assume there is only one SCREEN_PICKER)
|
|
13
|
+
let screenPickerSelectorContainerId;
|
|
14
|
+
|
|
15
|
+
export const onRegisterScreenPickerSelectorContainer = (id) => {
|
|
16
|
+
screenPickerSelectorContainerId = id;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const onUnregisterScreenPickerSelectorContainer = (id) => {
|
|
20
|
+
// reset screenSelectorId on unregistration
|
|
21
|
+
if (screenPickerSelectorContainerId === id) {
|
|
22
|
+
screenPickerSelectorContainerId = undefined;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
// SCREEN_PICKER_SELECTOR_CONTAINER
|
|
26
|
+
|
|
27
|
+
// SCREEN_PICKER_CONTENT_CONTAINER(we assume there is only one SCREEN_PICKER)
|
|
28
|
+
let screenPickerContentContainerId;
|
|
29
|
+
|
|
30
|
+
export const onRegisterScreenPickerContentContainer = (id) => {
|
|
31
|
+
screenPickerContentContainerId = id;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const onUnregisterScreenPickerContentContainer = (id) => {
|
|
35
|
+
// reset screenSelectorId on unregistration
|
|
36
|
+
if (screenPickerContentContainerId === id) {
|
|
37
|
+
screenPickerContentContainerId = undefined;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const isScreenPickerContentContainer = (node) =>
|
|
42
|
+
screenPickerContentContainerId === node?.id;
|
|
43
|
+
|
|
44
|
+
// SCREEN_PICKER_CONTENT_CONTAINER
|
|
45
|
+
|
|
46
|
+
export const findSelectedMenuId = (
|
|
47
|
+
focusableTree,
|
|
48
|
+
{ index, sectionKey }: { index: number; sectionKey: string }
|
|
49
|
+
) => {
|
|
50
|
+
const sectionName = QUICK_BRICK_NAVBAR_SECTIONS[sectionKey];
|
|
51
|
+
|
|
52
|
+
return pathOr(
|
|
53
|
+
undefined,
|
|
54
|
+
["children", index, "id"],
|
|
55
|
+
focusableTree.find(sectionName)
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const findSelectedTabId = (focusableTree, index: number): string => {
|
|
60
|
+
const screenSelectorContainerNode = focusableTree.find(
|
|
61
|
+
screenPickerSelectorContainerId
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const selectedTabId = screenSelectorContainerNode.children[index]?.id;
|
|
65
|
+
|
|
66
|
+
return selectedTabId;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const isTabsScreenContentFocused = (focusableTree, id) => {
|
|
70
|
+
const node = focusableTree.find(id);
|
|
71
|
+
|
|
72
|
+
if (isNil(node)) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (isNavBar(node)) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (isContent(node)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (isScreenPickerContentContainer(node)) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return isTabsScreenContentFocused(focusableTree, node.parentId);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const contextWithoutScrolling = (
|
|
92
|
+
source: FocusManager.FocusContext["source"]
|
|
93
|
+
): FocusManager.FocusContext => {
|
|
94
|
+
return {
|
|
95
|
+
source,
|
|
96
|
+
preserveScroll: true,
|
|
97
|
+
};
|
|
98
|
+
};
|
package/focusManager/utils.ts
CHANGED
|
@@ -4,11 +4,11 @@ import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/he
|
|
|
4
4
|
|
|
5
5
|
export const getFocusableId = (ref) => ref?.current?.props.id;
|
|
6
6
|
|
|
7
|
-
type Direction = "up" | "down" | "left" | "right";
|
|
8
|
-
|
|
9
7
|
const normalizeDirection = (direction) => direction.toLowerCase();
|
|
10
8
|
|
|
11
|
-
const checkDirection = (
|
|
9
|
+
const checkDirection = (
|
|
10
|
+
direction: FocusManager.Android.FocusNavigationDirections
|
|
11
|
+
) => {
|
|
12
12
|
invariant(!isNilOrEmpty(direction), "direction should not be empty");
|
|
13
13
|
|
|
14
14
|
invariant(
|
|
@@ -17,19 +17,25 @@ const checkDirection = (direction: Direction) => {
|
|
|
17
17
|
);
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
export const toFocusDirection = (
|
|
20
|
+
export const toFocusDirection = (
|
|
21
|
+
direction: FocusManager.Android.FocusNavigationDirections
|
|
22
|
+
) => {
|
|
21
23
|
checkDirection(direction);
|
|
22
24
|
|
|
23
25
|
return `nextFocus${capitalize(normalizeDirection(direction))}`;
|
|
24
26
|
};
|
|
25
27
|
|
|
26
|
-
export const isHorizontalDirection = (
|
|
28
|
+
export const isHorizontalDirection = (
|
|
29
|
+
direction: FocusManager.Android.FocusNavigationDirections
|
|
30
|
+
) => {
|
|
27
31
|
checkDirection(direction);
|
|
28
32
|
|
|
29
33
|
return ["left", "right"].includes(normalizeDirection(direction));
|
|
30
34
|
};
|
|
31
35
|
|
|
32
|
-
export const isVerticalDirection = (
|
|
36
|
+
export const isVerticalDirection = (
|
|
37
|
+
direction: FocusManager.Android.FocusNavigationDirections
|
|
38
|
+
) => {
|
|
33
39
|
checkDirection(direction);
|
|
34
40
|
|
|
35
41
|
return ["up", "down"].includes(normalizeDirection(direction));
|
|
@@ -365,6 +365,86 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
365
365
|
],
|
|
366
366
|
},
|
|
367
367
|
]),
|
|
368
|
+
fieldsGroup(
|
|
369
|
+
"Partial Player (Roku only)",
|
|
370
|
+
"This section allows you to configure width and height of video player in Partial Player",
|
|
371
|
+
[
|
|
372
|
+
{
|
|
373
|
+
key: "video_theater_width",
|
|
374
|
+
label: "Width of player",
|
|
375
|
+
type: "number_input",
|
|
376
|
+
initial_value: 1420,
|
|
377
|
+
placeholder: "1420",
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
key: "video_theater_height",
|
|
381
|
+
label: "Height of player",
|
|
382
|
+
type: "number_input",
|
|
383
|
+
initial_value: 900,
|
|
384
|
+
placeholder: "900",
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
key: "full_screen_button_offset_x",
|
|
388
|
+
label: "Fullscreen button X",
|
|
389
|
+
type: "number_input",
|
|
390
|
+
initial_value: 160,
|
|
391
|
+
placeholder: "160",
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
key: "full_screen_button_offset_y",
|
|
395
|
+
label: "Fullscreen button Y",
|
|
396
|
+
type: "number_input",
|
|
397
|
+
initial_value: 160,
|
|
398
|
+
placeholder: "160",
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
key: "full_screen_button_w",
|
|
402
|
+
label: "Fullscreen button width",
|
|
403
|
+
type: "number_input",
|
|
404
|
+
initial_value: 120,
|
|
405
|
+
placeholder: "120",
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
key: "full_screen_button_h",
|
|
409
|
+
label: "Fullscreen button height",
|
|
410
|
+
type: "number_input",
|
|
411
|
+
initial_value: 120,
|
|
412
|
+
placeholder: "120",
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
key: "full_screen_button_background_color",
|
|
416
|
+
type: "color_picker",
|
|
417
|
+
label: "Fullscreen Button background color",
|
|
418
|
+
initial_value: "#00000000",
|
|
419
|
+
placeholder: "color",
|
|
420
|
+
label_tooltip: "Pick Color",
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
key: "full_screen_button_background_url",
|
|
424
|
+
type: "text_input",
|
|
425
|
+
label: "Fullscreen Button background URL",
|
|
426
|
+
initial_value: "pkg:/images/tv_fullscreen.png",
|
|
427
|
+
placeholder: "",
|
|
428
|
+
label_tooltip: "",
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
key: "full_screen_button_highlighted_background_color",
|
|
432
|
+
type: "color_picker",
|
|
433
|
+
label: "Fullscreen Button highlighted background color",
|
|
434
|
+
initial_value: "#00000000",
|
|
435
|
+
placeholder: "color",
|
|
436
|
+
label_tooltip: "Pick Color",
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
key: "full_screen_button_highlighted_background_url",
|
|
440
|
+
type: "text_input",
|
|
441
|
+
label: "Fullscreen Button highlighted URL",
|
|
442
|
+
initial_value: "pkg:/images/tv_fullscreen.png",
|
|
443
|
+
placeholder: "",
|
|
444
|
+
label_tooltip: "",
|
|
445
|
+
},
|
|
446
|
+
]
|
|
447
|
+
),
|
|
368
448
|
fieldsGroup(
|
|
369
449
|
"Skip Button",
|
|
370
450
|
"This section allows you to configure the skip button styles for tv",
|
|
@@ -496,6 +576,20 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
496
576
|
key: "skip_button_style_text_android_font_size",
|
|
497
577
|
initial_value: 24,
|
|
498
578
|
},
|
|
579
|
+
{
|
|
580
|
+
type: "roku_font_selector",
|
|
581
|
+
label_tooltip: "",
|
|
582
|
+
label: "Roku Font Family",
|
|
583
|
+
key: "skip_button_style_text_roku_font_family",
|
|
584
|
+
initial_value: "Ubuntu-Bold",
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
type: "number_input",
|
|
588
|
+
label_tooltip: "",
|
|
589
|
+
label: "Roku Font Size",
|
|
590
|
+
key: "skip_button_style_text_roku_font_size",
|
|
591
|
+
initial_value: 24,
|
|
592
|
+
},
|
|
499
593
|
{
|
|
500
594
|
type: "select",
|
|
501
595
|
options: [
|
|
@@ -2904,6 +2998,18 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
2904
2998
|
key: "android_tv_audio_player_title_font_size",
|
|
2905
2999
|
initial_value: 40,
|
|
2906
3000
|
},
|
|
3001
|
+
{
|
|
3002
|
+
type: "roku_font_selector",
|
|
3003
|
+
label: "Roku Font Family",
|
|
3004
|
+
key: "roku_audio_player_title_font_family",
|
|
3005
|
+
initial_value: "Ubuntu-Bold",
|
|
3006
|
+
},
|
|
3007
|
+
{
|
|
3008
|
+
type: "number_input",
|
|
3009
|
+
label: "Roku Font Size",
|
|
3010
|
+
key: "roku_audio_player_title_font_size",
|
|
3011
|
+
initial_value: 40,
|
|
3012
|
+
},
|
|
2907
3013
|
]
|
|
2908
3014
|
),
|
|
2909
3015
|
// Audio Player Summary Font Family
|
|
@@ -2977,6 +3083,18 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
2977
3083
|
key: "android_tv_audio_player_summary_font_size",
|
|
2978
3084
|
initial_value: 22,
|
|
2979
3085
|
},
|
|
3086
|
+
{
|
|
3087
|
+
type: "roku_font_selector",
|
|
3088
|
+
label: "Roku Font Family",
|
|
3089
|
+
key: "roku_audio_player_summary_font_family",
|
|
3090
|
+
initial_value: "Ubuntu-Medium",
|
|
3091
|
+
},
|
|
3092
|
+
{
|
|
3093
|
+
type: "number_input",
|
|
3094
|
+
label: "Roku Font Size",
|
|
3095
|
+
key: "roku_audio_player_summary_font_size",
|
|
3096
|
+
initial_value: 22,
|
|
3097
|
+
},
|
|
2980
3098
|
]
|
|
2981
3099
|
),
|
|
2982
3100
|
fieldsGroup(
|
|
@@ -3344,6 +3462,60 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
3344
3462
|
},
|
|
3345
3463
|
]
|
|
3346
3464
|
),
|
|
3465
|
+
fieldsGroup(
|
|
3466
|
+
"Roku only",
|
|
3467
|
+
"This section allows you to configure RAF - Roku Ad Framework settings",
|
|
3468
|
+
[
|
|
3469
|
+
{
|
|
3470
|
+
key: "raf_enabled",
|
|
3471
|
+
type: "switch",
|
|
3472
|
+
initial_value: false,
|
|
3473
|
+
},
|
|
3474
|
+
{
|
|
3475
|
+
key: "raf_url",
|
|
3476
|
+
type: "text_input",
|
|
3477
|
+
label: "Ad URL",
|
|
3478
|
+
label_tooltip:
|
|
3479
|
+
"Publisher's ad URL. The default is the Roku Ad Server with a single pre-roll placeholder, with revenue split ad sharing by default. TO GET PAID A URL MUST BE PASSED IN HERE. Note: If you are putting ads in child targetted content then your ad url will have to use the ROKU_ADS_KIDS_CONTENT macro value, as per the docs here: developer.roku.com/docs/developer-program/advertising/integrating-roku-advertising-framework.md#url-parameter-macros",
|
|
3480
|
+
},
|
|
3481
|
+
{
|
|
3482
|
+
key: "genre",
|
|
3483
|
+
type: "text_input",
|
|
3484
|
+
label: "Roku Genre",
|
|
3485
|
+
initial_value: "Entertainment",
|
|
3486
|
+
label_tooltip:
|
|
3487
|
+
"Choose value from Roku genre tags, from developer.roku.com/en-gb/docs/developer-program/advertising/integrating-roku-advertising-framework.md",
|
|
3488
|
+
},
|
|
3489
|
+
{
|
|
3490
|
+
key: "nielsen_enabled",
|
|
3491
|
+
type: "checkbox",
|
|
3492
|
+
initial_value: false,
|
|
3493
|
+
label_tooltip:
|
|
3494
|
+
"Required only for apps launched in the US market, See developer.roku.com/en-gb/docs/developer-program/advertising/integrating-roku-advertising-framework.md for details on configuration",
|
|
3495
|
+
},
|
|
3496
|
+
{
|
|
3497
|
+
key: "nielsen_app_id",
|
|
3498
|
+
type: "text_input",
|
|
3499
|
+
initial_value: "",
|
|
3500
|
+
label_tooltip:
|
|
3501
|
+
"id of your app for nielsen leave blank if you don't have a specific id (that is almost always the case)",
|
|
3502
|
+
},
|
|
3503
|
+
{
|
|
3504
|
+
key: "nielsen_genre",
|
|
3505
|
+
type: "text_input",
|
|
3506
|
+
initial_value: "General",
|
|
3507
|
+
label_tooltip:
|
|
3508
|
+
"genre from developer.roku.com/en-gb/docs/developer-program/advertising/integrating-roku-advertising-framework.md#nielsen-dar-genre-tags",
|
|
3509
|
+
},
|
|
3510
|
+
{
|
|
3511
|
+
key: "is_kids_content",
|
|
3512
|
+
type: "checkbox",
|
|
3513
|
+
initial_value: false,
|
|
3514
|
+
label_tooltip:
|
|
3515
|
+
"If your content is directed at kids, this must be checked. See developer.roku.com/docs/developer-program/advertising/raf-api.md#setcontentgenregenres-as-string-kidscontent-as-boolean for more info. Also note your ad urls will have to use the ROKU_ADS_KIDS_CONTENT macro value, as per the docs here: developer.roku.com/docs/developer-program/advertising/integrating-roku-advertising-framework.md#url-parameter-macros",
|
|
3516
|
+
},
|
|
3517
|
+
]
|
|
3518
|
+
),
|
|
3347
3519
|
fieldsGroup(
|
|
3348
3520
|
"Audio Tracks",
|
|
3349
3521
|
"This section allows you to configure default audio track behavior for videos with multiple audio tracks",
|
package/manifestUtils/index.js
CHANGED
|
@@ -29,6 +29,9 @@ const {
|
|
|
29
29
|
getUpdatedSecondaryImageKeys,
|
|
30
30
|
} = require("./secondaryImage");
|
|
31
31
|
|
|
32
|
+
const DEFAULT_GRADIENT_IMAGE =
|
|
33
|
+
"data:image/png;base64,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";
|
|
34
|
+
|
|
32
35
|
module.exports = {
|
|
33
36
|
fontKey,
|
|
34
37
|
fontKeyTV,
|
|
@@ -47,5 +50,6 @@ module.exports = {
|
|
|
47
50
|
tvProgressBar,
|
|
48
51
|
secondaryImage,
|
|
49
52
|
getUpdatedSecondaryImageKeys,
|
|
53
|
+
DEFAULT_GRADIENT_IMAGE,
|
|
50
54
|
compact,
|
|
51
55
|
};
|
package/manifestUtils/keys.js
CHANGED
|
@@ -485,6 +485,18 @@ const TV_MENU_LABEL_FIELDS = [
|
|
|
485
485
|
type: ZAPPIFEST_FIELDS.number_input,
|
|
486
486
|
suffix: "LG letter spacing",
|
|
487
487
|
},
|
|
488
|
+
{
|
|
489
|
+
type: ZAPPIFEST_FIELDS.font_selector.roku,
|
|
490
|
+
suffix: "Roku font family",
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
type: ZAPPIFEST_FIELDS.number_input,
|
|
494
|
+
suffix: "Roku font size",
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
type: ZAPPIFEST_FIELDS.number_input,
|
|
498
|
+
suffix: "Roku line height",
|
|
499
|
+
},
|
|
488
500
|
{
|
|
489
501
|
type: ZAPPIFEST_FIELDS.select,
|
|
490
502
|
suffix: "text transform",
|
|
@@ -424,6 +424,12 @@ const titleFields = [
|
|
|
424
424
|
key: "vizio_font_family",
|
|
425
425
|
initial_value: fontFamily,
|
|
426
426
|
},
|
|
427
|
+
{
|
|
428
|
+
type: "roku_font_selector",
|
|
429
|
+
label: "Roku TV Font Family",
|
|
430
|
+
key: "roku_font_family",
|
|
431
|
+
initial_value: fontFamily,
|
|
432
|
+
},
|
|
427
433
|
...generateFontConfiguration(),
|
|
428
434
|
// text transform
|
|
429
435
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-utils",
|
|
3
|
-
"version": "14.0.0-rc.
|
|
3
|
+
"version": "14.0.0-rc.62",
|
|
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.0-rc.
|
|
30
|
+
"@applicaster/applicaster-types": "14.0.0-rc.62",
|
|
31
31
|
"buffer": "^5.2.1",
|
|
32
32
|
"camelize": "^1.0.0",
|
|
33
33
|
"dayjs": "^1.11.10",
|
|
@@ -5,3 +5,9 @@ export const getPickerSelectorId = (id) => `PickerSelector.${id}`;
|
|
|
5
5
|
export const SCREEN_PICKER_CONTAINER = "ScreenPickerContainer";
|
|
6
6
|
|
|
7
7
|
export const getScreenPickerId = (id) => `${SCREEN_PICKER_CONTAINER}.${id}`;
|
|
8
|
+
|
|
9
|
+
export const getScreenPickerSelectorContainerId = (id) =>
|
|
10
|
+
`${getScreenPickerId(id)}-screen-selector`;
|
|
11
|
+
|
|
12
|
+
export const getScreenPickerContentContainerId = (id) =>
|
|
13
|
+
`${getScreenPickerId(id)}-screen-container`;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { endsWith } from "../endsWith";
|
|
2
|
+
|
|
3
|
+
describe("endsWith", () => {
|
|
4
|
+
it("returns false when str is null", () => {
|
|
5
|
+
expect(endsWith("a", null)).toBe(false);
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it("returns false when str is undefined", () => {
|
|
9
|
+
expect(endsWith("a", undefined)).toBe(false);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("returns true when string ends with target", () => {
|
|
13
|
+
expect(endsWith("lo", "hello")).toBe(true);
|
|
14
|
+
expect(endsWith("", "hello")).toBe(true); // empty target always matches
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("returns false when string does not end with target", () => {
|
|
18
|
+
expect(endsWith("yo", "hello")).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("works with single character target", () => {
|
|
22
|
+
expect(endsWith("o", "hello")).toBe(true);
|
|
23
|
+
expect(endsWith("x", "hello")).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("is case-sensitive", () => {
|
|
27
|
+
expect(endsWith("Lo", "hello")).toBe(false);
|
|
28
|
+
expect(endsWith("lo", "hello")).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { omit } from "../omit";
|
|
2
|
+
|
|
3
|
+
test("example 1", () => {
|
|
4
|
+
const path = ["a", "b", "c"];
|
|
5
|
+
const record = { a: 1, b: 2, c: 3 };
|
|
6
|
+
|
|
7
|
+
const output = {};
|
|
8
|
+
|
|
9
|
+
expect(omit(path, record)).toEqual(output);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("example 2", () => {
|
|
13
|
+
const path = ["a", "b"];
|
|
14
|
+
const record = { a: 1, b: 2, c: 3 };
|
|
15
|
+
|
|
16
|
+
const output = { c: 3 };
|
|
17
|
+
|
|
18
|
+
expect(omit(path, record)).toEqual(output);
|
|
19
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { path } from "../path";
|
|
2
|
+
|
|
3
|
+
test("example 1", () => {
|
|
4
|
+
const route = ["a", "b", "c"];
|
|
5
|
+
const xs = { a: { b: { c: 1 } } };
|
|
6
|
+
|
|
7
|
+
const output = 1;
|
|
8
|
+
|
|
9
|
+
expect(path(route, xs)).toEqual(output);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("example 2", () => {
|
|
13
|
+
const route = ["a", "b"];
|
|
14
|
+
const xs = { a: { b: { c: 1 } } };
|
|
15
|
+
|
|
16
|
+
const output = { c: 1 };
|
|
17
|
+
|
|
18
|
+
expect(path(route, xs)).toEqual(output);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("example 3", () => {
|
|
22
|
+
const route = ["a", "b", "x"];
|
|
23
|
+
const xs = { a: { b: { c: 1 } } };
|
|
24
|
+
|
|
25
|
+
expect(path(route, xs)).toBeUndefined();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("example 4", () => {
|
|
29
|
+
const route = ["a", "b", "c"];
|
|
30
|
+
const xs = undefined;
|
|
31
|
+
|
|
32
|
+
expect(path(route, xs)).toBeUndefined();
|
|
33
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { take } from "../take";
|
|
2
|
+
|
|
3
|
+
describe("take", () => {
|
|
4
|
+
it("takes n elements from the beginning", () => {
|
|
5
|
+
expect(take(2, [1, 2, 3])).toEqual([1, 2]);
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it("returns the whole array if n is larger than length", () => {
|
|
9
|
+
expect(take(5, [1, 2, 3])).toEqual([1, 2, 3]);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("returns empty array if n is 0", () => {
|
|
13
|
+
expect(take(0, [1, 2, 3])).toEqual([]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("returns empty array for empty input array", () => {
|
|
17
|
+
expect(take(2, [])).toEqual([]);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("returns empty array if n is negative", () => {
|
|
21
|
+
expect(take(-1, [1, 2, 3])).toEqual([]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("works with strings in array", () => {
|
|
25
|
+
expect(take(2, ["a", "b", "c"])).toEqual(["a", "b"]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("works with objects in array", () => {
|
|
29
|
+
const arr = [{ id: 1 }, { id: 2 }];
|
|
30
|
+
expect(take(1, arr)).toEqual([{ id: 1 }]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("returns empty array if input is not an array", () => {
|
|
34
|
+
// @ts-expect-error testing non-array input
|
|
35
|
+
expect(take(2, null)).toEqual([]);
|
|
36
|
+
|
|
37
|
+
// @ts-expect-error testing non-array input
|
|
38
|
+
expect(take(2, undefined)).toEqual([]);
|
|
39
|
+
});
|
|
40
|
+
});
|
package/utils/index.ts
CHANGED
|
@@ -8,6 +8,14 @@ export { find } from "./find";
|
|
|
8
8
|
|
|
9
9
|
export { pathOr } from "./pathOr";
|
|
10
10
|
|
|
11
|
+
export { path } from "./path";
|
|
12
|
+
|
|
13
|
+
export { omit } from "./omit";
|
|
14
|
+
|
|
15
|
+
export { endsWith } from "./endsWith";
|
|
16
|
+
|
|
17
|
+
export { take } from "./take";
|
|
18
|
+
|
|
11
19
|
export {
|
|
12
20
|
cloneDeep as clone,
|
|
13
21
|
flatten,
|
|
@@ -19,10 +27,11 @@ export {
|
|
|
19
27
|
has,
|
|
20
28
|
flatMap,
|
|
21
29
|
difference,
|
|
22
|
-
take,
|
|
23
30
|
pick,
|
|
24
31
|
map,
|
|
25
32
|
trim,
|
|
26
33
|
toString,
|
|
27
34
|
last,
|
|
35
|
+
toLower,
|
|
36
|
+
isEqual as equals,
|
|
28
37
|
} from "lodash";
|
package/utils/omit.ts
ADDED
package/utils/path.ts
ADDED