@applicaster/zapp-react-native-utils 15.0.0-alpha.7607942912 → 15.0.0-alpha.7809066324
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/analyticsUtils/analyticsMapper.ts +10 -2
- package/appUtils/RiverFocusManager/{index.js → index.ts} +25 -18
- package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +3 -0
- package/appUtils/focusManager/index.ios.ts +35 -2
- package/appUtils/focusManagerAux/utils/index.ios.ts +122 -0
- package/appUtils/focusManagerAux/utils/utils.ios.ts +55 -1
- package/manifestUtils/defaultManifestConfigurations/player.js +21 -10
- package/package.json +2 -2
- package/reactHooks/navigation/index.ts +4 -0
- package/reactHooks/screen/__tests__/useCurrentScreenData.test.tsx +1 -1
- package/reactHooks/screen/__tests__/useTargetScreenData.test.tsx +1 -1
- package/searchUtils/const.ts +7 -0
- package/searchUtils/index.ts +3 -0
- package/testUtils/index.tsx +1 -1
- package/utils/__tests__/mergeRight.test.ts +48 -0
- package/utils/index.ts +3 -0
- package/utils/mergeRight.ts +5 -0
|
@@ -63,10 +63,14 @@ class StateHolder {
|
|
|
63
63
|
return value as string;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
public get analyticsCustomPropertiesConsumed(): boolean {
|
|
67
|
+
return this._cap !== undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
66
70
|
private acp(prefix: string, suffix: string): string | null {
|
|
67
71
|
if (this._cap === undefined) {
|
|
68
|
-
this._cap = this.
|
|
69
|
-
? JSON.parse(this.
|
|
72
|
+
this._cap = this.event.params[prefix]
|
|
73
|
+
? JSON.parse(this.event.params[prefix])
|
|
70
74
|
: null;
|
|
71
75
|
|
|
72
76
|
delete this.pluckedParams[prefix];
|
|
@@ -254,6 +258,10 @@ export class Mapper {
|
|
|
254
258
|
|
|
255
259
|
let params = resultParams.size ? Object.fromEntries(resultParams) : {};
|
|
256
260
|
|
|
261
|
+
if (state.analyticsCustomPropertiesConsumed) {
|
|
262
|
+
delete params[sourceType.analyticsCustomProperties];
|
|
263
|
+
}
|
|
264
|
+
|
|
257
265
|
// deal with the remaining params
|
|
258
266
|
if (!isEmptyOrNil(state.pluckedParams)) {
|
|
259
267
|
if (rule.strategy) {
|
|
@@ -1,11 +1,31 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import {
|
|
1
|
+
import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager/index.ios";
|
|
2
|
+
import { QUICK_BRICK_CONTENT } from "@applicaster/quick-brick-core/const";
|
|
3
|
+
import { isNil, isEmpty } from "@applicaster/zapp-react-native-utils/utils";
|
|
4
|
+
import { isNotNil } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
4
5
|
|
|
5
6
|
let riverFocusData = {};
|
|
6
7
|
let initialyPresentedScreenFocused = false;
|
|
7
8
|
|
|
8
9
|
export const riverFocusManager = (function () {
|
|
10
|
+
/**
|
|
11
|
+
* Create unique key that will be used for save focused group data inside specific screen
|
|
12
|
+
* @param {{ screenId: string, isInsideContainer: boolean }}
|
|
13
|
+
* screenId Unique Id of the screen from layout.json
|
|
14
|
+
* isInsideContainer If this screen a screen picker child
|
|
15
|
+
*
|
|
16
|
+
*/
|
|
17
|
+
function screenFocusableGroupId({
|
|
18
|
+
screenId,
|
|
19
|
+
isInsideContainer,
|
|
20
|
+
}: {
|
|
21
|
+
screenId: string;
|
|
22
|
+
isInsideContainer: Option<boolean>;
|
|
23
|
+
}) {
|
|
24
|
+
return `${QUICK_BRICK_CONTENT}-${screenId}${
|
|
25
|
+
isNil(isInsideContainer) ? "" : "-isInsideContainer"
|
|
26
|
+
}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
9
29
|
function setScreenFocusableData({
|
|
10
30
|
screenFocusableGroupId,
|
|
11
31
|
groupId,
|
|
@@ -78,8 +98,8 @@ export const riverFocusManager = (function () {
|
|
|
78
98
|
}) {
|
|
79
99
|
// Check if screen should be focused
|
|
80
100
|
const shouldFocus =
|
|
81
|
-
(initialyPresentedScreenFocused === false &&
|
|
82
|
-
|
|
101
|
+
(initialyPresentedScreenFocused === false && isEmpty(riverFocusData)) ||
|
|
102
|
+
isNotNil(riverFocusData[screenFocusableGroupId]) ||
|
|
83
103
|
isDeepLink;
|
|
84
104
|
|
|
85
105
|
// TODO: Uncommit it to start fixing bug where selection wrong item
|
|
@@ -118,19 +138,6 @@ export const riverFocusManager = (function () {
|
|
|
118
138
|
}
|
|
119
139
|
}
|
|
120
140
|
|
|
121
|
-
/**
|
|
122
|
-
* Create unique key that will be used for save focused group data inside specific screen
|
|
123
|
-
* @param {{ screenId: string, isInsideContainer: boolean }}
|
|
124
|
-
* screenId Unique Id of the screen from layout.json
|
|
125
|
-
* isInsideContainer If this screen a screen picker child
|
|
126
|
-
*
|
|
127
|
-
*/
|
|
128
|
-
function screenFocusableGroupId({ screenId, isInsideContainer }) {
|
|
129
|
-
return `RiverFocusableGroup-${screenId}${
|
|
130
|
-
R.isNil(isInsideContainer) ? "" : "-isInsideContainer"
|
|
131
|
-
}`;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
141
|
return {
|
|
135
142
|
setScreenFocusableData,
|
|
136
143
|
clearAllScreensData,
|
|
@@ -71,6 +71,9 @@ exports[`focusManagerIOS should be defined 1`] = `
|
|
|
71
71
|
"invokeHandler": [Function],
|
|
72
72
|
"isChildOf": [Function],
|
|
73
73
|
"isFocusOn": [Function],
|
|
74
|
+
"isFocusOnContent": [Function],
|
|
75
|
+
"isFocusOnMenu": [Function],
|
|
76
|
+
"isFocusOnTabsScreenContent": [Function],
|
|
74
77
|
"isGroupItemFocused": [Function],
|
|
75
78
|
"moveFocus": [Function],
|
|
76
79
|
"on": [Function],
|
|
@@ -4,7 +4,11 @@ import * as R from "ramda";
|
|
|
4
4
|
import {
|
|
5
5
|
isCurrentFocusOn,
|
|
6
6
|
isChildOf as isChildOfUtils,
|
|
7
|
-
|
|
7
|
+
isPartOfMenu,
|
|
8
|
+
isPartOfContent,
|
|
9
|
+
isPartOfTabsScreenContent,
|
|
10
|
+
} from "../focusManagerAux/utils/index.ios";
|
|
11
|
+
|
|
8
12
|
import { Tree } from "./treeDataStructure/Tree";
|
|
9
13
|
import { findFocusableNode } from "./treeDataStructure/Utils";
|
|
10
14
|
import { subscriber } from "../../functionUtils";
|
|
@@ -279,7 +283,9 @@ export const focusManager = (function () {
|
|
|
279
283
|
function setFocus(
|
|
280
284
|
id: string,
|
|
281
285
|
direction?: FocusManager.IOS.Direction,
|
|
282
|
-
options?: Partial<{
|
|
286
|
+
options?: Partial<{
|
|
287
|
+
groupFocusedChanged: boolean;
|
|
288
|
+
}>,
|
|
283
289
|
callback?: any
|
|
284
290
|
) {
|
|
285
291
|
blur(direction);
|
|
@@ -418,6 +424,30 @@ export const focusManager = (function () {
|
|
|
418
424
|
return id && isCurrentFocusOn(id, currentFocusNode);
|
|
419
425
|
}
|
|
420
426
|
|
|
427
|
+
function isFocusOnMenu(): boolean {
|
|
428
|
+
const currentFocusable = getCurrentFocus();
|
|
429
|
+
|
|
430
|
+
return isPartOfMenu(focusableTree, currentFocusable?.props?.id);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function isFocusOnContent(): boolean {
|
|
434
|
+
const currentFocusable = getCurrentFocus();
|
|
435
|
+
|
|
436
|
+
return isPartOfContent(focusableTree, currentFocusable?.props?.id);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function isFocusOnTabsScreenContent(
|
|
440
|
+
screenPickerContentContainerId: string
|
|
441
|
+
): boolean {
|
|
442
|
+
const currentFocusable = getCurrentFocus();
|
|
443
|
+
|
|
444
|
+
return isPartOfTabsScreenContent(
|
|
445
|
+
focusableTree,
|
|
446
|
+
screenPickerContentContainerId,
|
|
447
|
+
currentFocusable?.props?.id
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
|
|
421
451
|
function isChildOf(childId, parentId): boolean {
|
|
422
452
|
return isChildOfUtils(focusableTree, childId, parentId);
|
|
423
453
|
}
|
|
@@ -444,6 +474,9 @@ export const focusManager = (function () {
|
|
|
444
474
|
isGroupItemFocused,
|
|
445
475
|
getPreferredFocusChild,
|
|
446
476
|
isFocusOn,
|
|
477
|
+
isFocusOnMenu,
|
|
478
|
+
isFocusOnContent,
|
|
479
|
+
isFocusOnTabsScreenContent,
|
|
447
480
|
isChildOf,
|
|
448
481
|
};
|
|
449
482
|
})();
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { isNil, startsWith } from "@applicaster/zapp-react-native-utils/utils";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
QUICK_BRICK_CONTENT,
|
|
5
|
+
QUICK_BRICK_NAVBAR,
|
|
6
|
+
} from "@applicaster/quick-brick-core/const";
|
|
7
|
+
|
|
8
|
+
const isNavBar = (node) => startsWith(QUICK_BRICK_NAVBAR, node?.id);
|
|
9
|
+
const isContent = (node) => startsWith(QUICK_BRICK_CONTENT, node?.id);
|
|
10
|
+
const isRoot = (node) => node?.id === "root";
|
|
11
|
+
|
|
12
|
+
export const isPartOfTabsScreenContent = (
|
|
13
|
+
focusableTree,
|
|
14
|
+
screenPickerContentContainerId,
|
|
15
|
+
id
|
|
16
|
+
) => {
|
|
17
|
+
const node = focusableTree.findInTree(id);
|
|
18
|
+
|
|
19
|
+
if (isNil(node)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (isRoot(node)) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (isNavBar(node)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (isContent(node)) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (node?.id === screenPickerContentContainerId) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return isPartOfTabsScreenContent(
|
|
40
|
+
focusableTree,
|
|
41
|
+
screenPickerContentContainerId,
|
|
42
|
+
node.parent?.id
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const isPartOfMenu = (focusableTree, id): boolean => {
|
|
47
|
+
const node = focusableTree.findInTree(id);
|
|
48
|
+
|
|
49
|
+
if (isNil(node)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (isRoot(node)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (isNavBar(node)) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (isContent(node)) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return isPartOfMenu(focusableTree, node.parent?.id);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const isPartOfContent = (focusableTree, id) => {
|
|
69
|
+
const node = focusableTree.findInTree(id);
|
|
70
|
+
|
|
71
|
+
if (isNil(node)) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (isRoot(node)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (isNavBar(node)) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (isContent(node)) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return isPartOfContent(focusableTree, node.parent?.id);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const isCurrentFocusOn = (id, node) => {
|
|
91
|
+
if (!node) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (isRoot(node)) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (node?.id === id) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return isCurrentFocusOn(id, node.parent);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const isChildOf = (focusableTree, childId, parentId) => {
|
|
107
|
+
if (isNil(childId) || isNil(parentId)) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const childNode = focusableTree.findInTree(childId);
|
|
112
|
+
|
|
113
|
+
if (isNil(childNode)) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (childNode.parent?.id === parentId) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return isChildOf(focusableTree, childNode.parent?.id, parentId);
|
|
122
|
+
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { ReplaySubject, Subject } from "rxjs";
|
|
2
|
-
import { filter, switchMap, take, map } from "rxjs/operators";
|
|
2
|
+
import { filter, switchMap, take, withLatestFrom, map } from "rxjs/operators";
|
|
3
|
+
|
|
3
4
|
import { BUTTON_PREFIX } from "@applicaster/zapp-react-native-ui-components/Components/MasterCell/DefaultComponents/tv/TvActionButtons/const";
|
|
4
5
|
import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager/index.ios";
|
|
6
|
+
import { isPartOfMenu, isPartOfContent } from "./index.ios";
|
|
5
7
|
|
|
6
8
|
type FocusableID = string;
|
|
7
9
|
type RegistrationEvent = {
|
|
@@ -175,3 +177,55 @@ export const emitNativeRegistered = ({
|
|
|
175
177
|
focusableNativeGroupRegistrationSubject$.next({ id, groupId });
|
|
176
178
|
}
|
|
177
179
|
};
|
|
180
|
+
|
|
181
|
+
// /////
|
|
182
|
+
|
|
183
|
+
const focusedSubject$ = new Subject<FocusableID>();
|
|
184
|
+
|
|
185
|
+
const focused$ = focusedSubject$.asObservable();
|
|
186
|
+
|
|
187
|
+
export const emitFocused = (id: FocusableID): void => {
|
|
188
|
+
focusedSubject$.next(id);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export const topMenuItemFocused$ = focused$.pipe(
|
|
192
|
+
filter((id) => id && isPartOfMenu(focusManager.focusableTree, id))
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
export const contentFocused$ = focused$.pipe(
|
|
196
|
+
filter((id) => {
|
|
197
|
+
const isContent = isPartOfContent(focusManager.focusableTree, id);
|
|
198
|
+
|
|
199
|
+
return id && isContent;
|
|
200
|
+
})
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const createFocusableRegistry = () => {
|
|
204
|
+
const subject$ = new ReplaySubject<FocusableID | undefined>(1);
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
observable$: subject$.asObservable(),
|
|
208
|
+
register: (id: FocusableID) => {
|
|
209
|
+
// save focusable_id on registration
|
|
210
|
+
subject$.next(id);
|
|
211
|
+
},
|
|
212
|
+
unregister: () => {
|
|
213
|
+
// reset focusable_id on unregistration
|
|
214
|
+
subject$.next(undefined);
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
/// HOME_TOP_MENU_ITEM
|
|
220
|
+
export const HomeTopMenuItemRegistry = createFocusableRegistry();
|
|
221
|
+
|
|
222
|
+
export const homeTopMenuItemFocused$ = topMenuItemFocused$.pipe(
|
|
223
|
+
withLatestFrom(HomeTopMenuItemRegistry.observable$),
|
|
224
|
+
filter(([id, homeId]) => id === homeId)
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
/// SCREEN_PICKER
|
|
228
|
+
export const ScreenPickerContentContainerRegistry = createFocusableRegistry();
|
|
229
|
+
|
|
230
|
+
/// SEARCH_INPUT
|
|
231
|
+
export const SearchInputRegistry = createFocusableRegistry();
|
|
@@ -678,6 +678,22 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
678
678
|
}
|
|
679
679
|
|
|
680
680
|
if (isMobile(platform)) {
|
|
681
|
+
localizations.fields.push(
|
|
682
|
+
{
|
|
683
|
+
type: "text_input",
|
|
684
|
+
label: "Restrict playback on mobile networks alert title",
|
|
685
|
+
key: "mobile_connection_restricted_alert_title",
|
|
686
|
+
initial_value: "Restricted Connection Type",
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
type: "text_input",
|
|
690
|
+
label: "Restrict playback on mobile networks alert message",
|
|
691
|
+
key: "mobile_connection_restricted_alert_message",
|
|
692
|
+
initial_value:
|
|
693
|
+
"This content can only be viewed over a Wi-Fi or LAN network.",
|
|
694
|
+
}
|
|
695
|
+
);
|
|
696
|
+
|
|
681
697
|
general.fields.push(
|
|
682
698
|
{
|
|
683
699
|
section: "Default Timestamp Type",
|
|
@@ -1200,40 +1216,35 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
1200
1216
|
label: "Playback Speed 0.8x",
|
|
1201
1217
|
type: "uploader",
|
|
1202
1218
|
label_tooltip: "Playback Speed 0.8x",
|
|
1203
|
-
initial_value:
|
|
1204
|
-
"data:image/png;base64,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",
|
|
1219
|
+
initial_value: null,
|
|
1205
1220
|
},
|
|
1206
1221
|
{
|
|
1207
1222
|
key: "speed_1",
|
|
1208
1223
|
label: "Playback Speed 1x",
|
|
1209
1224
|
type: "uploader",
|
|
1210
1225
|
label_tooltip: "Playback Speed 1x",
|
|
1211
|
-
initial_value:
|
|
1212
|
-
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAYAAAA5ZDbSAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAI4SURBVHgB7dzhcRJBGIDhT8cCkg6SDmIHsQNLiBUYKwAqUCtIC5ZACVqBdIAdxN3JMSLecXcE8Tt5npkdJlxyf97ZcLcLRAAAAAAAAAAAAAAAAAAAAAAAAADAMb2IiViv17fl4aGMq45fub68vFwFv3kVyZWwF+VhVsZ9MFrqwANmLT1SBm5mbQ37NniWl5FMifu+PHwPcY8izQwuYa/iadbeBkeTaQbfxf64P8r4GoyS7l90h89lXJfxLRgl+23Sqox35f52WX8o/8aDcTLP4EUZrzdxOUzGGbws40MJ6/X2CDIFrhdRNeyn4GjSBBb275jKVTQHEvg/d/aB69Lo+sljGR/jADvnmEUiZx242a2qr/0XzVP35bmHkeeY7Zxj3pw3hXOfwTctz90NjdzEnbccuo0kzj1w1712b+Q9cfed9+TOOnCzSrboONwZuSfuopz3SyRx9hdZJcY8RkQeELfr2D/hNimGR55a3Cr9m+5OpcZpdqvabnNq5HpBdtPx5ynjVmbwlp6ZPLm4lcA7eiLvSh23ErjFwMjp41YCd3t85vEUBG7Rc7W8Mc+27txG4B0D426kjyzwlp64y47nU0cWuDFgEeNNdF94pY0scAxfoeq5uk4Z2Yb/yOXHqUW24X/A2vKAyGk+OGfDv13vIsaBy5onZ8P/T4NXqPZEtuGfwdaG/yp+vfF+HiNsRV5tnSPNhv9kvoSlaj75f9F2zBewAAAAAAAAAAAAAAAAAAAAAAAAwHT8BNhH51wNSx95AAAAAElFTkSuQmCC",
|
|
1226
|
+
initial_value: null,
|
|
1213
1227
|
},
|
|
1214
1228
|
{
|
|
1215
1229
|
key: "speed_1_2",
|
|
1216
1230
|
label: "Playback Speed 1.2x",
|
|
1217
1231
|
type: "uploader",
|
|
1218
1232
|
label_tooltip: "Playback Speed 1.2x",
|
|
1219
|
-
initial_value:
|
|
1220
|
-
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAYAAAA5ZDbSAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAUjSURBVHgB7d3bUeNIFAbgA7Xv2EUCmgiWjWBNBAwRYCKYdQQrIliIYEUEw0awymDIYDQBUFC8g+eccWsulPomteTu1v9VdZlSGyP0W1KrWxciAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgPgc0Mw8Pj4u+KUt4mm5XDaUqVED5oX5gV/ea6o3vGDvaQI8Hyt+ueAir4XmbTIvNZebnAIfJWBeoCf88g/tFqjOKS/ImkbE8/FezUdBfu5o9wVsKHGHFJBs/rjIAv1E5nBHpebjX/7xI/mHK+SL8Zk/429K3G8UiNoMykItaI/UPvZ/Lic0XMmfR7wmX1GiBq/BP621slAL2j9Z60KE25KQ/6JEDdoHD9jHieD7YJ6fNe22IjpPXG5p16Bq1DT5MpyReZciv/eO5/eJEtN7E63C/Wh5myyQBU1Hu888ODi42263lx0h1Vyu1ZdDvqxd8yvTZC0uKTFDNtGm4GQhbrjc0ERUy73QVNeLxeLctAZyXcUv56R3QQkK2or+9oGHhxXtNmfXNK2Voe6SHKhdxq2muuAvUUGJCRlww+X06Ojock/7qj810+89j2fvDHUFJSZUwLIp/mPsjgsL3S7jC/mZpHdtKkOPg2VhbPYcbEuOVbs2rw35mbJROLohAdeqYRKFgF+ywlCX3GFS7010xiMwZ5rpzVSDIyEFb0WnTLWS1111fHRQU4IQ8K+0vWCvr69J9kcjYEWNHK001c5jxNIvz2XLpfdolIyEtZ/x/Pw8qIMFAdP3ExNKTXVDjl2UqruzHZgoaDdQ4RWyGuZct5/x8vJSqRGyXmYfMC88WUNMvW6nHh03q45pziG/Cdf2uU5mHbAKtzK8xfesjloz3RqyIVzRu/U+24Adwr3y7U+XfgFujOn6srUhW8K9GnJIOsuAHcMtqYfj4+O1T8gO4ZY0wOwCdgj3duhCdQ157HDFrAJ2DHdNATiELCcmrjX1QcIVswl4ynBblpB1540FC1fMImB1nFsZ3hI83JYl5LeChiuyD1jt70yt4dHCbTmGHDxckXXAKtzS8JbRw23xYMXW8hZbfb+/S5mKKVxLa7nl3a3pItiVDaGpoTs5NVf6YeW8qjuP300t3FbwKymiDLgrIJ4m3XXWfuGEwpX/p6slHTTk6AJWIzJlR5UsDDnR/tTwu7ZwGy6Vuo6qF9dTg1w6MR4eHireN3cNBwYLOcY1+MxQt5Khs661WF0/VJJZQbtrqPpquLyzvcm1h0pa1xwyjRlyjI0s29jnief0Sfl2P/r2XfuKMWDbeczRnvimdi9rTbX2ONch5N5f3hgDrgx1N8u4r/BbaaZbOzEsIRfUU3QBq0aMXEvUvKmS1m/s1+nWHdOce6gMIffeao3dyJJj10ZTp5v+beCcN0s17farsk9uHFqvFenPqAjFuPVQ813wjx/UJO+TBiRk/gz5Oxfq7w0a8AcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANif7B8Q/dOlJHI7iEJNvlcl+8tCsg5Y3R/Ldm1QuUz46aI2ud9lx+XCr1HubhOLLNdgh6eQdjn3uZNPKnIN+DP5XzQtl6ha77+Rmuw20eoOOgX5K4bcfSdWOe6Dh9yMJYobuYSUY8BDnj2Y1XMLRY4BD7lJS3LPJrTJMeAht1lK7tmENmhF/4BWdEI25C/L3qwsA1YdFj6BXcX0LOSQcu+LLvnF1g258b2XVUrmMppUcvmdfhznNlz+43Kd+2jSV0z9PFgMpokeAAAAAElFTkSuQmCC",
|
|
1233
|
+
initial_value: null,
|
|
1221
1234
|
},
|
|
1222
1235
|
{
|
|
1223
1236
|
key: "speed_1_5",
|
|
1224
1237
|
label: "Playback Speed 1.5x",
|
|
1225
1238
|
type: "uploader",
|
|
1226
1239
|
label_tooltip: "Playback Speed 1.5x",
|
|
1227
|
-
initial_value:
|
|
1228
|
-
"data:image/png;base64,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",
|
|
1240
|
+
initial_value: null,
|
|
1229
1241
|
},
|
|
1230
1242
|
{
|
|
1231
1243
|
key: "speed_2",
|
|
1232
1244
|
label: "Playback Speed 2x",
|
|
1233
1245
|
type: "uploader",
|
|
1234
1246
|
label_tooltip: "Playback Speed 2x",
|
|
1235
|
-
initial_value:
|
|
1236
|
-
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAYAAAA5ZDbSAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAQnSURBVHgB7d2PUdRAFMfxh2MBUIGxArECjwqECjwqUCowV4FYgdABVCAdSAekg7ME35pFgnOb3ZDAvmy+n5lMGJLL3dzvkuyfZCMCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2LMnM7Hdbvd11p2ag4ODRtDLdMAa6kpnH3U61qkKrHbrp0sN/EbwiMmAfbBfdVrJMI1OZxr0leAvcwFruC7YWsY515DPBLYC1nC/6eyLTONCQz6VhXslRvg9d6pwnbX/wSyaiT1Yg6h0dhdZzZ1Xr6U9zzqHOn2QtgDW52jJhS8rAf/Q2Tqw2JWQT0JVIv/j+CnhUvaNvvZIFip7wL5+uw0sbqTdA5vINiqd/ZK2fryLbuLgtyyQhXPwYc+yTUpjhl/ne88qK1koCwGvQgs0uAtJ17duJQtlIeDQYfVWBojs6fuyUK8lP1c63hXmlOfMRZ5/newBT1WF0YJW37m8kYUy09Axgb6ABx3uSzKb7sI+vqrlqknVjsXJ9WC/HRlTpZpiG1MqZQ92zZxVYNmlJPBNpdv2z6c1cfrXjdrG1Ga/B+sX+Vln54HFV7onnUh8GytpW8O6BnVWBFrjTnJ3Xc56D/YFq1C47hCZ2mW46/y99qGlfI5QU+uhZDbbgDtt0CFJrWBeqBAWDTnSjn4jmc0y4E64oQYMF+65JPJVtU1gcTDkSLgbC71YszsHJ/QeuWuz1vK0bdfSFth2eXROTgi3FgNmFXBCuLf6xb6Xce9RSyTkuYTrzOmyWVdg6Tssu/Po0RT1z0jIjYR/YKbCdWYR8EuG23nPWsIh72IuXMd8wDnC7bx3LWkhmwzXsX7he7ZwO5+hlv6QzYbrmK0mWQjXezNyeVZW72wwEW6ktNxl9hpsc3uwfqmfZF7hOsnNmi/NVMA+3AsJh9tI24CfM9ybwP9Nhmzpzob7cEMaSbiEdoLPEWvEcH3Lg5s1c7Fy4Xss3PueoUaeKKVdeEgL1ZBmzZwsXPi+kv5eoUnoF74X+Rx9N77trArNIWQLh+hKMtOgjmVguI7/f9/h+lgyK+miuzGqwP+jjRiRkOnwN2JXh39yC1VPyFeSGQHLvwKYu3bLBd1IOwxELQP49U//28atZGahkFXJC9wcNvA+JwAAAAAAAAAAAAAAAAAAAAAAAIxWxIjvffytMW5M6e4ziO+fOTxkRNpZKjrgyGDh92oNeSOFKvbuQj9Ef8qQwrVft0hF7sEa2FpnQwdDyT78/nMoNeA7GT40RKMBv5XCFHeI9oO6VDJc5V9blBLPwWPGxcg+psbUSgx4X55uzGtNKjHgMcMcPvfItS+uxIDHDHySfdCUqVGKfkApekZSn3jWVWRrVpEB+waLIYFtSh1mqfS26FriD9U4G/KUtLlZSm9SrdM7eajnNjpd63Reem/SH2FDq35f8ateAAAAAElFTkSuQmCC",
|
|
1247
|
+
initial_value: null,
|
|
1237
1248
|
},
|
|
1238
1249
|
{
|
|
1239
1250
|
key: "sleep_timer",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-utils",
|
|
3
|
-
"version": "15.0.0-alpha.
|
|
3
|
+
"version": "15.0.0-alpha.7809066324",
|
|
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": "15.0.0-alpha.
|
|
30
|
+
"@applicaster/applicaster-types": "15.0.0-alpha.7809066324",
|
|
31
31
|
"buffer": "^5.2.1",
|
|
32
32
|
"camelize": "^1.0.0",
|
|
33
33
|
"dayjs": "^1.11.10",
|
|
@@ -2,7 +2,7 @@ import * as React from "react";
|
|
|
2
2
|
import { Provider } from "react-redux";
|
|
3
3
|
import { renderHook } from "@testing-library/react-hooks";
|
|
4
4
|
import configureStore from "redux-mock-store";
|
|
5
|
-
import thunk from "redux-thunk";
|
|
5
|
+
import { thunk } from "redux-thunk";
|
|
6
6
|
|
|
7
7
|
const mockStore = configureStore([thunk]);
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@ import * as React from "react";
|
|
|
2
2
|
import { Provider } from "react-redux";
|
|
3
3
|
import { renderHook } from "@testing-library/react-hooks";
|
|
4
4
|
import configureStore from "redux-mock-store";
|
|
5
|
-
import thunk from "redux-thunk";
|
|
5
|
+
import { thunk } from "redux-thunk";
|
|
6
6
|
|
|
7
7
|
const mockStore = configureStore([thunk]);
|
|
8
8
|
|
package/testUtils/index.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import React, { PropsWithChildren } from "react";
|
|
|
4
4
|
import configureStore from "redux-mock-store";
|
|
5
5
|
import { Provider } from "react-redux";
|
|
6
6
|
import { View } from "react-native";
|
|
7
|
-
import thunk from "redux-thunk";
|
|
7
|
+
import { thunk } from "redux-thunk";
|
|
8
8
|
import * as R from "ramda";
|
|
9
9
|
|
|
10
10
|
import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { mergeRight } from "../mergeRight";
|
|
2
|
+
|
|
3
|
+
describe("mergeRight", () => {
|
|
4
|
+
test("merges two objects with no overlapping keys", () => {
|
|
5
|
+
const a = { x: 1, y: 2 };
|
|
6
|
+
const b = { z: 3 };
|
|
7
|
+
|
|
8
|
+
const result = mergeRight(a, b);
|
|
9
|
+
|
|
10
|
+
expect(result).toEqual({ x: 1, y: 2, z: 3 });
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("overwrites keys from the second object", () => {
|
|
14
|
+
const a = { x: 1, y: 2 };
|
|
15
|
+
const b = { y: 10, z: 3 };
|
|
16
|
+
|
|
17
|
+
const result = mergeRight(a, b);
|
|
18
|
+
|
|
19
|
+
expect(result).toEqual({ x: 1, y: 10, z: 3 });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("does not mutate the original objects", () => {
|
|
23
|
+
const a = { x: 1 };
|
|
24
|
+
const b = { y: 2 };
|
|
25
|
+
|
|
26
|
+
const result = mergeRight(a, b);
|
|
27
|
+
|
|
28
|
+
expect(result).not.toBe(a);
|
|
29
|
+
expect(result).not.toBe(b);
|
|
30
|
+
expect(a).toEqual({ x: 1 });
|
|
31
|
+
expect(b).toEqual({ y: 2 });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("works with empty objects", () => {
|
|
35
|
+
expect(mergeRight({}, { a: 1 })).toEqual({ a: 1 });
|
|
36
|
+
expect(mergeRight({ a: 1 }, {})).toEqual({ a: 1 });
|
|
37
|
+
expect(mergeRight({}, {})).toEqual({});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("works with nested objects (shallow merge only)", () => {
|
|
41
|
+
const a = { x: { nested: 1 }, y: 2 };
|
|
42
|
+
const b = { x: { nested: 10 }, z: 3 };
|
|
43
|
+
|
|
44
|
+
const result = mergeRight(a, b);
|
|
45
|
+
|
|
46
|
+
expect(result).toEqual({ x: { nested: 10 }, y: 2, z: 3 });
|
|
47
|
+
});
|
|
48
|
+
});
|
package/utils/index.ts
CHANGED
|
@@ -18,6 +18,8 @@ export { take } from "./take";
|
|
|
18
18
|
|
|
19
19
|
export { mapAccum } from "./mapAccum";
|
|
20
20
|
|
|
21
|
+
export { mergeRight } from "./mergeRight";
|
|
22
|
+
|
|
21
23
|
export {
|
|
22
24
|
cloneDeep as clone,
|
|
23
25
|
flatten,
|
|
@@ -51,4 +53,5 @@ export {
|
|
|
51
53
|
findIndex,
|
|
52
54
|
set,
|
|
53
55
|
compact,
|
|
56
|
+
identity,
|
|
54
57
|
} from "lodash";
|