@applicaster/zapp-react-native-utils 16.0.0-rc.116 → 16.0.0-rc.118

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.
@@ -28,17 +28,23 @@ const RTL_LOCALES = [
28
28
  "yi", // Yiddish
29
29
  ];
30
30
 
31
+ // The strings for the UI language, else the first language present
32
+ export const getLocalizedStrings = (
33
+ localizations = {},
34
+ languageCode = getUILanguage()
35
+ ) =>
36
+ localizations?.[languageCode] ||
37
+ Object.values(localizations).find(
38
+ (value) => typeof value === "object" && value !== null
39
+ ) ||
40
+ {};
41
+
31
42
  export function useLocalizedStrings({ localizations = {} }) {
32
43
  const languageCode = getUILanguage();
33
44
 
34
45
  // TODO: probably we can call getLocalizations here from localizationsHelper.js
35
46
  const result = useMemo(
36
- () =>
37
- localizations?.[languageCode] ||
38
- Object.values(localizations).find(
39
- (value) => typeof value === "object" && value !== null
40
- ) ||
41
- {},
47
+ () => getLocalizedStrings(localizations, languageCode),
42
48
  [languageCode, localizations]
43
49
  );
44
50
 
@@ -830,6 +830,14 @@ function getPlayerConfiguration({ platform, version }) {
830
830
  type: "switch",
831
831
  label_tooltip:
832
832
  "Enable this option to have a floating video player when the app is in the background.",
833
+ },
834
+ {
835
+ key: "vertical_video_player",
836
+ label: "Vertical video mode",
837
+ initial_value: false,
838
+ type: "switch",
839
+ label_tooltip:
840
+ "Enable this option to use the vertical video player layout.",
833
841
  }
834
842
  );
835
843
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "16.0.0-rc.116",
3
+ "version": "16.0.0-rc.118",
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": "16.0.0-rc.116",
30
+ "@applicaster/applicaster-types": "16.0.0-rc.118",
31
31
  "buffer": "^5.2.1",
32
32
  "camelize": "^1.0.0",
33
33
  "dayjs": "^1.11.10",
@@ -0,0 +1,82 @@
1
+ import { renderHook } from "@testing-library/react-native";
2
+
3
+ const plugin = {
4
+ identifier: "cast",
5
+ configuration: {
6
+ title: "Configured",
7
+ localizations: { fr: { close: "Fermer" } },
8
+ },
9
+ };
10
+
11
+ jest.mock("@applicaster/zapp-react-native-redux/hooks", () => ({
12
+ usePlugins: () => [plugin],
13
+ }));
14
+
15
+ jest.mock(
16
+ "@applicaster/zapp-react-native-utils/appUtils/localizationsHelper",
17
+ () => ({ getUILanguage: () => "fr" })
18
+ );
19
+
20
+ import {
21
+ getPluginConfiguration,
22
+ mergeLocalizations,
23
+ usePluginConfiguration,
24
+ } from "../usePluginConfiguration";
25
+
26
+ const manifest = {
27
+ custom_configuration_fields: [
28
+ { key: "title", initial_value: "Default" },
29
+ { key: "color", initial_value: "red" },
30
+ ],
31
+ localizations: {
32
+ fields: [
33
+ { key: "close", initial_value: "Close" },
34
+ { key: "cancel", initial_value: "Cancel" },
35
+ ],
36
+ },
37
+ };
38
+
39
+ it("matches usePluginConfiguration outside React", () => {
40
+ const { result } = renderHook(() => usePluginConfiguration("cast", manifest));
41
+
42
+ expect(getPluginConfiguration(plugin, manifest)).toEqual(result.current);
43
+ });
44
+
45
+ // Expected values are what R.mergeDeepWith((l, r) => (isNil(l) ? r : l)) returned before ramda was removed
46
+ describe("mergeLocalizations", () => {
47
+ const defaults = { en: { close: "Close", cancel: "Cancel" } };
48
+
49
+ it("returns the defaults when nothing is configured", () => {
50
+ expect(mergeLocalizations(undefined, defaults)).toEqual(defaults);
51
+ });
52
+
53
+ it("keeps configured values, fills nil ones and missing keys from defaults", () => {
54
+ expect(
55
+ mergeLocalizations(
56
+ { en: { close: null, extra: "X" }, fr: { close: "Fermer" } },
57
+ defaults
58
+ )
59
+ ).toEqual({
60
+ en: { close: "Close", extra: "X", cancel: "Cancel" },
61
+ fr: { close: "Fermer" },
62
+ });
63
+ });
64
+
65
+ it("does not merge arrays", () => {
66
+ expect(
67
+ mergeLocalizations(
68
+ { en: { close: ["a"] } },
69
+ { en: { close: ["b"], cancel: "C" } }
70
+ )
71
+ ).toEqual({ en: { close: ["a"], cancel: "C" } });
72
+ });
73
+ });
74
+
75
+ it("keeps configured languages first, so the fallback language stays the configured one", () => {
76
+ const merged = mergeLocalizations(
77
+ { he: { close: "סגור" }, en: { close: "Close it" } },
78
+ { en: { close: "Close" } }
79
+ );
80
+
81
+ expect(Object.keys(merged)).toEqual(["he", "en"]);
82
+ });
@@ -1,4 +1,3 @@
1
- import * as R from "ramda";
2
1
  import { useMemo } from "react";
3
2
 
4
3
  import { usePlugins } from "@applicaster/zapp-react-native-redux/hooks";
@@ -6,7 +5,8 @@ import { usePlugins } from "@applicaster/zapp-react-native-redux/hooks";
6
5
  // importing this way prevents typescript packages importing from this
7
6
  // file to go crazy reporting errors on JS
8
7
  import { populateConfigurationValues } from "@applicaster/zapp-react-native-utils/configurationUtils";
9
- import { useLocalizedStrings } from "../localizationUtils";
8
+ import { getLocalizedStrings, useLocalizedStrings } from "../localizationUtils";
9
+ import { isNil, pathOr } from "../utils";
10
10
 
11
11
  const populateFields = (
12
12
  data,
@@ -14,16 +14,72 @@ const populateFields = (
14
14
  pathToValues: string | string[] = "configuration",
15
15
  skipDefaults = false
16
16
  ) =>
17
- R.compose(
18
- (config) =>
19
- populateConfigurationValues(manifestFields, config, skipDefaults),
20
- R.pathOr({}, R.unless(Array.isArray, Array)(pathToValues))
21
- )(data);
17
+ populateConfigurationValues(
18
+ manifestFields,
19
+ pathOr({}, pathToValues, data),
20
+ skipDefaults
21
+ );
22
22
 
23
23
  export function parsePluginConfiguration(plugin, manifest) {
24
24
  return populateFields(plugin, manifest.custom_configuration_fields);
25
25
  }
26
26
 
27
+ const getDefaultLocalizations = (manifest) =>
28
+ manifest?.localizations?.fields?.reduce(
29
+ (defaults, { key, initial_value }) => ({
30
+ en: { ...defaults.en, [key]: initial_value },
31
+ }),
32
+ { en: {} }
33
+ ) || {};
34
+
35
+ const isPlainObject = (value) =>
36
+ Object.prototype.toString.call(value) === "[object Object]";
37
+
38
+ // Deep merge where configured values win, except nil ones (defaults fill the gaps)
39
+ export const mergeLocalizations = (configured, defaults) => {
40
+ if (!isPlainObject(configured)) return { ...defaults };
41
+ if (!isPlainObject(defaults)) return { ...configured };
42
+
43
+ // Configured keys first, as R.mergeDeepWith: the first language is the fallback
44
+ return Object.keys({ ...configured, ...defaults }).reduce((merged, key) => {
45
+ const left = configured[key];
46
+ const right = defaults[key];
47
+
48
+ if (!(key in defaults)) {
49
+ merged[key] = left;
50
+ } else if (!(key in configured)) {
51
+ merged[key] = right;
52
+ } else if (isPlainObject(left) && isPlainObject(right)) {
53
+ merged[key] = mergeLocalizations(left, right);
54
+ } else {
55
+ merged[key] = isNil(left) ? right : left;
56
+ }
57
+
58
+ return merged;
59
+ }, {});
60
+ };
61
+
62
+ /**
63
+ * usePluginConfiguration outside React: the same result, read once instead of subscribed.
64
+ */
65
+ export function getPluginConfiguration<T = any, S = Record<string, any>>(
66
+ plugin,
67
+ manifest?: any
68
+ ): { configurationFields: T; localizations: S } {
69
+ return {
70
+ configurationFields: populateFields(
71
+ plugin,
72
+ manifest?.custom_configuration_fields
73
+ ),
74
+ localizations: getLocalizedStrings(
75
+ mergeLocalizations(
76
+ plugin?.configuration?.localizations,
77
+ getDefaultLocalizations(manifest)
78
+ )
79
+ ),
80
+ };
81
+ }
82
+
27
83
  export function usePluginConfiguration<T = any, S = Record<string, any>>(
28
84
  identifier: string,
29
85
  manifest?: any
@@ -31,7 +87,7 @@ export function usePluginConfiguration<T = any, S = Record<string, any>>(
31
87
  const plugins = usePlugins();
32
88
 
33
89
  const plugin = useMemo(
34
- () => R.find(R.propEq("identifier", identifier), plugins),
90
+ () => plugins?.find((item) => item?.identifier === identifier),
35
91
  [plugins, identifier]
36
92
  );
37
93
 
@@ -40,29 +96,20 @@ export function usePluginConfiguration<T = any, S = Record<string, any>>(
40
96
  [plugin, manifest]
41
97
  );
42
98
 
43
- const defaultLocalizations = useMemo(() => {
44
- return (
45
- manifest?.localizations?.fields?.reduce(
46
- (defaults, { key, initial_value }) =>
47
- R.assocPath(["en", key], initial_value, defaults),
48
- { en: {} }
49
- ) || {}
50
- );
51
- }, [manifest]);
52
-
53
- const localizationsParams = useMemo(() => {
54
- return {
55
- localizations: R.mergeDeepWith(
56
- (left, right) => {
57
- if (R.isNil(left)) return right;
99
+ const defaultLocalizations = useMemo(
100
+ () => getDefaultLocalizations(manifest),
101
+ [manifest]
102
+ );
58
103
 
59
- return left;
60
- },
104
+ const localizationsParams = useMemo(
105
+ () => ({
106
+ localizations: mergeLocalizations(
61
107
  plugin?.configuration?.localizations,
62
108
  defaultLocalizations
63
109
  ),
64
- };
65
- }, [plugin?.configuration?.localizations, defaultLocalizations]);
110
+ }),
111
+ [plugin?.configuration?.localizations, defaultLocalizations]
112
+ );
66
113
 
67
114
  const localizations: S = useLocalizedStrings(localizationsParams);
68
115
 
@@ -105,29 +152,20 @@ export function useLayoutConfiguration<
105
152
  "localizations",
106
153
  ];
107
154
 
108
- const defaultLocalizations = useMemo(() => {
109
- return (
110
- manifest?.localizations?.fields?.reduce(
111
- (defaults, { key, initial_value }) =>
112
- R.assocPath(["en", key], initial_value, defaults),
113
- { en: {} }
114
- ) || {}
115
- );
116
- }, [manifest]);
117
-
118
- const localizationsParams = useMemo(() => {
119
- return {
120
- localizations: R.mergeDeepWith(
121
- (left, right) => {
122
- if (R.isNil(left)) return right;
155
+ const defaultLocalizations = useMemo(
156
+ () => getDefaultLocalizations(manifest),
157
+ [manifest]
158
+ );
123
159
 
124
- return left;
125
- },
160
+ const localizationsParams = useMemo(
161
+ () => ({
162
+ localizations: mergeLocalizations(
126
163
  layoutData?.localizations,
127
164
  defaultLocalizations
128
165
  ),
129
- };
130
- }, [layoutData?.localizations, defaultLocalizations]);
166
+ }),
167
+ [layoutData?.localizations, defaultLocalizations]
168
+ );
131
169
 
132
170
  // Always call useLocalizedStrings regardless of whether localizations is in the keys
133
171
  const localizations: L = useLocalizedStrings(localizationsParams);