@applicaster/zapp-react-native-utils 16.0.0-alpha.8047301539 → 16.0.0-alpha.8154281859
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/ActionExecutor.ts +8 -10
- package/actionsExecutor/ActionExecutorContext.tsx +43 -326
- package/actionsExecutor/actions/appRestart.ts +24 -0
- package/actionsExecutor/actions/confirmDialog.ts +53 -0
- package/actionsExecutor/actions/index.ts +30 -0
- package/actionsExecutor/actions/localStorageRemove.ts +27 -0
- package/actionsExecutor/actions/localStorageSet.ts +28 -0
- package/actionsExecutor/actions/localStorageToggleFlag.ts +28 -0
- package/actionsExecutor/actions/navigateToScreen.ts +123 -0
- package/actionsExecutor/actions/refreshComponent.ts +79 -0
- package/actionsExecutor/actions/screenSetVariable.ts +35 -0
- package/actionsExecutor/actions/screenToggleFlag.ts +38 -0
- package/actionsExecutor/actions/sendCloudEvent.ts +88 -0
- package/actionsExecutor/actions/sessionStorageRemove.ts +19 -0
- package/actionsExecutor/actions/sessionStorageSet.ts +20 -0
- package/actionsExecutor/actions/sessionStorageToggleFlag.ts +15 -0
- package/actionsExecutor/actions/showToast.ts +31 -0
- package/actionsExecutor/actions/switchLayout.ts +40 -0
- package/actionsExecutor/types.ts +59 -0
- package/analyticsUtils/AnalyticsEvents/sendMenuClickEvent.ts +2 -2
- package/analyticsUtils/PlayerAnalyticsManager.ts +11 -1
- package/analyticsUtils/__tests__/analyticsMapper.test.ts +35 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testACP_events.json +1 -1
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testBlockUnlistedParams_rules.json +1 -1
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testEmptyRenameKeepsName_events.json +4 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testEmptyRenameKeepsName_rules.json +6 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testEventRegex_events.json +3 -9
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testIgnoreOrdering_events.json +18 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testIgnoreOrdering_rules.json +16 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexEventNoFallback_events.json +4 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexEventNoFallback_rules.json +6 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexMultiGroupRename_events.json +4 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexMultiGroupRename_rules.json +6 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexNonNamedFullMatch_events.json +4 -0
- package/analyticsUtils/__tests__/fixtures/analytics_mapper_testRegexNonNamedFullMatch_rules.json +6 -0
- package/analyticsUtils/__tests__/fixtures/index.js +20 -0
- package/analyticsUtils/analyticsMapper.ts +4 -1
- package/analyticsUtils/playerAnalyticsTracker.ts +26 -3
- package/appUtils/contextKeysManager/__tests__/getKey/failure.test.ts +1 -1
- package/appUtils/contextKeysManager/__tests__/removeKey/failure.test.ts +1 -1
- package/appUtils/contextKeysManager/__tests__/setKey/failure/invalidKey.test.ts +4 -4
- package/appUtils/contextKeysManager/index.ts +1 -1
- package/cellUtils/index.ts +3 -5
- package/colorUtils/__tests__/isTransparentColor.test.ts +76 -0
- package/colorUtils/__tests__/isValidColor.test.ts +70 -0
- package/colorUtils/index.ts +50 -0
- package/manifestUtils/_internals/index.js +6 -0
- package/manifestUtils/defaultManifestConfigurations/player.js +15 -15
- package/manifestUtils/fieldUtils/__tests__/fieldUtils.test.js +84 -0
- package/manifestUtils/fieldUtils/index.js +125 -0
- package/manifestUtils/keys.js +9 -0
- package/manifestUtils/mobileAction/button/index.js +16 -0
- package/manifestUtils/mobileAction/container/index.js +3 -1
- package/manifestUtils/mobileAction/groups/defaults.js +3 -1
- package/manifestUtils/platformIsTV.js +1 -0
- package/package.json +2 -2
- package/pipesUtils/__tests__/buildUrlWithQuery.test.ts +33 -0
- package/pipesUtils/__tests__/withPipesEndpoint.test.tsx +56 -0
- package/pipesUtils/index.ts +1 -0
- package/pipesUtils/withPipesEndpoint.tsx +54 -0
- package/reactHooks/actions/index.ts +51 -1
- package/reactHooks/cell-click/index.ts +18 -10
- package/reactHooks/feed/__mocks__/useMarkPipesDataStale.ts +4 -0
- package/reactHooks/feed/index.ts +5 -1
- package/reactHooks/feed/useBatchLoading.ts +9 -1
- package/reactHooks/feed/{usePipesCacheReset.ts → useMarkPipesDataStale.ts} +12 -4
- package/reactHooks/navigation/__mocks__/index.ts +4 -0
- package/reactHooks/navigation/__tests__/useIsScreenActive.test.ts +42 -0
- package/reactHooks/navigation/index.ts +1 -1
- package/reactHooks/navigation/useIsScreenActive.ts +29 -8
- package/reactHooks/state/useComponentScreenState.ts +1 -1
- package/reactHooks/utils/index.ts +3 -2
- package/riverComponetsMeasurementProvider/index.tsx +12 -8
- package/uiActionsRegistrator/index.ts +203 -0
- package/reactHooks/feed/__mocks__/usePipesCacheReset.ts +0 -1
|
@@ -2,17 +2,11 @@
|
|
|
2
2
|
{
|
|
3
3
|
"first": {
|
|
4
4
|
"name": "Screen viewed: Ads & redirects",
|
|
5
|
-
"params": {
|
|
6
|
-
"ignored_param": "removed_value",
|
|
7
|
-
"kept_param": "kept_value"
|
|
8
|
-
}
|
|
5
|
+
"params": {}
|
|
9
6
|
},
|
|
10
7
|
"second": {
|
|
11
8
|
"name": "Screen Ads & redirects",
|
|
12
|
-
"params": {
|
|
13
|
-
"ignored_param": "removed_value",
|
|
14
|
-
"kept_param": "kept_value"
|
|
15
|
-
}
|
|
9
|
+
"params": {}
|
|
16
10
|
}
|
|
17
11
|
}
|
|
18
|
-
]
|
|
12
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"rules": [
|
|
3
|
+
{
|
|
4
|
+
"event": "player_presented",
|
|
5
|
+
"ignore": true,
|
|
6
|
+
"strategy": "allowUnlisted"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"event": "screen_view",
|
|
10
|
+
"ignore": false,
|
|
11
|
+
"rename": "page_view",
|
|
12
|
+
"strategy": "allowUnlisted"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"strategy": "allowUnlisted"
|
|
16
|
+
}
|
|
@@ -14,6 +14,16 @@ import blockUnlistedEventEvents from "./analytics_mapper_testBlockUnlistedEvents
|
|
|
14
14
|
import blockUnlistedEventRules from "./analytics_mapper_testBlockUnlistedEvents_rules.json";
|
|
15
15
|
import ACPEventEvents from "./analytics_mapper_testACP_events.json";
|
|
16
16
|
import ACPEventRules from "./analytics_mapper_testACP_rules.json";
|
|
17
|
+
import emptyRenameRules from "./analytics_mapper_testEmptyRenameKeepsName_rules.json";
|
|
18
|
+
import emptyRenameEvents from "./analytics_mapper_testEmptyRenameKeepsName_events.json";
|
|
19
|
+
import ignoreOrderingRules from "./analytics_mapper_testIgnoreOrdering_rules.json";
|
|
20
|
+
import ignoreOrderingEvents from "./analytics_mapper_testIgnoreOrdering_events.json";
|
|
21
|
+
import regexEventNoFallbackRules from "./analytics_mapper_testRegexEventNoFallback_rules.json";
|
|
22
|
+
import regexEventNoFallbackEvents from "./analytics_mapper_testRegexEventNoFallback_events.json";
|
|
23
|
+
import regexMultiGroupRenameRules from "./analytics_mapper_testRegexMultiGroupRename_rules.json";
|
|
24
|
+
import regexMultiGroupRenameEvents from "./analytics_mapper_testRegexMultiGroupRename_events.json";
|
|
25
|
+
import regexNonNamedFullMatchRules from "./analytics_mapper_testRegexNonNamedFullMatch_rules.json";
|
|
26
|
+
import regexNonNamedFullMatchEvents from "./analytics_mapper_testRegexNonNamedFullMatch_events.json";
|
|
17
27
|
|
|
18
28
|
export const fixtures = {
|
|
19
29
|
renameRules,
|
|
@@ -32,4 +42,14 @@ export const fixtures = {
|
|
|
32
42
|
blockUnlistedEventRules,
|
|
33
43
|
ACPEventEvents,
|
|
34
44
|
ACPEventRules,
|
|
45
|
+
emptyRenameRules,
|
|
46
|
+
emptyRenameEvents,
|
|
47
|
+
ignoreOrderingRules,
|
|
48
|
+
ignoreOrderingEvents,
|
|
49
|
+
regexEventNoFallbackRules,
|
|
50
|
+
regexEventNoFallbackEvents,
|
|
51
|
+
regexMultiGroupRenameRules,
|
|
52
|
+
regexMultiGroupRenameEvents,
|
|
53
|
+
regexNonNamedFullMatchRules,
|
|
54
|
+
regexNonNamedFullMatchEvents,
|
|
35
55
|
};
|
|
@@ -76,7 +76,10 @@ class StateHolder {
|
|
|
76
76
|
delete this.pluckedParams[prefix];
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
// Stringify so values match the native iOS/Android mappers (which coerce
|
|
80
|
+
// analyticsCustomProperties values to strings) — keeps GA4 data consistent
|
|
81
|
+
// across web/LG/Samsung and native platforms.
|
|
82
|
+
return this._cap?.[suffix]?.toString() ?? null;
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
private async getFromLocalAndSession(
|
|
@@ -4,8 +4,14 @@ import { isTrue } from "@applicaster/zapp-react-native-utils/booleanUtils";
|
|
|
4
4
|
import { postAnalyticEvent } from "./manager";
|
|
5
5
|
import { isLive } from "../playerUtils";
|
|
6
6
|
import { extensionsEvents } from "./AnalyticsEvents/helper";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
AD_EVENT,
|
|
9
|
+
EVENT_TYPES,
|
|
10
|
+
GENERAL_EVENT,
|
|
11
|
+
PLAYER_DISPLAY_STATES,
|
|
12
|
+
} from "./events";
|
|
8
13
|
import { Player } from "../appUtils/playerManager/player";
|
|
14
|
+
import { isNil } from "@applicaster/zapp-react-native-utils/utils";
|
|
9
15
|
|
|
10
16
|
export interface PlayerAnalyticsTrackerI {
|
|
11
17
|
handleAnalyticEvent(
|
|
@@ -13,6 +19,8 @@ export interface PlayerAnalyticsTrackerI {
|
|
|
13
19
|
eventData?: QuickBrickPlayer.EventData
|
|
14
20
|
): void;
|
|
15
21
|
setPlayer(player: Player): void;
|
|
22
|
+
onPlayerPresented(): void;
|
|
23
|
+
onPlayerClosed(): void;
|
|
16
24
|
}
|
|
17
25
|
|
|
18
26
|
export class PlayerAnalyticsTracker implements PlayerAnalyticsTrackerI {
|
|
@@ -26,6 +34,15 @@ export class PlayerAnalyticsTracker implements PlayerAnalyticsTrackerI {
|
|
|
26
34
|
this.player = player;
|
|
27
35
|
}
|
|
28
36
|
|
|
37
|
+
onPlayerPresented() {
|
|
38
|
+
this.handleAnalyticEvent(GENERAL_EVENT.player_presented);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
onPlayerClosed() {
|
|
42
|
+
this.handleAnalyticEvent(GENERAL_EVENT.session_end);
|
|
43
|
+
this.handleAnalyticEvent(GENERAL_EVENT.player_closed);
|
|
44
|
+
}
|
|
45
|
+
|
|
29
46
|
isAdBreak(event: string): boolean {
|
|
30
47
|
const adBreakEvents = [
|
|
31
48
|
AD_EVENT.ad_break_start,
|
|
@@ -180,7 +197,8 @@ export class PlayerAnalyticsTracker implements PlayerAnalyticsTrackerI {
|
|
|
180
197
|
this.entry?.extensions?.free || this.entry?.extensions?.isFree;
|
|
181
198
|
|
|
182
199
|
const title = this.entry?.title;
|
|
183
|
-
const
|
|
200
|
+
const isStreamLive = isLive(this.entry);
|
|
201
|
+
const streamType = isStreamLive ? "live" : "vod"; // Todo: determine other types, channel, podcast, aod
|
|
184
202
|
|
|
185
203
|
const currentPosition = this.getCurrentPosition(
|
|
186
204
|
event,
|
|
@@ -201,6 +219,7 @@ export class PlayerAnalyticsTracker implements PlayerAnalyticsTrackerI {
|
|
|
201
219
|
);
|
|
202
220
|
|
|
203
221
|
const playerState = this.getPlayerState();
|
|
222
|
+
const playerType = this.player?.playerPluginId;
|
|
204
223
|
|
|
205
224
|
const analyticsCustomProperties = extensionsEvents(this.entry?.extensions);
|
|
206
225
|
|
|
@@ -210,7 +229,11 @@ export class PlayerAnalyticsTracker implements PlayerAnalyticsTrackerI {
|
|
|
210
229
|
name: title,
|
|
211
230
|
media_type: mediaType,
|
|
212
231
|
stream_type: streamType,
|
|
213
|
-
|
|
232
|
+
player_type: playerType,
|
|
233
|
+
// We do not pass duration for live streams anymore (it's now seekableDuration),
|
|
234
|
+
// but we can't mark it as optional in Analytics Mapper yet,
|
|
235
|
+
// so we set it to -1 for live streams
|
|
236
|
+
duration: isStreamLive && isNil(mediaDuration) ? -1 : mediaDuration,
|
|
214
237
|
current_position: currentPosition,
|
|
215
238
|
player_state: playerState,
|
|
216
239
|
stream_format: contentType,
|
|
@@ -46,7 +46,7 @@ describe("Context Keys Manager - getKey", () => {
|
|
|
46
46
|
|
|
47
47
|
expect(mockedLogger.warn).toHaveBeenCalledWith({
|
|
48
48
|
message:
|
|
49
|
-
"Provided key is not valid -
|
|
49
|
+
"Provided key is not valid - expecting an object with namespace and key properties",
|
|
50
50
|
data: { key: invalidKey },
|
|
51
51
|
});
|
|
52
52
|
|
|
@@ -47,7 +47,7 @@ describe("Context Keys Manager - removeKey", () => {
|
|
|
47
47
|
|
|
48
48
|
expect(mockedLogger.warn).toHaveBeenCalledWith({
|
|
49
49
|
message:
|
|
50
|
-
"Provided key is not valid -
|
|
50
|
+
"Provided key is not valid - expecting an object with namespace and key properties",
|
|
51
51
|
data: { key },
|
|
52
52
|
});
|
|
53
53
|
|
|
@@ -48,7 +48,7 @@ describe("Context Keys Manager - setKey", () => {
|
|
|
48
48
|
|
|
49
49
|
expect(mockedLogger.warn).toHaveBeenCalledWith({
|
|
50
50
|
message:
|
|
51
|
-
"Provided key is not valid -
|
|
51
|
+
"Provided key is not valid - expecting an object with namespace and key properties",
|
|
52
52
|
data: { key },
|
|
53
53
|
});
|
|
54
54
|
|
|
@@ -105,7 +105,7 @@ describe("Context Keys Manager - setKey", () => {
|
|
|
105
105
|
|
|
106
106
|
expect(mockedLogger.warn).toHaveBeenCalledWith({
|
|
107
107
|
message:
|
|
108
|
-
"Provided key is not valid -
|
|
108
|
+
"Provided key is not valid - expecting an object with namespace and key properties",
|
|
109
109
|
data: { key },
|
|
110
110
|
});
|
|
111
111
|
|
|
@@ -162,7 +162,7 @@ describe("Context Keys Manager - setKey", () => {
|
|
|
162
162
|
|
|
163
163
|
expect(mockedLogger.warn).toHaveBeenCalledWith({
|
|
164
164
|
message:
|
|
165
|
-
"Provided key is not valid -
|
|
165
|
+
"Provided key is not valid - expecting an object with namespace and key properties",
|
|
166
166
|
data: { key },
|
|
167
167
|
});
|
|
168
168
|
|
|
@@ -217,7 +217,7 @@ describe("Context Keys Manager - setKey", () => {
|
|
|
217
217
|
|
|
218
218
|
expect(mockedLogger.warn).toHaveBeenCalledWith({
|
|
219
219
|
message:
|
|
220
|
-
"Provided key is not valid -
|
|
220
|
+
"Provided key is not valid - expecting an object with namespace and key properties",
|
|
221
221
|
data: { key },
|
|
222
222
|
});
|
|
223
223
|
|
|
@@ -109,7 +109,7 @@ export class ContextKeysManager {
|
|
|
109
109
|
if (!keyIsValid(parsedKey)) {
|
|
110
110
|
this._logger.warn({
|
|
111
111
|
message:
|
|
112
|
-
"Provided key is not valid -
|
|
112
|
+
"Provided key is not valid - expecting an object with namespace and key properties",
|
|
113
113
|
data: { key },
|
|
114
114
|
});
|
|
115
115
|
|
package/cellUtils/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as R from "ramda";
|
|
2
2
|
import { dayjs } from "../dateUtils";
|
|
3
|
-
import validateColor from "validate-color";
|
|
4
3
|
|
|
4
|
+
import { isValidColor } from "@applicaster/zapp-react-native-utils/colorUtils";
|
|
5
5
|
import { transformColorCode as fixColorHexCode } from "@applicaster/zapp-react-native-utils/transform";
|
|
6
6
|
import {
|
|
7
7
|
capitalize,
|
|
@@ -468,15 +468,13 @@ export const getColorFromData = ({
|
|
|
468
468
|
data,
|
|
469
469
|
valueFromLayout,
|
|
470
470
|
}: GetColorFromData): string => {
|
|
471
|
-
|
|
472
|
-
// https://github.com/dreamyguy/validate-color/issues/44
|
|
473
|
-
if (validateColor(valueFromLayout.replace(".00", ""))) {
|
|
471
|
+
if (isValidColor(valueFromLayout)) {
|
|
474
472
|
return valueFromLayout;
|
|
475
473
|
}
|
|
476
474
|
|
|
477
475
|
const pathValue = R.path(valueFromLayout.split("."), data);
|
|
478
476
|
|
|
479
|
-
if (pathValue &&
|
|
477
|
+
if (pathValue && isValidColor(pathValue)) {
|
|
480
478
|
return pathValue;
|
|
481
479
|
}
|
|
482
480
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { isTransparentColor } from "..";
|
|
2
|
+
|
|
3
|
+
describe("isTransparentColor", () => {
|
|
4
|
+
it("returns true for transparent keyword and rgba colors with zero alpha", () => {
|
|
5
|
+
const transparentColors = [
|
|
6
|
+
"transparent",
|
|
7
|
+
"rgba(0,0,0,0)",
|
|
8
|
+
"rgba(255, 255, 255, 0)",
|
|
9
|
+
"rgba(0,0,0,0.0)",
|
|
10
|
+
"rgba(0,0,0,0.00)",
|
|
11
|
+
"rgba(0,0,0, 0)",
|
|
12
|
+
"hsla(0,0%,0%,0)",
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
expect.assertions(transparentColors.length);
|
|
16
|
+
|
|
17
|
+
transparentColors.forEach((color) => {
|
|
18
|
+
expect(isTransparentColor(color)).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("returns false for valid colors that are not fully transparent", () => {
|
|
23
|
+
const nonTransparentColors = [
|
|
24
|
+
"red",
|
|
25
|
+
"#fff",
|
|
26
|
+
"#ffffff",
|
|
27
|
+
"rgb(0,0,0)",
|
|
28
|
+
"rgba(0,0,0)",
|
|
29
|
+
"rgba(0,0,0,0.5)",
|
|
30
|
+
"rgba(255, 255, 255, 0.3)",
|
|
31
|
+
"rgba(255, 255, 255, 1.0)",
|
|
32
|
+
"rgba(239,239,239,1.0)",
|
|
33
|
+
"currentColor",
|
|
34
|
+
"inherit",
|
|
35
|
+
"#00000000",
|
|
36
|
+
"#fff0",
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
expect.assertions(nonTransparentColors.length);
|
|
40
|
+
|
|
41
|
+
nonTransparentColors.forEach((color) => {
|
|
42
|
+
expect(isTransparentColor(color)).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("returns false for case variants of the transparent keyword", () => {
|
|
47
|
+
expect(isTransparentColor("Transparent")).toBe(false);
|
|
48
|
+
expect(isTransparentColor("TRANSPARENT")).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("returns false for invalid color strings", () => {
|
|
52
|
+
const invalidColors = [
|
|
53
|
+
"invalid",
|
|
54
|
+
"",
|
|
55
|
+
"#gggggg",
|
|
56
|
+
"#fff.00",
|
|
57
|
+
"unset",
|
|
58
|
+
" rgba(0,0,0,0) ",
|
|
59
|
+
"transparent ",
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
expect.assertions(invalidColors.length);
|
|
63
|
+
|
|
64
|
+
invalidColors.forEach((color) => {
|
|
65
|
+
expect(isTransparentColor(color)).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("returns false for nullish and non-string values", () => {
|
|
70
|
+
expect(isTransparentColor(undefined)).toBe(false);
|
|
71
|
+
expect(isTransparentColor(null)).toBe(false);
|
|
72
|
+
expect(isTransparentColor(123)).toBe(false);
|
|
73
|
+
expect(isTransparentColor({})).toBe(false);
|
|
74
|
+
expect(isTransparentColor([])).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { isValidColor } from "..";
|
|
2
|
+
|
|
3
|
+
describe("isValidColor", () => {
|
|
4
|
+
it("returns true for valid hex colors", () => {
|
|
5
|
+
const validHexColors = ["#fff", "#ffffff", "#FF0000", "#000", "#abc123"];
|
|
6
|
+
|
|
7
|
+
expect.assertions(validHexColors.length);
|
|
8
|
+
|
|
9
|
+
validHexColors.forEach((color) => {
|
|
10
|
+
expect(isValidColor(color)).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("returns true for valid rgb and rgba colors", () => {
|
|
15
|
+
const validRgbColors = [
|
|
16
|
+
"rgb(255,0,0)",
|
|
17
|
+
"rgb(255, 0, 0)",
|
|
18
|
+
"rgba(255,0,0,0.5)",
|
|
19
|
+
"rgba(255, 0, 0, 0.5)",
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
expect.assertions(validRgbColors.length);
|
|
23
|
+
|
|
24
|
+
validRgbColors.forEach((color) => {
|
|
25
|
+
expect(isValidColor(color)).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("returns true for valid named and special CSS colors", () => {
|
|
30
|
+
const validNamedColors = ["red", "transparent", "currentColor", "inherit"];
|
|
31
|
+
|
|
32
|
+
expect.assertions(validNamedColors.length);
|
|
33
|
+
|
|
34
|
+
validNamedColors.forEach((color) => {
|
|
35
|
+
expect(isValidColor(color)).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("returns true for valid hsl colors", () => {
|
|
40
|
+
expect(isValidColor("hsl(0, 100%, 50%)")).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("returns false for invalid color strings", () => {
|
|
44
|
+
const invalidColors = [
|
|
45
|
+
"invalid",
|
|
46
|
+
"",
|
|
47
|
+
"#gggggg",
|
|
48
|
+
"#fff.00",
|
|
49
|
+
"unset",
|
|
50
|
+
undefined,
|
|
51
|
+
null,
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
expect.assertions(invalidColors.length);
|
|
55
|
+
|
|
56
|
+
invalidColors.forEach((color) => {
|
|
57
|
+
expect(isValidColor(color)).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("returns true for valid rgb and rgba colors", () => {
|
|
62
|
+
const validRgbColors = ["rgba(239,239,239,1.0)"];
|
|
63
|
+
|
|
64
|
+
expect.assertions(validRgbColors.length);
|
|
65
|
+
|
|
66
|
+
validRgbColors.forEach((color) => {
|
|
67
|
+
expect(isValidColor(color)).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import validateColor from "validate-color";
|
|
2
|
+
import { isNil } from "@applicaster/zapp-react-native-utils/utils";
|
|
3
|
+
import { isString } from "@applicaster/zapp-react-native-utils/stringUtils";
|
|
4
|
+
|
|
5
|
+
const normalizeRgbaAlpha = (color: string): string => {
|
|
6
|
+
return color.replace(
|
|
7
|
+
/^rgba\(([^)]+),\s*(\d+(?:\.\d+)?)\s*\)$/i,
|
|
8
|
+
(_, rgb, alpha) => {
|
|
9
|
+
const alphaValue = parseFloat(alpha);
|
|
10
|
+
|
|
11
|
+
if (!Number.isFinite(alphaValue) || !Number.isInteger(alphaValue)) {
|
|
12
|
+
return color;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return `rgba(${rgb},${alphaValue})`;
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const isValidColor = (color: string): boolean => {
|
|
21
|
+
if (isNil(color) || !isString(color)) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (validateColor(color)) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// validate-color rejects integer alpha values written as floats (e.g. 1.0)
|
|
30
|
+
// https://github.com/dreamyguy/validate-color/issues/44
|
|
31
|
+
const normalizedColor = normalizeRgbaAlpha(color);
|
|
32
|
+
|
|
33
|
+
return normalizedColor !== color && validateColor(normalizedColor);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function isRgbaAlphaZero(color: string): boolean {
|
|
37
|
+
const layers = color
|
|
38
|
+
.replace("rgba(", "")
|
|
39
|
+
.replace(")", "")
|
|
40
|
+
.split(",")
|
|
41
|
+
.map((layer) => layer.trim());
|
|
42
|
+
|
|
43
|
+
return Number(layers[3]) === 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const isTransparentColor = (color: string): boolean => {
|
|
47
|
+
return (
|
|
48
|
+
isValidColor(color) && (color === "transparent" || isRgbaAlphaZero(color))
|
|
49
|
+
);
|
|
50
|
+
};
|
|
@@ -157,6 +157,10 @@ function generateFieldsFromDefaultsWithoutPrefixedLabel(
|
|
|
157
157
|
|
|
158
158
|
if (conditions) {
|
|
159
159
|
generatedField.conditional_fields = conditions.map((condition) => {
|
|
160
|
+
if (condition.section === null) {
|
|
161
|
+
return { key: condition.key, condition_value: condition.value };
|
|
162
|
+
}
|
|
163
|
+
|
|
160
164
|
const section = condition.section ? `${condition.section}/` : "";
|
|
161
165
|
|
|
162
166
|
return {
|
|
@@ -164,6 +168,8 @@ function generateFieldsFromDefaultsWithoutPrefixedLabel(
|
|
|
164
168
|
condition_value: condition.value,
|
|
165
169
|
};
|
|
166
170
|
});
|
|
171
|
+
|
|
172
|
+
generatedField.rules = "all_conditions";
|
|
167
173
|
}
|
|
168
174
|
|
|
169
175
|
return generatedField;
|
|
@@ -781,21 +781,6 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
781
781
|
}
|
|
782
782
|
);
|
|
783
783
|
|
|
784
|
-
if (
|
|
785
|
-
platform.includes("android") ||
|
|
786
|
-
platform.includes("amazon") ||
|
|
787
|
-
platform.includes("ios") ||
|
|
788
|
-
platform.includes("tvos")
|
|
789
|
-
) {
|
|
790
|
-
general.fields.push({
|
|
791
|
-
key: "user_agent",
|
|
792
|
-
label: "User agent",
|
|
793
|
-
initial_value: "",
|
|
794
|
-
type: "text_input",
|
|
795
|
-
label_tooltip: "Override default user agent string",
|
|
796
|
-
});
|
|
797
|
-
}
|
|
798
|
-
|
|
799
784
|
if (platform === "android_for_quickbrick") {
|
|
800
785
|
general.fields.push({
|
|
801
786
|
key: "stop_playback_on_task_removal",
|
|
@@ -2415,6 +2400,21 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
2415
2400
|
);
|
|
2416
2401
|
}
|
|
2417
2402
|
|
|
2403
|
+
if (
|
|
2404
|
+
platform.includes("android") ||
|
|
2405
|
+
platform.includes("amazon") ||
|
|
2406
|
+
platform.includes("ios") ||
|
|
2407
|
+
platform.includes("tvos")
|
|
2408
|
+
) {
|
|
2409
|
+
general.fields.push({
|
|
2410
|
+
key: "user_agent",
|
|
2411
|
+
label: "User agent",
|
|
2412
|
+
initial_value: "",
|
|
2413
|
+
type: "text_input",
|
|
2414
|
+
label_tooltip: "Override default user agent string",
|
|
2415
|
+
});
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2418
2418
|
compact([
|
|
2419
2419
|
{
|
|
2420
2420
|
key: "liveCatchUpEnabled",
|
|
@@ -1,9 +1,93 @@
|
|
|
1
1
|
const {
|
|
2
|
+
parseShorthand,
|
|
3
|
+
margin,
|
|
4
|
+
padding,
|
|
2
5
|
withConditional,
|
|
3
6
|
getConditionalKey,
|
|
4
7
|
createConditionalField,
|
|
5
8
|
} = require("..");
|
|
6
9
|
|
|
10
|
+
describe("parseShorthand", () => {
|
|
11
|
+
it("1 value → all four sides equal", () => {
|
|
12
|
+
expect(parseShorthand("8")).toEqual([8, 8, 8, 8]);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("2 values → top/bottom, right/left", () => {
|
|
16
|
+
expect(parseShorthand("0 20")).toEqual([0, 20, 0, 20]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("3 values → top, right/left, bottom", () => {
|
|
20
|
+
expect(parseShorthand("0 0 20")).toEqual([0, 0, 20, 0]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("4 values → top right bottom left", () => {
|
|
24
|
+
expect(parseShorthand("1 2 3 4")).toEqual([1, 2, 3, 4]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("throws on invalid count", () => {
|
|
28
|
+
expect(() => parseShorthand("1 2 3 4 5")).toThrow();
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("margin", () => {
|
|
33
|
+
it("returns 4 field descriptors with correct suffixes", () => {
|
|
34
|
+
const fields = margin("margin", { mobile: "0" });
|
|
35
|
+
|
|
36
|
+
expect(fields.map((f) => f.suffix)).toEqual([
|
|
37
|
+
"margin top",
|
|
38
|
+
"margin right",
|
|
39
|
+
"margin bottom",
|
|
40
|
+
"margin left",
|
|
41
|
+
]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("sets type to number_input", () => {
|
|
45
|
+
margin("margin", { mobile: "0" }).forEach((f) => {
|
|
46
|
+
expect(f.type).toBe("number_input");
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("expands shorthand per platform", () => {
|
|
51
|
+
const fields = margin("margin", { mobile: "0 0 20", tv: "0 0 90" });
|
|
52
|
+
const byKey = Object.fromEntries(fields.map((f) => [f.suffix, f]));
|
|
53
|
+
expect(byKey["margin top"].initialValue).toEqual({ mobile: 0, tv: 0 });
|
|
54
|
+
expect(byKey["margin bottom"].initialValue).toEqual({ mobile: 20, tv: 90 });
|
|
55
|
+
expect(byKey["margin left"].initialValue).toEqual({ mobile: 0, tv: 0 });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("supports a custom suffix prefix", () => {
|
|
59
|
+
const fields = margin("input margin", { mobile: "4" });
|
|
60
|
+
expect(fields[0].suffix).toBe("input margin top");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("padding", () => {
|
|
65
|
+
it("returns 4 field descriptors with correct suffixes", () => {
|
|
66
|
+
const fields = padding("padding", { mobile: "0" });
|
|
67
|
+
|
|
68
|
+
expect(fields.map((f) => f.suffix)).toEqual([
|
|
69
|
+
"padding top",
|
|
70
|
+
"padding right",
|
|
71
|
+
"padding bottom",
|
|
72
|
+
"padding left",
|
|
73
|
+
]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("expands 2-value shorthand symmetrically", () => {
|
|
77
|
+
const fields = padding("padding", { mobile: "0 20", tv: "0 124" });
|
|
78
|
+
const byKey = Object.fromEntries(fields.map((f) => [f.suffix, f]));
|
|
79
|
+
|
|
80
|
+
expect(byKey["padding right"].initialValue).toEqual({
|
|
81
|
+
mobile: 20,
|
|
82
|
+
tv: 124,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(byKey["padding left"].initialValue).toEqual({ mobile: 20, tv: 124 });
|
|
86
|
+
expect(byKey["padding top"].initialValue).toEqual({ mobile: 0, tv: 0 });
|
|
87
|
+
expect(byKey["padding bottom"].initialValue).toEqual({ mobile: 0, tv: 0 });
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
7
91
|
describe("manifestUtils/fieldUtils", () => {
|
|
8
92
|
it("appends conditions and adds all_conditions when there is more than one condition", () => {
|
|
9
93
|
const config = {
|