sanity-plugin-media 4.3.6 → 5.0.0

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 (162) hide show
  1. package/package.json +6 -15
  2. package/dist/index.cjs +0 -4721
  3. package/dist/index.cjs.map +0 -1
  4. package/dist/index.d.cts +0 -239
  5. package/dist/index.d.cts.map +0 -1
  6. package/sanity.json +0 -8
  7. package/src/__tests__/fixtures/createEpicTestStore.ts +0 -28
  8. package/src/__tests__/fixtures/listenMock.ts +0 -9
  9. package/src/__tests__/fixtures/mockSanityClient.ts +0 -84
  10. package/src/__tests__/fixtures/renderWithProviders.tsx +0 -55
  11. package/src/__tests__/fixtures/rootState.ts +0 -27
  12. package/src/__tests__/fixtures/withinDialog.ts +0 -28
  13. package/src/components/AssetGridVirtualized/index.tsx +0 -94
  14. package/src/components/AssetMetadata/index.tsx +0 -122
  15. package/src/components/AssetTableVirtualized/index.tsx +0 -73
  16. package/src/components/AutoTagInputWrapper/index.tsx +0 -85
  17. package/src/components/Browser/Browser.test.tsx +0 -45
  18. package/src/components/Browser/index.tsx +0 -90
  19. package/src/components/Browser/useBrowserInit.ts +0 -126
  20. package/src/components/ButtonAssetCopy/index.tsx +0 -65
  21. package/src/components/ButtonViewGroup/index.tsx +0 -39
  22. package/src/components/CardAsset/CardAsset.test.tsx +0 -323
  23. package/src/components/CardAsset/index.tsx +0 -290
  24. package/src/components/CardUpload/index.tsx +0 -161
  25. package/src/components/Controls/index.tsx +0 -136
  26. package/src/components/DebugControls/index.tsx +0 -80
  27. package/src/components/Dialog/index.tsx +0 -11
  28. package/src/components/DialogAssetEdit/Details.tsx +0 -181
  29. package/src/components/DialogAssetEdit/DialogAssetEdit.test.tsx +0 -216
  30. package/src/components/DialogAssetEdit/index.tsx +0 -493
  31. package/src/components/DialogConfirm/index.tsx +0 -90
  32. package/src/components/DialogSearchFacets/index.tsx +0 -42
  33. package/src/components/DialogTagCreate/DialogTagCreate.test.tsx +0 -121
  34. package/src/components/DialogTagCreate/index.tsx +0 -111
  35. package/src/components/DialogTagEdit/DialogTagEdit.test.tsx +0 -165
  36. package/src/components/DialogTagEdit/index.tsx +0 -201
  37. package/src/components/DialogTags/index.tsx +0 -45
  38. package/src/components/Dialogs/index.tsx +0 -76
  39. package/src/components/DocumentList/index.tsx +0 -62
  40. package/src/components/FileAssetPreview/index.tsx +0 -37
  41. package/src/components/FileIcon/index.tsx +0 -43
  42. package/src/components/FormBuilderTool/FormBuilderTool.test.tsx +0 -63
  43. package/src/components/FormBuilderTool/index.tsx +0 -69
  44. package/src/components/FormFieldInputLabel/index.tsx +0 -66
  45. package/src/components/FormFieldInputTags/index.tsx +0 -98
  46. package/src/components/FormFieldInputText/index.tsx +0 -41
  47. package/src/components/FormFieldInputTextarea/index.tsx +0 -43
  48. package/src/components/FormSubmitButton/index.tsx +0 -59
  49. package/src/components/Header/index.tsx +0 -80
  50. package/src/components/Image/index.tsx +0 -41
  51. package/src/components/Items/index.tsx +0 -68
  52. package/src/components/Notifications/index.tsx +0 -24
  53. package/src/components/OrderSelect/index.tsx +0 -66
  54. package/src/components/PickedBar/index.tsx +0 -77
  55. package/src/components/Progress/index.tsx +0 -38
  56. package/src/components/ReduxProvider/index.tsx +0 -96
  57. package/src/components/SearchFacet/index.tsx +0 -66
  58. package/src/components/SearchFacetNumber/index.tsx +0 -133
  59. package/src/components/SearchFacetSelect/index.tsx +0 -110
  60. package/src/components/SearchFacetString/index.tsx +0 -88
  61. package/src/components/SearchFacetTags/index.tsx +0 -121
  62. package/src/components/SearchFacets/index.tsx +0 -72
  63. package/src/components/SearchFacetsControl/index.tsx +0 -140
  64. package/src/components/TableHeader/index.tsx +0 -110
  65. package/src/components/TableHeaderItem/index.tsx +0 -61
  66. package/src/components/TableRowAsset/index.tsx +0 -419
  67. package/src/components/TableRowUpload/index.tsx +0 -164
  68. package/src/components/Tag/index.tsx +0 -200
  69. package/src/components/TagIcon/index.tsx +0 -22
  70. package/src/components/TagView/index.tsx +0 -39
  71. package/src/components/TagViewHeader/index.tsx +0 -70
  72. package/src/components/TagsPanel/index.tsx +0 -40
  73. package/src/components/TagsVirtualized/index.tsx +0 -160
  74. package/src/components/TextInputNumber/index.tsx +0 -32
  75. package/src/components/TextInputSearch/index.tsx +0 -60
  76. package/src/components/Tool/index.tsx +0 -13
  77. package/src/components/UploadDropzone/UploadDropzone.test.tsx +0 -40
  78. package/src/components/UploadDropzone/index.tsx +0 -173
  79. package/src/config/orders.ts +0 -28
  80. package/src/config/searchFacets.ts +0 -312
  81. package/src/constants.ts +0 -87
  82. package/src/contexts/AssetSourceDispatchContext.tsx +0 -38
  83. package/src/contexts/DropzoneDispatchContext.tsx +0 -32
  84. package/src/contexts/ToolOptionsContext.tsx +0 -66
  85. package/src/formSchema/index.test.ts +0 -56
  86. package/src/formSchema/index.ts +0 -39
  87. package/src/hooks/useBreakpointIndex.ts +0 -50
  88. package/src/hooks/useKeyPress.ts +0 -39
  89. package/src/hooks/usePortalPopoverProps.ts +0 -13
  90. package/src/hooks/useTypedSelector.ts +0 -7
  91. package/src/hooks/useVersionedClient.ts +0 -6
  92. package/src/index.ts +0 -5
  93. package/src/modules/assets/actions.ts +0 -42
  94. package/src/modules/assets/deleteAndUpdateEpics.test.ts +0 -87
  95. package/src/modules/assets/fetchEpic.test.ts +0 -73
  96. package/src/modules/assets/index.ts +0 -782
  97. package/src/modules/assets/reducer.test.ts +0 -91
  98. package/src/modules/assets/tagsAndListenerEpics.test.ts +0 -206
  99. package/src/modules/debug/index.ts +0 -28
  100. package/src/modules/dialog/actions.ts +0 -10
  101. package/src/modules/dialog/epics.test.ts +0 -168
  102. package/src/modules/dialog/index.ts +0 -238
  103. package/src/modules/dialog/reducer.test.ts +0 -185
  104. package/src/modules/index.ts +0 -117
  105. package/src/modules/notifications/epics.test.ts +0 -374
  106. package/src/modules/notifications/index.ts +0 -199
  107. package/src/modules/notifications/reducer.test.ts +0 -54
  108. package/src/modules/search/index.test.ts +0 -36
  109. package/src/modules/search/index.ts +0 -167
  110. package/src/modules/selected/index.ts +0 -22
  111. package/src/modules/selectors.test.ts +0 -21
  112. package/src/modules/selectors.ts +0 -17
  113. package/src/modules/tags/epics.test.ts +0 -96
  114. package/src/modules/tags/index.test.ts +0 -42
  115. package/src/modules/tags/index.ts +0 -540
  116. package/src/modules/types.ts +0 -3
  117. package/src/modules/uploads/actions.ts +0 -13
  118. package/src/modules/uploads/epics.test.ts +0 -109
  119. package/src/modules/uploads/index.test.ts +0 -59
  120. package/src/modules/uploads/index.ts +0 -272
  121. package/src/operators/checkTagName.test.ts +0 -29
  122. package/src/operators/checkTagName.ts +0 -33
  123. package/src/operators/debugThrottle.ts +0 -25
  124. package/src/plugin.tsx +0 -54
  125. package/src/schemas/tag.ts +0 -28
  126. package/src/styled/GlobalStyles/index.tsx +0 -40
  127. package/src/styled/react-select/creatable.tsx +0 -184
  128. package/src/styled/react-select/single.tsx +0 -184
  129. package/src/types/index.ts +0 -346
  130. package/src/types/sanity-ui.d.ts +0 -5
  131. package/src/utils/applyMediaTags.ts +0 -87
  132. package/src/utils/blocksToText.test.ts +0 -43
  133. package/src/utils/blocksToText.ts +0 -27
  134. package/src/utils/constructFilter.test.ts +0 -120
  135. package/src/utils/constructFilter.ts +0 -98
  136. package/src/utils/generatePreviewBlobUrl.test.ts +0 -68
  137. package/src/utils/generatePreviewBlobUrl.ts +0 -53
  138. package/src/utils/getAssetResolution.test.ts +0 -13
  139. package/src/utils/getAssetResolution.ts +0 -7
  140. package/src/utils/getDocumentAssetIds.test.ts +0 -50
  141. package/src/utils/getDocumentAssetIds.ts +0 -35
  142. package/src/utils/getSchemeColor.test.ts +0 -12
  143. package/src/utils/getSchemeColor.ts +0 -43
  144. package/src/utils/getTagSelectOptions.test.ts +0 -44
  145. package/src/utils/getTagSelectOptions.ts +0 -16
  146. package/src/utils/getUniqueDocuments.test.ts +0 -26
  147. package/src/utils/getUniqueDocuments.ts +0 -15
  148. package/src/utils/imageDprUrl.test.ts +0 -46
  149. package/src/utils/imageDprUrl.ts +0 -27
  150. package/src/utils/isSupportedAssetType.test.ts +0 -16
  151. package/src/utils/isSupportedAssetType.ts +0 -15
  152. package/src/utils/mediaField.ts +0 -73
  153. package/src/utils/sanitizeFormData.test.ts +0 -59
  154. package/src/utils/sanitizeFormData.ts +0 -26
  155. package/src/utils/typeGuards.test.ts +0 -18
  156. package/src/utils/typeGuards.ts +0 -9
  157. package/src/utils/uploadSanityAsset.test.ts +0 -29
  158. package/src/utils/uploadSanityAsset.ts +0 -97
  159. package/src/utils/withMaxConcurrency.test.ts +0 -43
  160. package/src/utils/withMaxConcurrency.ts +0 -55
  161. package/src/utils/zodFormResolver.ts +0 -17
  162. package/v2-incompatible.js +0 -11
@@ -1,374 +0,0 @@
1
- // @vitest-environment node
2
-
3
- import {describe, expect, it, vi} from 'vitest'
4
-
5
- import {createEpicTestStore} from '../../__tests__/fixtures/createEpicTestStore'
6
- import {createMockSanityClient} from '../../__tests__/fixtures/mockSanityClient'
7
- import type {AssetItem, AssetType, ImageAsset, Tag} from '../../types'
8
- import {assetsActions, initialState as assetsInitialState} from '../assets'
9
- import {ASSETS_ACTIONS} from '../assets/actions'
10
- import {tagsActions} from '../tags'
11
- import {uploadsActions} from '../uploads'
12
- import {
13
- notificationsAssetsDeleteCompleteEpic,
14
- notificationsAssetsDeleteErrorEpic,
15
- notificationsAssetsTagsAddCompleteEpic,
16
- notificationsAssetsTagsRemoveCompleteEpic,
17
- notificationsAssetsUpdateCompleteEpic,
18
- notificationsAssetsUploadCompleteEpic,
19
- notificationsGenericErrorEpic,
20
- notificationsTagCreateCompleteEpic,
21
- notificationsTagDeleteCompleteEpic,
22
- notificationsTagUpdateCompleteEpic,
23
- } from './index'
24
-
25
- const sampleAsset = {
26
- _id: 'a1',
27
- _type: 'sanity.imageAsset',
28
- _createdAt: '',
29
- _updatedAt: '',
30
- _rev: 'r1',
31
- originalFilename: 'x.png',
32
- size: 1,
33
- mimeType: 'image/png',
34
- url: 'https://example.com/x.png',
35
- } as ImageAsset
36
-
37
- const sampleAsset2 = {
38
- ...sampleAsset,
39
- _id: 'a2',
40
- } as ImageAsset
41
-
42
- const sampleTag: Tag = {
43
- _id: 't1',
44
- _type: 'media.tag',
45
- _createdAt: '',
46
- _updatedAt: '',
47
- _rev: 'tr',
48
- name: {_type: 'slug', current: 'alpha'},
49
- }
50
-
51
- const assetItem = (asset: ImageAsset): AssetItem => ({
52
- _type: 'asset',
53
- asset,
54
- picked: false,
55
- updating: false,
56
- })
57
-
58
- function assetsWithRows(rows: Record<string, AssetItem>) {
59
- return {
60
- ...assetsInitialState,
61
- assetTypes: ['image'] as AssetType[],
62
- allIds: Object.keys(rows),
63
- byIds: rows,
64
- }
65
- }
66
-
67
- describe('notificationsAssetsDeleteCompleteEpic', () => {
68
- it('adds info notification with pluralized asset count', async () => {
69
- const store = createEpicTestStore(
70
- notificationsAssetsDeleteCompleteEpic,
71
- createMockSanityClient({}),
72
- {},
73
- )
74
- store.dispatch(assetsActions.deleteComplete({assetIds: ['x', 'y']}))
75
- await vi.waitFor(() => {
76
- expect(store.getState().notifications.items).toEqual([
77
- {asset: undefined, status: 'info', title: '2 assets deleted'},
78
- ])
79
- })
80
- })
81
- })
82
-
83
- describe('notificationsAssetsDeleteErrorEpic', () => {
84
- it('adds error notification with count', async () => {
85
- const store = createEpicTestStore(
86
- notificationsAssetsDeleteErrorEpic,
87
- createMockSanityClient({}),
88
- {
89
- assets: assetsWithRows({
90
- a1: {...assetItem(sampleAsset), updating: true},
91
- }),
92
- },
93
- )
94
- store.dispatch(assetsActions.deleteError({assetIds: ['a1'], error: {} as any}))
95
- await vi.waitFor(() => {
96
- const [n] = store.getState().notifications.items
97
- expect(n!.status).toBe('error')
98
- expect(n!.title).toBe(
99
- 'Unable to delete 1 asset. Please review any asset errors and try again.',
100
- )
101
- })
102
- })
103
- })
104
-
105
- describe('notificationsAssetsUploadCompleteEpic', () => {
106
- it('adds info notification from upload check results count', async () => {
107
- const store = createEpicTestStore(
108
- notificationsAssetsUploadCompleteEpic,
109
- createMockSanityClient({}),
110
- {},
111
- )
112
- store.dispatch(
113
- uploadsActions.checkComplete({
114
- results: {h1: 'id1', h2: null},
115
- }),
116
- )
117
- await vi.waitFor(() => {
118
- expect(store.getState().notifications.items).toEqual([
119
- {asset: undefined, status: 'info', title: 'Uploaded 2 assets'},
120
- ])
121
- })
122
- })
123
- })
124
-
125
- const tagsWithTagUpdating = {
126
- allIds: ['t1'],
127
- byIds: {
128
- t1: {_type: 'tag' as const, tag: sampleTag, picked: false, updating: true},
129
- },
130
- creating: false,
131
- fetchCount: -1,
132
- fetching: false,
133
- panelVisible: true,
134
- }
135
-
136
- describe('notificationsAssetsTagsAddCompleteEpic', () => {
137
- it('adds info notification with asset count', async () => {
138
- const store = createEpicTestStore(
139
- notificationsAssetsTagsAddCompleteEpic,
140
- createMockSanityClient({}),
141
- {
142
- assets: assetsWithRows({
143
- a1: {...assetItem(sampleAsset), updating: true},
144
- a2: {...assetItem(sampleAsset2), updating: true},
145
- }),
146
- tags: tagsWithTagUpdating,
147
- },
148
- )
149
- store.dispatch(
150
- ASSETS_ACTIONS.tagsAddComplete({
151
- assets: [assetItem(sampleAsset), assetItem(sampleAsset2)],
152
- tag: sampleTag,
153
- }),
154
- )
155
- await vi.waitFor(() => {
156
- expect(store.getState().notifications.items).toEqual([
157
- {asset: undefined, status: 'info', title: 'Tag added to 2 assets'},
158
- ])
159
- })
160
- })
161
- })
162
-
163
- describe('notificationsAssetsTagsRemoveCompleteEpic', () => {
164
- it('adds info notification with asset count', async () => {
165
- const store = createEpicTestStore(
166
- notificationsAssetsTagsRemoveCompleteEpic,
167
- createMockSanityClient({}),
168
- {
169
- assets: assetsWithRows({
170
- a1: {...assetItem(sampleAsset), updating: true},
171
- }),
172
- tags: tagsWithTagUpdating,
173
- },
174
- )
175
- store.dispatch(
176
- ASSETS_ACTIONS.tagsRemoveComplete({
177
- assets: [assetItem(sampleAsset)],
178
- tag: sampleTag,
179
- }),
180
- )
181
- await vi.waitFor(() => {
182
- expect(store.getState().notifications.items).toEqual([
183
- {asset: undefined, status: 'info', title: 'Tag removed from 1 asset'},
184
- ])
185
- })
186
- })
187
- })
188
-
189
- describe('notificationsAssetsUpdateCompleteEpic', () => {
190
- it('batches multiple updateComplete actions into one notification after buffer window', async () => {
191
- vi.useFakeTimers()
192
- const store = createEpicTestStore(
193
- notificationsAssetsUpdateCompleteEpic,
194
- createMockSanityClient({}),
195
- {
196
- assets: assetsWithRows({
197
- a1: {...assetItem(sampleAsset), updating: true},
198
- a2: {...assetItem(sampleAsset2), updating: true},
199
- }),
200
- },
201
- )
202
-
203
- store.dispatch(assetsActions.updateComplete({asset: sampleAsset}))
204
- store.dispatch(assetsActions.updateComplete({asset: sampleAsset2}))
205
- await vi.advanceTimersByTimeAsync(2000)
206
-
207
- expect(store.getState().notifications.items).toEqual([
208
- {asset: undefined, status: 'info', title: '2 assets updated'},
209
- ])
210
- vi.useRealTimers()
211
- })
212
-
213
- it('emits separate notifications when updates fall in different buffer windows', async () => {
214
- vi.useFakeTimers()
215
- const store = createEpicTestStore(
216
- notificationsAssetsUpdateCompleteEpic,
217
- createMockSanityClient({}),
218
- {
219
- assets: assetsWithRows({
220
- a1: {...assetItem(sampleAsset), updating: true},
221
- a2: {...assetItem(sampleAsset2), updating: true},
222
- }),
223
- },
224
- )
225
-
226
- store.dispatch(assetsActions.updateComplete({asset: sampleAsset}))
227
- await vi.advanceTimersByTimeAsync(2000)
228
- store.dispatch(assetsActions.updateComplete({asset: sampleAsset2}))
229
- await vi.advanceTimersByTimeAsync(2000)
230
-
231
- expect(store.getState().notifications.items).toEqual([
232
- {asset: undefined, status: 'info', title: '1 asset updated'},
233
- {asset: undefined, status: 'info', title: '1 asset updated'},
234
- ])
235
- vi.useRealTimers()
236
- })
237
- })
238
-
239
- describe('notificationsGenericErrorEpic', () => {
240
- it('maps assets.updateError to error notification title', async () => {
241
- const store = createEpicTestStore(notificationsGenericErrorEpic, createMockSanityClient({}), {
242
- assets: assetsWithRows({
243
- a1: {...assetItem(sampleAsset), updating: true},
244
- }),
245
- })
246
- store.dispatch(
247
- assetsActions.updateError({
248
- asset: sampleAsset,
249
- error: {message: 'patch failed', statusCode: 500},
250
- }),
251
- )
252
- await vi.waitFor(() => {
253
- expect(store.getState().notifications.items).toEqual([
254
- {asset: undefined, status: 'error', title: 'An error occurred: patch failed'},
255
- ])
256
- })
257
- })
258
-
259
- it('maps assets.fetchError (bare HttpError payload) to error notification title', async () => {
260
- const store = createEpicTestStore(notificationsGenericErrorEpic, createMockSanityClient({}), {
261
- assets: {
262
- ...assetsInitialState,
263
- assetTypes: ['image'] as AssetType[],
264
- fetching: true,
265
- },
266
- })
267
- store.dispatch(
268
- assetsActions.fetchError({
269
- message: 'fetch failed',
270
- statusCode: 503,
271
- }),
272
- )
273
- await vi.waitFor(() => {
274
- expect(store.getState().notifications.items[0]!.title).toBe('An error occurred: fetch failed')
275
- })
276
- })
277
-
278
- it('maps tags.createError to error notification title', async () => {
279
- const store = createEpicTestStore(notificationsGenericErrorEpic, createMockSanityClient({}), {
280
- tags: {
281
- creating: true,
282
- creatingError: undefined,
283
- allIds: [],
284
- byIds: {},
285
- fetchCount: -1,
286
- fetching: false,
287
- panelVisible: true,
288
- } as any,
289
- })
290
- store.dispatch(
291
- tagsActions.createError({
292
- name: 'n',
293
- error: {message: 'tag create', statusCode: 400},
294
- }),
295
- )
296
- await vi.waitFor(() => {
297
- expect(store.getState().notifications.items[0]!.title).toBe('An error occurred: tag create')
298
- })
299
- })
300
-
301
- it('maps uploads.uploadError to error notification title', async () => {
302
- const store = createEpicTestStore(notificationsGenericErrorEpic, createMockSanityClient({}), {
303
- uploads: {allIds: ['h'], byIds: {h: {} as any}},
304
- })
305
- store.dispatch(
306
- uploadsActions.uploadError({
307
- hash: 'h',
308
- error: {message: 'upload bad', statusCode: 413},
309
- }),
310
- )
311
- await vi.waitFor(() => {
312
- expect(store.getState().notifications.items[0]!.title).toBe('An error occurred: upload bad')
313
- })
314
- })
315
- })
316
-
317
- describe('notificationsTagCreateCompleteEpic', () => {
318
- it('adds tag created notification', async () => {
319
- const store = createEpicTestStore(
320
- notificationsTagCreateCompleteEpic,
321
- createMockSanityClient({}),
322
- {},
323
- )
324
- store.dispatch(tagsActions.createComplete({tag: sampleTag}))
325
- await vi.waitFor(() => {
326
- expect(store.getState().notifications.items).toEqual([
327
- {asset: undefined, status: 'info', title: 'Tag created'},
328
- ])
329
- })
330
- })
331
- })
332
-
333
- describe('notificationsTagDeleteCompleteEpic', () => {
334
- it('adds tag deleted notification', async () => {
335
- const store = createEpicTestStore(
336
- notificationsTagDeleteCompleteEpic,
337
- createMockSanityClient({}),
338
- {},
339
- )
340
- store.dispatch(tagsActions.deleteComplete({tagId: 't1'}))
341
- await vi.waitFor(() => {
342
- expect(store.getState().notifications.items).toEqual([
343
- {asset: undefined, status: 'info', title: 'Tag deleted'},
344
- ])
345
- })
346
- })
347
- })
348
-
349
- describe('notificationsTagUpdateCompleteEpic', () => {
350
- it('adds tag updated notification', async () => {
351
- const store = createEpicTestStore(
352
- notificationsTagUpdateCompleteEpic,
353
- createMockSanityClient({}),
354
- {
355
- tags: {
356
- allIds: ['t1'],
357
- byIds: {
358
- t1: {_type: 'tag', tag: sampleTag, picked: false, updating: true},
359
- },
360
- creating: false,
361
- fetchCount: -1,
362
- fetching: false,
363
- panelVisible: true,
364
- },
365
- },
366
- )
367
- store.dispatch(tagsActions.updateComplete({tag: sampleTag}))
368
- await vi.waitFor(() => {
369
- expect(store.getState().notifications.items).toEqual([
370
- {asset: undefined, status: 'info', title: 'Tag updated'},
371
- ])
372
- })
373
- })
374
- })
@@ -1,199 +0,0 @@
1
- import {type PayloadAction, createSlice} from '@reduxjs/toolkit'
2
- import pluralize from 'pluralize'
3
- import type {AnyAction} from 'redux'
4
- import {ofType} from 'redux-observable'
5
- import {of} from 'rxjs'
6
- import {bufferTime, filter, mergeMap} from 'rxjs/operators'
7
-
8
- import type {HttpError, ImageAsset, MyEpic} from '../../types'
9
- import {assetsActions} from '../assets'
10
- import {ASSETS_ACTIONS} from '../assets/actions'
11
- import {tagsActions} from '../tags'
12
- import {uploadsActions} from '../uploads'
13
-
14
- type Notification = {
15
- asset?: ImageAsset
16
- status?: 'error' | 'warning' | 'success' | 'info'
17
- title?: string
18
- }
19
-
20
- type NotificationsReducerState = {
21
- items: Notification[]
22
- }
23
-
24
- function messageFromGenericErrorPayload(payload: unknown): string {
25
- if (!payload || typeof payload !== 'object') {
26
- return 'Unknown error'
27
- }
28
- if (
29
- 'error' in payload &&
30
- payload.error &&
31
- typeof payload.error === 'object' &&
32
- payload.error !== null &&
33
- 'message' in payload.error
34
- ) {
35
- return String((payload.error as HttpError).message)
36
- }
37
- if ('message' in payload && typeof (payload as HttpError).message === 'string') {
38
- return String((payload as HttpError).message)
39
- }
40
- return 'Unknown error'
41
- }
42
-
43
- const initialState = {
44
- items: [],
45
- } as NotificationsReducerState
46
-
47
- const notificationsSlice = createSlice({
48
- name: 'notifications',
49
- initialState,
50
- reducers: {
51
- add(state, action: PayloadAction<Notification>) {
52
- const {asset, status, title} = action.payload
53
- state.items.push({
54
- asset,
55
- status,
56
- title,
57
- })
58
- },
59
- },
60
- })
61
-
62
- // Epics
63
-
64
- export const notificationsAssetsDeleteCompleteEpic: MyEpic = (action$) =>
65
- action$.pipe(
66
- filter(assetsActions.deleteComplete.match),
67
- mergeMap((action) => {
68
- const {assetIds} = action.payload
69
- const deletedCount = assetIds.length
70
- return of(
71
- notificationsSlice.actions.add({
72
- status: 'info',
73
- title: `${deletedCount} ${pluralize('asset', deletedCount)} deleted`,
74
- }),
75
- )
76
- }),
77
- )
78
-
79
- export const notificationsAssetsDeleteErrorEpic: MyEpic = (action$) =>
80
- action$.pipe(
81
- filter(assetsActions.deleteError.match),
82
- mergeMap((action) => {
83
- const {assetIds} = action.payload
84
- const count = assetIds.length
85
- return of(
86
- notificationsSlice.actions.add({
87
- status: 'error',
88
- title: `Unable to delete ${count} ${pluralize(
89
- 'asset',
90
- count,
91
- )}. Please review any asset errors and try again.`,
92
- }),
93
- )
94
- }),
95
- )
96
-
97
- export const notificationsAssetsUploadCompleteEpic: MyEpic = (action$) =>
98
- action$.pipe(
99
- filter(uploadsActions.checkComplete.match),
100
- mergeMap((action) => {
101
- const {results} = action.payload
102
-
103
- const count = Object.keys(results).length
104
- return of(
105
- notificationsSlice.actions.add({
106
- status: 'info',
107
- title: `Uploaded ${count} ${pluralize('asset', count)}`,
108
- }),
109
- )
110
- }),
111
- )
112
-
113
- export const notificationsAssetsTagsAddCompleteEpic: MyEpic = (action$) =>
114
- action$.pipe(
115
- filter(ASSETS_ACTIONS.tagsAddComplete.match),
116
- mergeMap((action) => {
117
- const count = action?.payload?.assets?.length
118
- return of(
119
- notificationsSlice.actions.add({
120
- status: 'info',
121
- title: `Tag added to ${count} ${pluralize('asset', count)}`,
122
- }),
123
- )
124
- }),
125
- )
126
-
127
- export const notificationsAssetsTagsRemoveCompleteEpic: MyEpic = (action$) =>
128
- action$.pipe(
129
- filter(ASSETS_ACTIONS.tagsRemoveComplete.match),
130
- mergeMap((action) => {
131
- const count = action?.payload?.assets?.length
132
- return of(
133
- notificationsSlice.actions.add({
134
- status: 'info',
135
- title: `Tag removed from ${count} ${pluralize('asset', count)}`,
136
- }),
137
- )
138
- }),
139
- )
140
-
141
- export const notificationsAssetsUpdateCompleteEpic: MyEpic = (action$) =>
142
- action$.pipe(
143
- filter(assetsActions.updateComplete.match),
144
- bufferTime(2000),
145
- filter((actions) => actions.length > 0),
146
- mergeMap((actions) => {
147
- const updatedCount = actions.length
148
- return of(
149
- notificationsSlice.actions.add({
150
- status: 'info',
151
- title: `${updatedCount} ${pluralize('asset', updatedCount)} updated`,
152
- }),
153
- )
154
- }),
155
- )
156
-
157
- export const notificationsGenericErrorEpic: MyEpic = (action$) =>
158
- action$.pipe(
159
- ofType(
160
- assetsActions.fetchError.type,
161
- assetsActions.updateError.type,
162
- tagsActions.createError.type,
163
- tagsActions.deleteError.type,
164
- tagsActions.fetchError.type,
165
- tagsActions.updateError.type,
166
- uploadsActions.uploadError.type,
167
- ),
168
- mergeMap((action: AnyAction) => {
169
- const title = `An error occurred: ${messageFromGenericErrorPayload(action['payload'])}`
170
- return of(
171
- notificationsSlice.actions.add({
172
- status: 'error',
173
- title,
174
- }),
175
- )
176
- }),
177
- )
178
-
179
- export const notificationsTagCreateCompleteEpic: MyEpic = (action$) =>
180
- action$.pipe(
181
- filter(tagsActions.createComplete.match),
182
- mergeMap(() => of(notificationsSlice.actions.add({status: 'info', title: `Tag created`}))),
183
- )
184
-
185
- export const notificationsTagDeleteCompleteEpic: MyEpic = (action$) =>
186
- action$.pipe(
187
- filter(tagsActions.deleteComplete.match),
188
- mergeMap(() => of(notificationsSlice.actions.add({status: 'info', title: `Tag deleted`}))),
189
- )
190
-
191
- export const notificationsTagUpdateCompleteEpic: MyEpic = (action$) =>
192
- action$.pipe(
193
- filter(tagsActions.updateComplete.match),
194
- mergeMap(() => of(notificationsSlice.actions.add({status: 'info', title: `Tag updated`}))),
195
- )
196
-
197
- export const notificationsActions = {...notificationsSlice.actions}
198
-
199
- export default notificationsSlice.reducer
@@ -1,54 +0,0 @@
1
- // @vitest-environment node
2
-
3
- import {describe, expect, it} from 'vitest'
4
-
5
- import {createTestRootState} from '../../__tests__/fixtures/rootState'
6
- import type {ImageAsset} from '../../types'
7
- import notificationsReducer, {notificationsActions} from './index'
8
-
9
- const sampleAsset = {
10
- _id: 'a1',
11
- _type: 'sanity.imageAsset',
12
- _createdAt: '',
13
- _updatedAt: '',
14
- _rev: 'r1',
15
- originalFilename: 'x.png',
16
- size: 1,
17
- mimeType: 'image/png',
18
- url: 'https://example.com/x.png',
19
- } as ImageAsset
20
-
21
- describe('notificationsReducer', () => {
22
- it('starts with no items', () => {
23
- const state = notificationsReducer(undefined, {type: '@@init'})
24
- expect(state.items).toEqual([])
25
- })
26
-
27
- it('add appends a notification with asset, status, and title', () => {
28
- const prev = createTestRootState().notifications
29
- const next = notificationsReducer(
30
- prev,
31
- notificationsActions.add({
32
- asset: sampleAsset,
33
- status: 'success',
34
- title: 'Done',
35
- }),
36
- )
37
- expect(next.items).toHaveLength(1)
38
- expect(next.items[0]).toEqual({
39
- asset: sampleAsset,
40
- status: 'success',
41
- title: 'Done',
42
- })
43
- })
44
-
45
- it('add allows partial payloads', () => {
46
- let state = createTestRootState().notifications
47
- state = notificationsReducer(state, notificationsActions.add({status: 'error', title: 'X'}))
48
- state = notificationsReducer(state, notificationsActions.add({title: 'Y'}))
49
- expect(state.items).toEqual([
50
- {asset: undefined, status: 'error', title: 'X'},
51
- {asset: undefined, status: undefined, title: 'Y'},
52
- ])
53
- })
54
- })
@@ -1,36 +0,0 @@
1
- // @vitest-environment node
2
-
3
- import {describe, expect, it} from 'vitest'
4
-
5
- import {inputs} from '../../config/searchFacets'
6
- import searchReducer, {searchActions} from './index'
7
-
8
- describe('search slice', () => {
9
- it('facetsAdd assigns an id and appends facet', () => {
10
- let state = searchReducer(undefined, {type: '@@INIT'} as never)
11
- state = searchReducer(state, searchActions.facetsAdd({facet: {...inputs.title}}))
12
- expect(state.facets).toHaveLength(1)
13
- expect(state.facets[0]!.name).toBe('title')
14
- expect(state.facets[0]!.id).toBeDefined()
15
- })
16
-
17
- it('querySet updates search string', () => {
18
- let state = searchReducer(undefined, {type: '@@INIT'} as never)
19
- state = searchReducer(state, searchActions.querySet({searchQuery: 'cats'}))
20
- expect(state.query).toBe('cats')
21
- })
22
-
23
- it('facetsClear removes all facets', () => {
24
- let state = searchReducer(undefined, {type: '@@INIT'} as never)
25
- state = searchReducer(state, searchActions.facetsAdd({facet: {...inputs.title}}))
26
- state = searchReducer(state, searchActions.facetsClear())
27
- expect(state.facets).toHaveLength(0)
28
- })
29
-
30
- it('facetsRemoveByName filters facets', () => {
31
- let state = searchReducer(undefined, {type: '@@INIT'} as never)
32
- state = searchReducer(state, searchActions.facetsAdd({facet: {...inputs.title}}))
33
- state = searchReducer(state, searchActions.facetsRemoveByName({facetName: 'title'}))
34
- expect(state.facets).toHaveLength(0)
35
- })
36
- })