@applicaster/zapp-react-native-utils 15.1.13-alpha.1034774223 → 15.1.13
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 +2 -40
- package/actionsExecutor/ActionExecutorContext.tsx +16 -70
- package/actionsExecutor/actions/index.ts +0 -4
- package/actionsExecutor/actions/refreshComponent.ts +0 -6
- package/actionsExecutor/actions/sendCloudEvent.ts +1 -6
- package/actionsExecutor/consts.ts +0 -23
- package/actionsExecutor/types.ts +1 -1
- package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +2 -5
- package/appUtils/playerManager/OverlayObserver/__tests__/utils.test.ts +2 -55
- package/appUtils/playerManager/OverlayObserver/utils.ts +20 -51
- package/arrayUtils/index.ts +0 -79
- package/configurationUtils/__tests__/manifestKeyParser.test.ts +0 -21
- package/configurationUtils/manifestKeyParser.ts +10 -50
- package/manifestUtils/defaultManifestConfigurations/player.js +1 -22
- package/manifestUtils/index.js +0 -3
- package/modalState/__tests__/index.test.ts +1 -30
- package/modalState/index.ts +83 -35
- package/package.json +2 -2
- package/playerUtils/__tests__/getPlayerActionButtons.test.ts +54 -0
- package/playerUtils/getPlayerActionButtons.ts +17 -0
- package/playerUtils/index.ts +1 -17
- package/reactHooks/actions/index.ts +2 -67
- package/reactHooks/cell-click/__tests__/index.test.js +64 -0
- package/reactHooks/cell-click/index.ts +21 -14
- package/reactHooks/usePluginConfiguration.ts +3 -6
- package/uiActionsRegistrator/index.ts +203 -25
- package/actionsExecutor/actions/__tests__/dismissBottomSheet.test.ts +0 -25
- package/actionsExecutor/actions/__tests__/loadItemsFromSource.test.ts +0 -88
- package/actionsExecutor/actions/__tests__/openBottomSheet.customContent.test.ts +0 -76
- package/actionsExecutor/actions/__tests__/openBottomSheet.inlineItems.test.ts +0 -212
- package/actionsExecutor/actions/dismissBottomSheet.ts +0 -23
- package/actionsExecutor/actions/openBottomSheet.ts +0 -325
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.android.tv.ts +0 -4
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.ios.tv.ts +0 -4
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.ts +0 -11
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.web.ts +0 -4
- package/arrayUtils/__tests__/reorderByIds.test.ts +0 -50
- package/configurationUtils/__tests__/modalBlocksParser.test.ts +0 -260
- package/configurationUtils/modalBlocksParser.ts +0 -296
- package/entryActions/__tests__/buildEntryActions.test.ts +0 -156
- package/entryActions/aliasPresentation.ts +0 -124
- package/entryActions/buildEntryActions.ts +0 -166
- package/entryActions/index.ts +0 -17
- package/entryActions/types.ts +0 -39
- package/manifestUtils/modalBlocks.js +0 -304
- package/modalState/ContentViewModel.ts +0 -113
- package/modalState/EditableCollection.ts +0 -17
- package/modalState/EditableCollectionRegistry.ts +0 -51
- package/modalState/ModalOrchestrator.ts +0 -211
- package/modalState/RemoteEditableCollection.ts +0 -227
- package/modalState/__tests__/ContentViewModel.editable.test.ts +0 -69
- package/modalState/__tests__/ContentViewModel.test.ts +0 -165
- package/modalState/__tests__/EditableCollectionRegistry.test.ts +0 -112
- package/modalState/__tests__/ModalOrchestrator.phase3.test.ts +0 -47
- package/modalState/__tests__/RemoteEditableCollection.test.ts +0 -326
- package/modalState/__tests__/useBottomSheetContent.test.ts +0 -349
- package/modalState/store.ts +0 -102
- package/modalState/types.ts +0 -159
- package/modalState/useBottomSheetContent.ts +0 -104
- package/playerUtils/__tests__/resolvePlayerActions.test.ts +0 -138
- package/playerUtils/resolvePlayerActions.ts +0 -71
- package/uiActionsRegistrator/__tests__/resolveActionIdentifiers.test.ts +0 -93
- package/uiActionsRegistrator/__tests__/subscribe.test.ts +0 -107
- package/uiActionsRegistrator/__tests__/usableActionItems.test.ts +0 -52
- package/uiActionsRegistrator/registry.ts +0 -335
- package/uiActionsRegistrator/resolveActionIdentifiers.ts +0 -100
- package/uiActionsRegistrator/usableActionItems.ts +0 -66
- /package/reactHooks/actions/__tests__/{index.test.tsx → index.test.js} +0 -0
|
@@ -11,30 +11,17 @@ const currentPlatform = platformSelect({
|
|
|
11
11
|
android_tv: "android",
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
// Do not change the order
|
|
15
|
-
|
|
16
|
-
// "focus" first would steal focused_* / focused_selected_* keys. Adding
|
|
17
|
-
// "focus" here affects every getAllSpecificStyles consumer (not only modals);
|
|
18
|
-
// property names that themselves start with focus_ (e.g. focus_font_color as
|
|
19
|
-
// a non-state style) would also be classified as the focus state.
|
|
20
|
-
const states = [
|
|
21
|
-
"focused_selected",
|
|
22
|
-
"pressed",
|
|
23
|
-
"focused",
|
|
24
|
-
"focus",
|
|
25
|
-
"selected",
|
|
26
|
-
"default",
|
|
27
|
-
];
|
|
14
|
+
// Do not change the order, focused_selected should be first
|
|
15
|
+
const states = ["focused_selected", "pressed", "focused", "selected"];
|
|
28
16
|
|
|
29
17
|
const platformSuffixes = ["ios", "tvos", "samsung", "lg", "android"];
|
|
30
|
-
type StateKey = (typeof states)[number];
|
|
18
|
+
type StateKey = (typeof states)[number] | "default";
|
|
31
19
|
|
|
32
20
|
export type GetAllSpecificStylesProps = {
|
|
33
21
|
componentName: string;
|
|
34
22
|
subComponentName: string;
|
|
35
23
|
configuration: Record<string, any>;
|
|
36
24
|
outStyles: Record<string, any>;
|
|
37
|
-
styleSeparator?: string;
|
|
38
25
|
};
|
|
39
26
|
|
|
40
27
|
/*
|
|
@@ -48,23 +35,14 @@ export const getAllSpecificStyles = ({
|
|
|
48
35
|
componentName,
|
|
49
36
|
subComponentName,
|
|
50
37
|
outStyles,
|
|
51
|
-
styleSeparator = "_style_",
|
|
52
38
|
}: GetAllSpecificStylesProps) => {
|
|
53
39
|
if (!outStyles) throw new Error("outStyles is required");
|
|
54
40
|
if (!componentName) throw new Error("componentName is required");
|
|
55
41
|
|
|
56
|
-
const
|
|
42
|
+
const prefix =
|
|
57
43
|
subComponentName?.length > 0
|
|
58
|
-
?
|
|
59
|
-
|
|
60
|
-
? `${componentName}${styleSeparator}${subComponentName}_`
|
|
61
|
-
: `${subComponentName}_`,
|
|
62
|
-
`${componentName}_style_${subComponentName}_`,
|
|
63
|
-
]
|
|
64
|
-
: [
|
|
65
|
-
styleSeparator ? `${componentName}${styleSeparator}` : "",
|
|
66
|
-
`${componentName}_style_`,
|
|
67
|
-
];
|
|
44
|
+
? `${componentName}_style_${subComponentName}_`
|
|
45
|
+
: `${componentName}_style_`;
|
|
68
46
|
|
|
69
47
|
const defaultKey = "default";
|
|
70
48
|
|
|
@@ -73,24 +51,12 @@ export const getAllSpecificStyles = ({
|
|
|
73
51
|
const tmpGenericDict: Record<StateKey, Record<string, any>> = {};
|
|
74
52
|
const tmpPlatformDict: Record<StateKey, Record<string, any>> = {};
|
|
75
53
|
|
|
76
|
-
const parseKey = (key: string,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (!matchedPrefix) {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Guard: for empty styleSeparator, if matching modern prefix, require dot in originalKey
|
|
84
|
-
if (
|
|
85
|
-
styleSeparator === "" &&
|
|
86
|
-
matchedPrefix === `${subComponentName}_` &&
|
|
87
|
-
!originalKey.includes(".")
|
|
88
|
-
) {
|
|
54
|
+
const parseKey = (key: string, value: any) => {
|
|
55
|
+
if (!key.startsWith(prefix)) {
|
|
89
56
|
return;
|
|
90
57
|
}
|
|
91
58
|
|
|
92
|
-
let styleName = key.slice(
|
|
93
|
-
|
|
59
|
+
let styleName = key.slice(prefix.length);
|
|
94
60
|
if (!styleName || styleName.startsWith("_")) return;
|
|
95
61
|
|
|
96
62
|
const platform = platformSuffixes.find((prefix) =>
|
|
@@ -118,11 +84,6 @@ export const getAllSpecificStyles = ({
|
|
|
118
84
|
state = defaultKey;
|
|
119
85
|
}
|
|
120
86
|
|
|
121
|
-
// Normalize Studio "focus" keys onto the runtime "focused" state bucket.
|
|
122
|
-
if (state === "focus") {
|
|
123
|
-
state = "focused";
|
|
124
|
-
}
|
|
125
|
-
|
|
126
87
|
if (!styleName) return;
|
|
127
88
|
|
|
128
89
|
const camelCaseKey = toCamelCase(styleName);
|
|
@@ -132,8 +93,7 @@ export const getAllSpecificStyles = ({
|
|
|
132
93
|
};
|
|
133
94
|
|
|
134
95
|
for (const [key, value] of Object.entries(configuration)) {
|
|
135
|
-
|
|
136
|
-
parseKey(formattedKey, key, value);
|
|
96
|
+
parseKey(key, value);
|
|
137
97
|
}
|
|
138
98
|
|
|
139
99
|
const allStates = Array.from(
|
|
@@ -204,20 +204,6 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
204
204
|
label_tooltip: "Label for close button accessibility",
|
|
205
205
|
type: "text_input",
|
|
206
206
|
},
|
|
207
|
-
{
|
|
208
|
-
key: "accessibility_overflow_label",
|
|
209
|
-
label: "Accessibility overflow label",
|
|
210
|
-
initial_value: "More actions",
|
|
211
|
-
label_tooltip: "Label for the overflow (…) button accessibility",
|
|
212
|
-
type: "text_input",
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
key: "accessibility_overflow_hint",
|
|
216
|
-
label: "Accessibility overflow hint",
|
|
217
|
-
initial_value: "Opens the remaining player actions",
|
|
218
|
-
label_tooltip: "Hint for the overflow (…) button accessibility",
|
|
219
|
-
type: "text_input",
|
|
220
|
-
},
|
|
221
207
|
{
|
|
222
208
|
key: "accessibility_close_hint",
|
|
223
209
|
label: "Accessibility close hint",
|
|
@@ -761,7 +747,7 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
761
747
|
label: "Player action buttons",
|
|
762
748
|
initial_value: null,
|
|
763
749
|
label_tooltip:
|
|
764
|
-
"
|
|
750
|
+
"type here the identifiers of the action buttons you want to show on the player, separated by commas. Only 2 action buttons may be added at the same time",
|
|
765
751
|
type: "text_input",
|
|
766
752
|
},
|
|
767
753
|
{
|
|
@@ -1061,13 +1047,6 @@ function getPlayerConfiguration({ platform, version }) {
|
|
|
1061
1047
|
type: "uploader",
|
|
1062
1048
|
label_tooltip: "Override default close badge / icon",
|
|
1063
1049
|
},
|
|
1064
|
-
{
|
|
1065
|
-
key: "overflow",
|
|
1066
|
-
label: "Overflow badge",
|
|
1067
|
-
type: "uploader",
|
|
1068
|
-
label_tooltip:
|
|
1069
|
-
"Override default overflow (…) badge / icon, shown when more action buttons resolve than fit on the player overlay",
|
|
1070
|
-
},
|
|
1071
1050
|
{
|
|
1072
1051
|
key: "lock",
|
|
1073
1052
|
label: "Lock player badge",
|
package/manifestUtils/index.js
CHANGED
|
@@ -23,7 +23,6 @@ const {
|
|
|
23
23
|
const { buttonKey } = require("./buttons");
|
|
24
24
|
const { tvBadges } = require("./tvBadges");
|
|
25
25
|
const { mobileProgressBar, tvProgressBar } = require("./progressBar");
|
|
26
|
-
const { getModalBlockStyles, getModalLocalizations } = require("./modalBlocks");
|
|
27
26
|
|
|
28
27
|
const {
|
|
29
28
|
secondaryImage,
|
|
@@ -53,6 +52,4 @@ module.exports = {
|
|
|
53
52
|
getUpdatedSecondaryImageKeys,
|
|
54
53
|
DEFAULT_GRADIENT_IMAGE,
|
|
55
54
|
compact,
|
|
56
|
-
getModalBlockStyles,
|
|
57
|
-
getModalLocalizations,
|
|
58
55
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { modalStore
|
|
1
|
+
import { modalStore } from "../";
|
|
2
2
|
|
|
3
3
|
describe("Modal State", () => {
|
|
4
4
|
it("should have initial state", () => {
|
|
@@ -48,33 +48,4 @@ describe("Modal State", () => {
|
|
|
48
48
|
});
|
|
49
49
|
});
|
|
50
50
|
});
|
|
51
|
-
|
|
52
|
-
describe("getMenuItemActions", () => {
|
|
53
|
-
it("returns actions array when actions is provided", () => {
|
|
54
|
-
const actions = [{ type: "action1" }, { type: "action2" }];
|
|
55
|
-
const item = { id: "1", title: "Item", actions };
|
|
56
|
-
|
|
57
|
-
expect(getMenuItemActions(item)).toEqual(actions);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("returns [action] when single action is provided", () => {
|
|
61
|
-
const action = { type: "action1" };
|
|
62
|
-
const item = { id: "1", title: "Item", action };
|
|
63
|
-
|
|
64
|
-
expect(getMenuItemActions(item)).toEqual([action]);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("returns action when action is passed as an array", () => {
|
|
68
|
-
const actions = [{ type: "action1" }];
|
|
69
|
-
const item = { id: "1", title: "Item", action: actions as any };
|
|
70
|
-
|
|
71
|
-
expect(getMenuItemActions(item)).toEqual(actions);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("returns empty array when no action or actions are provided", () => {
|
|
75
|
-
expect(getMenuItemActions({ id: "1", title: "Item" })).toEqual([]);
|
|
76
|
-
expect(getMenuItemActions(null)).toEqual([]);
|
|
77
|
-
expect(getMenuItemActions(undefined)).toEqual([]);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
51
|
});
|
package/modalState/index.ts
CHANGED
|
@@ -1,35 +1,83 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
1
|
+
import { useStore, createStore } from "zustand";
|
|
2
|
+
|
|
3
|
+
interface ModalState {
|
|
4
|
+
visible: boolean;
|
|
5
|
+
screen: any;
|
|
6
|
+
options: Record<string, unknown>;
|
|
7
|
+
props: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface ModalStore {
|
|
11
|
+
modalState: ModalState;
|
|
12
|
+
setModalState: (newState: Partial<ModalState>) => void;
|
|
13
|
+
openModal: (
|
|
14
|
+
item: any,
|
|
15
|
+
options?: Record<string, unknown>,
|
|
16
|
+
props?: Record<string, unknown>
|
|
17
|
+
) => void;
|
|
18
|
+
dismissModal: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const initialModalState: ModalState = {
|
|
22
|
+
visible: false,
|
|
23
|
+
screen: null,
|
|
24
|
+
options: {},
|
|
25
|
+
props: {},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const modalStore = createStore<ModalStore>((set) => ({
|
|
29
|
+
modalState: initialModalState,
|
|
30
|
+
setModalState: (newState) =>
|
|
31
|
+
set((state) => ({
|
|
32
|
+
modalState: { ...state.modalState, ...newState },
|
|
33
|
+
})),
|
|
34
|
+
openModal: ({ item, options = {}, props }: OpenModalArg) =>
|
|
35
|
+
set({
|
|
36
|
+
modalState: {
|
|
37
|
+
visible: true,
|
|
38
|
+
screen: item,
|
|
39
|
+
options: {
|
|
40
|
+
animated: true,
|
|
41
|
+
animationType: "slide",
|
|
42
|
+
onShow: () => {},
|
|
43
|
+
presentationStyle: "overFullScreen",
|
|
44
|
+
transparent: true,
|
|
45
|
+
...options,
|
|
46
|
+
onDismiss: () => set({ modalState: initialModalState }),
|
|
47
|
+
onRequestClose: () => set({ modalState: initialModalState }),
|
|
48
|
+
},
|
|
49
|
+
props,
|
|
50
|
+
},
|
|
51
|
+
}),
|
|
52
|
+
dismissModal: () => set(() => ({ modalState: initialModalState })),
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
export const useModalStore = <T>(selector: (state: ModalStore) => T) =>
|
|
56
|
+
useStore(modalStore, selector);
|
|
57
|
+
|
|
58
|
+
export const openModal = modalStore.getInitialState().openModal;
|
|
59
|
+
|
|
60
|
+
export const dismissModal = modalStore.getInitialState().dismissModal;
|
|
61
|
+
|
|
62
|
+
export const openBottomSheetModal = ({
|
|
63
|
+
ModalBottomSheetContent,
|
|
64
|
+
modalBottomSheetContentProps,
|
|
65
|
+
props = {},
|
|
66
|
+
options = {},
|
|
67
|
+
}: OpenModalBottomSheetArgs) => {
|
|
68
|
+
openModal({
|
|
69
|
+
item: {
|
|
70
|
+
ModalBottomSheetContent,
|
|
71
|
+
modalBottomSheetContentProps,
|
|
72
|
+
},
|
|
73
|
+
props,
|
|
74
|
+
options: {
|
|
75
|
+
animationType: "none",
|
|
76
|
+
animated: false,
|
|
77
|
+
...options,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const useModalStoreState = (): ModalState =>
|
|
83
|
+
useModalStore<ModalState>((state) => state.modalState);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-utils",
|
|
3
|
-
"version": "15.1.13
|
|
3
|
+
"version": "15.1.13",
|
|
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.1.13
|
|
30
|
+
"@applicaster/applicaster-types": "15.1.13",
|
|
31
31
|
"buffer": "^5.2.1",
|
|
32
32
|
"camelize": "^1.0.0",
|
|
33
33
|
"dayjs": "^1.11.10",
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { getPlayerActionButtons } from "../getPlayerActionButtons";
|
|
2
|
+
import { selectActionButtons } from "../../conf/player/selectors";
|
|
3
|
+
|
|
4
|
+
jest.mock("../../conf/player/selectors", () => ({
|
|
5
|
+
selectActionButtons: jest.fn(),
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
describe("getPlayerActionButtons", () => {
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
jest.clearAllMocks();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("returns an empty array if selectActionButtons returns undefined", () => {
|
|
14
|
+
(selectActionButtons as jest.Mock).mockReturnValue(undefined);
|
|
15
|
+
const result = getPlayerActionButtons({});
|
|
16
|
+
expect(result).toEqual([]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("returns an empty array if selectActionButtons returns null", () => {
|
|
20
|
+
(selectActionButtons as jest.Mock).mockReturnValue(null);
|
|
21
|
+
const result = getPlayerActionButtons({});
|
|
22
|
+
expect(result).toEqual([]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("returns an empty array if selectActionButtons returns empty string", () => {
|
|
26
|
+
(selectActionButtons as jest.Mock).mockReturnValue("");
|
|
27
|
+
const result = getPlayerActionButtons({});
|
|
28
|
+
expect(result).toEqual([]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("returns the first two trimmed action buttons", () => {
|
|
32
|
+
(selectActionButtons as jest.Mock).mockReturnValue(" play , pause , stop ");
|
|
33
|
+
const result = getPlayerActionButtons({});
|
|
34
|
+
expect(result).toEqual(["play", "pause"]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("returns only one button if only one is present", () => {
|
|
38
|
+
(selectActionButtons as jest.Mock).mockReturnValue(" play ");
|
|
39
|
+
const result = getPlayerActionButtons({});
|
|
40
|
+
expect(result).toEqual(["play"]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("trims whitespace from button names", () => {
|
|
44
|
+
(selectActionButtons as jest.Mock).mockReturnValue(" play , pause ");
|
|
45
|
+
const result = getPlayerActionButtons({});
|
|
46
|
+
expect(result).toEqual(["play", "pause"]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("returns an empty array if selectActionButtons returns only commas", () => {
|
|
50
|
+
(selectActionButtons as jest.Mock).mockReturnValue(" , , ");
|
|
51
|
+
const result = getPlayerActionButtons({});
|
|
52
|
+
expect(result).toEqual(["", ""]);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { map, take, trim } from "../utils";
|
|
2
|
+
import { selectActionButtons } from "../conf/player/selectors";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns the first two action buttons from the configuration.
|
|
6
|
+
* @param {Object} configuration - The player configuration object.
|
|
7
|
+
* @returns {Array} An array containing the first two action buttons.
|
|
8
|
+
*/
|
|
9
|
+
export const getPlayerActionButtons = (configuration: any) => {
|
|
10
|
+
const buttonsString = selectActionButtons(configuration);
|
|
11
|
+
|
|
12
|
+
if (!buttonsString) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return take(2, map(buttonsString.split(","), trim));
|
|
17
|
+
};
|
package/playerUtils/index.ts
CHANGED
|
@@ -7,23 +7,7 @@ import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
|
7
7
|
import { getBoolFromConfigValue } from "../configurationUtils";
|
|
8
8
|
import { Dimensions } from "react-native";
|
|
9
9
|
|
|
10
|
-
export {
|
|
11
|
-
|
|
12
|
-
export { isEntryAction } from "../entryActions";
|
|
13
|
-
|
|
14
|
-
export {
|
|
15
|
-
parseActionIdentifiers,
|
|
16
|
-
resolveActionIdentifiers,
|
|
17
|
-
} from "../uiActionsRegistrator";
|
|
18
|
-
|
|
19
|
-
export type { ResolveActionIdentifiersParams } from "../uiActionsRegistrator";
|
|
20
|
-
|
|
21
|
-
export type {
|
|
22
|
-
ActionButtonDescriptor,
|
|
23
|
-
OverflowDescriptor,
|
|
24
|
-
PlayerControlDescriptor,
|
|
25
|
-
ResolvePlayerActionsParams,
|
|
26
|
-
} from "./resolvePlayerActions";
|
|
10
|
+
export { getPlayerActionButtons } from "./getPlayerActionButtons";
|
|
27
11
|
|
|
28
12
|
export {
|
|
29
13
|
resolvePlayerContentText,
|
|
@@ -6,7 +6,6 @@ import { ActionsContext } from "@applicaster/zapp-react-native-ui-components/Con
|
|
|
6
6
|
import {
|
|
7
7
|
observeEntryState,
|
|
8
8
|
RegisteredActionValue,
|
|
9
|
-
uiActionsRegistry,
|
|
10
9
|
} from "../../uiActionsRegistrator";
|
|
11
10
|
import { reactHooksLogger } from "../logger";
|
|
12
11
|
|
|
@@ -35,13 +34,7 @@ export function useActions<T = unknown>(plugId: string): any {
|
|
|
35
34
|
if (!logged) {
|
|
36
35
|
logger.warning({
|
|
37
36
|
message: `useActions: Couldn't find an action for ${plugId} plugin`,
|
|
38
|
-
|
|
39
|
-
// live React context, which is far too large for a log line and can
|
|
40
|
-
// carry whatever state those plugins hold.
|
|
41
|
-
data: {
|
|
42
|
-
plugId,
|
|
43
|
-
availableActions: Object.keys(context?.actions ?? {}),
|
|
44
|
-
},
|
|
37
|
+
data: { actions: context?.actions },
|
|
45
38
|
});
|
|
46
39
|
|
|
47
40
|
setLogged(true);
|
|
@@ -87,31 +80,7 @@ export function useEntryActionState(
|
|
|
87
80
|
useEffect(() => {
|
|
88
81
|
if (!action) return undefined;
|
|
89
82
|
|
|
90
|
-
|
|
91
|
-
// handler lets RxJS rethrow an error notification asynchronously, where no
|
|
92
|
-
// error boundary catches it - one plugin faulting would take the app down
|
|
93
|
-
// rather than this one button. The button keeps its last known state.
|
|
94
|
-
let subscription;
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
subscription = observeEntryState(action, entry).subscribe({
|
|
98
|
-
next: setState,
|
|
99
|
-
error: (error) =>
|
|
100
|
-
logger.warning({
|
|
101
|
-
message:
|
|
102
|
-
"useEntryActionState: the action's state stream failed - the button keeps its last state",
|
|
103
|
-
data: { entryId, error },
|
|
104
|
-
}),
|
|
105
|
-
});
|
|
106
|
-
} catch (error) {
|
|
107
|
-
logger.warning({
|
|
108
|
-
message:
|
|
109
|
-
"useEntryActionState: could not observe the action - the button keeps its initial state",
|
|
110
|
-
data: { entryId, error },
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
return undefined;
|
|
114
|
-
}
|
|
83
|
+
const subscription = observeEntryState(action, entry).subscribe(setState);
|
|
115
84
|
|
|
116
85
|
return () => subscription.unsubscribe();
|
|
117
86
|
// eslint-disable-next-line @wogns3623/better-exhaustive-deps/exhaustive-deps
|
|
@@ -119,18 +88,6 @@ export function useEntryActionState(
|
|
|
119
88
|
|
|
120
89
|
const invokeAction = useCallback(
|
|
121
90
|
(options: InvokeArgsOptions = {}) => {
|
|
122
|
-
if (typeof action?.invokeAction !== "function") {
|
|
123
|
-
// A button was rendered and pressed, and the press goes nowhere. Only
|
|
124
|
-
// reachable on a user interaction, so it costs nothing on render. The
|
|
125
|
-
// action value carries no identifier, so the label is what locates the
|
|
126
|
-
// button that misbehaved.
|
|
127
|
-
logger.warning({
|
|
128
|
-
message:
|
|
129
|
-
"useEntryActionState: the pressed action has no invokeAction - the press did nothing",
|
|
130
|
-
data: { entryId, label: state?.label },
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
91
|
action?.invokeAction?.(entry, { updateState: setState, ...options });
|
|
135
92
|
},
|
|
136
93
|
// eslint-disable-next-line @wogns3623/better-exhaustive-deps/exhaustive-deps
|
|
@@ -139,25 +96,3 @@ export function useEntryActionState(
|
|
|
139
96
|
|
|
140
97
|
return { state, invokeAction };
|
|
141
98
|
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* A value that changes whenever a provider is added to or removed from
|
|
145
|
-
* `uiActionsRegistry`.
|
|
146
|
-
*
|
|
147
|
-
* Providers can register after the UI that wants them has rendered - the audio
|
|
148
|
-
* player registers playback speed only once its controller has state, and
|
|
149
|
-
* plugin-backed actions are bridged in from an effect. Put this in the
|
|
150
|
-
* dependency list of anything that resolves actions, so the list is re-read
|
|
151
|
-
* instead of being frozen at first paint.
|
|
152
|
-
*/
|
|
153
|
-
export function useRegistryRevision(): number {
|
|
154
|
-
const [revision, setRevision] = useState(0);
|
|
155
|
-
|
|
156
|
-
useEffect(
|
|
157
|
-
() =>
|
|
158
|
-
uiActionsRegistry.subscribe(() => setRevision((current) => current + 1)),
|
|
159
|
-
[]
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
return revision;
|
|
163
|
-
}
|
|
@@ -7,6 +7,7 @@ import { waitFor } from "@testing-library/react-native";
|
|
|
7
7
|
|
|
8
8
|
const mockNavigator = {
|
|
9
9
|
getPathname: () => "pathnameMock",
|
|
10
|
+
currentRoute: "pathnameMock",
|
|
10
11
|
push: jest.fn(),
|
|
11
12
|
data: {},
|
|
12
13
|
};
|
|
@@ -31,6 +32,7 @@ jest.mock("@applicaster/zapp-react-native-utils/reactHooks/screen", () => ({
|
|
|
31
32
|
),
|
|
32
33
|
useTargetScreenData: jest.fn(() => ({})),
|
|
33
34
|
useCurrentScreenData: jest.fn(() => ({})),
|
|
35
|
+
useScreenContext: jest.fn(() => ({})),
|
|
34
36
|
}));
|
|
35
37
|
|
|
36
38
|
jest.mock("@applicaster/zapp-react-native-ui-components/Contexts", () => ({
|
|
@@ -68,6 +70,12 @@ const wrapper = ({ children }) => (
|
|
|
68
70
|
</CellTapContext.Provider>
|
|
69
71
|
);
|
|
70
72
|
|
|
73
|
+
const actionExecutorWrapper = ({ children }) => (
|
|
74
|
+
<ActionExecutorContext.Provider value={actionExecutor}>
|
|
75
|
+
{children}
|
|
76
|
+
</ActionExecutorContext.Provider>
|
|
77
|
+
);
|
|
78
|
+
|
|
71
79
|
describe("useCellClick", () => {
|
|
72
80
|
it("returns a function", () => {
|
|
73
81
|
const { result } = renderHook(() => useCellClick(mockProps));
|
|
@@ -97,4 +105,60 @@ describe("useCellClick", () => {
|
|
|
97
105
|
|
|
98
106
|
cleanup();
|
|
99
107
|
});
|
|
108
|
+
|
|
109
|
+
it("does not push when the cell finishes a hook", async () => {
|
|
110
|
+
mockNavigator.push.mockClear();
|
|
111
|
+
|
|
112
|
+
const item = {
|
|
113
|
+
id: "profile-1",
|
|
114
|
+
type: { value: "feed" },
|
|
115
|
+
extensions: {
|
|
116
|
+
tap_actions: {
|
|
117
|
+
actions: [
|
|
118
|
+
{ type: "sessionStorageSet" },
|
|
119
|
+
{ type: "finishHook", options: { success: true } },
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const { result } = renderHook(() => useCellClick({ item }), {
|
|
126
|
+
wrapper: actionExecutorWrapper,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
await result.current();
|
|
130
|
+
|
|
131
|
+
await waitFor(() => {
|
|
132
|
+
expect(actionExecutor.handleEntryActions).toHaveBeenCalled();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
expect(mockNavigator.push).not.toHaveBeenCalled();
|
|
136
|
+
cleanup();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("pushes after tap actions that do not finish a hook", async () => {
|
|
140
|
+
mockNavigator.push.mockClear();
|
|
141
|
+
|
|
142
|
+
const item = {
|
|
143
|
+
id: "episode-1",
|
|
144
|
+
type: { value: "feed" },
|
|
145
|
+
extensions: {
|
|
146
|
+
tap_actions: {
|
|
147
|
+
actions: [{ type: "sessionStorageSet" }],
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const { result } = renderHook(() => useCellClick({ item }), {
|
|
153
|
+
wrapper: actionExecutorWrapper,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
await result.current();
|
|
157
|
+
|
|
158
|
+
await waitFor(() => {
|
|
159
|
+
expect(mockNavigator.push).toHaveBeenCalled();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
cleanup();
|
|
163
|
+
});
|
|
100
164
|
});
|
|
@@ -82,6 +82,12 @@ export const useCellClick = ({
|
|
|
82
82
|
|
|
83
83
|
logOnPress(selectedItem, pathname, component, onCellTap);
|
|
84
84
|
|
|
85
|
+
const tapActions = selectedItem?.extensions?.tap_actions?.actions;
|
|
86
|
+
|
|
87
|
+
const finishesHook =
|
|
88
|
+
Array.isArray(tapActions) &&
|
|
89
|
+
tapActions.some((action) => action?.type === "finishHook");
|
|
90
|
+
|
|
85
91
|
if (selectedItem) {
|
|
86
92
|
await actionExecutor?.handleEntryActions(selectedItem, {
|
|
87
93
|
component,
|
|
@@ -99,20 +105,21 @@ export const useCellClick = ({
|
|
|
99
105
|
url: selectedItem.link.href,
|
|
100
106
|
});
|
|
101
107
|
}
|
|
102
|
-
} else {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
108
|
+
} else if (isFunction(onCellTap)) {
|
|
109
|
+
onCellTap?.(selectedItem, _index);
|
|
110
|
+
} else if (!finishesHook) {
|
|
111
|
+
// finishHook already completes the hook and navigates to the original
|
|
112
|
+
// target. Pushing the cell as well stacks a second copy of that screen
|
|
113
|
+
// (e.g. home/home after a profile selector hook).
|
|
114
|
+
if (currentRoute === pathname || pathname.includes("video-modal")) {
|
|
115
|
+
const targetScreen = component?.data?.target;
|
|
116
|
+
|
|
117
|
+
push(
|
|
118
|
+
R.when(
|
|
119
|
+
() => targetScreen,
|
|
120
|
+
R.mergeLeft({ screen_type: targetScreen })
|
|
121
|
+
)(selectedItem)
|
|
122
|
+
);
|
|
116
123
|
}
|
|
117
124
|
}
|
|
118
125
|
},
|