sanity-plugin-mux-input 3.0.5 → 4.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.
- package/dist/index.js +20 -92
- package/dist/index.js.map +1 -1
- package/package.json +5 -15
- package/dist/index.cjs +0 -5746
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -288
- package/dist/index.d.cts.map +0 -1
- package/sanity.json +0 -8
- package/src/_exports/index.ts +0 -73
- package/src/actions/assets.ts +0 -152
- package/src/actions/secrets.ts +0 -110
- package/src/actions/upload.ts +0 -308
- package/src/clients/upChunkObservable.ts +0 -54
- package/src/components/AddCaptionDialog.tsx +0 -440
- package/src/components/CaptionsDialog.tsx +0 -23
- package/src/components/ConfigureApi.styled.tsx +0 -19
- package/src/components/ConfigureApi.tsx +0 -296
- package/src/components/DraggableWatermark.tsx +0 -885
- package/src/components/EditCaptionDialog.tsx +0 -511
- package/src/components/EditThumbnailDialog.tsx +0 -121
- package/src/components/ErrorBoundaryCard.tsx +0 -97
- package/src/components/FileInputButton.tsx +0 -54
- package/src/components/FileInputMenuItem.styled.tsx +0 -36
- package/src/components/FileInputMenuItem.tsx +0 -85
- package/src/components/FormField.tsx +0 -38
- package/src/components/IconInfo.tsx +0 -22
- package/src/components/ImportVideosFromMux.tsx +0 -339
- package/src/components/Input.styled.tsx +0 -22
- package/src/components/Input.tsx +0 -78
- package/src/components/InputBrowser.tsx +0 -41
- package/src/components/MuxLogo.tsx +0 -42
- package/src/components/Onboard.tsx +0 -65
- package/src/components/PageSelector.tsx +0 -54
- package/src/components/Player.styled.tsx +0 -11
- package/src/components/Player.tsx +0 -117
- package/src/components/PlayerActionsMenu.tsx +0 -191
- package/src/components/ResyncMetadata.tsx +0 -278
- package/src/components/SelectAsset.tsx +0 -39
- package/src/components/SelectSortOptions.tsx +0 -45
- package/src/components/SpinnerBox.tsx +0 -16
- package/src/components/StudioTool.tsx +0 -24
- package/src/components/TextTracksEditor.tsx +0 -117
- package/src/components/TextTracksManager.tsx +0 -738
- package/src/components/UploadConfiguration.tsx +0 -696
- package/src/components/UploadPlaceholder.tsx +0 -88
- package/src/components/UploadProgress.tsx +0 -80
- package/src/components/Uploader.styled.tsx +0 -65
- package/src/components/Uploader.tsx +0 -499
- package/src/components/VideoDetails/DeleteDialog.tsx +0 -148
- package/src/components/VideoDetails/VideoDetails.tsx +0 -358
- package/src/components/VideoDetails/VideoReferences.tsx +0 -63
- package/src/components/VideoDetails/useVideoDetails.ts +0 -103
- package/src/components/VideoInBrowser.tsx +0 -245
- package/src/components/VideoMetadata.tsx +0 -45
- package/src/components/VideoPlayer.tsx +0 -241
- package/src/components/VideoThumbnail.tsx +0 -139
- package/src/components/VideosBrowser.tsx +0 -100
- package/src/components/documentPreview/DocumentPreview.tsx +0 -84
- package/src/components/documentPreview/DraftStatus.tsx +0 -34
- package/src/components/documentPreview/MissingSchemaType.tsx +0 -32
- package/src/components/documentPreview/PaneItemPreview.tsx +0 -67
- package/src/components/documentPreview/PublishedStatus.tsx +0 -35
- package/src/components/documentPreview/TimeAgo.tsx +0 -12
- package/src/components/icons/Audio.tsx +0 -13
- package/src/components/icons/Resolution.tsx +0 -12
- package/src/components/icons/StopWatch.tsx +0 -20
- package/src/components/icons/ToolIcon.tsx +0 -19
- package/src/components/uploadConfiguration/PlaybackPolicy.tsx +0 -133
- package/src/components/uploadConfiguration/PlaybackPolicyOption.tsx +0 -76
- package/src/components/uploadConfiguration/PlaybackPolicyWarning.tsx +0 -29
- package/src/components/uploadConfiguration/ResolutionTierSelector.tsx +0 -72
- package/src/components/uploadConfiguration/StaticRenditionSelector.tsx +0 -180
- package/src/components/withFocusRing/helpers.ts +0 -24
- package/src/components/withFocusRing/index.ts +0 -1
- package/src/components/withFocusRing/withFocusRing.ts +0 -30
- package/src/context/DialogStateContext.tsx +0 -33
- package/src/context/DrmPlaybackWarningContext.tsx +0 -97
- package/src/hooks/useAccessControl.ts +0 -13
- package/src/hooks/useAssetDocumentValues.ts +0 -11
- package/src/hooks/useAssets.ts +0 -73
- package/src/hooks/useCancelUpload.ts +0 -22
- package/src/hooks/useClient.ts +0 -8
- package/src/hooks/useDialogState.ts +0 -11
- package/src/hooks/useDocReferences.ts +0 -21
- package/src/hooks/useFetchFileSize.ts +0 -55
- package/src/hooks/useImportMuxAssets.ts +0 -132
- package/src/hooks/useInView.ts +0 -41
- package/src/hooks/useMediaMetadata.ts +0 -104
- package/src/hooks/useMuxAssets.ts +0 -179
- package/src/hooks/useMuxPolling.ts +0 -52
- package/src/hooks/useResyncAsset.ts +0 -110
- package/src/hooks/useResyncMuxMetadata.ts +0 -169
- package/src/hooks/useSaveSecrets.ts +0 -78
- package/src/hooks/useSecretsDocumentValues.ts +0 -38
- package/src/hooks/useSecretsFormState.ts +0 -47
- package/src/plugin.tsx +0 -31
- package/src/sanity-ui.d.ts +0 -5
- package/src/schema.ts +0 -196
- package/src/util/addKeysToMuxData.ts +0 -30
- package/src/util/asserters.ts +0 -23
- package/src/util/assetTitlePlaceholder.ts +0 -31
- package/src/util/constants.ts +0 -15
- package/src/util/convertWatermarkToMux.ts +0 -160
- package/src/util/createSearchFilter.ts +0 -76
- package/src/util/createUrlParamsObject.ts +0 -29
- package/src/util/extractFiles.ts +0 -67
- package/src/util/formatBytes.ts +0 -31
- package/src/util/formatDriveShareLink.ts +0 -64
- package/src/util/formatSeconds.ts +0 -48
- package/src/util/generateJwt.ts +0 -57
- package/src/util/getAnimatedPosterSrc.ts +0 -26
- package/src/util/getPlaybackPolicy.ts +0 -69
- package/src/util/getPosterSrc.ts +0 -28
- package/src/util/getVideoMetadata.ts +0 -23
- package/src/util/getVideoSrc.ts +0 -23
- package/src/util/parsers.ts +0 -5
- package/src/util/pluginVersion.ts +0 -5
- package/src/util/readSecrets.ts +0 -38
- package/src/util/roundPxString.ts +0 -16
- package/src/util/textTracks.ts +0 -222
- package/src/util/tryWithSuspend.ts +0 -22
- package/src/util/types.ts +0 -566
- package/v2-incompatible.js +0 -11
|
@@ -1,499 +0,0 @@
|
|
|
1
|
-
import {ErrorOutlineIcon} from '@sanity/icons'
|
|
2
|
-
import {Button, type CardTone, Flex, Text, useToast} from '@sanity/ui'
|
|
3
|
-
import React, {useCallback, useEffect, useReducer, useRef, useState} from 'react'
|
|
4
|
-
import {type Observable, Subject, Subscription} from 'rxjs'
|
|
5
|
-
import {takeUntil, tap} from 'rxjs/operators'
|
|
6
|
-
import type {SanityClient} from 'sanity'
|
|
7
|
-
import {PatchEvent, set, setIfMissing} from 'sanity'
|
|
8
|
-
|
|
9
|
-
import {uploadFile, uploadUrl} from '../actions/upload'
|
|
10
|
-
import {DialogStateProvider} from '../context/DialogStateContext'
|
|
11
|
-
import {type DialogState, type SetDialogState} from '../hooks/useDialogState'
|
|
12
|
-
import {isServerError, isValidUrl} from '../util/asserters'
|
|
13
|
-
import {extractDroppedFiles} from '../util/extractFiles'
|
|
14
|
-
import {hasPlaybackPolicy} from '../util/getPlaybackPolicy'
|
|
15
|
-
import type {
|
|
16
|
-
MuxInputProps,
|
|
17
|
-
MuxNewAssetSettings,
|
|
18
|
-
PluginConfig,
|
|
19
|
-
Secrets,
|
|
20
|
-
VideoAssetDocument,
|
|
21
|
-
} from '../util/types'
|
|
22
|
-
import InputBrowser from './InputBrowser'
|
|
23
|
-
import Player from './Player'
|
|
24
|
-
import PlayerActionsMenu from './PlayerActionsMenu'
|
|
25
|
-
import UploadConfiguration from './UploadConfiguration'
|
|
26
|
-
import {UploadCard} from './Uploader.styled'
|
|
27
|
-
import UploadPlaceholder from './UploadPlaceholder'
|
|
28
|
-
import {UploadProgress} from './UploadProgress'
|
|
29
|
-
|
|
30
|
-
interface Props extends Pick<MuxInputProps, 'onChange' | 'readOnly'> {
|
|
31
|
-
config: PluginConfig
|
|
32
|
-
client: SanityClient
|
|
33
|
-
secrets: Secrets
|
|
34
|
-
asset: VideoAssetDocument | null | undefined
|
|
35
|
-
dialogState: DialogState
|
|
36
|
-
setDialogState: SetDialogState
|
|
37
|
-
needsSetup: boolean
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export type StagedUpload = {type: 'file'; files: FileList | File[]} | {type: 'url'; url: string}
|
|
41
|
-
type UploadStatus = {
|
|
42
|
-
progress: number
|
|
43
|
-
file?: {name: string | undefined; type: string}
|
|
44
|
-
uuid?: string
|
|
45
|
-
url?: string
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
interface State {
|
|
49
|
-
stagedUpload: StagedUpload | null
|
|
50
|
-
uploadStatus: UploadStatus | null
|
|
51
|
-
error: Error | null
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const INITIAL_STATE: State = {
|
|
55
|
-
stagedUpload: null,
|
|
56
|
-
uploadStatus: null,
|
|
57
|
-
error: null,
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
type UploadFileEvent = ReturnType<typeof uploadFile> extends Observable<infer T> ? T : never
|
|
61
|
-
type UploadUrlEvent = ReturnType<typeof uploadUrl> extends Observable<infer T> ? T : never
|
|
62
|
-
type UploaderStateAction =
|
|
63
|
-
| {action: 'stageUpload'; input: NonNullable<State['stagedUpload']>}
|
|
64
|
-
| {action: 'commitUpload'}
|
|
65
|
-
| ({action: 'progressInfo'} & (
|
|
66
|
-
| Extract<UploadFileEvent, {type: 'uuid' | 'file'}>
|
|
67
|
-
| Extract<UploadUrlEvent, {type: 'url'}>
|
|
68
|
-
))
|
|
69
|
-
| {action: 'progress'; percent: number}
|
|
70
|
-
| {action: 'error'; error: Error; settings: MuxNewAssetSettings}
|
|
71
|
-
| {action: 'complete' | 'reset'}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* The main interface for inputting a Mux Video. It handles staging an upload
|
|
75
|
-
* file, setting its configuration, displaying upload progress, and showing
|
|
76
|
-
* the preview player.
|
|
77
|
-
*/
|
|
78
|
-
export default function Uploader(props: Props) {
|
|
79
|
-
const toast = useToast()
|
|
80
|
-
const containerRef = useRef<HTMLDivElement>(null)
|
|
81
|
-
|
|
82
|
-
const dragEnteredEls = useRef<EventTarget[]>([])
|
|
83
|
-
const [dragState, setDragState] = useState<'valid' | 'invalid' | null>(null)
|
|
84
|
-
|
|
85
|
-
const cancelUploadButton = useRef(
|
|
86
|
-
(() => {
|
|
87
|
-
const events$ = new Subject()
|
|
88
|
-
return {
|
|
89
|
-
observable: events$.asObservable(),
|
|
90
|
-
handleClick: ((event) => events$.next(event)) as React.MouseEventHandler<HTMLButtonElement>,
|
|
91
|
-
}
|
|
92
|
-
})(),
|
|
93
|
-
).current
|
|
94
|
-
|
|
95
|
-
const uploadRef = useRef<Subscription | null>(null)
|
|
96
|
-
const uploadingDocumentId = useRef<string | null>(null)
|
|
97
|
-
const [state, dispatch] = useReducer(
|
|
98
|
-
// oxlint-disable-next-line react/react-compiler
|
|
99
|
-
(prev: State, action: UploaderStateAction) => {
|
|
100
|
-
switch (action.action) {
|
|
101
|
-
case 'stageUpload':
|
|
102
|
-
return Object.assign({}, INITIAL_STATE, {stagedUpload: action.input})
|
|
103
|
-
case 'commitUpload':
|
|
104
|
-
return Object.assign({}, prev, {uploadStatus: {progress: 0}})
|
|
105
|
-
case 'progressInfo': {
|
|
106
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
107
|
-
const {type, action: _, ...payload} = action
|
|
108
|
-
return Object.assign({}, prev, {
|
|
109
|
-
uploadStatus: {
|
|
110
|
-
...prev.uploadStatus,
|
|
111
|
-
progress: prev.uploadStatus!.progress,
|
|
112
|
-
...payload,
|
|
113
|
-
},
|
|
114
|
-
} satisfies Pick<typeof prev, 'uploadStatus'>)
|
|
115
|
-
}
|
|
116
|
-
case 'progress':
|
|
117
|
-
return Object.assign({}, prev, {
|
|
118
|
-
uploadStatus: {
|
|
119
|
-
...prev.uploadStatus,
|
|
120
|
-
progress: action.percent,
|
|
121
|
-
},
|
|
122
|
-
} satisfies Pick<typeof prev, 'uploadStatus'>)
|
|
123
|
-
case 'reset':
|
|
124
|
-
case 'complete':
|
|
125
|
-
// Clear upload observable on completion
|
|
126
|
-
uploadRef.current?.unsubscribe()
|
|
127
|
-
uploadRef.current = null
|
|
128
|
-
uploadingDocumentId.current = null
|
|
129
|
-
return INITIAL_STATE
|
|
130
|
-
case 'error': {
|
|
131
|
-
// Clear upload observable on error
|
|
132
|
-
uploadRef.current?.unsubscribe()
|
|
133
|
-
uploadRef.current = null
|
|
134
|
-
uploadingDocumentId.current = null
|
|
135
|
-
|
|
136
|
-
let error = action.error
|
|
137
|
-
if (isServerError(action.error) && hasPlaybackPolicy(action.settings, 'drm')) {
|
|
138
|
-
error = new Error(
|
|
139
|
-
'Unknown Error while uploading DRM protected content. Make sure your DRM configuration ID is valid and set correctly',
|
|
140
|
-
)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return Object.assign({}, INITIAL_STATE, {error: error})
|
|
144
|
-
}
|
|
145
|
-
default:
|
|
146
|
-
return prev
|
|
147
|
-
}
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
stagedUpload: null,
|
|
151
|
-
uploadStatus: null,
|
|
152
|
-
error: null,
|
|
153
|
-
},
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
// Make sure we close out the upload observer on dismount
|
|
157
|
-
// and cleanup orphaned documents if upload was in progress
|
|
158
|
-
useEffect(() => {
|
|
159
|
-
const cleanup = () => {
|
|
160
|
-
// Cancel subscription
|
|
161
|
-
if (uploadRef.current && !uploadRef.current.closed) {
|
|
162
|
-
uploadRef.current.unsubscribe()
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Delete orphaned document if upload was in progress and document is different from the saved asset
|
|
166
|
-
if (uploadingDocumentId.current && props.asset?._id !== uploadingDocumentId.current) {
|
|
167
|
-
const docId = uploadingDocumentId.current
|
|
168
|
-
uploadingDocumentId.current = null
|
|
169
|
-
|
|
170
|
-
props.client.delete(docId).catch((err) => {
|
|
171
|
-
console.warn('Failed to cleanup orphaned upload document:', err)
|
|
172
|
-
})
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const handleBeforeUnload = () => {
|
|
177
|
-
cleanup()
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
window.addEventListener('beforeunload', handleBeforeUnload)
|
|
181
|
-
window.addEventListener('pagehide', handleBeforeUnload)
|
|
182
|
-
|
|
183
|
-
return () => {
|
|
184
|
-
window.removeEventListener('beforeunload', handleBeforeUnload)
|
|
185
|
-
window.removeEventListener('pagehide', handleBeforeUnload)
|
|
186
|
-
cleanup()
|
|
187
|
-
}
|
|
188
|
-
}, [props.client, props.asset?._id])
|
|
189
|
-
|
|
190
|
-
/* -------------------------------------------------------------------------- */
|
|
191
|
-
/* Uploading */
|
|
192
|
-
/* -------------------------------------------------------------------------- */
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Begins a file or URL upload with the staged files or URL.
|
|
196
|
-
*
|
|
197
|
-
* Should only be called from the UploadConfiguration component, which provides
|
|
198
|
-
* the Mux configuration for the direct asset upload.
|
|
199
|
-
*
|
|
200
|
-
* @param settings The Mux new_asset_settings object to send to Sanity
|
|
201
|
-
* @param watermark Optional watermark configuration
|
|
202
|
-
* @returns
|
|
203
|
-
*/
|
|
204
|
-
const startUpload = (
|
|
205
|
-
settings: MuxNewAssetSettings,
|
|
206
|
-
watermark?: import('../util/types').WatermarkConfig,
|
|
207
|
-
) => {
|
|
208
|
-
const {stagedUpload} = state
|
|
209
|
-
if (!stagedUpload || uploadRef.current) return
|
|
210
|
-
dispatch({action: 'commitUpload'})
|
|
211
|
-
let uploadObservable: Observable<UploadFileEvent | UploadUrlEvent>
|
|
212
|
-
switch (stagedUpload.type) {
|
|
213
|
-
case 'url':
|
|
214
|
-
uploadObservable = uploadUrl({
|
|
215
|
-
client: props.client,
|
|
216
|
-
url: stagedUpload.url,
|
|
217
|
-
settings,
|
|
218
|
-
watermark,
|
|
219
|
-
})
|
|
220
|
-
break
|
|
221
|
-
case 'file':
|
|
222
|
-
uploadObservable = uploadFile({
|
|
223
|
-
client: props.client,
|
|
224
|
-
file: stagedUpload.files[0]!,
|
|
225
|
-
settings,
|
|
226
|
-
watermark,
|
|
227
|
-
}).pipe(
|
|
228
|
-
takeUntil(
|
|
229
|
-
cancelUploadButton.observable.pipe(
|
|
230
|
-
tap(() => {
|
|
231
|
-
if (uploadingDocumentId.current) {
|
|
232
|
-
void props.client.delete(uploadingDocumentId.current)
|
|
233
|
-
uploadingDocumentId.current = null
|
|
234
|
-
}
|
|
235
|
-
}),
|
|
236
|
-
),
|
|
237
|
-
),
|
|
238
|
-
)
|
|
239
|
-
break
|
|
240
|
-
}
|
|
241
|
-
uploadRef.current = uploadObservable.subscribe({
|
|
242
|
-
next: (event) => {
|
|
243
|
-
switch (event.type) {
|
|
244
|
-
case 'uuid':
|
|
245
|
-
// Track the document ID for cleanup on unmount
|
|
246
|
-
uploadingDocumentId.current = event.uuid
|
|
247
|
-
dispatch({action: 'progressInfo', ...event})
|
|
248
|
-
break
|
|
249
|
-
case 'file':
|
|
250
|
-
case 'url':
|
|
251
|
-
dispatch({action: 'progressInfo', ...event})
|
|
252
|
-
break
|
|
253
|
-
case 'progress':
|
|
254
|
-
dispatch({action: 'progress', percent: event.percent})
|
|
255
|
-
break
|
|
256
|
-
case 'success':
|
|
257
|
-
dispatch({action: 'progress', percent: 100})
|
|
258
|
-
uploadingDocumentId.current = null
|
|
259
|
-
props.onChange(
|
|
260
|
-
PatchEvent.from([
|
|
261
|
-
setIfMissing({asset: {}}),
|
|
262
|
-
set({_type: 'reference', _weak: true, _ref: event.asset._id}, ['asset']),
|
|
263
|
-
]),
|
|
264
|
-
)
|
|
265
|
-
break
|
|
266
|
-
case 'pause':
|
|
267
|
-
case 'resume':
|
|
268
|
-
default:
|
|
269
|
-
break
|
|
270
|
-
}
|
|
271
|
-
},
|
|
272
|
-
complete: () => dispatch({action: 'complete'}),
|
|
273
|
-
error: (error) => dispatch({action: 'error', error, settings}),
|
|
274
|
-
})
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const invalidFileToast = useCallback(() => {
|
|
278
|
-
toast.push({
|
|
279
|
-
status: 'error',
|
|
280
|
-
title: `Invalid file type. Accepted types: ${props.config.acceptedMimeTypes?.join(', ')}`,
|
|
281
|
-
})
|
|
282
|
-
}, [props.config.acceptedMimeTypes, toast])
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Validates if any file in the provided FileList or File array has an unsupported MIME type
|
|
286
|
-
* @param files - FileList or File array to validate
|
|
287
|
-
* @returns true if any file has an invalid MIME type, false if all files are valid
|
|
288
|
-
*/
|
|
289
|
-
const isInvalidFile = (files: FileList | File[]) => {
|
|
290
|
-
const isInvalid = Array.from(files).some((file) => {
|
|
291
|
-
return !props.config.acceptedMimeTypes?.some((acceptedType) => {
|
|
292
|
-
// Convert mime type pattern to regex (e.g., 'audio/*' -> /^audio\/.*$/)
|
|
293
|
-
const pattern = `^${acceptedType.replace('*', '.*')}$`
|
|
294
|
-
return new RegExp(pattern).test(file.type)
|
|
295
|
-
})
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
return isInvalid
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/* -------------------------- Upload Initialization ------------------------- */
|
|
302
|
-
// The below populate the uploadInput state field, which then triggers the
|
|
303
|
-
// upload configuration, or the startUpload function if no config is required.
|
|
304
|
-
|
|
305
|
-
// Stages an upload from the file selector
|
|
306
|
-
const handleUpload = (files: FileList | File[]) => {
|
|
307
|
-
if (isInvalidFile(files)) return
|
|
308
|
-
dispatch({
|
|
309
|
-
action: 'stageUpload',
|
|
310
|
-
input: {type: 'file', files},
|
|
311
|
-
})
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Stages and validates an upload from pasting an asset URL
|
|
315
|
-
const handlePaste: React.ClipboardEventHandler<HTMLInputElement> = (event) => {
|
|
316
|
-
const target = event.target as HTMLElement
|
|
317
|
-
|
|
318
|
-
// Ignore paste coming from the VTT URL input
|
|
319
|
-
if (target.closest('#vtt-url')) {
|
|
320
|
-
return
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
event.preventDefault()
|
|
324
|
-
event.stopPropagation()
|
|
325
|
-
const clipboardData =
|
|
326
|
-
event.clipboardData || (window as Window & {clipboardData?: DataTransfer}).clipboardData
|
|
327
|
-
const url = clipboardData?.getData('text')?.trim()
|
|
328
|
-
if (!isValidUrl(url)) {
|
|
329
|
-
toast.push({status: 'error', title: 'Invalid URL for Mux video input.'})
|
|
330
|
-
return
|
|
331
|
-
}
|
|
332
|
-
dispatch({action: 'stageUpload', input: {type: 'url', url: url}})
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// Stages and validates an upload from dragging+dropping files or folders
|
|
336
|
-
const handleDrop: React.DragEventHandler<HTMLDivElement> = (event) => {
|
|
337
|
-
event.preventDefault()
|
|
338
|
-
event.stopPropagation()
|
|
339
|
-
if (dragState === 'invalid') {
|
|
340
|
-
invalidFileToast()
|
|
341
|
-
setDragState(null)
|
|
342
|
-
return
|
|
343
|
-
}
|
|
344
|
-
setDragState(null)
|
|
345
|
-
void extractDroppedFiles(event.nativeEvent.dataTransfer!).then((files) => {
|
|
346
|
-
dispatch({
|
|
347
|
-
action: 'stageUpload',
|
|
348
|
-
input: {type: 'file', files},
|
|
349
|
-
})
|
|
350
|
-
})
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/* ------------------------------- Drag State ------------------------------- */
|
|
354
|
-
|
|
355
|
-
const handleDragOver: React.DragEventHandler<HTMLDivElement> = (event) => {
|
|
356
|
-
event.preventDefault()
|
|
357
|
-
event.stopPropagation()
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
const handleDragEnter: React.DragEventHandler<HTMLDivElement> = (event) => {
|
|
361
|
-
event.stopPropagation()
|
|
362
|
-
dragEnteredEls.current.push(event.target)
|
|
363
|
-
const type = event.dataTransfer.items?.[0]?.type
|
|
364
|
-
const mimeTypes = props.config.acceptedMimeTypes
|
|
365
|
-
|
|
366
|
-
// Check if the dragged file type matches any of the accepted mime types
|
|
367
|
-
const isValidType = mimeTypes?.some((acceptedType) => {
|
|
368
|
-
// Convert mime type pattern to regex (e.g., 'video/*' -> /^video\/.*$/)
|
|
369
|
-
const pattern = `^${acceptedType.replace('*', '.*')}$`
|
|
370
|
-
return new RegExp(pattern).test(type!)
|
|
371
|
-
})
|
|
372
|
-
|
|
373
|
-
setDragState(isValidType ? 'valid' : 'invalid')
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
const handleDragLeave: React.DragEventHandler<HTMLDivElement> = (event) => {
|
|
377
|
-
event.stopPropagation()
|
|
378
|
-
const idx = dragEnteredEls.current.indexOf(event.target)
|
|
379
|
-
if (idx > -1) {
|
|
380
|
-
dragEnteredEls.current.splice(idx, 1)
|
|
381
|
-
}
|
|
382
|
-
if (dragEnteredEls.current.length === 0) {
|
|
383
|
-
setDragState(null)
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/* -------------------------------- Rendering ------------------------------- */
|
|
388
|
-
|
|
389
|
-
// Upload has errored
|
|
390
|
-
if (state.error !== null) {
|
|
391
|
-
const error = state.error
|
|
392
|
-
return (
|
|
393
|
-
<Flex gap={3} direction="column" justify="center" align="center">
|
|
394
|
-
<Text size={5} muted>
|
|
395
|
-
<ErrorOutlineIcon />
|
|
396
|
-
</Text>
|
|
397
|
-
<Text>Something went wrong</Text>
|
|
398
|
-
{error instanceof Error && error.message && (
|
|
399
|
-
<Text size={1} muted weight="semibold" style={{textAlign: 'center'}}>
|
|
400
|
-
{error.message}
|
|
401
|
-
</Text>
|
|
402
|
-
)}
|
|
403
|
-
<Button text="Upload another file" onClick={() => dispatch({action: 'reset'})} />
|
|
404
|
-
</Flex>
|
|
405
|
-
)
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// Upload is in progress
|
|
409
|
-
if (state.uploadStatus !== null) {
|
|
410
|
-
const {uploadStatus} = state
|
|
411
|
-
return (
|
|
412
|
-
<UploadProgress
|
|
413
|
-
// oxlint-disable-next-line react/react-compiler
|
|
414
|
-
onCancel={cancelUploadButton.handleClick}
|
|
415
|
-
progress={uploadStatus.progress}
|
|
416
|
-
filename={uploadStatus.file?.name || uploadStatus.url}
|
|
417
|
-
/>
|
|
418
|
-
)
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// Upload needs configuration
|
|
422
|
-
if (state.stagedUpload !== null) {
|
|
423
|
-
return (
|
|
424
|
-
<UploadConfiguration
|
|
425
|
-
stagedUpload={state.stagedUpload}
|
|
426
|
-
pluginConfig={props.config}
|
|
427
|
-
secrets={props.secrets}
|
|
428
|
-
startUpload={startUpload}
|
|
429
|
-
onClose={() => dispatch({action: 'reset'})}
|
|
430
|
-
/>
|
|
431
|
-
)
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// Default: No staged upload
|
|
435
|
-
let tone: CardTone | undefined
|
|
436
|
-
if (dragState) tone = dragState === 'valid' ? 'positive' : 'critical'
|
|
437
|
-
|
|
438
|
-
const acceptMimeString = props.config?.acceptedMimeTypes?.length
|
|
439
|
-
? props.config.acceptedMimeTypes.join(',')
|
|
440
|
-
: 'video/*, audio/*'
|
|
441
|
-
|
|
442
|
-
return (
|
|
443
|
-
<>
|
|
444
|
-
<UploadCard
|
|
445
|
-
tone={tone}
|
|
446
|
-
onDrop={handleDrop}
|
|
447
|
-
onDragOver={handleDragOver}
|
|
448
|
-
onDragLeave={handleDragLeave}
|
|
449
|
-
onDragEnter={handleDragEnter}
|
|
450
|
-
onPaste={handlePaste}
|
|
451
|
-
ref={containerRef}
|
|
452
|
-
>
|
|
453
|
-
{props.asset ? (
|
|
454
|
-
<DialogStateProvider
|
|
455
|
-
dialogState={props.dialogState}
|
|
456
|
-
setDialogState={props.setDialogState}
|
|
457
|
-
>
|
|
458
|
-
<Player
|
|
459
|
-
readOnly={props.readOnly}
|
|
460
|
-
asset={props.asset}
|
|
461
|
-
onChange={props.onChange}
|
|
462
|
-
config={props.config}
|
|
463
|
-
buttons={
|
|
464
|
-
<PlayerActionsMenu
|
|
465
|
-
accept={acceptMimeString}
|
|
466
|
-
asset={props.asset}
|
|
467
|
-
dialogState={props.dialogState}
|
|
468
|
-
setDialogState={props.setDialogState}
|
|
469
|
-
onChange={props.onChange}
|
|
470
|
-
onSelect={handleUpload}
|
|
471
|
-
readOnly={props.readOnly}
|
|
472
|
-
config={props.config}
|
|
473
|
-
/>
|
|
474
|
-
}
|
|
475
|
-
/>
|
|
476
|
-
</DialogStateProvider>
|
|
477
|
-
) : (
|
|
478
|
-
<UploadPlaceholder
|
|
479
|
-
accept={acceptMimeString}
|
|
480
|
-
hovering={dragState !== null}
|
|
481
|
-
onSelect={handleUpload}
|
|
482
|
-
readOnly={!!props.readOnly}
|
|
483
|
-
setDialogState={props.setDialogState}
|
|
484
|
-
needsSetup={props.needsSetup}
|
|
485
|
-
config={props.config}
|
|
486
|
-
/>
|
|
487
|
-
)}
|
|
488
|
-
</UploadCard>
|
|
489
|
-
{props.dialogState === 'select-video' && (
|
|
490
|
-
<InputBrowser
|
|
491
|
-
config={props.config}
|
|
492
|
-
asset={props.asset}
|
|
493
|
-
onChange={props.onChange}
|
|
494
|
-
setDialogState={props.setDialogState}
|
|
495
|
-
/>
|
|
496
|
-
)}
|
|
497
|
-
</>
|
|
498
|
-
)
|
|
499
|
-
}
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import {TrashIcon} from '@sanity/icons'
|
|
2
|
-
import {Box, Button, Card, Checkbox, Dialog, Flex, Heading, Stack, Text, useToast} from '@sanity/ui'
|
|
3
|
-
import {useEffect, useState} from 'react'
|
|
4
|
-
import type {SanityDocument} from 'sanity'
|
|
5
|
-
|
|
6
|
-
import {deleteAsset} from '../../actions/assets'
|
|
7
|
-
import {useClient} from '../../hooks/useClient'
|
|
8
|
-
import {DIALOGS_Z_INDEX} from '../../util/constants'
|
|
9
|
-
import type {VideoAssetDocument} from '../../util/types'
|
|
10
|
-
import SpinnerBox from '../SpinnerBox'
|
|
11
|
-
import VideoReferences from './VideoReferences'
|
|
12
|
-
|
|
13
|
-
export default function DeleteDialog({
|
|
14
|
-
asset,
|
|
15
|
-
references,
|
|
16
|
-
referencesLoading,
|
|
17
|
-
cancelDelete,
|
|
18
|
-
succeededDeleting,
|
|
19
|
-
}: {
|
|
20
|
-
asset: VideoAssetDocument
|
|
21
|
-
references?: SanityDocument[]
|
|
22
|
-
referencesLoading: boolean
|
|
23
|
-
cancelDelete: () => void
|
|
24
|
-
succeededDeleting: () => void
|
|
25
|
-
}) {
|
|
26
|
-
const client = useClient()
|
|
27
|
-
const [state, setState] = useState<
|
|
28
|
-
'processing_deletion' | 'checkingReferences' | 'error_deleting' | 'cantDelete' | 'confirm'
|
|
29
|
-
>('checkingReferences')
|
|
30
|
-
const [deleteOnMux, setDeleteOnMux] = useState(true)
|
|
31
|
-
const toast = useToast()
|
|
32
|
-
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
if (state !== 'checkingReferences' || referencesLoading) return
|
|
35
|
-
|
|
36
|
-
// oxlint-disable-next-line react/react-compiler
|
|
37
|
-
setState(references?.length ? 'cantDelete' : 'confirm')
|
|
38
|
-
}, [state, references, referencesLoading])
|
|
39
|
-
|
|
40
|
-
async function confirmDelete() {
|
|
41
|
-
if (state !== 'confirm') return
|
|
42
|
-
|
|
43
|
-
setState('processing_deletion')
|
|
44
|
-
const worked = await deleteAsset({client, asset, deleteOnMux})
|
|
45
|
-
if (worked === true) {
|
|
46
|
-
toast.push({title: 'Successfully deleted video', status: 'success'})
|
|
47
|
-
succeededDeleting()
|
|
48
|
-
} else if (worked === 'failed-mux') {
|
|
49
|
-
toast.push({
|
|
50
|
-
title: 'Deleted video in Sanity',
|
|
51
|
-
description: "But it wasn't deleted in Mux",
|
|
52
|
-
status: 'warning',
|
|
53
|
-
})
|
|
54
|
-
succeededDeleting()
|
|
55
|
-
} else {
|
|
56
|
-
toast.push({title: 'Failed deleting video', status: 'error'})
|
|
57
|
-
|
|
58
|
-
setState('error_deleting')
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<Dialog
|
|
64
|
-
animate
|
|
65
|
-
header={'Delete video'}
|
|
66
|
-
zOffset={DIALOGS_Z_INDEX}
|
|
67
|
-
id="deleting-video-details-dialog"
|
|
68
|
-
onClose={cancelDelete}
|
|
69
|
-
onClickOutside={cancelDelete}
|
|
70
|
-
width={1}
|
|
71
|
-
position="fixed"
|
|
72
|
-
>
|
|
73
|
-
<Card
|
|
74
|
-
padding={3}
|
|
75
|
-
style={{
|
|
76
|
-
minHeight: '150px',
|
|
77
|
-
display: 'flex',
|
|
78
|
-
alignItems: 'center',
|
|
79
|
-
justifyContent: 'center',
|
|
80
|
-
}}
|
|
81
|
-
>
|
|
82
|
-
<Stack space={3}>
|
|
83
|
-
{state === 'checkingReferences' && (
|
|
84
|
-
<>
|
|
85
|
-
<Heading size={2}>Checking if video can be deleted</Heading>
|
|
86
|
-
<SpinnerBox />
|
|
87
|
-
</>
|
|
88
|
-
)}
|
|
89
|
-
{state === 'cantDelete' && (
|
|
90
|
-
<>
|
|
91
|
-
<Heading size={2}>Video can't be deleted</Heading>
|
|
92
|
-
<Text size={2} style={{marginBottom: '2rem'}}>
|
|
93
|
-
There are {references?.length} document{references && references.length > 0 && 's'}{' '}
|
|
94
|
-
pointing to this video. Remove their references to this file or delete them before
|
|
95
|
-
proceeding.
|
|
96
|
-
</Text>
|
|
97
|
-
<VideoReferences references={references} isLoaded={!referencesLoading} />
|
|
98
|
-
</>
|
|
99
|
-
)}
|
|
100
|
-
{state === 'confirm' && (
|
|
101
|
-
<>
|
|
102
|
-
<Heading size={2}>Are you sure you want to delete this video?</Heading>
|
|
103
|
-
<Text size={2}>This action is irreversible</Text>
|
|
104
|
-
<Stack space={4} marginY={4}>
|
|
105
|
-
<Flex align="center" as="label">
|
|
106
|
-
<Checkbox
|
|
107
|
-
checked={deleteOnMux}
|
|
108
|
-
onChange={() => setDeleteOnMux((prev) => !prev)}
|
|
109
|
-
/>
|
|
110
|
-
<Text style={{margin: '0 10px'}}>Delete asset on Mux</Text>
|
|
111
|
-
</Flex>
|
|
112
|
-
<Flex align="center" as="label">
|
|
113
|
-
<Checkbox disabled checked />
|
|
114
|
-
<Text style={{margin: '0 10px'}}>Delete video from dataset</Text>
|
|
115
|
-
</Flex>
|
|
116
|
-
<Box>
|
|
117
|
-
<Button
|
|
118
|
-
icon={TrashIcon}
|
|
119
|
-
fontSize={2}
|
|
120
|
-
padding={3}
|
|
121
|
-
text="Delete video"
|
|
122
|
-
tone="critical"
|
|
123
|
-
onClick={confirmDelete}
|
|
124
|
-
disabled={['processing_deletion', 'checkingReferences', 'cantDelete'].some(
|
|
125
|
-
(s) => s === state,
|
|
126
|
-
)}
|
|
127
|
-
/>
|
|
128
|
-
</Box>
|
|
129
|
-
</Stack>
|
|
130
|
-
</>
|
|
131
|
-
)}
|
|
132
|
-
{state === 'processing_deletion' && (
|
|
133
|
-
<>
|
|
134
|
-
<Heading size={2}>Deleting video...</Heading>
|
|
135
|
-
<SpinnerBox />
|
|
136
|
-
</>
|
|
137
|
-
)}
|
|
138
|
-
{state === 'error_deleting' && (
|
|
139
|
-
<>
|
|
140
|
-
<Heading size={2}>Something went wrong!</Heading>
|
|
141
|
-
<Text size={2}>Try deleting the video again by clicking the button below</Text>
|
|
142
|
-
</>
|
|
143
|
-
)}
|
|
144
|
-
</Stack>
|
|
145
|
-
</Card>
|
|
146
|
-
</Dialog>
|
|
147
|
-
)
|
|
148
|
-
}
|