@applicaster/zapp-react-native-utils 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/README.md +0 -6
- package/actionUtils/index.ts +7 -0
- package/actionsExecutor/ActionExecutorContext.tsx +83 -6
- package/appUtils/HooksManager/index.ts +35 -0
- package/appUtils/focusManager/treeDataStructure/Tree/__tests__/Tree.test.js +46 -0
- package/appUtils/focusManager/treeDataStructure/Tree/index.js +18 -18
- package/appUtils/focusManagerAux/utils/index.ts +12 -6
- package/appUtils/focusManagerAux/utils/utils.ios.ts +6 -3
- package/appUtils/localizationsHelper.ts +4 -0
- package/appUtils/playerManager/index.ts +9 -0
- package/appUtils/playerManager/player.ts +1 -1
- package/appUtils/playerManager/playerNative.ts +2 -1
- package/appUtils/playerManager/usePlayer.tsx +5 -3
- package/cellUtils/__tests__/cellUtils.test.ts +39 -0
- package/cellUtils/index.ts +11 -1
- package/componentsUtils/index.ts +8 -0
- package/dateUtils/__tests__/dayjs.test.ts +0 -3
- package/dateUtils/index.ts +2 -0
- package/manifestUtils/_internals/__tests__/index.test.js +41 -0
- package/manifestUtils/_internals/index.js +33 -0
- package/manifestUtils/defaultManifestConfigurations/player.js +6 -16
- package/manifestUtils/fieldUtils/__tests__/fieldUtils.test.js +49 -0
- package/manifestUtils/fieldUtils/index.js +54 -0
- package/manifestUtils/index.js +2 -0
- package/manifestUtils/keys.js +228 -0
- package/manifestUtils/mobileAction/button/__tests__/mobileActionButton.test.js +168 -0
- package/manifestUtils/mobileAction/button/index.js +140 -0
- package/manifestUtils/mobileAction/container/__tests__/mobileActionButtonsContainer.test.js +102 -0
- package/manifestUtils/mobileAction/container/index.js +73 -0
- package/manifestUtils/mobileAction/groups/__tests__/buildMobileActionButtonGroups.test.js +127 -0
- package/manifestUtils/mobileAction/groups/defaults.js +76 -0
- package/manifestUtils/mobileAction/groups/index.js +80 -0
- package/numberUtils/__tests__/toNumber.test.ts +27 -12
- package/numberUtils/__tests__/toPositiveNumber.test.ts +32 -4
- package/numberUtils/index.ts +5 -1
- package/package.json +3 -3
- package/pluginUtils/index.ts +4 -5
- package/reactHooks/casting/index.ts +1 -0
- package/reactHooks/casting/useIsCasting.tsx +57 -0
- package/reactHooks/cell-click/index.ts +2 -1
- package/reactHooks/feed/index.ts +0 -2
- package/reactHooks/feed/useInflatedUrl.ts +1 -1
- package/reactHooks/resolvers/useComponentResolver.ts +13 -3
- package/reactHooks/screen/__tests__/useCurrentScreenIsHook.test.ts +103 -0
- package/reactHooks/screen/__tests__/useCurrentScreenIsStartupHook.test.ts +94 -0
- package/reactHooks/screen/index.ts +4 -0
- package/reactHooks/screen/useCurrentScreenIsHook.ts +9 -0
- package/reactHooks/screen/useCurrentScreenIsStartupHook.ts +8 -0
- package/reactHooks/state/__tests__/useComponentScreenState.test.ts +246 -0
- package/reactHooks/state/index.ts +2 -0
- package/reactHooks/state/useComponentScreenState.ts +45 -0
- package/refreshUtils/RefreshCoordinator/__tests__/refreshCoordinator.test.ts +206 -0
- package/refreshUtils/RefreshCoordinator/index.ts +245 -0
- package/refreshUtils/RefreshCoordinator/utils/__tests__/getDataRefreshConfig.test.ts +104 -0
- package/refreshUtils/RefreshCoordinator/utils/index.ts +29 -0
- package/screenPickerUtils/index.ts +5 -0
- package/screenUtils/index.ts +3 -0
- package/utils/__tests__/clone.test.ts +158 -0
- package/utils/__tests__/path.test.ts +7 -0
- package/utils/clone.ts +7 -0
- package/utils/index.ts +2 -1
- package/reactHooks/feed/__tests__/useFeedRefresh.test.tsx +0 -75
- package/reactHooks/feed/useFeedRefresh.tsx +0 -65
package/README.md
CHANGED
|
@@ -245,12 +245,6 @@ const connectionType = useConnectionInfo(true);
|
|
|
245
245
|
|
|
246
246
|
`@applicaster/zapp-react-native/reactHooks`
|
|
247
247
|
|
|
248
|
-
- `useFeedRefresh: ({ reloadData: function, component: { id: boolean | string, rules: {enable_data_refreshing: boolean, refreshing_interval: number} } }) => void` - Hook will call `reloadData` function, in the specified intervals if `enable_data_refreshing` is set to true;
|
|
249
|
-
|
|
250
|
-
```javascript
|
|
251
|
-
useFeedRefresh({ reloadData, component });
|
|
252
|
-
```
|
|
253
|
-
|
|
254
248
|
- `useFeedLoader: ({ feedUrl: string, pipesOptions?: { clearCache?: boolean, loadLocalFavourites?: boolean, silentRefresh?: boolean} }) => ({data: ?ApplicasterFeed, loading: boolean, url: string, error: Error,reloadData: (silentRefresh?: boolean) => void, loadNext: () => void})` - Hook will load data to the redux store and return a feed for the provided DSP URL. If the data for the provided url was already loaded, it will return that value
|
|
255
249
|
|
|
256
250
|
```javascript
|
|
@@ -24,6 +24,10 @@ import {
|
|
|
24
24
|
resolveObjectValues,
|
|
25
25
|
} from "../appUtils/contextKeysManager/contextResolver";
|
|
26
26
|
import { useNavigation, useRivers } from "../reactHooks";
|
|
27
|
+
import {
|
|
28
|
+
getInflatedDataSourceUrl,
|
|
29
|
+
getSearchContext,
|
|
30
|
+
} from "../reactHooks/feed/useInflatedUrl";
|
|
27
31
|
|
|
28
32
|
import { useContentTypes } from "@applicaster/zapp-react-native-redux/hooks";
|
|
29
33
|
import { useSubscriberFor } from "../reactHooks/useSubscriberFor";
|
|
@@ -104,12 +108,42 @@ const prepareDefaultActions = (actionExecutor) => {
|
|
|
104
108
|
async (_action, context) => {
|
|
105
109
|
const dispatch = appStore.getDispatch();
|
|
106
110
|
|
|
107
|
-
const
|
|
108
|
-
context?.component?.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
+
const parentComponent = findParentComponent(
|
|
112
|
+
context?.component?.id,
|
|
113
|
+
context?.screenData
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const componentSource = context?.component?.data?.source;
|
|
111
117
|
|
|
112
|
-
|
|
118
|
+
const componentData = componentSource
|
|
119
|
+
? context.component.data
|
|
120
|
+
: parentComponent?.data;
|
|
121
|
+
|
|
122
|
+
const source = componentData?.source;
|
|
123
|
+
const mapping = componentData?.mapping;
|
|
124
|
+
|
|
125
|
+
let dataSource = source;
|
|
126
|
+
|
|
127
|
+
if (source && mapping) {
|
|
128
|
+
dataSource =
|
|
129
|
+
getInflatedDataSourceUrl({
|
|
130
|
+
source,
|
|
131
|
+
contexts: {
|
|
132
|
+
entry: context?.entryContext,
|
|
133
|
+
screen: context?.screenData,
|
|
134
|
+
search: getSearchContext(null, mapping),
|
|
135
|
+
},
|
|
136
|
+
mapping,
|
|
137
|
+
}) || source;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
log_info(`handleAction: refreshComponent for dataSource:${dataSource}`, {
|
|
141
|
+
source,
|
|
142
|
+
inflatedUrl: dataSource,
|
|
143
|
+
mapping,
|
|
144
|
+
entryContextId: context?.entryContext?.id,
|
|
145
|
+
entryId: context?.entry?.id,
|
|
146
|
+
});
|
|
113
147
|
|
|
114
148
|
// TODO: In theory we should wait callback to complete, before completing the action, but now it's not needed
|
|
115
149
|
// TODO: handle focused item removal
|
|
@@ -315,6 +349,44 @@ export function withActionExecutor(Component) {
|
|
|
315
349
|
return ActionResult.Error;
|
|
316
350
|
}
|
|
317
351
|
|
|
352
|
+
const navigationAction = action.options?.navigationAction;
|
|
353
|
+
const entrySource = action.options?.entry;
|
|
354
|
+
|
|
355
|
+
const entry = entrySource
|
|
356
|
+
? entrySource === "@{entry/}"
|
|
357
|
+
? context?.entry
|
|
358
|
+
: entrySource
|
|
359
|
+
: null;
|
|
360
|
+
|
|
361
|
+
if (entry) {
|
|
362
|
+
if (typeof entry !== "object") {
|
|
363
|
+
log_error(
|
|
364
|
+
`navigateToScreen: entry option is not an object, entry: ${entry}`
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
return ActionResult.Error;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
log_info(
|
|
371
|
+
`navigateToScreen: navigating to screen type: ${screenType} with entry id: ${entry.id}`
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
const overriddenEntry = {
|
|
375
|
+
...entry,
|
|
376
|
+
type: {
|
|
377
|
+
value: screenType,
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
if (navigationAction === "push") {
|
|
382
|
+
navigator.push(overriddenEntry);
|
|
383
|
+
} else {
|
|
384
|
+
navigator.replace(overriddenEntry);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return ActionResult.Success;
|
|
388
|
+
}
|
|
389
|
+
|
|
318
390
|
const screenId = contentTypes?.[screenType]?.screen_id || null;
|
|
319
391
|
|
|
320
392
|
if (!screenId) {
|
|
@@ -334,7 +406,12 @@ export function withActionExecutor(Component) {
|
|
|
334
406
|
}
|
|
335
407
|
|
|
336
408
|
context?.callback?.({ success: false, error: null, abort: true });
|
|
337
|
-
|
|
409
|
+
|
|
410
|
+
if (navigationAction === "push") {
|
|
411
|
+
navigator.push(river);
|
|
412
|
+
} else {
|
|
413
|
+
navigator.replace(river);
|
|
414
|
+
}
|
|
338
415
|
|
|
339
416
|
return ActionResult.Success;
|
|
340
417
|
}
|
|
@@ -11,6 +11,10 @@ import { HOOKS_EVENTS, HOOKS_TYPE } from "./constants";
|
|
|
11
11
|
|
|
12
12
|
import { hooksManagerLogger } from "./logger";
|
|
13
13
|
import { HookManager, HookManagerArgs } from "./types";
|
|
14
|
+
import {
|
|
15
|
+
actionExecutor,
|
|
16
|
+
ActionResult,
|
|
17
|
+
} from "../../actionsExecutor/ActionExecutor";
|
|
14
18
|
|
|
15
19
|
/**
|
|
16
20
|
* orders the hooks according to their weight
|
|
@@ -280,6 +284,8 @@ export function HooksManager({
|
|
|
280
284
|
{}
|
|
281
285
|
);
|
|
282
286
|
|
|
287
|
+
actionExecutor.unregisterAction("finishHook");
|
|
288
|
+
|
|
283
289
|
return;
|
|
284
290
|
}
|
|
285
291
|
|
|
@@ -294,6 +300,8 @@ export function HooksManager({
|
|
|
294
300
|
}
|
|
295
301
|
);
|
|
296
302
|
|
|
303
|
+
actionExecutor.unregisterAction("finishHook");
|
|
304
|
+
|
|
297
305
|
return hookPlugin.setStateAndNotify(HOOKS_EVENTS.ERROR, {
|
|
298
306
|
error,
|
|
299
307
|
hookPlugin,
|
|
@@ -316,6 +324,8 @@ export function HooksManager({
|
|
|
316
324
|
// TODO: Temporary hack to pass getLoginProtocol to other plugins to refresh in case token expired, need be deleted later
|
|
317
325
|
delete payload.getLoginProtocol;
|
|
318
326
|
|
|
327
|
+
actionExecutor.unregisterAction("finishHook");
|
|
328
|
+
|
|
319
329
|
hookPlugin.setStateAndNotify(HOOKS_EVENTS.CANCEL, {
|
|
320
330
|
hookPlugin,
|
|
321
331
|
payload,
|
|
@@ -381,6 +391,27 @@ export function HooksManager({
|
|
|
381
391
|
};
|
|
382
392
|
}
|
|
383
393
|
|
|
394
|
+
function registerFinishHookAction(payload, callback) {
|
|
395
|
+
// Ensure no stale finishHook remains (e.g. presentUI re-entry from runInBackground)
|
|
396
|
+
actionExecutor.unregisterAction("finishHook");
|
|
397
|
+
|
|
398
|
+
actionExecutor.registerAction("finishHook", async (action: ActionType) => {
|
|
399
|
+
const { success, errorMessage, abort } = action.options;
|
|
400
|
+
|
|
401
|
+
actionExecutor.unregisterAction("finishHook");
|
|
402
|
+
|
|
403
|
+
if (errorMessage) {
|
|
404
|
+
callback({ success, error: new Error(errorMessage), payload, abort });
|
|
405
|
+
} else {
|
|
406
|
+
callback({ success, error: null, payload, abort });
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
hooksManagerLogger.info("finishHook action executed, finishing flow");
|
|
410
|
+
|
|
411
|
+
return ActionResult.Success;
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
384
415
|
/**
|
|
385
416
|
* presents a screen hook by triggering an event invoking the handler with
|
|
386
417
|
* the appropriate route & payload
|
|
@@ -408,6 +439,8 @@ export function HooksManager({
|
|
|
408
439
|
}
|
|
409
440
|
);
|
|
410
441
|
|
|
442
|
+
registerFinishHookAction(payload, callback);
|
|
443
|
+
|
|
411
444
|
hookPlugin.setStateAndNotify(HOOKS_EVENTS.PRESENT_SCREEN_HOOK, {
|
|
412
445
|
hookPlugin,
|
|
413
446
|
route: targetScreenRoute,
|
|
@@ -467,6 +500,8 @@ export function HooksManager({
|
|
|
467
500
|
}
|
|
468
501
|
);
|
|
469
502
|
|
|
503
|
+
registerFinishHookAction(payload, callback);
|
|
504
|
+
|
|
470
505
|
hookPlugin.module.runInBackground(
|
|
471
506
|
payload,
|
|
472
507
|
callback,
|
|
@@ -373,3 +373,49 @@ describe("addNode", () => {
|
|
|
373
373
|
checkParents(tree.root);
|
|
374
374
|
});
|
|
375
375
|
});
|
|
376
|
+
|
|
377
|
+
describe("findInTree", () => {
|
|
378
|
+
function createNode(id, children) {
|
|
379
|
+
return {
|
|
380
|
+
id,
|
|
381
|
+
children,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
it("returns a direct child match from root children", () => {
|
|
386
|
+
const tree = new Tree(treeLoaded);
|
|
387
|
+
const direct = createNode("direct-node");
|
|
388
|
+
|
|
389
|
+
tree.root.children = [direct];
|
|
390
|
+
|
|
391
|
+
expect(tree.findInTree("direct-node")).toEqual(direct);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it("returns a nested descendant match", () => {
|
|
395
|
+
const tree = new Tree(treeLoaded);
|
|
396
|
+
const nested = createNode("nested-node");
|
|
397
|
+
const intermediate = createNode("intermediate-node", [nested]);
|
|
398
|
+
const rootNode = createNode("root-node", [intermediate]);
|
|
399
|
+
|
|
400
|
+
tree.root.children = [rootNode];
|
|
401
|
+
|
|
402
|
+
expect(tree.findInTree("nested-node")).toEqual(nested);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it("returns null when node id does not exist", () => {
|
|
406
|
+
const tree = new Tree(treeLoaded);
|
|
407
|
+
const leaf = createNode("leaf-node");
|
|
408
|
+
const rootNode = createNode("root-node", [leaf]);
|
|
409
|
+
|
|
410
|
+
tree.root.children = [rootNode];
|
|
411
|
+
|
|
412
|
+
expect(tree.findInTree("missing-node")).toEqual(null);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it("returns null when tree has no children", () => {
|
|
416
|
+
const tree = new Tree(treeLoaded);
|
|
417
|
+
tree.root.children = null;
|
|
418
|
+
|
|
419
|
+
expect(tree.findInTree("any-node")).toEqual(null);
|
|
420
|
+
});
|
|
421
|
+
});
|
|
@@ -205,31 +205,31 @@ export class Tree {
|
|
|
205
205
|
* @returns founded node or null
|
|
206
206
|
*/
|
|
207
207
|
findInTree(id) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
return this.findInArray(id, this.root.children, retVal);
|
|
208
|
+
return this.findInArray(id, this.root.children);
|
|
211
209
|
}
|
|
212
210
|
|
|
213
|
-
findInArray(id, children
|
|
214
|
-
if (!
|
|
215
|
-
|
|
211
|
+
findInArray(id, children) {
|
|
212
|
+
if (!children) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
216
215
|
|
|
217
|
-
|
|
218
|
-
children.forEach((child) => {
|
|
219
|
-
if (child.children) {
|
|
220
|
-
retVal = this.findInArray(id, child.children, retVal);
|
|
216
|
+
const directMatch = children.find((obj) => obj.id === id);
|
|
221
217
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
218
|
+
if (directMatch) {
|
|
219
|
+
return directMatch;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
for (const child of children) {
|
|
223
|
+
if (child.children) {
|
|
224
|
+
const nestedMatch = this.findInArray(id, child.children);
|
|
225
|
+
|
|
226
|
+
if (nestedMatch) {
|
|
227
|
+
return nestedMatch;
|
|
228
|
+
}
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
return
|
|
232
|
+
return null;
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
/**
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
15
|
getFocusableId,
|
|
16
|
-
|
|
16
|
+
isTabsScreenContentContainerId,
|
|
17
17
|
} from "@applicaster/zapp-react-native-utils/screenPickerUtils";
|
|
18
18
|
|
|
19
19
|
// run check each 300 ms
|
|
@@ -24,8 +24,6 @@ const isTopMenu = (node) => startsWith(QUICK_BRICK_NAVBAR, node?.id);
|
|
|
24
24
|
const isContent = (node) => startsWith(QUICK_BRICK_CONTENT, node?.id);
|
|
25
25
|
const isRoot = (node) => node?.id === "root";
|
|
26
26
|
|
|
27
|
-
const isScrenPicker = (node) => startsWith(SCREEN_PICKER_CONTAINER, node?.id);
|
|
28
|
-
|
|
29
27
|
type Props = {
|
|
30
28
|
maxTimeout: number;
|
|
31
29
|
conditionFn: () => boolean;
|
|
@@ -136,7 +134,7 @@ export const isTabsScreenOnContentFocused = (node) => {
|
|
|
136
134
|
return false;
|
|
137
135
|
}
|
|
138
136
|
|
|
139
|
-
if (
|
|
137
|
+
if (isTabsScreenContentContainerId(node?.id)) {
|
|
140
138
|
return true;
|
|
141
139
|
}
|
|
142
140
|
|
|
@@ -144,6 +142,10 @@ export const isTabsScreenOnContentFocused = (node) => {
|
|
|
144
142
|
};
|
|
145
143
|
|
|
146
144
|
export const isCurrentFocusOnMenu = (node) => {
|
|
145
|
+
if (isNil(node)) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
|
|
147
149
|
if (isRoot(node)) {
|
|
148
150
|
return false;
|
|
149
151
|
}
|
|
@@ -156,10 +158,14 @@ export const isCurrentFocusOnMenu = (node) => {
|
|
|
156
158
|
return false;
|
|
157
159
|
}
|
|
158
160
|
|
|
159
|
-
return isCurrentFocusOnMenu(node
|
|
161
|
+
return isCurrentFocusOnMenu(node?.parent);
|
|
160
162
|
};
|
|
161
163
|
|
|
162
164
|
export const isCurrentFocusOnContent = (node) => {
|
|
165
|
+
if (isNil(node)) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
163
169
|
if (isRoot(node)) {
|
|
164
170
|
return false;
|
|
165
171
|
}
|
|
@@ -172,7 +178,7 @@ export const isCurrentFocusOnContent = (node) => {
|
|
|
172
178
|
return true;
|
|
173
179
|
}
|
|
174
180
|
|
|
175
|
-
return isCurrentFocusOnContent(node
|
|
181
|
+
return isCurrentFocusOnContent(node?.parent);
|
|
176
182
|
};
|
|
177
183
|
|
|
178
184
|
export const isCurrentFocusOn = (id, node) => {
|
|
@@ -106,9 +106,8 @@ const focusableNativeViewRegistration = ({ focusableView, focusableGroup }) => {
|
|
|
106
106
|
);
|
|
107
107
|
};
|
|
108
108
|
|
|
109
|
-
export const
|
|
109
|
+
export const firstFocusableViewInContentRegistrationFactory = () =>
|
|
110
110
|
focusableViewRegistrationSubject$.pipe(
|
|
111
|
-
take(1), // we care about only first FocusableView registration
|
|
112
111
|
switchMap((focusableView) =>
|
|
113
112
|
// start waiting registration of its parent FocusableGroup
|
|
114
113
|
focusableGroupRegistrationSubject$.pipe(
|
|
@@ -126,7 +125,11 @@ export const firstFocusableViewRegistrationFactory = () =>
|
|
|
126
125
|
focusableView,
|
|
127
126
|
focusableGroup,
|
|
128
127
|
})
|
|
129
|
-
)
|
|
128
|
+
),
|
|
129
|
+
filter(({ focusableView }) =>
|
|
130
|
+
isPartOfContent(focusManager.focusableTree, focusableView.id)
|
|
131
|
+
),
|
|
132
|
+
take(1) // we care about only first FocusableView registration
|
|
130
133
|
);
|
|
131
134
|
|
|
132
135
|
// registration on RN level(into RN focusManager)
|
|
@@ -33,6 +33,7 @@ function deprecationWarning(method) {
|
|
|
33
33
|
export interface PlayerLifecycleListener {
|
|
34
34
|
onRegistered?: (player: Player) => void;
|
|
35
35
|
onUnRegistered?: (player: Player) => void;
|
|
36
|
+
onCastingChanged?: (isCasting: boolean) => void;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
type PlayersMap = {
|
|
@@ -253,10 +254,18 @@ export class PlayerManager {
|
|
|
253
254
|
|
|
254
255
|
public registerCastingReceiver(receiver: Player) {
|
|
255
256
|
this.castingReceiver = receiver;
|
|
257
|
+
|
|
258
|
+
this.lifecycleListeners.forEach((listener) => {
|
|
259
|
+
listener.onCastingChanged?.(true);
|
|
260
|
+
});
|
|
256
261
|
}
|
|
257
262
|
|
|
258
263
|
public unregisterCastingReceiver() {
|
|
259
264
|
this.castingReceiver = null;
|
|
265
|
+
|
|
266
|
+
this.lifecycleListeners.forEach((listener) => {
|
|
267
|
+
listener.onCastingChanged?.(false);
|
|
268
|
+
});
|
|
260
269
|
}
|
|
261
270
|
|
|
262
271
|
/**
|
|
@@ -229,8 +229,9 @@ export class PlayerNative extends Player {
|
|
|
229
229
|
};
|
|
230
230
|
|
|
231
231
|
closeNativePlayer = () => {
|
|
232
|
-
// TODO: Delete does not work
|
|
232
|
+
// TODO: Delete, does not work (component is null)
|
|
233
233
|
this.currentPlayerComponent()?.closeNativePlayer?.();
|
|
234
|
+
this.getPlayerModule()?.stopBackgroundPlayback?.();
|
|
234
235
|
};
|
|
235
236
|
|
|
236
237
|
togglePlayPause = () => {
|
|
@@ -22,7 +22,6 @@ export const usePlayer = (playerId?: string): Player => {
|
|
|
22
22
|
|
|
23
23
|
const isCasting = playerManager.isCasting();
|
|
24
24
|
|
|
25
|
-
// TODO: We need to prerender when we start/stop casting, fix this
|
|
26
25
|
const getPlayer = (playerId, isCasting) =>
|
|
27
26
|
getControlledPlayer(playerId, isCasting);
|
|
28
27
|
|
|
@@ -46,7 +45,7 @@ export const usePlayer = (playerId?: string): Player => {
|
|
|
46
45
|
return playerManager.addLifecycleListener({
|
|
47
46
|
onRegistered: (player) => {
|
|
48
47
|
if (playerIdToUse === player.playerId) {
|
|
49
|
-
setPlayer(getPlayer(playerIdToUse, isCasting));
|
|
48
|
+
setPlayer(getPlayer(playerIdToUse, playerManager.isCasting()));
|
|
50
49
|
}
|
|
51
50
|
},
|
|
52
51
|
onUnRegistered: (player) => {
|
|
@@ -54,8 +53,11 @@ export const usePlayer = (playerId?: string): Player => {
|
|
|
54
53
|
setPlayer(null);
|
|
55
54
|
}
|
|
56
55
|
},
|
|
56
|
+
onCastingChanged: () => {
|
|
57
|
+
setPlayer(getPlayer(playerIdToUse, playerManager.isCasting()));
|
|
58
|
+
},
|
|
57
59
|
} as PlayerLifecycleListener);
|
|
58
|
-
}, [playerIdToUse
|
|
60
|
+
}, [playerIdToUse]);
|
|
59
61
|
|
|
60
62
|
return player;
|
|
61
63
|
};
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
ifEmptyUseFallback,
|
|
4
4
|
textTransform,
|
|
5
5
|
getKeyForLabel,
|
|
6
|
+
getDataTransformForLabel,
|
|
6
7
|
getLabel,
|
|
7
8
|
getAspectRatio,
|
|
8
9
|
getCellWidth,
|
|
@@ -56,6 +57,44 @@ describe("getKeyForLabel", () => {
|
|
|
56
57
|
});
|
|
57
58
|
});
|
|
58
59
|
|
|
60
|
+
describe("getDataTransformForLabel", () => {
|
|
61
|
+
it("returns dataKey when dataKey is not other", () => {
|
|
62
|
+
expect(getDataTransformForLabel(VAL.dataKey, VAL.customKey)).toEqual(
|
|
63
|
+
VAL.dataKey
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("returns customKey when dataKey is other", () => {
|
|
68
|
+
expect(getDataTransformForLabel(VAL.other, VAL.customKey)).toEqual(
|
|
69
|
+
VAL.customKey
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("preserves full date formats containing dots", () => {
|
|
74
|
+
expect(getDataTransformForLabel(VAL.other, "DD.MM.YYYY")).toEqual(
|
|
75
|
+
"DD.MM.YYYY"
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("returns default date format when selected key is null or undefined", () => {
|
|
80
|
+
expect(getDataTransformForLabel(EMPTY.nulls, VAL.customKey)).toEqual(
|
|
81
|
+
"DD/MM/YYYY"
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
expect(getDataTransformForLabel(EMPTY.undef, VAL.customKey)).toEqual(
|
|
85
|
+
"DD/MM/YYYY"
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
expect(getDataTransformForLabel(VAL.other, EMPTY.nulls)).toEqual(
|
|
89
|
+
"DD/MM/YYYY"
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
expect(getDataTransformForLabel(VAL.other, EMPTY.undef)).toEqual(
|
|
93
|
+
"DD/MM/YYYY"
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
59
98
|
describe("getLabel", () => {
|
|
60
99
|
it("returns a string from dataKey's path", () => {
|
|
61
100
|
expect(getLabel(VAL.dataKey, VAL.customKey)(entry)).toEqual(entry.id);
|
package/cellUtils/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as R from "ramda";
|
|
2
|
-
import dayjs from "
|
|
2
|
+
import { dayjs } from "../dateUtils";
|
|
3
3
|
import validateColor from "validate-color";
|
|
4
4
|
|
|
5
5
|
import { transformColorCode as fixColorHexCode } from "@applicaster/zapp-react-native-utils/transform";
|
|
@@ -31,6 +31,16 @@ export const getKeyForLabel = (dataKey, customKey) => {
|
|
|
31
31
|
return keyToUse.split(".");
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Provides `format` for date formatting. Returns customKey if dataKey is "other", otherwise it returns dataKey.
|
|
36
|
+
* Should consist of valid dayjs tokens - https://day.js.org/docs/en/parse/string-format#list-of-all-available-parsing-tokens
|
|
37
|
+
*/
|
|
38
|
+
export const getDataTransformForLabel = (dataKey, customKey) => {
|
|
39
|
+
const keyToUse = dataKey === CUSTOM_KEY ? customKey : dataKey;
|
|
40
|
+
|
|
41
|
+
return keyToUse ?? "DD/MM/YYYY";
|
|
42
|
+
};
|
|
43
|
+
|
|
34
44
|
/**
|
|
35
45
|
* This method will return true if the argument passed to it is either empty or nil
|
|
36
46
|
* The method prevents zero from being evaluated as falsey in an || condition
|
package/componentsUtils/index.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { last } from "@applicaster/zapp-react-native-utils/utils";
|
|
2
|
+
|
|
1
3
|
const GROUP = "group-qb";
|
|
2
4
|
const GROUP_INFO = "group-info-qb";
|
|
3
5
|
const GROUP_INFO_OLD = "group-info";
|
|
@@ -29,6 +31,12 @@ export const isFirstComponentGallery = (
|
|
|
29
31
|
return isGallery(components?.[0]);
|
|
30
32
|
};
|
|
31
33
|
|
|
34
|
+
export const isLastComponentGallery = (
|
|
35
|
+
components: ZappUIComponent[]
|
|
36
|
+
): boolean => {
|
|
37
|
+
return isGallery(last(components));
|
|
38
|
+
};
|
|
39
|
+
|
|
32
40
|
export const isGroup = (item): boolean => item?.component_type === GROUP;
|
|
33
41
|
|
|
34
42
|
export const isEmptyGroup = (item): boolean =>
|
package/dateUtils/index.ts
CHANGED
|
@@ -4,12 +4,14 @@ import timezone from "dayjs/plugin/timezone";
|
|
|
4
4
|
import isoWeek from "dayjs/plugin/isoWeek";
|
|
5
5
|
import duration from "dayjs/plugin/duration";
|
|
6
6
|
import relativeTime from "dayjs/plugin/relativeTime";
|
|
7
|
+
import customParseFormat from "dayjs/plugin/customParseFormat";
|
|
7
8
|
|
|
8
9
|
// Extend dayjs with plugins
|
|
9
10
|
dayjs.extend(utc);
|
|
10
11
|
dayjs.extend(timezone);
|
|
11
12
|
dayjs.extend(isoWeek);
|
|
12
13
|
dayjs.extend(duration);
|
|
14
|
+
dayjs.extend(customParseFormat);
|
|
13
15
|
|
|
14
16
|
dayjs.extend(relativeTime, {
|
|
15
17
|
thresholds: [
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const {
|
|
2
|
+
isKeyHasSuffix,
|
|
3
|
+
isKeyHasAnyOfSuffixes,
|
|
4
|
+
getKeyWithPrefixGenerator,
|
|
5
|
+
} = require("..");
|
|
6
|
+
|
|
7
|
+
describe("manifestUtils/_internals helpers", () => {
|
|
8
|
+
it("checks a key suffix", () => {
|
|
9
|
+
expect(
|
|
10
|
+
isKeyHasSuffix("button_enabled", "mobile_button_1_button_enabled")
|
|
11
|
+
).toBe(true);
|
|
12
|
+
|
|
13
|
+
expect(
|
|
14
|
+
isKeyHasSuffix("button_enabled", "mobile_button_1_assign_action")
|
|
15
|
+
).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("checks key against multiple suffixes", () => {
|
|
19
|
+
const suffixes = ["font_color", "focused_font_color", "text_transform"];
|
|
20
|
+
|
|
21
|
+
expect(
|
|
22
|
+
isKeyHasAnyOfSuffixes(suffixes, "mobile_button_1_text_transform")
|
|
23
|
+
).toBe(true);
|
|
24
|
+
|
|
25
|
+
expect(
|
|
26
|
+
isKeyHasAnyOfSuffixes(suffixes, "mobile_button_1_asset_alignment")
|
|
27
|
+
).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("generates stable key names using a prefix", () => {
|
|
31
|
+
const withMobileButtonPrefix = getKeyWithPrefixGenerator("mobile_button_2");
|
|
32
|
+
|
|
33
|
+
expect(withMobileButtonPrefix("display_mode")).toBe(
|
|
34
|
+
"mobile_button_2_display_mode"
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
expect(withMobileButtonPrefix("asset_enabled")).toBe(
|
|
38
|
+
"mobile_button_2_asset_enabled"
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
});
|