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.
- package/package.json +6 -15
- package/dist/index.cjs +0 -4721
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -239
- package/dist/index.d.cts.map +0 -1
- package/sanity.json +0 -8
- package/src/__tests__/fixtures/createEpicTestStore.ts +0 -28
- package/src/__tests__/fixtures/listenMock.ts +0 -9
- package/src/__tests__/fixtures/mockSanityClient.ts +0 -84
- package/src/__tests__/fixtures/renderWithProviders.tsx +0 -55
- package/src/__tests__/fixtures/rootState.ts +0 -27
- package/src/__tests__/fixtures/withinDialog.ts +0 -28
- package/src/components/AssetGridVirtualized/index.tsx +0 -94
- package/src/components/AssetMetadata/index.tsx +0 -122
- package/src/components/AssetTableVirtualized/index.tsx +0 -73
- package/src/components/AutoTagInputWrapper/index.tsx +0 -85
- package/src/components/Browser/Browser.test.tsx +0 -45
- package/src/components/Browser/index.tsx +0 -90
- package/src/components/Browser/useBrowserInit.ts +0 -126
- package/src/components/ButtonAssetCopy/index.tsx +0 -65
- package/src/components/ButtonViewGroup/index.tsx +0 -39
- package/src/components/CardAsset/CardAsset.test.tsx +0 -323
- package/src/components/CardAsset/index.tsx +0 -290
- package/src/components/CardUpload/index.tsx +0 -161
- package/src/components/Controls/index.tsx +0 -136
- package/src/components/DebugControls/index.tsx +0 -80
- package/src/components/Dialog/index.tsx +0 -11
- package/src/components/DialogAssetEdit/Details.tsx +0 -181
- package/src/components/DialogAssetEdit/DialogAssetEdit.test.tsx +0 -216
- package/src/components/DialogAssetEdit/index.tsx +0 -493
- package/src/components/DialogConfirm/index.tsx +0 -90
- package/src/components/DialogSearchFacets/index.tsx +0 -42
- package/src/components/DialogTagCreate/DialogTagCreate.test.tsx +0 -121
- package/src/components/DialogTagCreate/index.tsx +0 -111
- package/src/components/DialogTagEdit/DialogTagEdit.test.tsx +0 -165
- package/src/components/DialogTagEdit/index.tsx +0 -201
- package/src/components/DialogTags/index.tsx +0 -45
- package/src/components/Dialogs/index.tsx +0 -76
- package/src/components/DocumentList/index.tsx +0 -62
- package/src/components/FileAssetPreview/index.tsx +0 -37
- package/src/components/FileIcon/index.tsx +0 -43
- package/src/components/FormBuilderTool/FormBuilderTool.test.tsx +0 -63
- package/src/components/FormBuilderTool/index.tsx +0 -69
- package/src/components/FormFieldInputLabel/index.tsx +0 -66
- package/src/components/FormFieldInputTags/index.tsx +0 -98
- package/src/components/FormFieldInputText/index.tsx +0 -41
- package/src/components/FormFieldInputTextarea/index.tsx +0 -43
- package/src/components/FormSubmitButton/index.tsx +0 -59
- package/src/components/Header/index.tsx +0 -80
- package/src/components/Image/index.tsx +0 -41
- package/src/components/Items/index.tsx +0 -68
- package/src/components/Notifications/index.tsx +0 -24
- package/src/components/OrderSelect/index.tsx +0 -66
- package/src/components/PickedBar/index.tsx +0 -77
- package/src/components/Progress/index.tsx +0 -38
- package/src/components/ReduxProvider/index.tsx +0 -96
- package/src/components/SearchFacet/index.tsx +0 -66
- package/src/components/SearchFacetNumber/index.tsx +0 -133
- package/src/components/SearchFacetSelect/index.tsx +0 -110
- package/src/components/SearchFacetString/index.tsx +0 -88
- package/src/components/SearchFacetTags/index.tsx +0 -121
- package/src/components/SearchFacets/index.tsx +0 -72
- package/src/components/SearchFacetsControl/index.tsx +0 -140
- package/src/components/TableHeader/index.tsx +0 -110
- package/src/components/TableHeaderItem/index.tsx +0 -61
- package/src/components/TableRowAsset/index.tsx +0 -419
- package/src/components/TableRowUpload/index.tsx +0 -164
- package/src/components/Tag/index.tsx +0 -200
- package/src/components/TagIcon/index.tsx +0 -22
- package/src/components/TagView/index.tsx +0 -39
- package/src/components/TagViewHeader/index.tsx +0 -70
- package/src/components/TagsPanel/index.tsx +0 -40
- package/src/components/TagsVirtualized/index.tsx +0 -160
- package/src/components/TextInputNumber/index.tsx +0 -32
- package/src/components/TextInputSearch/index.tsx +0 -60
- package/src/components/Tool/index.tsx +0 -13
- package/src/components/UploadDropzone/UploadDropzone.test.tsx +0 -40
- package/src/components/UploadDropzone/index.tsx +0 -173
- package/src/config/orders.ts +0 -28
- package/src/config/searchFacets.ts +0 -312
- package/src/constants.ts +0 -87
- package/src/contexts/AssetSourceDispatchContext.tsx +0 -38
- package/src/contexts/DropzoneDispatchContext.tsx +0 -32
- package/src/contexts/ToolOptionsContext.tsx +0 -66
- package/src/formSchema/index.test.ts +0 -56
- package/src/formSchema/index.ts +0 -39
- package/src/hooks/useBreakpointIndex.ts +0 -50
- package/src/hooks/useKeyPress.ts +0 -39
- package/src/hooks/usePortalPopoverProps.ts +0 -13
- package/src/hooks/useTypedSelector.ts +0 -7
- package/src/hooks/useVersionedClient.ts +0 -6
- package/src/index.ts +0 -5
- package/src/modules/assets/actions.ts +0 -42
- package/src/modules/assets/deleteAndUpdateEpics.test.ts +0 -87
- package/src/modules/assets/fetchEpic.test.ts +0 -73
- package/src/modules/assets/index.ts +0 -782
- package/src/modules/assets/reducer.test.ts +0 -91
- package/src/modules/assets/tagsAndListenerEpics.test.ts +0 -206
- package/src/modules/debug/index.ts +0 -28
- package/src/modules/dialog/actions.ts +0 -10
- package/src/modules/dialog/epics.test.ts +0 -168
- package/src/modules/dialog/index.ts +0 -238
- package/src/modules/dialog/reducer.test.ts +0 -185
- package/src/modules/index.ts +0 -117
- package/src/modules/notifications/epics.test.ts +0 -374
- package/src/modules/notifications/index.ts +0 -199
- package/src/modules/notifications/reducer.test.ts +0 -54
- package/src/modules/search/index.test.ts +0 -36
- package/src/modules/search/index.ts +0 -167
- package/src/modules/selected/index.ts +0 -22
- package/src/modules/selectors.test.ts +0 -21
- package/src/modules/selectors.ts +0 -17
- package/src/modules/tags/epics.test.ts +0 -96
- package/src/modules/tags/index.test.ts +0 -42
- package/src/modules/tags/index.ts +0 -540
- package/src/modules/types.ts +0 -3
- package/src/modules/uploads/actions.ts +0 -13
- package/src/modules/uploads/epics.test.ts +0 -109
- package/src/modules/uploads/index.test.ts +0 -59
- package/src/modules/uploads/index.ts +0 -272
- package/src/operators/checkTagName.test.ts +0 -29
- package/src/operators/checkTagName.ts +0 -33
- package/src/operators/debugThrottle.ts +0 -25
- package/src/plugin.tsx +0 -54
- package/src/schemas/tag.ts +0 -28
- package/src/styled/GlobalStyles/index.tsx +0 -40
- package/src/styled/react-select/creatable.tsx +0 -184
- package/src/styled/react-select/single.tsx +0 -184
- package/src/types/index.ts +0 -346
- package/src/types/sanity-ui.d.ts +0 -5
- package/src/utils/applyMediaTags.ts +0 -87
- package/src/utils/blocksToText.test.ts +0 -43
- package/src/utils/blocksToText.ts +0 -27
- package/src/utils/constructFilter.test.ts +0 -120
- package/src/utils/constructFilter.ts +0 -98
- package/src/utils/generatePreviewBlobUrl.test.ts +0 -68
- package/src/utils/generatePreviewBlobUrl.ts +0 -53
- package/src/utils/getAssetResolution.test.ts +0 -13
- package/src/utils/getAssetResolution.ts +0 -7
- package/src/utils/getDocumentAssetIds.test.ts +0 -50
- package/src/utils/getDocumentAssetIds.ts +0 -35
- package/src/utils/getSchemeColor.test.ts +0 -12
- package/src/utils/getSchemeColor.ts +0 -43
- package/src/utils/getTagSelectOptions.test.ts +0 -44
- package/src/utils/getTagSelectOptions.ts +0 -16
- package/src/utils/getUniqueDocuments.test.ts +0 -26
- package/src/utils/getUniqueDocuments.ts +0 -15
- package/src/utils/imageDprUrl.test.ts +0 -46
- package/src/utils/imageDprUrl.ts +0 -27
- package/src/utils/isSupportedAssetType.test.ts +0 -16
- package/src/utils/isSupportedAssetType.ts +0 -15
- package/src/utils/mediaField.ts +0 -73
- package/src/utils/sanitizeFormData.test.ts +0 -59
- package/src/utils/sanitizeFormData.ts +0 -26
- package/src/utils/typeGuards.test.ts +0 -18
- package/src/utils/typeGuards.ts +0 -9
- package/src/utils/uploadSanityAsset.test.ts +0 -29
- package/src/utils/uploadSanityAsset.ts +0 -97
- package/src/utils/withMaxConcurrency.test.ts +0 -43
- package/src/utils/withMaxConcurrency.ts +0 -55
- package/src/utils/zodFormResolver.ts +0 -17
- package/v2-incompatible.js +0 -11
|
@@ -1,540 +0,0 @@
|
|
|
1
|
-
import {createSelector, createSlice, isAnyOf, type PayloadAction} from '@reduxjs/toolkit'
|
|
2
|
-
import type {ClientError, Transaction} from '@sanity/client'
|
|
3
|
-
import groq from 'groq'
|
|
4
|
-
import type {Selector} from 'react-redux'
|
|
5
|
-
import {ofType} from 'redux-observable'
|
|
6
|
-
import {from, Observable, of} from 'rxjs'
|
|
7
|
-
import {bufferTime, catchError, filter, mergeMap, switchMap, withLatestFrom} from 'rxjs/operators'
|
|
8
|
-
|
|
9
|
-
import {TAG_DOCUMENT_NAME} from '../../constants'
|
|
10
|
-
import checkTagName from '../../operators/checkTagName'
|
|
11
|
-
import debugThrottle from '../../operators/debugThrottle'
|
|
12
|
-
import type {Asset, HttpError, MyEpic, TagSelectOption, Tag, TagItem} from '../../types'
|
|
13
|
-
import getTagSelectOptions from '../../utils/getTagSelectOptions'
|
|
14
|
-
import {ASSETS_ACTIONS} from '../assets/actions'
|
|
15
|
-
import {DIALOG_ACTIONS} from '../dialog/actions'
|
|
16
|
-
import type {RootReducerState} from '../types'
|
|
17
|
-
|
|
18
|
-
type TagsReducerState = {
|
|
19
|
-
allIds: string[]
|
|
20
|
-
byIds: Record<string, TagItem>
|
|
21
|
-
creating: boolean
|
|
22
|
-
creatingError?: HttpError
|
|
23
|
-
fetchCount: number
|
|
24
|
-
fetching: boolean
|
|
25
|
-
fetchingError?: HttpError
|
|
26
|
-
// totalCount: number
|
|
27
|
-
panelVisible: boolean
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const initialState = {
|
|
31
|
-
allIds: [],
|
|
32
|
-
byIds: {},
|
|
33
|
-
creating: false,
|
|
34
|
-
creatingError: undefined,
|
|
35
|
-
fetchCount: -1,
|
|
36
|
-
fetching: false,
|
|
37
|
-
fetchingError: undefined,
|
|
38
|
-
panelVisible: true,
|
|
39
|
-
} as TagsReducerState
|
|
40
|
-
|
|
41
|
-
const tagsSlice = createSlice({
|
|
42
|
-
name: 'tags',
|
|
43
|
-
initialState,
|
|
44
|
-
extraReducers: (builder) => {
|
|
45
|
-
builder
|
|
46
|
-
.addCase(DIALOG_ACTIONS.showTagCreate, (state) => {
|
|
47
|
-
delete state.creatingError
|
|
48
|
-
})
|
|
49
|
-
.addCase(DIALOG_ACTIONS.showTagEdit, (state, action) => {
|
|
50
|
-
const {tagId} = action.payload
|
|
51
|
-
delete state.byIds[tagId]!.error
|
|
52
|
-
})
|
|
53
|
-
.addMatcher(
|
|
54
|
-
isAnyOf(
|
|
55
|
-
ASSETS_ACTIONS.tagsAddComplete,
|
|
56
|
-
ASSETS_ACTIONS.tagsAddError,
|
|
57
|
-
ASSETS_ACTIONS.tagsRemoveComplete,
|
|
58
|
-
ASSETS_ACTIONS.tagsRemoveError,
|
|
59
|
-
),
|
|
60
|
-
(state, action) => {
|
|
61
|
-
const {tag} = action.payload
|
|
62
|
-
state.byIds[tag._id]!.updating = false
|
|
63
|
-
},
|
|
64
|
-
)
|
|
65
|
-
.addMatcher(
|
|
66
|
-
isAnyOf(ASSETS_ACTIONS.tagsAddRequest, ASSETS_ACTIONS.tagsRemoveRequest),
|
|
67
|
-
(state, action) => {
|
|
68
|
-
const {tag} = action.payload
|
|
69
|
-
state.byIds[tag._id]!.updating = true
|
|
70
|
-
},
|
|
71
|
-
)
|
|
72
|
-
},
|
|
73
|
-
reducers: {
|
|
74
|
-
createComplete(state, action: PayloadAction<{assetId?: string; tag: Tag}>) {
|
|
75
|
-
const {tag} = action.payload
|
|
76
|
-
state.creating = false
|
|
77
|
-
if (!state.allIds.includes(tag._id)) {
|
|
78
|
-
state.allIds.push(tag._id)
|
|
79
|
-
}
|
|
80
|
-
state.byIds[tag._id] = {
|
|
81
|
-
_type: 'tag',
|
|
82
|
-
picked: false,
|
|
83
|
-
tag,
|
|
84
|
-
updating: false,
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
createError(state, action: PayloadAction<{error: HttpError; name: string}>) {
|
|
88
|
-
state.creating = false
|
|
89
|
-
state.creatingError = action.payload.error
|
|
90
|
-
},
|
|
91
|
-
createRequest(state, _action: PayloadAction<{assetId?: string; name: string}>) {
|
|
92
|
-
state.creating = true
|
|
93
|
-
delete state.creatingError
|
|
94
|
-
},
|
|
95
|
-
deleteComplete(state, action: PayloadAction<{tagId: string}>) {
|
|
96
|
-
const {tagId} = action.payload
|
|
97
|
-
const deleteIndex = state.allIds.indexOf(tagId)
|
|
98
|
-
if (deleteIndex >= 0) {
|
|
99
|
-
state.allIds.splice(deleteIndex, 1)
|
|
100
|
-
}
|
|
101
|
-
delete state.byIds[tagId]
|
|
102
|
-
},
|
|
103
|
-
deleteError(state, action: PayloadAction<{error: HttpError; tag: Tag}>) {
|
|
104
|
-
const {error, tag} = action.payload
|
|
105
|
-
|
|
106
|
-
const tagId = tag?._id
|
|
107
|
-
state.byIds[tagId]!.error = error
|
|
108
|
-
state.byIds[tagId]!.updating = false
|
|
109
|
-
},
|
|
110
|
-
deleteRequest(state, action: PayloadAction<{tag: Tag}>) {
|
|
111
|
-
const tagId = action.payload?.tag?._id
|
|
112
|
-
state.byIds[tagId]!.picked = false
|
|
113
|
-
state.byIds[tagId]!.updating = true
|
|
114
|
-
|
|
115
|
-
Object.keys(state.byIds).forEach((key) => {
|
|
116
|
-
delete state.byIds[key]!.error
|
|
117
|
-
})
|
|
118
|
-
},
|
|
119
|
-
fetchComplete(state, action: PayloadAction<{tags: Tag[]}>) {
|
|
120
|
-
const {tags} = action.payload
|
|
121
|
-
|
|
122
|
-
tags?.forEach((tag) => {
|
|
123
|
-
state.allIds.push(tag._id)
|
|
124
|
-
state.byIds[tag._id] = {
|
|
125
|
-
_type: 'tag',
|
|
126
|
-
picked: false,
|
|
127
|
-
tag,
|
|
128
|
-
updating: false,
|
|
129
|
-
}
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
state.fetching = false
|
|
133
|
-
state.fetchCount = tags.length || 0
|
|
134
|
-
delete state.fetchingError
|
|
135
|
-
},
|
|
136
|
-
fetchError(state, action: PayloadAction<{error: HttpError}>) {
|
|
137
|
-
const {error} = action.payload
|
|
138
|
-
state.fetching = false
|
|
139
|
-
state.fetchingError = error
|
|
140
|
-
},
|
|
141
|
-
fetchRequest: {
|
|
142
|
-
reducer: (state, _action: PayloadAction<{query: string}>) => {
|
|
143
|
-
state.fetching = true
|
|
144
|
-
delete state.fetchingError
|
|
145
|
-
},
|
|
146
|
-
prepare: () => {
|
|
147
|
-
// Construct query
|
|
148
|
-
const query = groq`
|
|
149
|
-
{
|
|
150
|
-
"items": *[
|
|
151
|
-
_type == "${TAG_DOCUMENT_NAME}"
|
|
152
|
-
&& !(_id in path("drafts.**"))
|
|
153
|
-
] {
|
|
154
|
-
_createdAt,
|
|
155
|
-
_updatedAt,
|
|
156
|
-
_id,
|
|
157
|
-
_rev,
|
|
158
|
-
_type,
|
|
159
|
-
name
|
|
160
|
-
} | order(name.current asc),
|
|
161
|
-
}
|
|
162
|
-
`
|
|
163
|
-
return {payload: {query}}
|
|
164
|
-
},
|
|
165
|
-
},
|
|
166
|
-
// Queue batch tag creation
|
|
167
|
-
listenerCreateQueue(_state, _action: PayloadAction<{tag: Tag}>) {
|
|
168
|
-
//
|
|
169
|
-
},
|
|
170
|
-
// Apply created tags (via sanity real-time events)
|
|
171
|
-
listenerCreateQueueComplete(state, action: PayloadAction<{tags: Tag[]}>) {
|
|
172
|
-
const {tags} = action.payload
|
|
173
|
-
|
|
174
|
-
tags?.forEach((tag) => {
|
|
175
|
-
state.byIds[tag._id] = {
|
|
176
|
-
_type: 'tag',
|
|
177
|
-
picked: false,
|
|
178
|
-
tag,
|
|
179
|
-
updating: false,
|
|
180
|
-
}
|
|
181
|
-
if (!state.allIds.includes(tag._id)) {
|
|
182
|
-
state.allIds.push(tag._id)
|
|
183
|
-
}
|
|
184
|
-
})
|
|
185
|
-
},
|
|
186
|
-
// Queue batch tag deletion
|
|
187
|
-
listenerDeleteQueue(_state, _action: PayloadAction<{tagId: string}>) {
|
|
188
|
-
//
|
|
189
|
-
},
|
|
190
|
-
// Apply deleted tags (via sanity real-time events)
|
|
191
|
-
listenerDeleteQueueComplete(state, action: PayloadAction<{tagIds: string[]}>) {
|
|
192
|
-
const {tagIds} = action.payload
|
|
193
|
-
|
|
194
|
-
tagIds?.forEach((tagId) => {
|
|
195
|
-
const deleteIndex = state.allIds.indexOf(tagId)
|
|
196
|
-
if (deleteIndex >= 0) {
|
|
197
|
-
state.allIds.splice(deleteIndex, 1)
|
|
198
|
-
}
|
|
199
|
-
delete state.byIds[tagId]
|
|
200
|
-
})
|
|
201
|
-
},
|
|
202
|
-
// Queue batch tag updates
|
|
203
|
-
listenerUpdateQueue(_state, _action: PayloadAction<{tag: Tag}>) {
|
|
204
|
-
//
|
|
205
|
-
},
|
|
206
|
-
// Apply updated tags (via sanity real-time events)
|
|
207
|
-
listenerUpdateQueueComplete(state, action: PayloadAction<{tags: Tag[]}>) {
|
|
208
|
-
const {tags} = action.payload
|
|
209
|
-
|
|
210
|
-
tags?.forEach((tag) => {
|
|
211
|
-
if (state.byIds[tag._id]) {
|
|
212
|
-
state.byIds[tag._id]!.tag = tag
|
|
213
|
-
}
|
|
214
|
-
})
|
|
215
|
-
},
|
|
216
|
-
// Set tag panel visibility
|
|
217
|
-
panelVisibleSet(state, action: PayloadAction<{panelVisible: boolean}>) {
|
|
218
|
-
const {panelVisible} = action.payload
|
|
219
|
-
state.panelVisible = panelVisible
|
|
220
|
-
},
|
|
221
|
-
// Sort all tags by name
|
|
222
|
-
sort(state) {
|
|
223
|
-
state.allIds.sort((a, b) => {
|
|
224
|
-
const tagA = state.byIds[a]!.tag.name.current
|
|
225
|
-
const tagB = state.byIds[b]!.tag.name.current
|
|
226
|
-
|
|
227
|
-
if (tagA < tagB) {
|
|
228
|
-
return -1
|
|
229
|
-
} else if (tagA > tagB) {
|
|
230
|
-
return 1
|
|
231
|
-
}
|
|
232
|
-
return 0
|
|
233
|
-
})
|
|
234
|
-
},
|
|
235
|
-
updateComplete(state, action: PayloadAction<{closeDialogId?: string; tag: Tag}>) {
|
|
236
|
-
const {tag} = action.payload
|
|
237
|
-
state.byIds[tag._id]!.tag = tag
|
|
238
|
-
state.byIds[tag._id]!.updating = false
|
|
239
|
-
},
|
|
240
|
-
updateError(state, action: PayloadAction<{tag: Tag; error: HttpError}>) {
|
|
241
|
-
const {error, tag} = action.payload
|
|
242
|
-
const tagId = tag?._id
|
|
243
|
-
state.byIds[tagId]!.error = error
|
|
244
|
-
state.byIds[tagId]!.updating = false
|
|
245
|
-
},
|
|
246
|
-
updateRequest(
|
|
247
|
-
state,
|
|
248
|
-
action: PayloadAction<{
|
|
249
|
-
closeDialogId?: string
|
|
250
|
-
formData: Record<string, any>
|
|
251
|
-
tag: Tag
|
|
252
|
-
}>,
|
|
253
|
-
) {
|
|
254
|
-
const {tag} = action.payload
|
|
255
|
-
state.byIds[tag?._id]!.updating = true
|
|
256
|
-
},
|
|
257
|
-
},
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
// Epics
|
|
261
|
-
|
|
262
|
-
// On tag create request:
|
|
263
|
-
// - async check to see if tag already exists
|
|
264
|
-
// - throw if tag already exists
|
|
265
|
-
// - otherwise, create new tag
|
|
266
|
-
export const tagsCreateEpic: MyEpic = (action$, state$, {client}) =>
|
|
267
|
-
action$.pipe(
|
|
268
|
-
filter(tagsSlice.actions.createRequest.match),
|
|
269
|
-
withLatestFrom(state$),
|
|
270
|
-
mergeMap(([action, state]) => {
|
|
271
|
-
const {assetId, name} = action.payload
|
|
272
|
-
|
|
273
|
-
return of(action).pipe(
|
|
274
|
-
debugThrottle(state.debug.badConnection),
|
|
275
|
-
checkTagName(client, name),
|
|
276
|
-
mergeMap(() =>
|
|
277
|
-
client.observable.create({
|
|
278
|
-
_type: TAG_DOCUMENT_NAME,
|
|
279
|
-
name: {
|
|
280
|
-
_type: 'slug',
|
|
281
|
-
current: name,
|
|
282
|
-
},
|
|
283
|
-
}),
|
|
284
|
-
),
|
|
285
|
-
mergeMap((result) => of(tagsSlice.actions.createComplete({assetId, tag: result as Tag}))),
|
|
286
|
-
catchError((error: ClientError) =>
|
|
287
|
-
of(
|
|
288
|
-
tagsSlice.actions.createError({
|
|
289
|
-
error: {
|
|
290
|
-
message: error?.message || 'Internal error',
|
|
291
|
-
statusCode: error?.statusCode || 500,
|
|
292
|
-
},
|
|
293
|
-
name,
|
|
294
|
-
}),
|
|
295
|
-
),
|
|
296
|
-
),
|
|
297
|
-
)
|
|
298
|
-
}),
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
// On tag delete request
|
|
302
|
-
// - find referenced assets
|
|
303
|
-
// - remove tag from referenced assets in a sanity transaction
|
|
304
|
-
export const tagsDeleteEpic: MyEpic = (action$, state$, {client}) =>
|
|
305
|
-
action$.pipe(
|
|
306
|
-
filter(tagsSlice.actions.deleteRequest.match),
|
|
307
|
-
withLatestFrom(state$),
|
|
308
|
-
mergeMap(([action, state]) => {
|
|
309
|
-
const {tag} = action.payload
|
|
310
|
-
return of(action).pipe(
|
|
311
|
-
// Optionally throttle
|
|
312
|
-
debugThrottle(state.debug.badConnection),
|
|
313
|
-
// Fetch assets which reference this tag
|
|
314
|
-
mergeMap(() =>
|
|
315
|
-
client.observable.fetch<Asset[]>(
|
|
316
|
-
groq`*[
|
|
317
|
-
_type in ["sanity.fileAsset", "sanity.imageAsset"]
|
|
318
|
-
&& references(*[_type == "media.tag" && name.current == $tagName]._id)
|
|
319
|
-
] {
|
|
320
|
-
_id,
|
|
321
|
-
_rev,
|
|
322
|
-
opt
|
|
323
|
-
}`,
|
|
324
|
-
{tagName: tag.name.current},
|
|
325
|
-
),
|
|
326
|
-
),
|
|
327
|
-
// Create transaction which remove tag references from all matched assets and delete tag
|
|
328
|
-
mergeMap((assets) => {
|
|
329
|
-
const patches = assets.map((asset) => ({
|
|
330
|
-
id: asset._id,
|
|
331
|
-
patch: {
|
|
332
|
-
// this will cause the transaction to fail if the document has been modified since it was fetched.
|
|
333
|
-
ifRevisionID: asset._rev,
|
|
334
|
-
unset: [`opt.media.tags[_ref == "${tag._id}"]`],
|
|
335
|
-
},
|
|
336
|
-
}))
|
|
337
|
-
|
|
338
|
-
const transaction: Transaction = patches.reduce(
|
|
339
|
-
(tx, patch) => tx.patch(patch.id, patch.patch),
|
|
340
|
-
client.transaction(),
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
transaction.delete(tag._id)
|
|
344
|
-
|
|
345
|
-
return from(transaction.commit())
|
|
346
|
-
}),
|
|
347
|
-
// Dispatch complete action
|
|
348
|
-
mergeMap(() => of(tagsSlice.actions.deleteComplete({tagId: tag._id}))),
|
|
349
|
-
catchError((error: ClientError) =>
|
|
350
|
-
of(
|
|
351
|
-
tagsSlice.actions.deleteError({
|
|
352
|
-
error: {
|
|
353
|
-
message: error?.message || 'Internal error',
|
|
354
|
-
statusCode: error?.statusCode || 500,
|
|
355
|
-
},
|
|
356
|
-
tag,
|
|
357
|
-
}),
|
|
358
|
-
),
|
|
359
|
-
),
|
|
360
|
-
)
|
|
361
|
-
}),
|
|
362
|
-
)
|
|
363
|
-
|
|
364
|
-
// Async fetch tags
|
|
365
|
-
export const tagsFetchEpic: MyEpic = (action$, state$, {client}) =>
|
|
366
|
-
action$.pipe(
|
|
367
|
-
filter(tagsSlice.actions.fetchRequest.match),
|
|
368
|
-
withLatestFrom(state$),
|
|
369
|
-
switchMap(([action, state]) => {
|
|
370
|
-
const {query} = action.payload
|
|
371
|
-
|
|
372
|
-
return of(action).pipe(
|
|
373
|
-
// Optionally throttle
|
|
374
|
-
debugThrottle(state.debug.badConnection),
|
|
375
|
-
// Fetch tags
|
|
376
|
-
mergeMap(() =>
|
|
377
|
-
client.observable.fetch<{
|
|
378
|
-
items: Tag[]
|
|
379
|
-
}>(query),
|
|
380
|
-
),
|
|
381
|
-
// Dispatch complete action
|
|
382
|
-
mergeMap((result) => {
|
|
383
|
-
const {items} = result
|
|
384
|
-
return of(tagsSlice.actions.fetchComplete({tags: items}))
|
|
385
|
-
}),
|
|
386
|
-
catchError((error: ClientError) =>
|
|
387
|
-
of(
|
|
388
|
-
tagsSlice.actions.fetchError({
|
|
389
|
-
error: {
|
|
390
|
-
message: error?.message || 'Internal error',
|
|
391
|
-
statusCode: error?.statusCode || 500,
|
|
392
|
-
},
|
|
393
|
-
}),
|
|
394
|
-
),
|
|
395
|
-
),
|
|
396
|
-
)
|
|
397
|
-
}),
|
|
398
|
-
)
|
|
399
|
-
|
|
400
|
-
// TODO: merge all buffer epics
|
|
401
|
-
// Buffer tag creation via sanity subscriber
|
|
402
|
-
export const tagsListenerCreateQueueEpic: MyEpic = (action$) =>
|
|
403
|
-
action$.pipe(
|
|
404
|
-
filter(tagsSlice.actions.listenerCreateQueue.match),
|
|
405
|
-
bufferTime(2000),
|
|
406
|
-
filter((actions) => actions.length > 0),
|
|
407
|
-
mergeMap((actions) => {
|
|
408
|
-
const tags = actions?.map((action) => action.payload.tag)
|
|
409
|
-
return of(tagsSlice.actions.listenerCreateQueueComplete({tags}))
|
|
410
|
-
}),
|
|
411
|
-
)
|
|
412
|
-
|
|
413
|
-
// TODO: merge all buffer epics
|
|
414
|
-
// Buffer tag deletion via sanity subscriber
|
|
415
|
-
export const tagsListenerDeleteQueueEpic: MyEpic = (action$) =>
|
|
416
|
-
action$.pipe(
|
|
417
|
-
filter(tagsSlice.actions.listenerDeleteQueue.match),
|
|
418
|
-
bufferTime(2000),
|
|
419
|
-
filter((actions) => actions.length > 0),
|
|
420
|
-
mergeMap((actions) => {
|
|
421
|
-
const tagIds = actions?.map((action) => action.payload.tagId)
|
|
422
|
-
return of(tagsSlice.actions.listenerDeleteQueueComplete({tagIds}))
|
|
423
|
-
}),
|
|
424
|
-
)
|
|
425
|
-
|
|
426
|
-
// TODO: merge all buffer epics
|
|
427
|
-
// Buffer tag update via sanity subscriber
|
|
428
|
-
export const tagsListenerUpdateQueueEpic: MyEpic = (action$) =>
|
|
429
|
-
action$.pipe(
|
|
430
|
-
filter(tagsSlice.actions.listenerUpdateQueue.match),
|
|
431
|
-
bufferTime(2000),
|
|
432
|
-
filter((actions) => actions.length > 0),
|
|
433
|
-
mergeMap((actions) => {
|
|
434
|
-
const tags = actions?.map((action) => action.payload.tag)
|
|
435
|
-
return of(tagsSlice.actions.listenerUpdateQueueComplete({tags}))
|
|
436
|
-
}),
|
|
437
|
-
)
|
|
438
|
-
|
|
439
|
-
// On successful tag creation or updates:
|
|
440
|
-
// - Re-sort all tags
|
|
441
|
-
export const tagsSortEpic: MyEpic = (action$) =>
|
|
442
|
-
action$.pipe(
|
|
443
|
-
ofType(
|
|
444
|
-
tagsSlice.actions.listenerCreateQueueComplete.type,
|
|
445
|
-
tagsSlice.actions.listenerUpdateQueueComplete.type,
|
|
446
|
-
),
|
|
447
|
-
bufferTime(1000),
|
|
448
|
-
filter((actions) => actions.length > 0),
|
|
449
|
-
mergeMap(() => of(tagsSlice.actions.sort())),
|
|
450
|
-
)
|
|
451
|
-
|
|
452
|
-
// On tag update request
|
|
453
|
-
// - check if tag name already exists
|
|
454
|
-
// - throw if tag already exists
|
|
455
|
-
// - otherwise, patch document
|
|
456
|
-
export const tagsUpdateEpic: MyEpic = (action$, state$, {client}) =>
|
|
457
|
-
action$.pipe(
|
|
458
|
-
filter(tagsSlice.actions.updateRequest.match),
|
|
459
|
-
withLatestFrom(state$),
|
|
460
|
-
mergeMap(([action, state]) => {
|
|
461
|
-
const {closeDialogId, formData, tag} = action.payload
|
|
462
|
-
|
|
463
|
-
return of(action).pipe(
|
|
464
|
-
// Optionally throttle
|
|
465
|
-
debugThrottle(state.debug.badConnection),
|
|
466
|
-
// Check if tag name is available, throw early if not
|
|
467
|
-
checkTagName(client, formData?.['name']?.current),
|
|
468
|
-
// Patch document (Update tag)
|
|
469
|
-
mergeMap(
|
|
470
|
-
() =>
|
|
471
|
-
from(
|
|
472
|
-
client
|
|
473
|
-
.patch(tag._id)
|
|
474
|
-
.set({name: {_type: 'slug', current: formData?.['name'].current}})
|
|
475
|
-
.commit(),
|
|
476
|
-
) as Observable<Tag>,
|
|
477
|
-
),
|
|
478
|
-
// Dispatch complete action
|
|
479
|
-
mergeMap((updatedTag: Tag) => {
|
|
480
|
-
return of(
|
|
481
|
-
tagsSlice.actions.updateComplete({
|
|
482
|
-
closeDialogId,
|
|
483
|
-
tag: updatedTag,
|
|
484
|
-
}),
|
|
485
|
-
)
|
|
486
|
-
}),
|
|
487
|
-
catchError((error: ClientError) =>
|
|
488
|
-
of(
|
|
489
|
-
tagsSlice.actions.updateError({
|
|
490
|
-
error: {
|
|
491
|
-
message: error?.message || 'Internal error',
|
|
492
|
-
statusCode: error?.statusCode || 500,
|
|
493
|
-
},
|
|
494
|
-
tag,
|
|
495
|
-
}),
|
|
496
|
-
),
|
|
497
|
-
),
|
|
498
|
-
)
|
|
499
|
-
}),
|
|
500
|
-
)
|
|
501
|
-
|
|
502
|
-
// Selectors
|
|
503
|
-
|
|
504
|
-
const selectTagsByIds = (state: RootReducerState) => state.tags.byIds
|
|
505
|
-
|
|
506
|
-
const selectTagsAllIds = (state: RootReducerState) => state.tags.allIds
|
|
507
|
-
|
|
508
|
-
export const selectTags: Selector<RootReducerState, TagItem[]> = createSelector(
|
|
509
|
-
[selectTagsByIds, selectTagsAllIds],
|
|
510
|
-
(byIds, allIds) => allIds.map((id) => byIds[id]!),
|
|
511
|
-
)
|
|
512
|
-
|
|
513
|
-
export const selectTagById = createSelector(
|
|
514
|
-
[selectTagsByIds, (_state: RootReducerState, tagId: string) => tagId],
|
|
515
|
-
(byIds, tagId) => byIds[tagId],
|
|
516
|
-
)
|
|
517
|
-
|
|
518
|
-
// TODO: use createSelector
|
|
519
|
-
// Map tag references to react-select options, skipping over items with no linked tags
|
|
520
|
-
export const selectTagSelectOptions =
|
|
521
|
-
(asset?: Asset) =>
|
|
522
|
-
(state: RootReducerState): TagSelectOption[] | null => {
|
|
523
|
-
const tags = asset?.opt?.media?.tags?.reduce((acc: TagItem[], v) => {
|
|
524
|
-
const tagItem = state.tags.byIds[v._ref]
|
|
525
|
-
if (tagItem?.tag) {
|
|
526
|
-
acc.push(tagItem)
|
|
527
|
-
}
|
|
528
|
-
return acc
|
|
529
|
-
}, [])
|
|
530
|
-
|
|
531
|
-
if (tags && tags?.length > 0) {
|
|
532
|
-
return getTagSelectOptions(tags)
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
return null
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
export const tagsActions = {...tagsSlice.actions}
|
|
539
|
-
|
|
540
|
-
export default tagsSlice.reducer
|
package/src/modules/types.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import {createAction} from '@reduxjs/toolkit'
|
|
2
|
-
import type {SanityAssetDocument, SanityImageAssetDocument} from '@sanity/client'
|
|
3
|
-
|
|
4
|
-
export const UPLOADS_ACTIONS = {
|
|
5
|
-
uploadComplete: createAction(
|
|
6
|
-
'uploads/uploadComplete',
|
|
7
|
-
function prepare({asset}: {asset: SanityAssetDocument | SanityImageAssetDocument}) {
|
|
8
|
-
return {
|
|
9
|
-
payload: {asset},
|
|
10
|
-
}
|
|
11
|
-
},
|
|
12
|
-
),
|
|
13
|
-
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
|
|
3
|
-
import type {SanityImageAssetDocument} from '@sanity/client'
|
|
4
|
-
import {of} from 'rxjs'
|
|
5
|
-
import {describe, expect, it, vi, beforeEach, afterEach} from 'vitest'
|
|
6
|
-
|
|
7
|
-
import {createEpicTestStore} from '../../__tests__/fixtures/createEpicTestStore'
|
|
8
|
-
import {createMockSanityClient} from '../../__tests__/fixtures/mockSanityClient'
|
|
9
|
-
import {initialState as assetsInitialState} from '../assets'
|
|
10
|
-
import {uploadsAssetStartEpic, uploadsCheckRequestEpic, uploadsActions} from './index'
|
|
11
|
-
|
|
12
|
-
vi.mock('../../utils/generatePreviewBlobUrl', () => ({
|
|
13
|
-
generatePreviewBlobUrl$: () => of('blob:http://preview'),
|
|
14
|
-
}))
|
|
15
|
-
|
|
16
|
-
const uploadedAsset = {
|
|
17
|
-
_id: 'asset-new',
|
|
18
|
-
_type: 'sanity.imageAsset',
|
|
19
|
-
sha1hash: 'deadbeef',
|
|
20
|
-
_createdAt: '',
|
|
21
|
-
_updatedAt: '',
|
|
22
|
-
_rev: 'r',
|
|
23
|
-
originalFilename: 'f.png',
|
|
24
|
-
mimeType: 'image/png',
|
|
25
|
-
size: 10,
|
|
26
|
-
url: '',
|
|
27
|
-
} as SanityImageAssetDocument
|
|
28
|
-
|
|
29
|
-
vi.mock('../../utils/uploadSanityAsset', () => ({
|
|
30
|
-
uploadAsset$: () =>
|
|
31
|
-
of({
|
|
32
|
-
type: 'complete' as const,
|
|
33
|
-
asset: uploadedAsset,
|
|
34
|
-
}),
|
|
35
|
-
hashFile$: () => of('deadbeef'),
|
|
36
|
-
withMaxConcurrency: (fn: unknown) => fn,
|
|
37
|
-
}))
|
|
38
|
-
|
|
39
|
-
describe('uploadsAssetStartEpic', () => {
|
|
40
|
-
it('dispatches preview, progress path, and uploadComplete', async () => {
|
|
41
|
-
const client = createMockSanityClient()
|
|
42
|
-
|
|
43
|
-
const store = createEpicTestStore(uploadsAssetStartEpic, client)
|
|
44
|
-
|
|
45
|
-
const file = new File(['x'], 'f.png', {type: 'image/png'})
|
|
46
|
-
const uploadItem = {
|
|
47
|
-
_type: 'upload' as const,
|
|
48
|
-
assetType: 'image' as const,
|
|
49
|
-
hash: 'deadbeef',
|
|
50
|
-
name: 'f.png',
|
|
51
|
-
size: file.size,
|
|
52
|
-
status: 'queued' as const,
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
store.dispatch(uploadsActions.uploadStart({file, uploadItem}))
|
|
56
|
-
|
|
57
|
-
await vi.waitFor(() => {
|
|
58
|
-
expect(store.getState().uploads.byIds['deadbeef']?.objectUrl).toBe('blob:http://preview')
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
await vi.waitFor(() => {
|
|
62
|
-
expect(store.getState().assets.byIds['asset-new']).toBeDefined()
|
|
63
|
-
})
|
|
64
|
-
})
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
describe('uploadsCheckRequestEpic', () => {
|
|
68
|
-
beforeEach(() => {
|
|
69
|
-
vi.useFakeTimers()
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
afterEach(() => {
|
|
73
|
-
vi.useRealTimers()
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
it('after delay, fetches sha hashes and dispatches checkComplete + insertUploads', async () => {
|
|
77
|
-
const client = createMockSanityClient({
|
|
78
|
-
observable: {
|
|
79
|
-
fetch: vi.fn(() => of(['hh'])),
|
|
80
|
-
},
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
const store = createEpicTestStore(uploadsCheckRequestEpic, client, {
|
|
84
|
-
assets: {...assetsInitialState, assetTypes: ['image']},
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
const asset = {
|
|
88
|
-
_id: 'id-1',
|
|
89
|
-
_type: 'sanity.imageAsset',
|
|
90
|
-
sha1hash: 'hh',
|
|
91
|
-
_createdAt: '',
|
|
92
|
-
_updatedAt: '',
|
|
93
|
-
_rev: 'r',
|
|
94
|
-
originalFilename: 'f.png',
|
|
95
|
-
mimeType: 'image/png',
|
|
96
|
-
size: 1,
|
|
97
|
-
url: '',
|
|
98
|
-
} as SanityImageAssetDocument
|
|
99
|
-
|
|
100
|
-
store.dispatch(uploadsActions.checkRequest({assets: [asset]}))
|
|
101
|
-
|
|
102
|
-
await vi.advanceTimersByTimeAsync(1100)
|
|
103
|
-
|
|
104
|
-
await vi.waitFor(() => {
|
|
105
|
-
expect(client.observable.fetch).toHaveBeenCalled()
|
|
106
|
-
expect(store.getState().uploads.byIds['hh']).toBeUndefined()
|
|
107
|
-
})
|
|
108
|
-
})
|
|
109
|
-
})
|