@applicaster/zapp-react-native-utils 14.0.0-alpha.5974411329 → 14.0.0-alpha.6000342231
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/actionsExecutor/ActionExecutorContext.tsx +0 -1
- package/actionsExecutor/ScreenActions.ts +20 -19
- package/analyticsUtils/AnalyticPlayerListener.ts +5 -2
- package/analyticsUtils/__tests__/analyticsUtils.test.js +0 -11
- package/analyticsUtils/playerAnalyticsTracker.ts +2 -1
- package/appUtils/accessibilityManager/const.ts +13 -0
- package/appUtils/accessibilityManager/hooks.ts +35 -1
- package/appUtils/accessibilityManager/index.ts +151 -30
- package/appUtils/accessibilityManager/utils.ts +24 -0
- package/appUtils/contextKeysManager/contextResolver.ts +12 -1
- package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +8 -0
- package/appUtils/focusManager/__tests__/focusManager.test.js +1 -1
- package/appUtils/focusManager/events.ts +2 -0
- package/appUtils/focusManager/index.ios.ts +27 -0
- package/appUtils/focusManager/index.ts +86 -11
- package/appUtils/focusManagerAux/utils/index.ts +112 -3
- package/appUtils/focusManagerAux/utils/utils.ios.ts +35 -0
- package/appUtils/platform/platformUtils.ts +33 -3
- package/appUtils/playerManager/conts.ts +21 -0
- package/arrayUtils/__tests__/allTruthy.test.ts +24 -0
- package/arrayUtils/__tests__/anyThruthy.test.ts +24 -0
- package/arrayUtils/index.ts +5 -0
- package/configurationUtils/__tests__/manifestKeyParser.test.ts +0 -1
- package/configurationUtils/index.ts +1 -1
- package/focusManager/FocusManager.ts +78 -4
- package/focusManager/aux/index.ts +98 -0
- package/focusManager/utils.ts +12 -6
- package/index.d.ts +1 -1
- package/manifestUtils/defaultManifestConfigurations/player.js +188 -2
- package/manifestUtils/index.js +4 -0
- package/manifestUtils/keys.js +12 -0
- package/manifestUtils/sharedConfiguration/screenPicker/stylesFields.js +6 -0
- package/navigationUtils/index.ts +20 -17
- package/package.json +2 -2
- package/playerUtils/PlayerTTS/PlayerTTS.ts +359 -0
- package/playerUtils/PlayerTTS/index.ts +1 -0
- package/playerUtils/getPlayerActionButtons.ts +1 -1
- package/playerUtils/usePlayerTTS.ts +21 -0
- package/reactHooks/cell-click/__tests__/index.test.js +3 -0
- package/reactHooks/debugging/__tests__/index.test.js +0 -1
- package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +8 -2
- package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +57 -37
- package/reactHooks/feed/index.ts +2 -0
- package/reactHooks/feed/useBatchLoading.ts +14 -9
- package/reactHooks/feed/useFeedLoader.tsx +39 -59
- package/reactHooks/feed/useInflatedUrl.ts +23 -29
- package/reactHooks/feed/useLoadPipesDataDispatch.ts +63 -0
- package/reactHooks/layout/index.ts +1 -1
- package/reactHooks/navigation/useScreenStateStore.ts +3 -3
- package/reactHooks/state/index.ts +1 -1
- package/reactHooks/state/useHomeRiver.ts +4 -2
- package/screenPickerUtils/index.ts +13 -0
- package/storage/ScreenSingleValueProvider.ts +25 -22
- package/storage/ScreenStateMultiSelectProvider.ts +26 -23
- package/utils/__tests__/endsWith.test.ts +30 -0
- package/utils/__tests__/find.test.ts +36 -0
- package/utils/__tests__/omit.test.ts +19 -0
- package/utils/__tests__/path.test.ts +33 -0
- package/utils/__tests__/pathOr.test.ts +37 -0
- package/utils/__tests__/startsWith.test.ts +30 -0
- package/utils/__tests__/take.test.ts +40 -0
- package/utils/endsWith.ts +9 -0
- package/utils/find.ts +3 -0
- package/utils/index.ts +19 -1
- package/utils/omit.ts +5 -0
- package/utils/path.ts +5 -0
- package/utils/pathOr.ts +5 -0
- package/utils/startsWith.ts +9 -0
- package/utils/take.ts +5 -0
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import { complement, compose, isNil, map, min, prop, take, uniq } from "ramda";
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import {
|
|
4
|
-
ZappPipes,
|
|
5
|
-
useAppDispatch,
|
|
6
|
-
useZappPipesFeed,
|
|
7
|
-
} from "@applicaster/zapp-react-native-redux";
|
|
3
|
+
import { useZappPipesFeed } from "@applicaster/zapp-react-native-redux";
|
|
8
4
|
import { isNilOrEmpty } from "../../reactUtils/helpers";
|
|
9
5
|
import { ZappPipesSearchContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
|
|
10
6
|
import {
|
|
11
7
|
getInflatedDataSourceUrl,
|
|
12
8
|
getSearchContext,
|
|
9
|
+
useLoadPipesDataDispatch,
|
|
13
10
|
} from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
14
11
|
import { isGallery } from "@applicaster/zapp-react-native-utils/componentsUtils";
|
|
15
12
|
import { useScreenContext } from "../screen";
|
|
@@ -65,7 +62,6 @@ export const useBatchLoading = (
|
|
|
65
62
|
componentsToRender: { data?: ZappDataSource; component_type: string }[],
|
|
66
63
|
options: Options
|
|
67
64
|
) => {
|
|
68
|
-
const dispatch = useAppDispatch();
|
|
69
65
|
const { screen: screenContext, entry: entryContext } = useScreenContext();
|
|
70
66
|
const [searchContext] = ZappPipesSearchContext.useZappPipesContext();
|
|
71
67
|
const [hasEverBeenReady, setHasEverBeenReady] = React.useState(false);
|
|
@@ -122,6 +118,8 @@ export const useBatchLoading = (
|
|
|
122
118
|
|
|
123
119
|
const feeds = useZappPipesFeed(feedUrls);
|
|
124
120
|
|
|
121
|
+
const loadPipesDataDispatcher = useLoadPipesDataDispatch();
|
|
122
|
+
|
|
125
123
|
// dispatch loadPipesData for each feed that is not loaded
|
|
126
124
|
const runBatchLoading = React.useCallback(() => {
|
|
127
125
|
batchComponents.forEach((rawData: any) => {
|
|
@@ -140,13 +138,20 @@ export const useBatchLoading = (
|
|
|
140
138
|
|
|
141
139
|
if (mappedFeedUrl) {
|
|
142
140
|
// 4. load data
|
|
143
|
-
return
|
|
144
|
-
|
|
141
|
+
return loadPipesDataDispatcher(
|
|
142
|
+
mappedFeedUrl,
|
|
143
|
+
{
|
|
144
|
+
riverId: options.riverId,
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
withResolvers: true,
|
|
148
|
+
withScreenRouteMapping: true,
|
|
149
|
+
}
|
|
145
150
|
);
|
|
146
151
|
}
|
|
147
152
|
}
|
|
148
153
|
});
|
|
149
|
-
}, [feedUrls, feeds]);
|
|
154
|
+
}, [feedUrls, feeds, loadPipesDataDispatcher]);
|
|
150
155
|
|
|
151
156
|
React.useEffect(() => {
|
|
152
157
|
runBatchLoading();
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import React, { useEffect } from "react";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
ZappPipes,
|
|
5
|
-
useAppDispatch,
|
|
6
|
-
useZappPipesFeed,
|
|
7
|
-
} from "@applicaster/zapp-react-native-redux";
|
|
3
|
+
import { useZappPipesFeed } from "@applicaster/zapp-react-native-redux";
|
|
8
4
|
|
|
9
5
|
import { reactHooksLogger } from "../logger";
|
|
10
6
|
import { shouldDispatchData, useIsInitialRender } from "../utils";
|
|
11
7
|
import { useInflatedUrl } from "./useInflatedUrl";
|
|
12
|
-
import {
|
|
13
|
-
import { useScreenResolvers } from "@applicaster/zapp-react-native-utils/actionsExecutor/screenResolver";
|
|
8
|
+
import { useLoadPipesDataDispatch } from "./useLoadPipesDataDispatch";
|
|
14
9
|
|
|
15
10
|
const logger = reactHooksLogger.addSubsystem("useFeedLoader");
|
|
16
11
|
|
|
@@ -42,74 +37,63 @@ export const useFeedLoader = ({
|
|
|
42
37
|
mapping,
|
|
43
38
|
pipesOptions = {},
|
|
44
39
|
}: Props): FeedLoaderResponse => {
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
if (!feedUrl) {
|
|
47
|
-
logger.warning({
|
|
48
|
-
message: "Required parameter feedUrl is missing",
|
|
49
|
-
data: { feedUrl },
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
}, []);
|
|
53
|
-
|
|
54
40
|
const isInitialRender = useIsInitialRender();
|
|
55
|
-
const dispatch = useAppDispatch();
|
|
56
|
-
const { screenData } = useRoute();
|
|
57
|
-
const resolvers = useScreenResolvers();
|
|
58
41
|
|
|
59
42
|
const callableFeedUrl = useInflatedUrl({ feedUrl, mapping });
|
|
60
43
|
|
|
61
44
|
const currentFeed = useZappPipesFeed(callableFeedUrl);
|
|
62
45
|
|
|
63
|
-
const
|
|
64
|
-
(screenData as LegacyNavigationScreenData)?.targetScreen?.id ??
|
|
65
|
-
screenData?.id;
|
|
46
|
+
const loadPipesDataDispatcher = useLoadPipesDataDispatch();
|
|
66
47
|
|
|
67
48
|
const reloadData = React.useCallback<ReloadDataFunction>(
|
|
68
49
|
(silentRefresh = true, callback) => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
50
|
+
loadPipesDataDispatcher(
|
|
51
|
+
callableFeedUrl,
|
|
52
|
+
{
|
|
53
|
+
clearCache: true,
|
|
54
|
+
silentRefresh,
|
|
55
|
+
callback,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
withResolvers: true,
|
|
59
|
+
}
|
|
60
|
+
);
|
|
80
61
|
},
|
|
81
|
-
[callableFeedUrl
|
|
62
|
+
[callableFeedUrl]
|
|
82
63
|
);
|
|
83
64
|
|
|
84
65
|
const loadNext: FeedLoaderResponse["loadNext"] = React.useCallback(() => {
|
|
85
66
|
if (callableFeedUrl) {
|
|
86
67
|
const nextFeed = currentFeed?.data?.next;
|
|
87
68
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
69
|
+
loadPipesDataDispatcher(
|
|
70
|
+
nextFeed,
|
|
71
|
+
{
|
|
72
|
+
silentRefresh: true,
|
|
73
|
+
parentFeed: callableFeedUrl,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
withResolvers: true,
|
|
77
|
+
}
|
|
78
|
+
);
|
|
98
79
|
}
|
|
99
|
-
}, [callableFeedUrl, currentFeed?.data?.next
|
|
80
|
+
}, [callableFeedUrl, currentFeed?.data?.next]);
|
|
100
81
|
|
|
101
82
|
useEffect(() => {
|
|
102
83
|
if (
|
|
103
84
|
shouldDispatchData(callableFeedUrl, currentFeed, pipesOptions.clearCache)
|
|
104
85
|
) {
|
|
105
86
|
if (callableFeedUrl && !pipesOptions.skipLoading) {
|
|
106
|
-
|
|
107
|
-
|
|
87
|
+
loadPipesDataDispatcher(
|
|
88
|
+
callableFeedUrl,
|
|
89
|
+
{
|
|
108
90
|
...pipesOptions,
|
|
109
91
|
clearCache: true,
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
withResolvers: true,
|
|
95
|
+
withScreenRouteMapping: true,
|
|
96
|
+
}
|
|
113
97
|
);
|
|
114
98
|
} else if (!callableFeedUrl) {
|
|
115
99
|
logger.info({
|
|
@@ -133,20 +117,16 @@ export const useFeedLoader = ({
|
|
|
133
117
|
jsOnly: true,
|
|
134
118
|
});
|
|
135
119
|
}
|
|
136
|
-
}, [
|
|
120
|
+
}, []);
|
|
137
121
|
|
|
138
122
|
// Reload feed when feedUrl changes, unless skipLoading is true
|
|
139
123
|
useEffect(() => {
|
|
140
124
|
if (!isInitialRender && callableFeedUrl && !pipesOptions.skipLoading) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
riverId,
|
|
145
|
-
resolvers,
|
|
146
|
-
})
|
|
147
|
-
);
|
|
125
|
+
loadPipesDataDispatcher(callableFeedUrl, pipesOptions, {
|
|
126
|
+
withResolvers: true,
|
|
127
|
+
});
|
|
148
128
|
}
|
|
149
|
-
}, [callableFeedUrl,
|
|
129
|
+
}, [callableFeedUrl, isInitialRender, pipesOptions.skipLoading]);
|
|
150
130
|
|
|
151
131
|
return React.useMemo(() => {
|
|
152
132
|
if (!callableFeedUrl || !feedUrl) {
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from "@applicaster/zapp-pipes-v2-client";
|
|
19
19
|
import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
|
|
20
20
|
import { ENDPOINT_TAGS } from "../../types";
|
|
21
|
+
import { isNilOrEmpty } from "../../reactUtils/helpers";
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* will match any occurrence in a string of one or more word characters
|
|
@@ -75,15 +76,19 @@ export const getInflatedDataSourceUrl: GetInflatedDataSourceUrl = ({
|
|
|
75
76
|
* https://foo.com/shows/A1234
|
|
76
77
|
*/
|
|
77
78
|
|
|
78
|
-
if (!
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
if (!isNilOrEmpty(mapping)) {
|
|
80
|
+
if (!source) {
|
|
81
|
+
if (__DEV__) {
|
|
82
|
+
// eslint-disable-next-line no-console
|
|
83
|
+
throw new Error(
|
|
84
|
+
"getInflatedDataSourceUrl: source is empty while mapping is provided"
|
|
85
|
+
);
|
|
86
|
+
}
|
|
85
87
|
|
|
86
|
-
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
return source || null;
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
// Hack because in tv we expect to get key names instead of values from the fake entry
|
|
@@ -193,28 +198,17 @@ export function useInflatedUrl({
|
|
|
193
198
|
|
|
194
199
|
const url = useMemo(
|
|
195
200
|
() =>
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
: feedUrl,
|
|
207
|
-
[feedUrl, mapping]
|
|
201
|
+
getInflatedDataSourceUrl({
|
|
202
|
+
source: feedUrl,
|
|
203
|
+
contexts: {
|
|
204
|
+
entry: entryContext,
|
|
205
|
+
screen: screenContext,
|
|
206
|
+
search: getSearchContext(searchContext, mapping),
|
|
207
|
+
},
|
|
208
|
+
mapping,
|
|
209
|
+
}),
|
|
210
|
+
[entryContext, feedUrl, mapping, screenContext, searchContext]
|
|
208
211
|
);
|
|
209
212
|
|
|
210
|
-
if (!feedUrl) {
|
|
211
|
-
logger.warning({
|
|
212
|
-
message: "Required parameter feedUrl is missing",
|
|
213
|
-
data: { feedUrl },
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
return null;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
213
|
return url;
|
|
220
214
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useAppDispatch,
|
|
3
|
+
ZappPipes,
|
|
4
|
+
} from "@applicaster/zapp-react-native-redux";
|
|
5
|
+
import { applyScreenRouteDefaults } from "@applicaster/zapp-react-native-redux/ZappPipes/feedProcessor";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { useScreenResolvers } from "../../actionsExecutor/screenResolver";
|
|
8
|
+
import { useRoute } from "../navigation";
|
|
9
|
+
import { useScreenStateStore } from "../navigation/useScreenStateStore";
|
|
10
|
+
|
|
11
|
+
export const useLoadPipesDataDispatch = () => {
|
|
12
|
+
const screenStateStore = useScreenStateStore();
|
|
13
|
+
const resolvers = useScreenResolvers();
|
|
14
|
+
const dispatch = useAppDispatch();
|
|
15
|
+
|
|
16
|
+
const { pathname, screenData } = useRoute();
|
|
17
|
+
|
|
18
|
+
const riverId =
|
|
19
|
+
(screenData as LegacyNavigationScreenData)?.targetScreen?.id ??
|
|
20
|
+
screenData?.id;
|
|
21
|
+
|
|
22
|
+
const onLoadCB = (options) => (data, _err) => {
|
|
23
|
+
options?.callback?.();
|
|
24
|
+
|
|
25
|
+
if (data) {
|
|
26
|
+
applyScreenRouteDefaults(data, screenStateStore, pathname);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return React.useCallback(
|
|
31
|
+
(
|
|
32
|
+
url: string,
|
|
33
|
+
options: {
|
|
34
|
+
callback?: (data: unknown, error?: Error | null | undefined) => void;
|
|
35
|
+
riverId?: string;
|
|
36
|
+
clearCache?: boolean;
|
|
37
|
+
silentRefresh?: boolean;
|
|
38
|
+
parentFeed?: string;
|
|
39
|
+
} = {},
|
|
40
|
+
{
|
|
41
|
+
withResolvers = false,
|
|
42
|
+
withScreenRouteMapping = false,
|
|
43
|
+
}: {
|
|
44
|
+
withResolvers?: boolean;
|
|
45
|
+
withScreenRouteMapping?: boolean;
|
|
46
|
+
} = {}
|
|
47
|
+
) => {
|
|
48
|
+
if (url) {
|
|
49
|
+
dispatch(
|
|
50
|
+
ZappPipes.loadPipesData(url, {
|
|
51
|
+
riverId,
|
|
52
|
+
resolvers: withResolvers ? resolvers : undefined,
|
|
53
|
+
...options,
|
|
54
|
+
callback: withScreenRouteMapping
|
|
55
|
+
? onLoadCB(options)
|
|
56
|
+
: options?.callback,
|
|
57
|
+
})
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
[]
|
|
62
|
+
);
|
|
63
|
+
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useScreenContextV2 } from "../screen/useScreenContext";
|
|
2
2
|
import { useMemo } from "react";
|
|
3
3
|
|
|
4
4
|
export const useScreenStateStore = () => {
|
|
5
|
-
const
|
|
5
|
+
const _stateStore = useScreenContextV2()._stateStore;
|
|
6
6
|
|
|
7
|
-
return useMemo(() =>
|
|
7
|
+
return useMemo(() => _stateStore, []);
|
|
8
8
|
};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import * as R from "ramda";
|
|
2
1
|
import { useRivers } from "./useRivers";
|
|
3
2
|
|
|
3
|
+
export const getHomeRiver = (rivers: Record<string, ZappRiver>) =>
|
|
4
|
+
Object.values(rivers).find((river: ZappRiver) => river.home);
|
|
5
|
+
|
|
4
6
|
export const useHomeRiver = () => {
|
|
5
7
|
const rivers = useRivers();
|
|
6
8
|
|
|
7
|
-
return
|
|
9
|
+
return getHomeRiver(rivers);
|
|
8
10
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const getFocusableId = (id) => `PickerItem.${id}`;
|
|
2
|
+
|
|
3
|
+
export const getPickerSelectorId = (id) => `PickerSelector.${id}`;
|
|
4
|
+
|
|
5
|
+
export const SCREEN_PICKER_CONTAINER = "ScreenPickerContainer";
|
|
6
|
+
|
|
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`;
|
|
@@ -2,6 +2,7 @@ import { BehaviorSubject } from "rxjs";
|
|
|
2
2
|
import { SingleValueProvider } from "./StorageSingleSelectProvider";
|
|
3
3
|
import { createLogger } from "../logger";
|
|
4
4
|
import { bridgeLogger } from "../../zapp-react-native-bridge/logger";
|
|
5
|
+
import { useScreenStateStore } from "../reactHooks/navigation/useScreenStateStore";
|
|
5
6
|
|
|
6
7
|
export const { log_debug, log_error } = createLogger({
|
|
7
8
|
category: "ScreenSingleValueProvider",
|
|
@@ -22,7 +23,7 @@ export class ScreenSingleValueProvider implements SingleValueProvider {
|
|
|
22
23
|
public static getProvider(
|
|
23
24
|
key: string,
|
|
24
25
|
screenRoute: string,
|
|
25
|
-
screenStateStore:
|
|
26
|
+
screenStateStore: ReturnType<typeof useScreenStateStore>
|
|
26
27
|
): SingleValueProvider {
|
|
27
28
|
if (!key) {
|
|
28
29
|
throw new Error("ScreenSingleValueProvider: Key is required");
|
|
@@ -66,7 +67,7 @@ export class ScreenSingleValueProvider implements SingleValueProvider {
|
|
|
66
67
|
private constructor(
|
|
67
68
|
private key: string,
|
|
68
69
|
route: string,
|
|
69
|
-
private screenStateStore:
|
|
70
|
+
private screenStateStore: ReturnType<typeof useScreenStateStore>
|
|
70
71
|
) {
|
|
71
72
|
if (!key) {
|
|
72
73
|
throw new Error("ScreenSingleValueProvider: Key is required");
|
|
@@ -88,7 +89,9 @@ export class ScreenSingleValueProvider implements SingleValueProvider {
|
|
|
88
89
|
log_debug("ScreenSingleValueProvider: Initializing", { key, route });
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
private updateStore(
|
|
92
|
+
private updateStore(
|
|
93
|
+
screenStateStore: ReturnType<typeof useScreenStateStore>
|
|
94
|
+
): void {
|
|
92
95
|
this.cleanup();
|
|
93
96
|
this.screenStateStore = screenStateStore;
|
|
94
97
|
this.setupScreenStateSubscription();
|
|
@@ -100,25 +103,25 @@ export class ScreenSingleValueProvider implements SingleValueProvider {
|
|
|
100
103
|
(state) => ({
|
|
101
104
|
value: state.data[this.key],
|
|
102
105
|
exists: this.key in state.data,
|
|
103
|
-
})
|
|
104
|
-
(current, previous) => {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
106
|
+
})
|
|
107
|
+
// (current, previous) => {
|
|
108
|
+
// if (!current.exists && previous.exists) {
|
|
109
|
+
// log_debug(
|
|
110
|
+
// `ScreenSingleValueProvider: Key deleted from store: ${this.key}`
|
|
111
|
+
// );
|
|
112
|
+
|
|
113
|
+
// // TODO: If we need to handle deletion, we can do it here
|
|
114
|
+
// }
|
|
115
|
+
|
|
116
|
+
// if (current.value !== previous.value) {
|
|
117
|
+
// this.valueSubject.next(current.value || null);
|
|
118
|
+
|
|
119
|
+
// log_debug(`ScreenSingleValueProvider: Key updated: ${this.key}`, {
|
|
120
|
+
// previous: previous.value,
|
|
121
|
+
// current: current.value,
|
|
122
|
+
// });
|
|
123
|
+
// }
|
|
124
|
+
// }
|
|
122
125
|
);
|
|
123
126
|
|
|
124
127
|
log_debug(
|
|
@@ -2,6 +2,7 @@ import { MultiSelectProvider } from "./StorageMultiSelectProvider";
|
|
|
2
2
|
import { BehaviorSubject } from "rxjs";
|
|
3
3
|
import { createLogger } from "../logger";
|
|
4
4
|
import { bridgeLogger } from "../../zapp-react-native-bridge/logger";
|
|
5
|
+
import { useScreenStateStore } from "../reactHooks/navigation/useScreenStateStore";
|
|
5
6
|
|
|
6
7
|
export const { log_debug, log_error } = createLogger({
|
|
7
8
|
category: "ScreenMultiSelectProvider",
|
|
@@ -22,7 +23,7 @@ export class ScreenMultiSelectProvider implements MultiSelectProvider {
|
|
|
22
23
|
public static getProvider(
|
|
23
24
|
key: string,
|
|
24
25
|
screenRoute: string,
|
|
25
|
-
screenStateStore:
|
|
26
|
+
screenStateStore: ReturnType<typeof useScreenStateStore>
|
|
26
27
|
): MultiSelectProvider {
|
|
27
28
|
if (!key) {
|
|
28
29
|
throw new Error("ScreenMultiSelectProvider: Key is required");
|
|
@@ -66,7 +67,7 @@ export class ScreenMultiSelectProvider implements MultiSelectProvider {
|
|
|
66
67
|
private constructor(
|
|
67
68
|
private key: string,
|
|
68
69
|
route: string,
|
|
69
|
-
private screenStateStore:
|
|
70
|
+
private screenStateStore: ReturnType<typeof useScreenStateStore>
|
|
70
71
|
) {
|
|
71
72
|
if (!key) {
|
|
72
73
|
throw new Error("ScreenMultiSelectProvider: Key is required");
|
|
@@ -88,7 +89,9 @@ export class ScreenMultiSelectProvider implements MultiSelectProvider {
|
|
|
88
89
|
log_debug("ScreenMultiSelectProvider: Initializing", { key, route });
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
private updateStore(
|
|
92
|
+
private updateStore(
|
|
93
|
+
screenStateStore: ReturnType<typeof useScreenStateStore>
|
|
94
|
+
): void {
|
|
92
95
|
if (screenStateStore === this.screenStateStore) {
|
|
93
96
|
return;
|
|
94
97
|
}
|
|
@@ -104,26 +107,26 @@ export class ScreenMultiSelectProvider implements MultiSelectProvider {
|
|
|
104
107
|
(state) => ({
|
|
105
108
|
value: state.data[this.key],
|
|
106
109
|
exists: this.key in state.data,
|
|
107
|
-
})
|
|
108
|
-
(current, previous) => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
110
|
+
})
|
|
111
|
+
// (current, previous) => {
|
|
112
|
+
// if (!current.exists && previous.exists) {
|
|
113
|
+
// log_debug(
|
|
114
|
+
// `ScreenMultiSelectProvider: Key deleted from store: ${this.key}`
|
|
115
|
+
// );
|
|
116
|
+
|
|
117
|
+
// // TODO: If we need to handle deletion, we can do it here
|
|
118
|
+
// }
|
|
119
|
+
|
|
120
|
+
// if (current.value !== previous.value) {
|
|
121
|
+
// const items = this.parseStoredValue(current.value);
|
|
122
|
+
// this.itemSubject.next(items);
|
|
123
|
+
|
|
124
|
+
// log_debug(`ScreenMultiSelectProvider: Key updated: ${this.key}`, {
|
|
125
|
+
// previous: this.parseStoredValue(previous.value),
|
|
126
|
+
// current: items,
|
|
127
|
+
// });
|
|
128
|
+
// }
|
|
129
|
+
// }
|
|
127
130
|
);
|
|
128
131
|
|
|
129
132
|
log_debug(
|
|
@@ -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,36 @@
|
|
|
1
|
+
import { find } from "../find";
|
|
2
|
+
|
|
3
|
+
test("example 1", () => {
|
|
4
|
+
const predicate = <T>(_: T, index: number): boolean => index === 0;
|
|
5
|
+
const xs = ["1", "2", "2", "3", "4"];
|
|
6
|
+
|
|
7
|
+
expect(find(predicate, xs)).toBe("1");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("example 2", () => {
|
|
11
|
+
const predicate = <T>(_: T, index: number): boolean => index === 0;
|
|
12
|
+
const xs: string[] = [];
|
|
13
|
+
|
|
14
|
+
expect(find(predicate, xs)).toBe(undefined);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("example 3", () => {
|
|
18
|
+
const predicate = () => false;
|
|
19
|
+
const xs = ["1", "2", "2", "3"];
|
|
20
|
+
|
|
21
|
+
expect(find(predicate, xs)).toBe(undefined);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("example 4", () => {
|
|
25
|
+
const predicate = <T>(_: T, index: number): boolean => index === 1;
|
|
26
|
+
const xs = ["1", "2", "2", "3"];
|
|
27
|
+
|
|
28
|
+
expect(find(predicate, xs)).toBe("2");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("example 5", () => {
|
|
32
|
+
const predicate = <T>(_: T, index: number): boolean => index === 2;
|
|
33
|
+
const xs = ["1", "2.1", "2", "3", "2", "4"];
|
|
34
|
+
|
|
35
|
+
expect(find(predicate, xs)).toBe("2");
|
|
36
|
+
});
|
|
@@ -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
|
+
});
|