@applicaster/zapp-react-native-utils 16.0.0-alpha.1052711356 → 16.0.0-alpha.1054344748
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 +50 -3
- package/actionsExecutor/ActionExecutorContext.tsx +97 -19
- package/actionsExecutor/__tests__/ActionExecutor.test.ts +71 -0
- package/actionsExecutor/__tests__/feedDecorator.test.ts +61 -0
- package/actionsExecutor/actions/__tests__/dismissBottomSheet.test.ts +3 -0
- package/actionsExecutor/actions/__tests__/goBack.test.ts +62 -0
- package/actionsExecutor/actions/__tests__/goHome.test.ts +19 -0
- package/actionsExecutor/actions/__tests__/navigateToScreen.test.ts +133 -0
- package/actionsExecutor/actions/__tests__/openBottomSheet.inlineItems.test.ts +300 -0
- package/actionsExecutor/actions/__tests__/presentScreen.test.ts +127 -0
- package/actionsExecutor/actions/__tests__/showAlert.test.ts +49 -0
- package/actionsExecutor/actions/__tests__/showToast.test.ts +29 -4
- package/actionsExecutor/actions/dismissBottomSheet.ts +2 -1
- package/actionsExecutor/actions/goBack.ts +60 -0
- package/actionsExecutor/actions/goHome.ts +31 -0
- package/actionsExecutor/actions/index.ts +8 -0
- package/actionsExecutor/actions/navigateToScreen.ts +18 -4
- package/actionsExecutor/actions/openBottomSheet.ts +51 -31
- package/actionsExecutor/actions/presentScreen.ts +78 -0
- package/actionsExecutor/actions/sendCloudEvent.ts +5 -8
- package/actionsExecutor/actions/showAlert.ts +51 -0
- package/actionsExecutor/actions/showToast.ts +62 -6
- package/actionsExecutor/consts.ts +27 -0
- package/actionsExecutor/feedDecorator.ts +6 -6
- package/analyticsUtils/PlayerAnalyticsManager.ts +1 -1
- package/appUtils/HooksManager/index.ts +9 -0
- package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +142 -59
- package/appUtils/playerManager/OverlayObserver/__tests__/getUpcomingQueue.test.ts +67 -0
- package/appUtils/playerManager/OverlayObserver/__tests__/liveContent.test.ts +205 -0
- package/appUtils/playerManager/OverlayObserver/__tests__/utils.test.ts +56 -0
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.android.tv.ts +4 -0
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.ios.tv.ts +4 -0
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.ts +24 -0
- package/appUtils/playerManager/OverlayObserver/getUpcomingQueue.web.ts +4 -0
- package/appUtils/playerManager/OverlayObserver/utils.ts +51 -20
- package/appUtils/playerManager/__tests__/playerContent.test.ts +403 -0
- package/appUtils/playerManager/__tests__/playerFactory.test.ts +1 -1
- package/appUtils/playerManager/__tests__/playerNative.test.ts +114 -0
- package/appUtils/playerManager/__tests__/playerNativeCommand.test.ts +62 -0
- package/appUtils/playerManager/__tests__/playerToolbox.test.ts +97 -0
- package/appUtils/playerManager/__tests__/usePlayerContent.test.tsx +64 -0
- package/appUtils/playerManager/{conts.ts → const.ts} +33 -0
- package/appUtils/playerManager/index.ts +3 -1
- package/appUtils/playerManager/player.ts +125 -3
- package/appUtils/playerManager/playerFactory.ts +1 -1
- package/appUtils/playerManager/playerNative.ts +28 -4
- package/appUtils/playerManager/playerNativeCommand.ts +3 -6
- package/appUtils/playerManager/playerToolbox.ts +192 -0
- package/appUtils/playerManager/usePlayerContent.tsx +43 -0
- package/appUtils/playerManager/usePlayerControllerSetup.tsx +1 -1
- package/appUtils/playerManager/userLanguagePreference.ts +1 -1
- package/arrayUtils/__tests__/reorderByIds.test.ts +63 -0
- package/arrayUtils/index.ts +62 -0
- package/cellUtils/index.ts +30 -3
- package/configurationUtils/__tests__/manifestKeyParser.test.ts +21 -0
- package/configurationUtils/__tests__/modalBlocksParser.test.ts +269 -0
- package/configurationUtils/manifestKeyParser.ts +50 -10
- package/configurationUtils/modalBlocksParser.ts +214 -0
- package/entryActions/__tests__/buildEntryActions.test.ts +235 -0
- package/entryActions/aliasPresentation.ts +108 -0
- package/entryActions/buildEntryActions.ts +167 -0
- package/entryActions/index.ts +15 -0
- package/entryActions/types.ts +39 -0
- package/manifestUtils/__tests__/player.audioControls.test.js +158 -0
- package/manifestUtils/_internals/index.js +8 -3
- package/manifestUtils/defaultManifestConfigurations/generalContent.js +6 -0
- package/manifestUtils/defaultManifestConfigurations/player.js +330 -1
- package/manifestUtils/index.js +3 -0
- package/manifestUtils/modalBlocks.js +304 -0
- package/modalState/ContentViewModel.ts +2 -0
- package/modalState/ModalOrchestrator.ts +23 -9
- package/modalState/RemoteEditableCollection.ts +77 -31
- package/modalState/__tests__/ContentViewModel.test.ts +3 -3
- package/modalState/__tests__/ModalOrchestrator.phase3.test.ts +35 -0
- package/modalState/__tests__/RemoteEditableCollection.test.ts +157 -1
- package/modalState/__tests__/index.test.ts +30 -1
- package/modalState/__tests__/useBottomSheetContent.test.ts +0 -23
- package/modalState/__tests__/useModalStoreState.test.ts +142 -0
- package/modalState/index.ts +6 -1
- package/modalState/store.ts +9 -2
- package/modalState/types.ts +56 -4
- package/modalState/useBottomSheetContent.ts +37 -26
- package/package.json +2 -2
- package/playerUtils/__tests__/contentDataKeys.test.ts +297 -0
- package/playerUtils/__tests__/resolvePlayerActions.test.ts +138 -0
- package/playerUtils/__tests__/resolvePlayerTitleSubtitle.test.ts +180 -0
- package/playerUtils/contentDataKeys.ts +134 -0
- package/playerUtils/index.ts +37 -16
- package/playerUtils/isAudioItem.ts +26 -0
- package/playerUtils/resolvePlayerActions.ts +71 -0
- package/playerUtils/resolvePlayerTitleSubtitle.ts +76 -0
- package/pluginUtils/__tests__/pluginUtils.test.js +32 -0
- package/pluginUtils/index.ts +29 -0
- package/reactHooks/actions/index.ts +67 -2
- package/reactHooks/cell-click/__tests__/index.test.js +64 -0
- package/reactHooks/cell-click/index.ts +21 -14
- package/reactHooks/connection/index.tsx +8 -5
- package/reactHooks/feed/__tests__/useMarkPipesDataStale.test.tsx +243 -0
- package/reactHooks/feed/useMarkPipesDataStale.ts +68 -23
- package/reactHooks/index.ts +2 -0
- package/reactHooks/layout/index.ts +1 -1
- package/reactHooks/navigation/__tests__/index.test.tsx +176 -1
- package/reactHooks/navigation/__tests__/usePresentScreen.test.ts +154 -0
- package/reactHooks/navigation/index.ts +2 -0
- package/reactHooks/navigation/usePresentScreen.ts +157 -0
- package/reactHooks/navigation/useRoute.ts +22 -5
- package/reactHooks/usePluginConfiguration.ts +4 -1
- package/reactHooks/utils/__tests__/index.test.js +22 -2
- package/reactHooks/utils/index.ts +11 -1
- package/uiActionsRegistrator/__tests__/resolveActionIdentifiers.test.ts +93 -0
- package/uiActionsRegistrator/__tests__/subscribe.test.ts +133 -0
- package/uiActionsRegistrator/__tests__/usableActionItems.test.ts +52 -0
- package/uiActionsRegistrator/index.ts +25 -203
- package/uiActionsRegistrator/registry.ts +351 -0
- package/uiActionsRegistrator/resolveActionIdentifiers.ts +100 -0
- package/uiActionsRegistrator/usableActionItems.ts +66 -0
- package/zappFrameworkUtils/localStorageHelper.ts +10 -2
- package/appUtils/playerManager/usePlayerTitleSummaryOverlay.tsx +0 -56
- package/playerUtils/__tests__/getPlayerActionButtons.test.ts +0 -54
- package/playerUtils/getPlayerActionButtons.ts +0 -17
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
function getModalBlockStyles({ prefix }) {
|
|
2
|
+
return [
|
|
3
|
+
{
|
|
4
|
+
group: true,
|
|
5
|
+
label: "Header Styles",
|
|
6
|
+
folded: true,
|
|
7
|
+
fields: [
|
|
8
|
+
{
|
|
9
|
+
type: "color_picker_rgba",
|
|
10
|
+
key: `${prefix}_style_header_container_default_backgroundColor`,
|
|
11
|
+
label: "Header Background Color",
|
|
12
|
+
initial_value: "rgba(30,30,30,1)",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
type: "color_picker_rgba",
|
|
16
|
+
key: `${prefix}_style_header_container_default_borderBottomColor`,
|
|
17
|
+
label: "Header Border Bottom Color",
|
|
18
|
+
initial_value: "transparent",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
type: "number_input",
|
|
22
|
+
key: `${prefix}_style_header_container_default_borderBottomSize`,
|
|
23
|
+
label: "Header Border Bottom Size",
|
|
24
|
+
initial_value: 0,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
type: "color_picker_rgba",
|
|
28
|
+
key: `${prefix}_style_header_title_default_fontColor`,
|
|
29
|
+
label: "Header Title Color",
|
|
30
|
+
initial_value: "#EFEFEF",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
type: "number_input",
|
|
34
|
+
key: `${prefix}_style_header_title_default_fontSize`,
|
|
35
|
+
label: "Header Title Font Size",
|
|
36
|
+
initial_value: 18,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: "number_input",
|
|
40
|
+
key: `${prefix}_style_header_title_default_lineHeight`,
|
|
41
|
+
label: "Header Title Line Height",
|
|
42
|
+
initial_value: 32,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: "color_picker_rgba",
|
|
46
|
+
key: `${prefix}_style_header_close_button_default_backgroundColor`,
|
|
47
|
+
label: "Close Button Background Color",
|
|
48
|
+
initial_value: "transparent",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
type: "color_picker_rgba",
|
|
52
|
+
key: `${prefix}_style_header_close_button_hover_backgroundColor`,
|
|
53
|
+
label: "Close Button Hover Background Color",
|
|
54
|
+
initial_value: "rgba(46,46,46,1)",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: "uploader",
|
|
58
|
+
key: `${prefix}_style_header_close_button_asset`,
|
|
59
|
+
label: "Close Button Icon Asset",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
type: "color_picker_rgba",
|
|
63
|
+
key: `${prefix}_style_header_action_buttons_default_backgroundColor`,
|
|
64
|
+
label: "Header Action Button Background Color",
|
|
65
|
+
initial_value: "rgba(46,46,46,1)",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
type: "color_picker_rgba",
|
|
69
|
+
key: `${prefix}_style_header_action_buttons_hover_backgroundColor`,
|
|
70
|
+
label: "Header Action Button Hover Color",
|
|
71
|
+
initial_value: "rgba(62,62,62,1)",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
type: "color_picker_rgba",
|
|
75
|
+
key: `${prefix}_style_header_action_buttons_default_fontColor`,
|
|
76
|
+
label: "Header Action Button Title Color",
|
|
77
|
+
initial_value: "#EFEFEF",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
type: "number_input",
|
|
81
|
+
key: `${prefix}_style_header_action_buttons_default_fontSize`,
|
|
82
|
+
label: "Header Action Button Font Size",
|
|
83
|
+
initial_value: 14,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
type: "number_input",
|
|
87
|
+
key: `${prefix}_style_header_action_buttons_default_borderRadius`,
|
|
88
|
+
label: "Header Action Button Radius",
|
|
89
|
+
initial_value: 16,
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
group: true,
|
|
95
|
+
label: "Item Styles",
|
|
96
|
+
folded: true,
|
|
97
|
+
fields: [
|
|
98
|
+
{
|
|
99
|
+
type: "color_picker_rgba",
|
|
100
|
+
key: `${prefix}_style_item_background_default_backgroundColor`,
|
|
101
|
+
label: "Item Background Color",
|
|
102
|
+
initial_value: "transparent",
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
type: "color_picker_rgba",
|
|
106
|
+
key: `${prefix}_style_item_background_focus_backgroundColor`,
|
|
107
|
+
label: "Item Focus/Touch Color",
|
|
108
|
+
initial_value: "rgba(62,62,62,1)",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
type: "number_input",
|
|
112
|
+
key: `${prefix}_style_item_background_default_borderRadius`,
|
|
113
|
+
label: "Item Corner Radius",
|
|
114
|
+
initial_value: 12,
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
type: "number_input",
|
|
118
|
+
key: `${prefix}_style_item_background_default_paddingTop`,
|
|
119
|
+
label: "Item Padding Top",
|
|
120
|
+
initial_value: 8,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
type: "number_input",
|
|
124
|
+
key: `${prefix}_style_item_background_default_paddingBottom`,
|
|
125
|
+
label: "Item Padding Bottom",
|
|
126
|
+
initial_value: 8,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
type: "number_input",
|
|
130
|
+
key: `${prefix}_style_item_background_default_paddingLeft`,
|
|
131
|
+
label: "Item Padding Left",
|
|
132
|
+
initial_value: 10,
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
type: "number_input",
|
|
136
|
+
key: `${prefix}_style_item_background_default_paddingRight`,
|
|
137
|
+
label: "Item Padding Right",
|
|
138
|
+
initial_value: 10,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
type: "number_input",
|
|
142
|
+
key: `${prefix}_style_item_image_default_width`,
|
|
143
|
+
label: "Item Image Width",
|
|
144
|
+
initial_value: 48,
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
type: "number_input",
|
|
148
|
+
key: `${prefix}_style_item_image_default_height`,
|
|
149
|
+
label: "Item Image Height",
|
|
150
|
+
initial_value: 48,
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
type: "number_input",
|
|
154
|
+
key: `${prefix}_style_item_image_default_borderRadius`,
|
|
155
|
+
label: "Item Image Radius",
|
|
156
|
+
initial_value: 6,
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
type: "color_picker_rgba",
|
|
160
|
+
key: `${prefix}_style_item_text_label_1_default_fontColor`,
|
|
161
|
+
label: "Item Title Color",
|
|
162
|
+
initial_value: "#FFFFFF",
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
type: "number_input",
|
|
166
|
+
key: `${prefix}_style_item_text_label_1_default_fontSize`,
|
|
167
|
+
label: "Item Title Font Size",
|
|
168
|
+
initial_value: 15,
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
type: "color_picker_rgba",
|
|
172
|
+
key: `${prefix}_style_item_text_label_2_default_fontColor`,
|
|
173
|
+
label: "Item Subtitle Color",
|
|
174
|
+
initial_value: "#999999",
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
type: "number_input",
|
|
178
|
+
key: `${prefix}_style_item_text_label_2_default_fontSize`,
|
|
179
|
+
label: "Item Subtitle Font Size",
|
|
180
|
+
initial_value: 13,
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
group: true,
|
|
186
|
+
label: "Action Styles",
|
|
187
|
+
folded: true,
|
|
188
|
+
fields: [
|
|
189
|
+
{
|
|
190
|
+
type: "color_picker_rgba",
|
|
191
|
+
key: `${prefix}_style_action_background_default_backgroundColor`,
|
|
192
|
+
label: "Action Background Color",
|
|
193
|
+
initial_value: "transparent",
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
type: "color_picker_rgba",
|
|
197
|
+
key: `${prefix}_style_action_background_hover_backgroundColor`,
|
|
198
|
+
label: "Action Hover/Touch Color",
|
|
199
|
+
initial_value: "rgba(46,46,46,1)",
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
type: "number_input",
|
|
203
|
+
key: `${prefix}_style_action_background_default_borderRadius`,
|
|
204
|
+
label: "Action Corner Radius",
|
|
205
|
+
initial_value: 12,
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
type: "number_input",
|
|
209
|
+
key: `${prefix}_style_action_background_default_paddingTop`,
|
|
210
|
+
label: "Action Padding Top",
|
|
211
|
+
initial_value: 14,
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
type: "number_input",
|
|
215
|
+
key: `${prefix}_style_action_background_default_paddingBottom`,
|
|
216
|
+
label: "Action Padding Bottom",
|
|
217
|
+
initial_value: 14,
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
type: "color_picker_rgba",
|
|
221
|
+
key: `${prefix}_style_action_title_default_fontColor`,
|
|
222
|
+
label: "Action Title Color",
|
|
223
|
+
initial_value: "#EFEFEF",
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
type: "number_input",
|
|
227
|
+
key: `${prefix}_style_action_title_default_fontSize`,
|
|
228
|
+
label: "Action Title Font Size",
|
|
229
|
+
initial_value: 14,
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
type: "uploader",
|
|
233
|
+
key: `${prefix}_style_action_trailing_icon_chevron_asset`,
|
|
234
|
+
label: "Action Chevron Asset",
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
group: true,
|
|
240
|
+
label: "Button Styles",
|
|
241
|
+
folded: true,
|
|
242
|
+
fields: [
|
|
243
|
+
{
|
|
244
|
+
type: "color_picker_rgba",
|
|
245
|
+
key: `${prefix}_style_button_background_default_backgroundColor`,
|
|
246
|
+
label: "Button Background Color",
|
|
247
|
+
initial_value: "#FFFFFF",
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
type: "color_picker_rgba",
|
|
251
|
+
key: `${prefix}_style_button_background_focus_backgroundColor`,
|
|
252
|
+
label: "Button Focus Color",
|
|
253
|
+
initial_value: "#E0E0E0",
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
type: "color_picker_rgba",
|
|
257
|
+
key: `${prefix}_style_button_background_inactive_backgroundColor`,
|
|
258
|
+
label: "Button Inactive Color",
|
|
259
|
+
initial_value: "#999999",
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
type: "number_input",
|
|
263
|
+
key: `${prefix}_style_button_background_default_borderRadius`,
|
|
264
|
+
label: "Button Corner Radius",
|
|
265
|
+
initial_value: 12,
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
type: "color_picker_rgba",
|
|
269
|
+
key: `${prefix}_style_button_title_default_fontColor`,
|
|
270
|
+
label: "Button Title Color",
|
|
271
|
+
initial_value: "#000000",
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
type: "number_input",
|
|
275
|
+
key: `${prefix}_style_button_title_default_fontSize`,
|
|
276
|
+
label: "Button Title Font Size",
|
|
277
|
+
initial_value: 15,
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
},
|
|
281
|
+
];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function getModalLocalizations({ prefix }) {
|
|
285
|
+
return [
|
|
286
|
+
{
|
|
287
|
+
type: "text_input",
|
|
288
|
+
label: "Queue Header Title",
|
|
289
|
+
key: `${prefix}_header_title`,
|
|
290
|
+
initial_value: "Queue",
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
type: "text_input",
|
|
294
|
+
label: "Go To Queue Action Label",
|
|
295
|
+
key: `${prefix}_label_go_to_queue`,
|
|
296
|
+
initial_value: "Go To Queue",
|
|
297
|
+
},
|
|
298
|
+
];
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
module.exports = {
|
|
302
|
+
getModalBlockStyles,
|
|
303
|
+
getModalLocalizations,
|
|
304
|
+
};
|
|
@@ -16,6 +16,7 @@ export interface ContentState {
|
|
|
16
16
|
role?: string;
|
|
17
17
|
behavior?: SelectionBehavior;
|
|
18
18
|
dynamicCollection?: DynamicCollectionOptions;
|
|
19
|
+
themePluginId?: string;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export class ContentViewModel {
|
|
@@ -34,6 +35,7 @@ export class ContentViewModel {
|
|
|
34
35
|
role: content.role,
|
|
35
36
|
behavior: content.behavior,
|
|
36
37
|
dynamicCollection: content.dynamicCollection,
|
|
38
|
+
themePluginId: content.themePluginId,
|
|
37
39
|
});
|
|
38
40
|
|
|
39
41
|
this.initEditableCollection(content);
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { dismissModal, modalStore, openBottomSheetModal } from "./store";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
getMenuItemActions,
|
|
4
|
+
isPresentSubMenuAction,
|
|
5
|
+
Menu,
|
|
6
|
+
MenuItem,
|
|
7
|
+
PresentSubMenuAction,
|
|
8
|
+
} from "./types";
|
|
3
9
|
import { ContentViewModel } from "./ContentViewModel";
|
|
4
10
|
|
|
5
11
|
export type { Menu, MenuItem };
|
|
6
12
|
|
|
7
|
-
export { isPresentSubMenuAction };
|
|
13
|
+
export { isPresentSubMenuAction, getMenuItemActions };
|
|
8
14
|
|
|
9
15
|
/**
|
|
10
16
|
* Converts a Menu model into the props expected by BottomSheetModalContent.
|
|
@@ -37,6 +43,8 @@ function menuToModalArgs(
|
|
|
37
43
|
itemsUrl: menu.content.itemsUrl,
|
|
38
44
|
title: menu.header?.title ?? menu.content.title,
|
|
39
45
|
summary: menu.header?.subtitle,
|
|
46
|
+
styleOverrides: menu.content.styleOverrides,
|
|
47
|
+
emptyMessage: menu.content.emptyMessage,
|
|
40
48
|
onPress: onItemPress,
|
|
41
49
|
viewModel,
|
|
42
50
|
},
|
|
@@ -135,15 +143,21 @@ class ModalOrchestrator {
|
|
|
135
143
|
private _buildOnPress(
|
|
136
144
|
onItemPress?: (item: MenuItem) => void | Promise<void>
|
|
137
145
|
): (item: MenuItem) => void | Promise<void> {
|
|
138
|
-
return (item: MenuItem) => {
|
|
139
|
-
if (
|
|
140
|
-
|
|
141
|
-
this.open(item.action.menu, onItemPress);
|
|
142
|
-
|
|
143
|
-
return;
|
|
146
|
+
return async (item: MenuItem) => {
|
|
147
|
+
if (onItemPress) {
|
|
148
|
+
await onItemPress(item);
|
|
144
149
|
}
|
|
145
150
|
|
|
146
|
-
|
|
151
|
+
const actions = getMenuItemActions(item);
|
|
152
|
+
|
|
153
|
+
const subMenuAction = actions.find(isPresentSubMenuAction) as
|
|
154
|
+
| PresentSubMenuAction
|
|
155
|
+
| undefined;
|
|
156
|
+
|
|
157
|
+
if (subMenuAction) {
|
|
158
|
+
// Navigate deeper into the sub-menu
|
|
159
|
+
this.open(subMenuAction.menu, onItemPress);
|
|
160
|
+
}
|
|
147
161
|
};
|
|
148
162
|
}
|
|
149
163
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { BehaviorSubject, Observable } from "rxjs";
|
|
2
2
|
import { Content, MenuItem, Operation } from "./types";
|
|
3
3
|
import { EditableCollection } from "./EditableCollection";
|
|
4
|
+
import { reorderByIds } from "../arrayUtils";
|
|
4
5
|
import { actionExecutor } from "../actionsExecutor/ActionExecutor";
|
|
5
6
|
import { createLogger } from "../logger";
|
|
6
7
|
|
|
7
|
-
const { log_warning, log_error } = createLogger({
|
|
8
|
+
const { log_debug, log_warning, log_error } = createLogger({
|
|
8
9
|
subsystem: "modalState",
|
|
9
10
|
category: "RemoteEditableCollection",
|
|
10
11
|
});
|
|
@@ -15,6 +16,7 @@ export class RemoteEditableCollection implements EditableCollection {
|
|
|
15
16
|
private itemsSubject: BehaviorSubject<MenuItem[]>;
|
|
16
17
|
private refetch: () => Promise<void>;
|
|
17
18
|
private content: Content;
|
|
19
|
+
private pendingMutationsCount = 0;
|
|
18
20
|
|
|
19
21
|
constructor(content: Content, refetch: () => Promise<void>) {
|
|
20
22
|
this.content = content;
|
|
@@ -29,6 +31,14 @@ export class RemoteEditableCollection implements EditableCollection {
|
|
|
29
31
|
* a successful mutation reloads the feed). Overrides optimistic state.
|
|
30
32
|
*/
|
|
31
33
|
sync(items: MenuItem[]): void {
|
|
34
|
+
if (this.pendingMutationsCount > 0) {
|
|
35
|
+
log_debug(
|
|
36
|
+
`sync: Suppressing server sync because ${this.pendingMutationsCount} local mutation(s) are in-flight`
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
32
42
|
this.itemsSubject.next(items);
|
|
33
43
|
}
|
|
34
44
|
|
|
@@ -39,16 +49,26 @@ export class RemoteEditableCollection implements EditableCollection {
|
|
|
39
49
|
|
|
40
50
|
if (!item) return;
|
|
41
51
|
|
|
52
|
+
this.pendingMutationsCount++;
|
|
53
|
+
|
|
42
54
|
// Optimistic update
|
|
43
55
|
const newItems = items.filter((_, i) => i !== index);
|
|
44
56
|
this.itemsSubject.next(newItems);
|
|
45
57
|
|
|
58
|
+
let actionFailed = false;
|
|
59
|
+
|
|
46
60
|
try {
|
|
47
61
|
await this.executeActionAlias(item, "remove_item");
|
|
48
|
-
await this.refetch();
|
|
49
62
|
} catch (e) {
|
|
63
|
+
actionFailed = true;
|
|
50
64
|
log_error("Remove failed, rolling back", { error: e });
|
|
51
65
|
this.itemsSubject.next(snapshot);
|
|
66
|
+
} finally {
|
|
67
|
+
this.pendingMutationsCount = Math.max(0, this.pendingMutationsCount - 1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!actionFailed) {
|
|
71
|
+
await this.refetch();
|
|
52
72
|
}
|
|
53
73
|
}
|
|
54
74
|
|
|
@@ -57,32 +77,31 @@ export class RemoteEditableCollection implements EditableCollection {
|
|
|
57
77
|
const snapshot = [...items];
|
|
58
78
|
|
|
59
79
|
if (itemIds && itemIds.length > 0) {
|
|
60
|
-
|
|
61
|
-
const newItems
|
|
62
|
-
|
|
63
|
-
itemIds.forEach((id) => {
|
|
64
|
-
const found = itemMap.get(id);
|
|
65
|
-
if (found) newItems.push(found);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
items.forEach((item) => {
|
|
69
|
-
if (!newItems.includes(item)) newItems.push(item);
|
|
70
|
-
});
|
|
71
|
-
|
|
80
|
+
this.pendingMutationsCount++;
|
|
81
|
+
const newItems = reorderByIds(items, itemIds);
|
|
72
82
|
this.itemsSubject.next(newItems);
|
|
73
83
|
|
|
84
|
+
let actionFailed = false;
|
|
85
|
+
|
|
74
86
|
try {
|
|
75
87
|
const targetItem = newItems[0] || items[0];
|
|
76
88
|
|
|
77
89
|
await this.executeActionAlias(targetItem, "reorder_item", {
|
|
78
90
|
itemIds,
|
|
79
91
|
});
|
|
80
|
-
|
|
81
|
-
await this.refetch();
|
|
82
92
|
} catch (e) {
|
|
83
|
-
|
|
93
|
+
actionFailed = true;
|
|
84
94
|
log_error("Move failed, rolling back", { error: e });
|
|
85
95
|
this.itemsSubject.next(snapshot);
|
|
96
|
+
} finally {
|
|
97
|
+
this.pendingMutationsCount = Math.max(
|
|
98
|
+
0,
|
|
99
|
+
this.pendingMutationsCount - 1
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!actionFailed) {
|
|
104
|
+
await this.refetch();
|
|
86
105
|
}
|
|
87
106
|
}
|
|
88
107
|
}
|
|
@@ -99,15 +118,30 @@ export class RemoteEditableCollection implements EditableCollection {
|
|
|
99
118
|
return;
|
|
100
119
|
}
|
|
101
120
|
|
|
121
|
+
this.pendingMutationsCount++;
|
|
122
|
+
let actionFailed = false;
|
|
123
|
+
|
|
102
124
|
try {
|
|
103
125
|
for (const action of addEvents) {
|
|
104
|
-
|
|
126
|
+
if (this.content.context) {
|
|
127
|
+
await actionExecutor.handleAction(
|
|
128
|
+
action as any,
|
|
129
|
+
this.content.context
|
|
130
|
+
);
|
|
131
|
+
} else {
|
|
132
|
+
await actionExecutor.handleAction(action as any);
|
|
133
|
+
}
|
|
105
134
|
}
|
|
106
|
-
|
|
107
|
-
await this.refetch();
|
|
108
135
|
} catch (e) {
|
|
136
|
+
actionFailed = true;
|
|
109
137
|
log_error("Add/create collection failed", { error: e });
|
|
110
138
|
this.itemsSubject.next(snapshot);
|
|
139
|
+
} finally {
|
|
140
|
+
this.pendingMutationsCount = Math.max(0, this.pendingMutationsCount - 1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!actionFailed) {
|
|
144
|
+
await this.refetch();
|
|
111
145
|
}
|
|
112
146
|
}
|
|
113
147
|
|
|
@@ -115,32 +149,46 @@ export class RemoteEditableCollection implements EditableCollection {
|
|
|
115
149
|
const items = this.itemsSubject.value;
|
|
116
150
|
const snapshot = [...items];
|
|
117
151
|
|
|
152
|
+
this.pendingMutationsCount++;
|
|
118
153
|
this.itemsSubject.next([]);
|
|
119
154
|
|
|
155
|
+
let actionFailed = false;
|
|
156
|
+
|
|
120
157
|
try {
|
|
121
158
|
const firstItem = items[0];
|
|
122
159
|
|
|
123
160
|
if (firstItem) {
|
|
124
|
-
await this.executeActionAlias(firstItem,
|
|
161
|
+
await this.executeActionAlias(firstItem, [
|
|
162
|
+
"delete_collection",
|
|
163
|
+
"clear_collection",
|
|
164
|
+
]);
|
|
125
165
|
}
|
|
126
|
-
|
|
127
|
-
await this.refetch();
|
|
128
166
|
} catch (e) {
|
|
167
|
+
actionFailed = true;
|
|
129
168
|
log_error("deleteCollection failed, rolling back", { error: e });
|
|
130
169
|
this.itemsSubject.next(snapshot);
|
|
170
|
+
} finally {
|
|
171
|
+
this.pendingMutationsCount = Math.max(0, this.pendingMutationsCount - 1);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!actionFailed) {
|
|
175
|
+
await this.refetch();
|
|
131
176
|
}
|
|
132
177
|
}
|
|
133
178
|
|
|
134
179
|
private async executeActionAlias(
|
|
135
180
|
item: MenuItem,
|
|
136
|
-
alias: string,
|
|
181
|
+
alias: string | string[],
|
|
137
182
|
data?: Record<string, unknown>
|
|
138
183
|
): Promise<void> {
|
|
139
184
|
const entryActions = item.entryActions || [];
|
|
185
|
+
const aliases = Array.isArray(alias) ? alias : [alias];
|
|
140
186
|
|
|
141
|
-
const actionGroup = entryActions.find(
|
|
187
|
+
const actionGroup = entryActions.find(
|
|
188
|
+
(ea) => ea.button?.alias && aliases.includes(ea.button.alias)
|
|
189
|
+
);
|
|
142
190
|
|
|
143
|
-
|
|
191
|
+
log_debug("executeActionAlias:", {
|
|
144
192
|
alias,
|
|
145
193
|
itemId: item.id,
|
|
146
194
|
entryActionsCount: entryActions.length,
|
|
@@ -150,7 +198,7 @@ export class RemoteEditableCollection implements EditableCollection {
|
|
|
150
198
|
});
|
|
151
199
|
|
|
152
200
|
if (!actionGroup || !actionGroup.actions) {
|
|
153
|
-
log_warning(`No entry action found for alias "${
|
|
201
|
+
log_warning(`No entry action found for alias "${aliases.join(", ")}"`, {
|
|
154
202
|
itemId: item.id,
|
|
155
203
|
});
|
|
156
204
|
|
|
@@ -173,14 +221,12 @@ export class RemoteEditableCollection implements EditableCollection {
|
|
|
173
221
|
}
|
|
174
222
|
: action;
|
|
175
223
|
|
|
176
|
-
|
|
177
|
-
"[RemoteEditableCollection] handling action:",
|
|
178
|
-
actionToExecute
|
|
179
|
-
);
|
|
224
|
+
log_debug("handling action:", actionToExecute);
|
|
180
225
|
|
|
181
226
|
await actionExecutor.handleAction(
|
|
182
227
|
actionToExecute as any,
|
|
183
228
|
{
|
|
229
|
+
...this.content.context,
|
|
184
230
|
entry: item as any,
|
|
185
231
|
} as any
|
|
186
232
|
);
|
|
@@ -18,7 +18,7 @@ describe("ContentViewModel", () => {
|
|
|
18
18
|
|
|
19
19
|
const viewModel = new ContentViewModel(content);
|
|
20
20
|
|
|
21
|
-
expect(viewModel.getState()).
|
|
21
|
+
expect(viewModel.getState()).toMatchObject({
|
|
22
22
|
items: content.items,
|
|
23
23
|
isLoading: false,
|
|
24
24
|
role: undefined,
|
|
@@ -71,7 +71,7 @@ describe("ContentViewModel", () => {
|
|
|
71
71
|
expect.stringMatching(/^http:\/\/example\.com\/items\?_t=\d+$/)
|
|
72
72
|
);
|
|
73
73
|
|
|
74
|
-
expect(viewModel.getState()).
|
|
74
|
+
expect(viewModel.getState()).toMatchObject({
|
|
75
75
|
items: mockItems,
|
|
76
76
|
isLoading: false,
|
|
77
77
|
role: "collection_selector",
|
|
@@ -154,7 +154,7 @@ describe("ContentViewModel", () => {
|
|
|
154
154
|
|
|
155
155
|
await new Promise(process.nextTick);
|
|
156
156
|
|
|
157
|
-
expect(viewModel.getState()).
|
|
157
|
+
expect(viewModel.getState()).toMatchObject({
|
|
158
158
|
items: [],
|
|
159
159
|
isLoading: false,
|
|
160
160
|
error: "Network Error",
|
|
@@ -25,4 +25,39 @@ describe("ModalOrchestrator phase 3", () => {
|
|
|
25
25
|
expect(props.viewModel.getState().role).toBe("collection_selector");
|
|
26
26
|
expect(props.title).toBe("Playlists");
|
|
27
27
|
});
|
|
28
|
+
|
|
29
|
+
it("forwards header title and subtitle as summary", () => {
|
|
30
|
+
modalOrchestrator.open({
|
|
31
|
+
header: {
|
|
32
|
+
title: "Sleep Timer",
|
|
33
|
+
subtitle: "remaining",
|
|
34
|
+
},
|
|
35
|
+
content: {
|
|
36
|
+
title: "Sleep Timer",
|
|
37
|
+
items: [{ id: "5", title: "5 minutes" }],
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const props = (modalStore.getState().modalState.screen as any)
|
|
42
|
+
?.modalBottomSheetContentProps;
|
|
43
|
+
|
|
44
|
+
expect(props.title).toBe("Sleep Timer");
|
|
45
|
+
expect(props.summary).toBe("remaining");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("forwards the queue empty message onto the sheet props", () => {
|
|
49
|
+
modalOrchestrator.open({
|
|
50
|
+
header: { title: "Queue" },
|
|
51
|
+
content: {
|
|
52
|
+
title: "Queue",
|
|
53
|
+
items: [],
|
|
54
|
+
emptyMessage: "Queued items will be added here",
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const props = (modalStore.getState().modalState.screen as any)
|
|
59
|
+
?.modalBottomSheetContentProps;
|
|
60
|
+
|
|
61
|
+
expect(props.emptyMessage).toBe("Queued items will be added here");
|
|
62
|
+
});
|
|
28
63
|
});
|