@nascentdigital/funnel-editor 4.6.1 → 5.0.1

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.
Files changed (209) hide show
  1. package/dist/EditorApp.js +1 -1
  2. package/dist/EditorRoutes.js +7 -5
  3. package/dist/changesets/screens/ChangeSetViewer.js +1 -1
  4. package/dist/changesets/screens/ChangeSetsExplorer.js +1 -2
  5. package/dist/collection/hooks/CollectionController.d.ts +5 -16
  6. package/dist/collection/hooks/CollectionController.js +18 -37
  7. package/dist/collection/hooks/ICollectionController.d.ts +24 -0
  8. package/dist/collection/hooks/ICollectionController.js +20 -0
  9. package/dist/collection/hooks/index.d.ts +1 -0
  10. package/dist/collection/hooks/index.js +1 -0
  11. package/dist/collection/hooks/useCollectionController.d.ts +3 -3
  12. package/dist/collection/screens/CollectionEditor.js +2 -2
  13. package/dist/components.d.ts +20 -0
  14. package/dist/components.js +41 -0
  15. package/dist/core/components/feedback/dialog/MinimalDialogLayout.js +1 -1
  16. package/dist/core/components/fields/BooleanField.d.ts +2 -1
  17. package/dist/core/components/fields/BooleanField.js +2 -2
  18. package/dist/core/components/fields/DateField.d.ts +2 -1
  19. package/dist/core/components/fields/DateField.js +2 -2
  20. package/dist/core/components/fields/DynamicPagesEditor.d.ts +2 -1
  21. package/dist/core/components/fields/DynamicPagesEditor.js +1 -1
  22. package/dist/core/components/fields/EnumField.d.ts +2 -1
  23. package/dist/core/components/fields/EnumField.js +2 -2
  24. package/dist/core/components/fields/FieldCard.d.ts +1 -0
  25. package/dist/core/components/fields/FieldCard.js +8 -1
  26. package/dist/core/components/fields/FieldEditor.d.ts +2 -1
  27. package/dist/core/components/fields/FieldEditor.js +11 -11
  28. package/dist/core/components/fields/FieldLayout.d.ts +2 -1
  29. package/dist/core/components/fields/FieldLayout.js +3 -3
  30. package/dist/core/components/fields/ListEditor.d.ts +2 -1
  31. package/dist/core/components/fields/ListEditor.js +5 -6
  32. package/dist/core/components/fields/OptInField.d.ts +2 -1
  33. package/dist/core/components/fields/OptInField.js +2 -2
  34. package/dist/core/components/fields/RichTextEditor.js +1 -1
  35. package/dist/core/components/fields/RichTextField.d.ts +2 -1
  36. package/dist/core/components/fields/RichTextField.js +3 -5
  37. package/dist/core/components/fields/TextField.d.ts +2 -0
  38. package/dist/core/components/fields/TextField.js +2 -2
  39. package/dist/core/components/fields/TimestampField.d.ts +2 -1
  40. package/dist/core/components/fields/TimestampField.js +3 -3
  41. package/dist/core/components/fields/asset/AssetEditor.js +12 -44
  42. package/dist/core/components/fields/asset/AssetPicker.js +6 -6
  43. package/dist/core/components/fields/asset/AssetUploader.js +23 -31
  44. package/dist/core/components/fields/asset/preview/VideoPreview.js +16 -11
  45. package/dist/core/components/fields/asset/useAssetFields.d.ts +2 -17
  46. package/dist/core/components/fields/asset/useAssetFields.js +0 -21
  47. package/dist/core/components/fields/collection/CollectionEditor.js +1 -1
  48. package/dist/core/components/fields/collection/CollectionPicker.d.ts +2 -2
  49. package/dist/core/components/fields/collection/CollectionPicker.js +3 -4
  50. package/dist/core/components/fields/dynamicPages/PagePriorityDialog.js +1 -1
  51. package/dist/core/components/fields/embed/EmbeddedVideoEditor.js +34 -27
  52. package/dist/core/components/fields/factories/FieldValueFactory.js +11 -4
  53. package/dist/core/components/fields/factories/MediaFactory.d.ts +13 -0
  54. package/dist/core/components/fields/factories/MediaFactory.js +256 -0
  55. package/dist/core/components/fields/list/CreateFieldButton.js +1 -1
  56. package/dist/core/components/fields/media/MediaCard.d.ts +7 -0
  57. package/dist/core/components/fields/media/MediaCard.js +92 -0
  58. package/dist/core/components/fields/media/MediaEditor.d.ts +37 -0
  59. package/dist/core/components/fields/media/MediaEditor.js +235 -0
  60. package/dist/core/components/fields/media/MediaPicker.d.ts +15 -0
  61. package/dist/core/components/fields/media/MediaPicker.js +251 -0
  62. package/dist/core/components/fields/media/index.d.ts +3 -0
  63. package/dist/core/components/fields/media/index.js +19 -0
  64. package/dist/core/components/fields/object/ObjectEditor.js +1 -1
  65. package/dist/core/components/fields/page/PagePicker.d.ts +2 -2
  66. package/dist/core/components/fields/page/PagePicker.js +1 -1
  67. package/dist/core/components/interaction/Combobox.js +1 -1
  68. package/dist/core/components/interaction/DateTimePicker.d.ts +0 -1
  69. package/dist/core/components/interaction/DateTimePicker.js +0 -2
  70. package/dist/core/components/interaction/MultiSelectControls.js +2 -5
  71. package/dist/core/components/interaction/Popup.js +1 -0
  72. package/dist/core/components/interaction/QuickEditor.js +1 -1
  73. package/dist/core/components/interaction/QuickPicker.js +1 -1
  74. package/dist/core/components/interaction/SideSheet.js +1 -1
  75. package/dist/core/components/interaction/centersheet/CenterSheet.js +7 -1
  76. package/dist/core/components/interaction/centersheet/CenterSheetContainer.js +3 -1
  77. package/dist/core/components/transitions/FadeIn.js +2 -14
  78. package/dist/core/components/visuals/Icon.d.ts +1 -1
  79. package/dist/core/components/visuals/Icon.js +3 -0
  80. package/dist/core/containers/AssetRepositoryContainer.d.ts +2 -0
  81. package/dist/core/containers/AssetRepositoryContainer.js +18 -0
  82. package/dist/core/containers/CollectionRepositoryContainer.d.ts +2 -0
  83. package/dist/core/containers/CollectionRepositoryContainer.js +18 -0
  84. package/dist/core/containers/PageRepositoryContainer.d.ts +2 -0
  85. package/dist/core/containers/PageRepositoryContainer.js +18 -0
  86. package/dist/core/containers/index.d.ts +3 -0
  87. package/dist/core/containers/index.js +19 -0
  88. package/dist/core/domain/Block.d.ts +1 -0
  89. package/dist/core/domain/Block.js +8 -5
  90. package/dist/core/domain/BlockConfiguration.d.ts +4 -1
  91. package/dist/core/domain/BlockConfiguration.js +64 -3
  92. package/dist/core/domain/Collection.d.ts +10 -28
  93. package/dist/core/domain/Collection.js +2 -16
  94. package/dist/core/domain/CollectionBrowser.d.ts +3 -2
  95. package/dist/core/domain/CollectionBrowser.js +3 -3
  96. package/dist/core/domain/CollectionFieldProxy.d.ts +12 -8
  97. package/dist/core/domain/CollectionFieldProxy.js +14 -8
  98. package/dist/core/domain/CollectionGroup.d.ts +0 -5
  99. package/dist/core/domain/CollectionGroup.js +0 -7
  100. package/dist/core/domain/CollectionSummary.d.ts +2 -2
  101. package/dist/core/domain/Collections.d.ts +3 -3
  102. package/dist/core/domain/Collections.js +4 -4
  103. package/dist/core/domain/ICollection.d.ts +71 -0
  104. package/dist/core/domain/ICollection.js +20 -0
  105. package/dist/core/domain/IPage.d.ts +72 -0
  106. package/dist/core/domain/IPage.js +20 -0
  107. package/dist/core/domain/IPageRefStore.d.ts +3 -3
  108. package/dist/core/domain/ItemBase.d.ts +6 -6
  109. package/dist/core/domain/ItemBase.js +5 -5
  110. package/dist/core/domain/Page.d.ts +9 -22
  111. package/dist/core/domain/Page.js +3 -18
  112. package/dist/core/domain/PageOverview.d.ts +2 -2
  113. package/dist/core/domain/PageRefStoreProxy.d.ts +4 -4
  114. package/dist/core/domain/PageSettings.d.ts +3 -3
  115. package/dist/core/domain/PageSummary.d.ts +2 -2
  116. package/dist/core/domain/Pages.js +4 -6
  117. package/dist/core/domain/SectionBlock.js +2 -1
  118. package/dist/core/domain/Site.d.ts +4 -4
  119. package/dist/core/domain/Site.js +3 -3
  120. package/dist/core/domain/index.d.ts +2 -1
  121. package/dist/core/domain/index.js +2 -1
  122. package/dist/core/hooks/index.d.ts +4 -3
  123. package/dist/core/hooks/index.js +4 -3
  124. package/dist/core/hooks/useAssetRepository.d.ts +3 -0
  125. package/dist/core/hooks/useAssetRepository.js +11 -0
  126. package/dist/core/hooks/useCollection.d.ts +4 -4
  127. package/dist/core/hooks/useCollectionRepository.d.ts +3 -2
  128. package/dist/core/hooks/useCollectionRepository.js +4 -4
  129. package/dist/core/hooks/usePage.d.ts +4 -4
  130. package/dist/core/hooks/usePageRef.d.ts +1 -1
  131. package/dist/core/hooks/usePageRepository.d.ts +3 -2
  132. package/dist/core/hooks/usePageRepository.js +4 -4
  133. package/dist/core/hooks/useTempFields.d.ts +2 -3
  134. package/dist/core/index.d.ts +1 -0
  135. package/dist/core/index.js +1 -0
  136. package/dist/core/repository/AssetRepository.d.ts +10 -0
  137. package/dist/core/repository/AssetRepository.js +22 -0
  138. package/dist/core/repository/CollectionRepository.d.ts +21 -20
  139. package/dist/core/repository/CollectionRepository.js +23 -161
  140. package/dist/core/repository/CollectionSerializer.d.ts +4 -1
  141. package/dist/core/repository/CollectionSerializer.js +3 -3
  142. package/dist/core/repository/IAssetRepository.d.ts +25 -0
  143. package/dist/core/repository/ICollectionRepository.d.ts +62 -0
  144. package/dist/core/repository/ICollectionRepository.js +2 -0
  145. package/dist/core/repository/IPageRepository.d.ts +40 -0
  146. package/dist/core/repository/IPageRepository.js +2 -0
  147. package/dist/core/repository/PageRepository.d.ts +6 -9
  148. package/dist/core/repository/PageRepository.js +7 -280
  149. package/dist/core/repository/PageSerializer.d.ts +34 -1
  150. package/dist/core/repository/PageSerializer.js +4 -0
  151. package/dist/core/repository/SerializeCollectionUpdate.d.ts +8 -0
  152. package/dist/core/repository/SerializeCollectionUpdate.js +168 -0
  153. package/dist/core/repository/SerializePageUpdate.d.ts +11 -0
  154. package/dist/core/repository/SerializePageUpdate.js +292 -0
  155. package/dist/core/repository/index.d.ts +4 -0
  156. package/dist/core/repository/index.js +4 -0
  157. package/dist/dashboard/screens/Dashboard.js +1 -1
  158. package/dist/devtools/screens/Devtools.js +1 -1
  159. package/dist/explore/components/CreatePageDialog.d.ts +2 -2
  160. package/dist/explore/screens/CollectionExplorer.js +1 -1
  161. package/dist/explore/screens/CollectionsExplorer.js +1 -1
  162. package/dist/explore/screens/PageExplorer.js +1 -1
  163. package/dist/explore/screens/SiteExplorer.js +1 -1
  164. package/dist/page/components/BlockEditor.js +1 -1
  165. package/dist/page/components/PageHeader.d.ts +2 -2
  166. package/dist/page/components/PageNotCreated.d.ts +2 -2
  167. package/dist/page/components/PageNotFound.d.ts +2 -2
  168. package/dist/page/components/SettingsEditor.js +0 -7
  169. package/dist/page/components/config/BlockConfigButton.d.ts +2 -1
  170. package/dist/page/components/config/BlockConfigButton.js +84 -24
  171. package/dist/page/components/config/BlockPreview.d.ts +8 -0
  172. package/dist/page/components/config/BlockPreview.js +53 -0
  173. package/dist/page/components/config/index.d.ts +1 -1
  174. package/dist/page/components/config/index.js +1 -1
  175. package/dist/page/components/config/preview/ColumnLayout.d.ts +10 -0
  176. package/dist/page/components/config/preview/ColumnLayout.js +15 -0
  177. package/dist/page/components/config/preview/ItemLayout.d.ts +12 -0
  178. package/dist/page/components/config/preview/ItemLayout.js +45 -0
  179. package/dist/page/components/config/preview/ListPanel.d.ts +12 -0
  180. package/dist/page/components/config/preview/ListPanel.js +14 -0
  181. package/dist/page/components/config/preview/MessagePanel.d.ts +12 -0
  182. package/dist/page/components/config/preview/MessagePanel.js +21 -0
  183. package/dist/page/components/config/preview/Panel.d.ts +15 -0
  184. package/dist/page/components/config/preview/Panel.js +74 -0
  185. package/dist/page/components/config/preview/RowLayout.d.ts +10 -0
  186. package/dist/page/components/config/preview/RowLayout.js +26 -0
  187. package/dist/page/components/config/preview/StackLayout.d.ts +10 -0
  188. package/dist/page/components/config/preview/StackLayout.js +15 -0
  189. package/dist/page/components/config/preview/VisualPanel.d.ts +12 -0
  190. package/dist/page/components/config/preview/VisualPanel.js +27 -0
  191. package/dist/page/components/config/preview/index.d.ts +3 -0
  192. package/dist/page/components/config/preview/index.js +19 -0
  193. package/dist/page/components/panels/BlockConfigPanel.js +4 -4
  194. package/dist/page/hooks/IPageController.d.ts +42 -0
  195. package/dist/page/hooks/IPageController.js +32 -0
  196. package/dist/page/hooks/PageController.d.ts +11 -36
  197. package/dist/page/hooks/PageController.js +7 -33
  198. package/dist/page/hooks/index.d.ts +1 -0
  199. package/dist/page/hooks/index.js +1 -0
  200. package/dist/page/hooks/usePageController.d.ts +3 -3
  201. package/dist/page/screens/PageEditor.js +1 -1
  202. package/dist/styles/index.css +809 -60
  203. package/dist/styles/tailwind.js +1 -0
  204. package/dist/users/screens/UserExplorer.js +1 -1
  205. package/package.json +3 -2
  206. package/dist/core/domain/TranslateSchema.d.ts +0 -7
  207. package/dist/page/components/config/LegacyBlockPreview.d.ts +0 -8
  208. package/dist/page/components/config/LegacyBlockPreview.js +0 -452
  209. /package/dist/core/{domain/TranslateSchema.js → repository/IAssetRepository.js} +0 -0
@@ -0,0 +1,256 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MediaFactory = MediaFactory;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const funnel_core_1 = require("@nascentdigital/funnel-core");
6
+ const funnel_ui_1 = require("@nascentdigital/funnel-ui");
7
+ const react_1 = require("react");
8
+ const interaction_1 = require("../../interaction");
9
+ const asset_1 = require("../asset");
10
+ const embed_1 = require("../embed");
11
+ const media_1 = require("../media");
12
+ // component
13
+ function MediaFactory({ schema, language, media: initialValue, anchor, onCommit: onCommitRaw, onCancel: onCancelRaw }) {
14
+ // bind refs
15
+ const menu = (0, interaction_1.useMenuController)();
16
+ const pickerRef = (0, react_1.useRef)(null);
17
+ const editorRef = (0, react_1.useRef)(null);
18
+ const assetPickerRef = (0, react_1.useRef)(null);
19
+ const assetUploaderRef = (0, react_1.useRef)(null);
20
+ const embeddedVideoEditorRef = (0, react_1.useRef)(null);
21
+ const anchorRef = (0, react_1.useRef)(anchor);
22
+ anchorRef.current = anchor;
23
+ const onCommitRef = (0, react_1.useRef)(onCommitRaw);
24
+ onCommitRef.current = onCommitRaw;
25
+ const onCancelRef = (0, react_1.useRef)(onCancelRaw);
26
+ onCancelRef.current = onCancelRaw;
27
+ const collectionOptions = schema.collections;
28
+ // open menu when bound
29
+ (0, react_1.useEffect)(() => {
30
+ // open menu if new
31
+ if (!initialValue) {
32
+ menu.items = mediaMenuOption;
33
+ menu.open(anchorRef.current);
34
+ }
35
+ // or open editor
36
+ else {
37
+ // blow up if editor isn't available (shouldn't happen)
38
+ const editor = editorRef.current;
39
+ const anchor = anchorRef.current;
40
+ if (!editor) {
41
+ throw new funnel_core_1.IllegalStateError('Unable to resolve reference to MediaEditor');
42
+ }
43
+ // start picker (cancel if nothing was picked
44
+ editor.edit({
45
+ schema,
46
+ target: { type: 'media', media: initialValue },
47
+ anchor
48
+ })
49
+ .then(async (media) => {
50
+ var _a;
51
+ console.debug('[MediaFactory/onEdit] editing completed', media);
52
+ // cancel if there's no media
53
+ if (!media) {
54
+ onCancelRef.current();
55
+ }
56
+ // or switch to picker
57
+ else if (media === 'pick') {
58
+ // start picker (cancel if nothing was picked
59
+ const media = await ((_a = pickerRef.current) === null || _a === void 0 ? void 0 : _a.pick({ schema }));
60
+ if (!media) {
61
+ onCancelRef.current();
62
+ }
63
+ // otherwise, return result
64
+ else {
65
+ onCommitRef.current({
66
+ id: media.id,
67
+ type: media.template.type
68
+ });
69
+ }
70
+ }
71
+ // or commit change
72
+ else {
73
+ onCommitRef.current(media);
74
+ }
75
+ })
76
+ .catch(console.error);
77
+ }
78
+ }, [menu, schema, collectionOptions, initialValue]);
79
+ // helpers
80
+ const onEdit = async (target) => {
81
+ var _a;
82
+ // start picker (cancel if nothing was picked
83
+ const media = await ((_a = editorRef.current) === null || _a === void 0 ? void 0 : _a.edit({
84
+ schema,
85
+ target,
86
+ anchor
87
+ }));
88
+ // stop if cancelled
89
+ if (!media) {
90
+ onCancel();
91
+ }
92
+ // or switch to picker
93
+ else if (media === 'pick') {
94
+ // pick media
95
+ const pick = await onPickMedia();
96
+ // cancel if nothing got picked
97
+ if (!pick) {
98
+ onCancel();
99
+ }
100
+ // or commit selection
101
+ else {
102
+ onCommit({
103
+ id: pick.id,
104
+ type: pick.template.type
105
+ });
106
+ }
107
+ }
108
+ // otherwise, return result
109
+ else {
110
+ onCommit(media);
111
+ }
112
+ };
113
+ const onPickMedia = async () => {
114
+ var _a;
115
+ // start picker (cancel if nothing was picked
116
+ const media = await ((_a = pickerRef.current) === null || _a === void 0 ? void 0 : _a.pick({ schema }));
117
+ return media !== null && media !== void 0 ? media : undefined;
118
+ };
119
+ const onUploadAsset = async () => {
120
+ var _a;
121
+ // launch uploader (or skip if there's no result)
122
+ const result = await ((_a = assetUploaderRef.current) === null || _a === void 0 ? void 0 : _a.upload({
123
+ schema: mediaUploadSchema,
124
+ anchor
125
+ }));
126
+ if (!result) {
127
+ return undefined;
128
+ }
129
+ // or switch to picking
130
+ else if (result === 'pick') {
131
+ return onPickAsset();
132
+ }
133
+ // create new image media
134
+ const { value: asset } = result;
135
+ if (funnel_core_1.Schema.AssetField.LinkedValue.Image.filter(asset)) {
136
+ console.debug('[MediaFactory] creating new image media');
137
+ return asset;
138
+ }
139
+ // or create new video media
140
+ else if (funnel_core_1.Schema.AssetField.LinkedValue.Video.filter(asset)) {
141
+ console.debug('[MediaFactory] creating new video media');
142
+ return asset;
143
+ }
144
+ // or abort
145
+ else {
146
+ return undefined;
147
+ }
148
+ };
149
+ const onPickAsset = async () => {
150
+ var _a;
151
+ // launch picker (or cancel if nothing is selected)
152
+ const result = await ((_a = assetPickerRef.current) === null || _a === void 0 ? void 0 : _a.pick({
153
+ schema: mediaUploadSchema
154
+ }));
155
+ if (!result) {
156
+ return undefined;
157
+ }
158
+ // or launch uploader if the directive was received
159
+ else if (result === 'upload') {
160
+ return onUploadAsset();
161
+ }
162
+ // create new image media
163
+ const { value: asset } = result;
164
+ if (funnel_core_1.Schema.AssetField.LinkedValue.Image.filter(asset)) {
165
+ console.debug('[MediaFactory] creating new image media');
166
+ return asset;
167
+ }
168
+ // or create new video media
169
+ else if (funnel_core_1.Schema.AssetField.LinkedValue.Video.filter(asset)) {
170
+ console.debug('[MediaFactory] creating new video media');
171
+ return asset;
172
+ }
173
+ // or abort
174
+ else {
175
+ return undefined;
176
+ }
177
+ };
178
+ const onLinkVideo = async () => {
179
+ var _a, _b;
180
+ // start editor (cancel if dismissed)
181
+ const value = await ((_a = embeddedVideoEditorRef.current) === null || _a === void 0 ? void 0 : _a.edit({
182
+ schema: funnel_ui_1.Media.Video.Source.schema.content.remote.schema,
183
+ anchor: anchorRef.current
184
+ }));
185
+ return (_b = value === null || value === void 0 ? void 0 : value.value) !== null && _b !== void 0 ? _b : undefined;
186
+ };
187
+ const onMenuSelect = async (option) => {
188
+ // process based on option
189
+ switch (option) {
190
+ case 'pick': {
191
+ // pick media (or cancel if nothing was picked)
192
+ const media = await onPickMedia();
193
+ if (!media) {
194
+ onCancel();
195
+ }
196
+ // or commit
197
+ else {
198
+ onCommitRef.current({
199
+ id: media.id,
200
+ type: media.template.type
201
+ });
202
+ }
203
+ // stop processing
204
+ break;
205
+ }
206
+ case 'add.asset': {
207
+ // get visual (or cancel)
208
+ const asset = await onUploadAsset();
209
+ if (!asset) {
210
+ onCancel();
211
+ break;
212
+ }
213
+ // open editor with asset
214
+ await onEdit(funnel_core_1.Schema.AssetField.LinkedValue.Image.filter(asset)
215
+ ? { type: 'image', image: asset }
216
+ : { type: 'video.local', video: asset });
217
+ // stop processing
218
+ break;
219
+ }
220
+ case 'add.url': {
221
+ // link video (or cancel)
222
+ const video = await onLinkVideo();
223
+ if (!video) {
224
+ onCancel();
225
+ break;
226
+ }
227
+ // open editor with video
228
+ await onEdit({ type: 'video.remote', video });
229
+ // stop processing
230
+ break;
231
+ }
232
+ }
233
+ };
234
+ const onCommit = (media) => onCommitRef.current(media);
235
+ const onCancel = () => onCancelRef.current();
236
+ // render
237
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(interaction_1.Menu, { controller: menu, onItemClick: onMenuSelect, onClose: onCancel }), (0, jsx_runtime_1.jsx)(media_1.MediaPicker, { ref: pickerRef, language: language }), (0, jsx_runtime_1.jsx)(media_1.MediaEditor, { ref: editorRef, language: language }), (0, jsx_runtime_1.jsx)(asset_1.AssetUploader, { ref: assetUploaderRef }), (0, jsx_runtime_1.jsx)(asset_1.AssetPicker, { ref: assetPickerRef }), (0, jsx_runtime_1.jsx)(embed_1.EmbeddedVideoEditor, { ref: embeddedVideoEditorRef, language: language })] }));
238
+ }
239
+ const mediaMenuOption = [
240
+ {
241
+ id: 'pick',
242
+ label: 'Use existing media',
243
+ icon: 'FileSearchCorner'
244
+ },
245
+ {
246
+ id: 'add.asset',
247
+ label: 'Upload new asset',
248
+ icon: 'ImageDown'
249
+ },
250
+ {
251
+ id: 'add.url',
252
+ label: 'Link to Video',
253
+ icon: 'Link2'
254
+ }
255
+ ];
256
+ const mediaUploadSchema = funnel_core_1.Schema.Field.visual({ name: 'Image or Video' });
@@ -39,6 +39,6 @@ function CreateFieldButton(_a) {
39
39
  setCreating(false);
40
40
  };
41
41
  // render
42
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(interaction_1.Button, Object.assign({}, props, { anchorId: id, onClick: onCreateStart, variant: "text", iconLeft: "Plus", children: children })), creating &&
42
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(interaction_1.Button, Object.assign({}, props, { anchorId: id, onClick: onCreateStart, variant: "secondary", iconLeft: "Plus", children: children })), creating &&
43
43
  (0, jsx_runtime_1.jsx)(factories_1.FieldValueFactory, { itemSchema: schema, anchor: id, onCommit: onCreate, onCancel: onCreateCancel })] }));
44
44
  }
@@ -0,0 +1,7 @@
1
+ import { Field } from '@nascentdigital/funnel-core';
2
+ import { Media } from '@nascentdigital/funnel-ui';
3
+ import type { FieldCard } from '../FieldCard';
4
+ export declare function MediaCard({ className, field, ...props }: MediaCard.Props): import("react/jsx-runtime").JSX.Element;
5
+ export declare namespace MediaCard {
6
+ type Props = FieldCard.Props.For<Field<Media.Schema>>;
7
+ }
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ var __rest = (this && this.__rest) || function (s, e) {
3
+ var t = {};
4
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
+ t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
+ t[p[i]] = s[p[i]];
10
+ }
11
+ return t;
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.MediaCard = MediaCard;
15
+ const jsx_runtime_1 = require("react/jsx-runtime");
16
+ const funnel_core_1 = require("@nascentdigital/funnel-core");
17
+ const funnel_ui_1 = require("@nascentdigital/funnel-ui");
18
+ const react_1 = require("react");
19
+ const hooks_1 = require("../../../hooks");
20
+ const AssetPreview_1 = require("../asset/AssetPreview");
21
+ const FieldCardLayout_1 = require("../FieldCardLayout");
22
+ // component
23
+ function MediaCard(_a) {
24
+ var _b;
25
+ var { className, field } = _a, props = __rest(_a, ["className", "field"]);
26
+ // bind props + hooks
27
+ const [active, setActive] = (0, react_1.useState)(false);
28
+ const collectionStore = (0, hooks_1.useCollectionStore)();
29
+ const assetStore = (0, hooks_1.useAssetStore)();
30
+ const value = (_b = field.value) === null || _b === void 0 ? void 0 : _b.value;
31
+ const media = value
32
+ ? funnel_core_1.Schema.CollectionField.Value.ForRef.filter(value)
33
+ ? collectionStore.getCollection(value.id)
34
+ : value
35
+ : undefined;
36
+ // render
37
+ return ((0, jsx_runtime_1.jsx)(FieldCardLayout_1.FieldCardLayout, Object.assign({}, props, { className: className, field: field, onActiveChanged: setActive, children: (0, jsx_runtime_1.jsx)("div", { className: "relative flex-1 w-full h-full flex flex-col", children: renderMedia({ assetStore, media, active }) }) })));
38
+ }
39
+ function renderMedia({ assetStore, media, active }) {
40
+ var _a, _b, _c, _d, _e, _f, _g, _h;
41
+ // skip if there's no media
42
+ if (!media) {
43
+ return ((0, jsx_runtime_1.jsx)("div", { className: "flex-1" }));
44
+ }
45
+ // or image
46
+ else if (funnel_ui_1.Media.Value.isImage(media)) {
47
+ // bind props
48
+ const { image: desktopImageData, mobileImage: mobileImageData } = (_a = media.fields) !== null && _a !== void 0 ? _a : {};
49
+ const desktopImageId = (_b = desktopImageData === null || desktopImageData === void 0 ? void 0 : desktopImageData.value) === null || _b === void 0 ? void 0 : _b.id;
50
+ const desktopImage = desktopImageId
51
+ ? assetStore.getAsset(desktopImageId)
52
+ : undefined;
53
+ const mobileImageId = (_c = mobileImageData === null || mobileImageData === void 0 ? void 0 : mobileImageData.value) === null || _c === void 0 ? void 0 : _c.id;
54
+ const mobileImage = mobileImageId
55
+ ? assetStore.getAsset(mobileImageId)
56
+ : undefined;
57
+ const mobileImageUrl = (_d = mobileImage === null || mobileImage === void 0 ? void 0 : mobileImage.value) === null || _d === void 0 ? void 0 : _d.url;
58
+ // render image (crossfade)
59
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(AssetPreview_1.AssetPreview, { asset: desktopImage === null || desktopImage === void 0 ? void 0 : desktopImage.value, active: active }), mobileImageUrl &&
60
+ (0, jsx_runtime_1.jsx)("img", { className: "absolute inset-0 object-cover z-supersurface interpolate opacity-none hover:opacity-full", src: mobileImageUrl, alt: "Mobile preview" })] }));
61
+ }
62
+ // or video
63
+ else if (funnel_ui_1.Media.Value.isVideo(media)) {
64
+ // bind props
65
+ const { video, cover: coverRaw } = (_e = media.fields) !== null && _e !== void 0 ? _e : {};
66
+ const { local: localRaw, remote: remoteRaw } = video.value;
67
+ const coverId = (_f = coverRaw === null || coverRaw === void 0 ? void 0 : coverRaw.value) === null || _f === void 0 ? void 0 : _f.id;
68
+ const cover = coverId
69
+ ? assetStore.getAsset(coverId)
70
+ : undefined;
71
+ const localVideoId = (_g = localRaw === null || localRaw === void 0 ? void 0 : localRaw.value) === null || _g === void 0 ? void 0 : _g.id;
72
+ const localVideo = localVideoId
73
+ ? assetStore.getAsset(localVideoId)
74
+ : undefined;
75
+ const remoteThumbUrl = (_h = remoteRaw === null || remoteRaw === void 0 ? void 0 : remoteRaw.value) === null || _h === void 0 ? void 0 : _h.thumbUrl;
76
+ // render video
77
+ if (localVideo) {
78
+ return ((0, jsx_runtime_1.jsx)(AssetPreview_1.AssetPreview, { asset: localVideo === null || localVideo === void 0 ? void 0 : localVideo.value, active: active }));
79
+ }
80
+ // or remove
81
+ else if (remoteThumbUrl) {
82
+ return ((0, jsx_runtime_1.jsx)("img", { className: "w-full h-full object-cover", src: remoteThumbUrl, alt: "Video thumbnail preview", draggable: false }));
83
+ }
84
+ else {
85
+ return null;
86
+ }
87
+ }
88
+ // or skip (shouldn't happen
89
+ else {
90
+ return ((0, jsx_runtime_1.jsx)("div", { className: "flex-1" }));
91
+ }
92
+ }
@@ -0,0 +1,37 @@
1
+ import { Language, Schema } from '@nascentdigital/funnel-core';
2
+ import { Media } from '@nascentdigital/funnel-ui';
3
+ import { Popup } from '../../interaction';
4
+ export declare const MediaEditor: import("react").ForwardRefExoticComponent<MediaEditor.Props & import("react").RefAttributes<MediaEditor.Ref>>;
5
+ export declare namespace MediaEditor {
6
+ type Target = Target.Image | Target.LocalVideo | Target.RemoteVideo | Target.Media;
7
+ namespace Target {
8
+ type Image = {
9
+ type: 'image';
10
+ image: Schema.AssetField.LinkedValue.Image;
11
+ };
12
+ type LocalVideo = {
13
+ type: 'video.local';
14
+ video: Schema.AssetField.LinkedValue.Video;
15
+ };
16
+ type RemoteVideo = {
17
+ type: 'video.remote';
18
+ video: Schema.EmbeddedVideoField.Value;
19
+ };
20
+ type Media = {
21
+ type: 'media';
22
+ media: Media.Value;
23
+ };
24
+ }
25
+ type Props = {
26
+ language: Language;
27
+ };
28
+ interface Ref {
29
+ edit(options: Options): Promise<Result>;
30
+ }
31
+ type Options = {
32
+ schema: Media.Schema;
33
+ target: Target;
34
+ anchor: Popup.Anchor;
35
+ };
36
+ type Result = Media.Value | 'pick' | undefined;
37
+ }
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MediaEditor = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const funnel_core_1 = require("@nascentdigital/funnel-core");
6
+ const funnel_ui_1 = require("@nascentdigital/funnel-ui");
7
+ const framer_motion_1 = require("framer-motion");
8
+ const react_1 = require("react");
9
+ const domain_1 = require("../../../domain");
10
+ const hooks_1 = require("../../../hooks");
11
+ const feedback_1 = require("../../feedback");
12
+ const interaction_1 = require("../../interaction");
13
+ const FieldEditor_1 = require("../FieldEditor");
14
+ const FieldErrorController_1 = require("../FieldErrorController");
15
+ const OptInField_1 = require("../OptInField");
16
+ // component
17
+ exports.MediaEditor = (0, react_1.forwardRef)(function MediaEditor({ language }, ref) {
18
+ var _a, _b;
19
+ // bind props + hooks
20
+ const [deferral, setDeferral] = (0, react_1.useState)(undefined);
21
+ const [result, setResult] = (0, react_1.useState)();
22
+ const [target, setTarget] = (0, react_1.useState)();
23
+ const [anchor, setAnchor] = (0, react_1.useState)();
24
+ const [fieldSchema, setFieldSchema] = (0, react_1.useState)();
25
+ const [mediaSchema, setMediaSchema] = (0, react_1.useState)();
26
+ const [shared, setShared] = (0, react_1.useState)(false);
27
+ const [errorLevel, setErrorLevel] = (0, react_1.useState)('none');
28
+ const [error, setError] = (0, react_1.useState)();
29
+ const [field, setField] = (0, react_1.useState)();
30
+ const page = (0, hooks_1.usePossiblePage)();
31
+ const collection = (0, hooks_1.usePossibleCollection)();
32
+ const collectionRepository = (0, hooks_1.useCollectionRepository)();
33
+ const collectionStore = page !== null && page !== void 0 ? page : collection;
34
+ const assetStore = page !== null && page !== void 0 ? page : collection;
35
+ const dialog = (0, feedback_1.useDialog)();
36
+ const valid = (_a = field === null || field === void 0 ? void 0 : field.valid) !== null && _a !== void 0 ? _a : false;
37
+ const modified = (_b = field === null || field === void 0 ? void 0 : field.modified) !== null && _b !== void 0 ? _b : false;
38
+ // sync with field
39
+ (0, hooks_1.useCollectionFieldProxy)(field);
40
+ // export service
41
+ (0, react_1.useImperativeHandle)(ref, () => ({
42
+ edit: async ({ schema, target, anchor }) => {
43
+ // reset state
44
+ setFieldSchema(schema);
45
+ setMediaSchema(target.type === 'image' || (target.type === 'media' && funnel_ui_1.Media.Value.isImage(target.media))
46
+ ? funnel_ui_1.Media.Image.collection
47
+ : funnel_ui_1.Media.Video.collection);
48
+ setTarget(target);
49
+ setField(undefined);
50
+ setShared(false);
51
+ setAnchor(anchor);
52
+ setErrorLevel('none');
53
+ setError(undefined);
54
+ // create deferral
55
+ const deferral = new funnel_core_1.Deferral();
56
+ setDeferral(deferral);
57
+ setResult(undefined);
58
+ // wait until deferral is resolved
59
+ return deferral.promise;
60
+ }
61
+ }), []);
62
+ // create field
63
+ (0, react_1.useEffect)(() => {
64
+ // ignore if schemas aren't set
65
+ if (!fieldSchema || !mediaSchema || !collectionStore || !assetStore || !target) {
66
+ return;
67
+ }
68
+ // create new field (wraps existing data for editing)
69
+ domain_1.CollectionFieldProxy
70
+ .create({
71
+ collectionRepository,
72
+ collectionStore,
73
+ assetStore,
74
+ schema: fieldSchema,
75
+ collectionSchema: mediaSchema,
76
+ language,
77
+ initialValue: target.type === 'media' ? target.media : undefined
78
+ })
79
+ .then(field => {
80
+ // bind image (if partial)
81
+ switch (target.type) {
82
+ case 'image': {
83
+ const collection = field;
84
+ const imageField = collection.getField('image');
85
+ imageField.value = funnel_core_1.Schema.AssetField.Value.toData(target.image);
86
+ break;
87
+ }
88
+ case 'video.local': {
89
+ const collection = field;
90
+ const videoField = collection.getField('video');
91
+ const typeField = videoField.getField('type');
92
+ typeField.value = funnel_core_1.Schema.EnumField.Value.toData('local');
93
+ const localVideoField = videoField.getField('local');
94
+ localVideoField.optIn();
95
+ localVideoField.value = funnel_core_1.Schema.AssetField.Value.toData(target.video);
96
+ break;
97
+ }
98
+ case 'video.remote': {
99
+ const collection = field;
100
+ const videoField = collection.getField('video');
101
+ const typeField = videoField.getField('type');
102
+ typeField.value = funnel_core_1.Schema.EnumField.Value.toData('remote');
103
+ const remoteVideoField = videoField.getField('remote');
104
+ remoteVideoField.optIn();
105
+ remoteVideoField.value = funnel_core_1.Schema.EmbeddedVideoField.Value.toData(target.video);
106
+ break;
107
+ }
108
+ }
109
+ // bind field
110
+ setField(field);
111
+ setShared(!field.isNew && field.isRef);
112
+ });
113
+ }, [language, collectionRepository, collectionStore, assetStore, fieldSchema, mediaSchema, target]);
114
+ // skip if there's no schema or value
115
+ if (!fieldSchema || !mediaSchema || !field || !target || !collectionStore || !assetStore) {
116
+ return null;
117
+ }
118
+ // helpers
119
+ const onValidate = () => {
120
+ // update error level
121
+ setErrorLevel('all');
122
+ // return validity
123
+ return valid;
124
+ };
125
+ const onSave = async () => {
126
+ // skip there's no field (should never happen)
127
+ if (!field) {
128
+ return;
129
+ }
130
+ // confirm if it's shared (skip if user bails)
131
+ if (shared) {
132
+ const confirmed = await dialog.confirm(field.isNew
133
+ ?
134
+ {
135
+ title: `Create a new ${mediaSchema.name}?`,
136
+ message: ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("p", { children: [(0, jsx_runtime_1.jsx)("b", { children: field.name }), " will be created as a shared content entry."] }), (0, jsx_runtime_1.jsxs)("p", { children: ["You will also need to go to the Collection editor in order to publish this entry before attempting to the changes to this ", page ? 'page' : 'collection', " can go live."] })] })),
137
+ confirmLabel: 'Update'
138
+ }
139
+ :
140
+ {
141
+ title: `Update shared ${mediaSchema.name}?`,
142
+ message: 'Updating this content may affect other pages.',
143
+ confirmLabel: 'Update'
144
+ });
145
+ if (!confirmed) {
146
+ return;
147
+ }
148
+ }
149
+ // TODO: handle errors
150
+ // save
151
+ await field.save(shared);
152
+ // update after closing
153
+ setResult(field.value);
154
+ };
155
+ const onCancel = async () => {
156
+ // skip there's no field (should never happen)
157
+ if (!field) {
158
+ return;
159
+ }
160
+ // confirm if there are unsaved changes
161
+ if (field.modified) {
162
+ // confirm
163
+ const confirmed = await dialog.confirm({
164
+ title: 'Are you sure?',
165
+ message: 'You have unsaved changes that you\'ll lose.',
166
+ confirmLabel: 'Yes',
167
+ destructive: true
168
+ });
169
+ // skip if user cancelled
170
+ if (!confirmed) {
171
+ return;
172
+ }
173
+ }
174
+ // set result (triggers editor to close)
175
+ setResult(null);
176
+ };
177
+ const onClosed = () => {
178
+ // skip if missing or already completed
179
+ if (!deferral || deferral.isComplete) {
180
+ console.debug('[MediaEditor/onClosed] ignoring invalid trigger');
181
+ return;
182
+ }
183
+ console.debug('[MediaEditor/onClosed] completing edit:', result);
184
+ // clear field
185
+ setFieldSchema(undefined);
186
+ setMediaSchema(undefined);
187
+ setTarget(undefined);
188
+ setField(undefined);
189
+ // complete deferral
190
+ deferral.resolve(result !== null && result !== void 0 ? result : undefined);
191
+ };
192
+ // render
193
+ return ((0, jsx_runtime_1.jsx)(interaction_1.MiniEditor, { className: "flex flex-col gap-l8", title: `Edit Media`, open: !!deferral && result === undefined, placement: "right", anchor: anchor, anchorOffset: 4, disableSave: !modified, canSave: onValidate, onSave: onSave, renderSaveLabel: state => state === 'saving' ? 'Updating...' : 'Update', customActions: (0, jsx_runtime_1.jsx)(interaction_1.Button, { variant: "secondary", size: "sm", onClick: () => setResult('pick'), children: "Use existing media" }), onClose: onCancel, onClosed: onClosed, children: (0, jsx_runtime_1.jsx)(hooks_1.CollectionStoreContext.Provider, { value: collectionStore, children: (0, jsx_runtime_1.jsxs)(hooks_1.AssetStoreContext.Provider, { value: assetStore, children: [mediaSchema.description && !error &&
194
+ (0, jsx_runtime_1.jsx)(feedback_1.Note, { variant: "tip", children: mediaSchema.description }), (0, jsx_runtime_1.jsx)(framer_motion_1.AnimatePresence, { children: (0, jsx_runtime_1.jsxs)(FieldErrorController_1.FieldErrorController, { errorLevel: errorLevel, children: [(0, jsx_runtime_1.jsx)(FieldEditor_1.FieldEditor, { field: field.nameField, autoFocus: true }), renderFields({ field })] }) })] }) }) }));
195
+ });
196
+ function renderFields(args) {
197
+ // render image fields
198
+ const value = args.field.value;
199
+ if (funnel_ui_1.Media.Value.isImage(value)) {
200
+ return renderImageFields(args);
201
+ }
202
+ // or video fields
203
+ else {
204
+ return renderVideoFields(args);
205
+ }
206
+ }
207
+ function renderImageFields({ field }) {
208
+ // resolve fields
209
+ const desktopImageField = field.getField('image');
210
+ const mobileImageField = field.getField('mobileImage');
211
+ // render
212
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(FieldEditor_1.FieldEditor, { field: desktopImageField, disableDelete: true }), mobileImageField.exists
213
+ ? (0, jsx_runtime_1.jsx)(FieldEditor_1.FieldEditor, { field: mobileImageField })
214
+ : (0, jsx_runtime_1.jsx)(OptInField_1.OptInField, { className: "self-start", field: mobileImageField })] }));
215
+ }
216
+ function renderVideoFields({ field }) {
217
+ var _a, _b;
218
+ // resolve fields
219
+ const sourceField = field.getField('video');
220
+ const localVideoField = sourceField.getField('local');
221
+ const localVideo = (_a = localVideoField.value) === null || _a === void 0 ? void 0 : _a.value;
222
+ const remoteVideoField = sourceField.getField('remote');
223
+ const remoteVideo = (_b = remoteVideoField.value) === null || _b === void 0 ? void 0 : _b.value;
224
+ const coverField = field.getField('cover');
225
+ // TODO render error if there's no video source
226
+ if (!localVideo && !remoteVideo) {
227
+ return null;
228
+ }
229
+ // render
230
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(FieldEditor_1.FieldEditor, { field: localVideo
231
+ ? localVideoField
232
+ : remoteVideoField, disableDelete: true }), coverField.exists
233
+ ? (0, jsx_runtime_1.jsx)(FieldEditor_1.FieldEditor, { field: coverField })
234
+ : (0, jsx_runtime_1.jsx)(OptInField_1.OptInField, { className: "self-start", field: coverField })] }));
235
+ }
@@ -0,0 +1,15 @@
1
+ import { Language, Schema } from '@nascentdigital/funnel-core';
2
+ import { ICollection } from '../../../domain';
3
+ export declare const MediaPicker: import("react").ForwardRefExoticComponent<MediaPicker.Props & import("react").RefAttributes<MediaPicker.Ref>>;
4
+ export declare namespace MediaPicker {
5
+ type Props = {
6
+ language: Language;
7
+ };
8
+ interface Ref {
9
+ pick(options?: Options): Promise<Result>;
10
+ }
11
+ type Options = {
12
+ schema: Schema.CollectionField;
13
+ };
14
+ type Result = ICollection.Ref | undefined;
15
+ }