sanity-plugin-mux-input 3.0.4 → 4.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 (130) hide show
  1. package/README.md +0 -2
  2. package/dist/index.d.ts +134 -193
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +2893 -4417
  5. package/dist/index.js.map +1 -1
  6. package/package.json +33 -43
  7. package/dist/index.cjs +0 -7270
  8. package/dist/index.cjs.map +0 -1
  9. package/dist/index.d.cts +0 -347
  10. package/sanity.json +0 -8
  11. package/src/_exports/index.ts +0 -73
  12. package/src/actions/assets.ts +0 -152
  13. package/src/actions/secrets.ts +0 -111
  14. package/src/actions/upload.ts +0 -310
  15. package/src/clients/upChunkObservable.ts +0 -54
  16. package/src/components/AddCaptionDialog.tsx +0 -440
  17. package/src/components/CaptionsDialog.tsx +0 -23
  18. package/src/components/ConfigureApi.styled.tsx +0 -19
  19. package/src/components/ConfigureApi.tsx +0 -296
  20. package/src/components/DraggableWatermark.tsx +0 -877
  21. package/src/components/EditCaptionDialog.tsx +0 -510
  22. package/src/components/EditThumbnailDialog.tsx +0 -122
  23. package/src/components/ErrorBoundaryCard.tsx +0 -96
  24. package/src/components/FileInputButton.tsx +0 -54
  25. package/src/components/FileInputMenuItem.styled.tsx +0 -36
  26. package/src/components/FileInputMenuItem.tsx +0 -85
  27. package/src/components/FormField.tsx +0 -38
  28. package/src/components/IconInfo.tsx +0 -22
  29. package/src/components/ImportVideosFromMux.tsx +0 -342
  30. package/src/components/Input.styled.tsx +0 -22
  31. package/src/components/Input.tsx +0 -78
  32. package/src/components/InputBrowser.tsx +0 -41
  33. package/src/components/InputError.tsx +0 -17
  34. package/src/components/MuxLogo.tsx +0 -42
  35. package/src/components/Onboard.tsx +0 -65
  36. package/src/components/PageSelector.tsx +0 -54
  37. package/src/components/Player.styled.tsx +0 -55
  38. package/src/components/Player.tsx +0 -117
  39. package/src/components/PlayerActionsMenu.tsx +0 -190
  40. package/src/components/ResyncMetadata.tsx +0 -280
  41. package/src/components/SelectAsset.tsx +0 -39
  42. package/src/components/SelectSortOptions.tsx +0 -45
  43. package/src/components/SpinnerBox.tsx +0 -16
  44. package/src/components/StudioTool.tsx +0 -24
  45. package/src/components/TextTracksEditor.tsx +0 -117
  46. package/src/components/TextTracksManager.tsx +0 -737
  47. package/src/components/UploadConfiguration.tsx +0 -694
  48. package/src/components/UploadPlaceholder.tsx +0 -88
  49. package/src/components/UploadProgress.tsx +0 -80
  50. package/src/components/Uploader.styled.tsx +0 -66
  51. package/src/components/Uploader.tsx +0 -498
  52. package/src/components/VideoDetails/DeleteDialog.tsx +0 -147
  53. package/src/components/VideoDetails/VideoDetails.tsx +0 -358
  54. package/src/components/VideoDetails/VideoReferences.tsx +0 -63
  55. package/src/components/VideoDetails/useVideoDetails.ts +0 -103
  56. package/src/components/VideoInBrowser.tsx +0 -245
  57. package/src/components/VideoMetadata.tsx +0 -45
  58. package/src/components/VideoPlayer.tsx +0 -235
  59. package/src/components/VideoThumbnail.tsx +0 -138
  60. package/src/components/VideosBrowser.tsx +0 -100
  61. package/src/components/documentPreview/DocumentPreview.tsx +0 -83
  62. package/src/components/documentPreview/DraftStatus.tsx +0 -34
  63. package/src/components/documentPreview/MissingSchemaType.tsx +0 -32
  64. package/src/components/documentPreview/PaneItemPreview.tsx +0 -74
  65. package/src/components/documentPreview/PublishedStatus.tsx +0 -35
  66. package/src/components/documentPreview/TimeAgo.tsx +0 -12
  67. package/src/components/documentPreview/paneItemTypes.ts +0 -7
  68. package/src/components/icons/Audio.tsx +0 -13
  69. package/src/components/icons/Resolution.tsx +0 -12
  70. package/src/components/icons/StopWatch.tsx +0 -20
  71. package/src/components/icons/ToolIcon.tsx +0 -19
  72. package/src/components/uploadConfiguration/PlaybackPolicy.tsx +0 -133
  73. package/src/components/uploadConfiguration/PlaybackPolicyOption.tsx +0 -76
  74. package/src/components/uploadConfiguration/PlaybackPolicyWarning.tsx +0 -29
  75. package/src/components/uploadConfiguration/ResolutionTierSelector.tsx +0 -72
  76. package/src/components/uploadConfiguration/StaticRenditionSelector.tsx +0 -180
  77. package/src/components/withFocusRing/helpers.ts +0 -24
  78. package/src/components/withFocusRing/index.ts +0 -1
  79. package/src/components/withFocusRing/withFocusRing.ts +0 -30
  80. package/src/context/DialogStateContext.tsx +0 -36
  81. package/src/context/DrmPlaybackWarningContext.tsx +0 -93
  82. package/src/hooks/useAccessControl.ts +0 -13
  83. package/src/hooks/useAssetDocumentValues.ts +0 -11
  84. package/src/hooks/useAssets.ts +0 -68
  85. package/src/hooks/useCancelUpload.ts +0 -22
  86. package/src/hooks/useClient.ts +0 -8
  87. package/src/hooks/useDialogState.ts +0 -11
  88. package/src/hooks/useDocReferences.ts +0 -21
  89. package/src/hooks/useFetchFileSize.ts +0 -54
  90. package/src/hooks/useImportMuxAssets.ts +0 -132
  91. package/src/hooks/useInView.ts +0 -42
  92. package/src/hooks/useMediaMetadata.ts +0 -103
  93. package/src/hooks/useMuxAssets.ts +0 -179
  94. package/src/hooks/useMuxPolling.ts +0 -49
  95. package/src/hooks/useResyncAsset.ts +0 -110
  96. package/src/hooks/useResyncMuxMetadata.ts +0 -176
  97. package/src/hooks/useSaveSecrets.ts +0 -78
  98. package/src/hooks/useSecretsDocumentValues.ts +0 -38
  99. package/src/hooks/useSecretsFormState.ts +0 -47
  100. package/src/plugin.tsx +0 -31
  101. package/src/sanity-ui.d.ts +0 -5
  102. package/src/schema.ts +0 -196
  103. package/src/util/addKeysToMuxData.ts +0 -30
  104. package/src/util/areSecretsSignable.ts +0 -5
  105. package/src/util/asserters.ts +0 -36
  106. package/src/util/assetTitlePlaceholder.ts +0 -31
  107. package/src/util/constants.ts +0 -15
  108. package/src/util/convertWatermarkToMux.ts +0 -160
  109. package/src/util/createSearchFilter.ts +0 -76
  110. package/src/util/createUrlParamsObject.ts +0 -29
  111. package/src/util/extractFiles.ts +0 -67
  112. package/src/util/formatBytes.ts +0 -32
  113. package/src/util/formatDriveShareLink.ts +0 -64
  114. package/src/util/formatSeconds.ts +0 -49
  115. package/src/util/generateJwt.ts +0 -57
  116. package/src/util/getAnimatedPosterSrc.ts +0 -26
  117. package/src/util/getPlaybackPolicy.ts +0 -69
  118. package/src/util/getPosterSrc.ts +0 -28
  119. package/src/util/getStoryboardSrc.ts +0 -27
  120. package/src/util/getVideoMetadata.ts +0 -23
  121. package/src/util/getVideoSrc.ts +0 -23
  122. package/src/util/isSigned.ts +0 -20
  123. package/src/util/parsers.ts +0 -5
  124. package/src/util/pluginVersion.ts +0 -1
  125. package/src/util/readSecrets.ts +0 -38
  126. package/src/util/roundPxString.ts +0 -16
  127. package/src/util/textTracks.ts +0 -222
  128. package/src/util/tryWithSuspend.ts +0 -22
  129. package/src/util/types.ts +0 -596
  130. package/v2-incompatible.js +0 -11
@@ -1,510 +0,0 @@
1
- import {DownloadIcon, TranslateIcon, UploadIcon} from '@sanity/icons'
2
- import {
3
- Autocomplete,
4
- Button,
5
- Card,
6
- Dialog,
7
- Flex,
8
- Label,
9
- Spinner,
10
- Stack,
11
- Text,
12
- TextInput,
13
- useToast,
14
- } from '@sanity/ui'
15
- import LanguagesList from 'iso-639-1'
16
- import {useEffect, useId, useRef, useState} from 'react'
17
-
18
- import {addTextTrackFromUrl, deleteTextTrack, getAsset} from '../actions/assets'
19
- import {useClient} from '../hooks/useClient'
20
- import {addKeysToMuxData} from '../util/addKeysToMuxData'
21
- import {generateJwt} from '../util/generateJwt'
22
- import {getPlaybackId} from '../util/getPlaybackPolicy'
23
- import {getPlaybackPolicy} from '../util/getPlaybackPolicy'
24
- import {downloadVttFile, extractErrorMessage, pollTrackStatus} from '../util/textTracks'
25
- import type {MuxTextTrack, VideoAssetDocument} from '../util/types'
26
-
27
- const LANGUAGE_OPTIONS = LanguagesList.getAllCodes().map((code) => ({
28
- value: code,
29
- label: LanguagesList.getNativeName(code),
30
- }))
31
-
32
- export interface Props {
33
- asset: VideoAssetDocument
34
- track: MuxTextTrack
35
- onUpdate: (track: MuxTextTrack, oldTrackId?: string) => void
36
- onClose: () => void
37
- }
38
-
39
- export default function EditCaptionDialog({asset, track, onUpdate, onClose}: Props) {
40
- const client = useClient()
41
- const toast = useToast()
42
- const dialogId = `EditCaptionDialog${useId()}`
43
-
44
- const isAutogenerated =
45
- track.text_source === 'generated_live' ||
46
- track.text_source === 'generated_live_final' ||
47
- track.text_source === 'generated_vod'
48
-
49
- const [vttUrl, setVttUrl] = useState('')
50
- const [languageCode, setLanguageCode] = useState(track.language_code || '')
51
- const [selectedLanguage, setSelectedLanguage] = useState<{value: string; label: string} | null>(
52
- () => {
53
- const baseCode = track.language_code?.split('-')[0]
54
- const found = LANGUAGE_OPTIONS.find(
55
- (opt) => opt.value === track.language_code || opt.value === baseCode
56
- )
57
- if (found) return found
58
- if (track.name) {
59
- const foundByName = LANGUAGE_OPTIONS.find((opt) => opt.label === track.name)
60
- if (foundByName) return foundByName
61
- }
62
- return null
63
- }
64
- )
65
- const [name, setName] = useState(track.name || '')
66
- const [isSubmitting, setIsSubmitting] = useState(false)
67
- const [downloading, setDownloading] = useState(false)
68
- const [selectedFile, setSelectedFile] = useState<File | null>(null)
69
- const fileInputRef = useRef<HTMLInputElement>(null)
70
-
71
- useEffect(() => {
72
- setLanguageCode(track.language_code || '')
73
- setName(track.name || '')
74
- setVttUrl('')
75
- const baseCode = track.language_code?.split('-')[0]
76
- const foundByCode = LANGUAGE_OPTIONS.find(
77
- (opt) => opt.value === track.language_code || opt.value === baseCode
78
- )
79
- const foundByName = track.name ? LANGUAGE_OPTIONS.find((opt) => opt.label === track.name) : null
80
- setSelectedLanguage(foundByCode || foundByName || null)
81
- }, [track, asset, client])
82
-
83
- const handleDownloadCurrentFile = async () => {
84
- setDownloading(true)
85
- try {
86
- await downloadVttFile(client, asset, track)
87
- } catch (error) {
88
- let errorMessage = 'Please try again'
89
- let title = 'Failed to download VTT file'
90
-
91
- if (error instanceof Error) {
92
- errorMessage = error.message
93
- if (error.message.includes('Track')) {
94
- title = 'Cannot download'
95
- }
96
- } else if (error === 'Track ID is missing' || error === 'Track is not ready yet') {
97
- errorMessage = String(error)
98
- title = 'Cannot download'
99
- }
100
-
101
- toast.push({
102
- title,
103
- status: 'error',
104
- description: errorMessage,
105
- })
106
- } finally {
107
- setDownloading(false)
108
- }
109
- }
110
-
111
- const getCurrentFileName = () => {
112
- if (track.id && asset.filename) {
113
- return `${asset.filename}-${track.language_code || 'en'}.vtt`
114
- }
115
- return `captions-${track.language_code || 'en'}.vtt`
116
- }
117
-
118
- const uploadVttFile = async (file: File): Promise<string> => {
119
- const assetDocument = await client.assets.upload('file', file, {
120
- filename: file.name,
121
- })
122
- return assetDocument.url
123
- }
124
-
125
- const refreshAssetData = async () => {
126
- if (!asset._id || !asset.assetId) return
127
- try {
128
- const latestAssetData = await getAsset(client, asset.assetId)
129
- const dataWithKeys = addKeysToMuxData(latestAssetData.data)
130
- await client
131
- .patch(asset._id)
132
- .set({data: dataWithKeys, status: latestAssetData.data.status})
133
- .commit({returnDocuments: false})
134
- } catch (refreshError) {
135
- console.error('Failed to refresh asset data:', refreshError)
136
- }
137
- }
138
-
139
- const handleUpdateTrackWithNewUrl = async () => {
140
- if (!asset.assetId) {
141
- throw new Error('Asset ID is required')
142
- }
143
-
144
- const trimmedName = name.trim()
145
- const trimmedLanguageCode = languageCode.trim()
146
-
147
- const oldTrackId = track.id
148
-
149
- try {
150
- await deleteTextTrack(client, asset.assetId, oldTrackId)
151
- } catch (deleteError) {
152
- toast.push({
153
- title: 'Failed to delete old track',
154
- status: 'error',
155
- description: 'Could not delete the old track. Please try again or delete it manually.',
156
- })
157
- setIsSubmitting(false)
158
- throw deleteError
159
- }
160
-
161
- let vttUrlToUse = vttUrl.trim()
162
-
163
- if (selectedFile) {
164
- try {
165
- vttUrlToUse = await uploadVttFile(selectedFile)
166
- } catch (uploadError) {
167
- toast.push({
168
- title: 'Failed to upload VTT file',
169
- status: 'error',
170
- description: 'Could not upload the VTT file to Sanity. Please try again.',
171
- })
172
- setIsSubmitting(false)
173
- throw uploadError
174
- }
175
- }
176
-
177
- try {
178
- await addTextTrackFromUrl(client, asset.assetId, vttUrlToUse, {
179
- language_code: trimmedLanguageCode,
180
- name: trimmedName,
181
- text_type: 'subtitles',
182
- })
183
- } catch (error: unknown) {
184
- toast.push({
185
- title: 'Failed to update caption track',
186
- status: 'error',
187
- description: extractErrorMessage(error, 'Failed to update caption track'),
188
- })
189
- setIsSubmitting(false)
190
- throw error
191
- }
192
-
193
- const result = await pollTrackStatus({
194
- client,
195
- assetId: asset.assetId,
196
- trackName: trimmedName,
197
- trackLanguageCode: trimmedLanguageCode,
198
- onTrackErrored: async (erroredTrack) => {
199
- const errorMessage =
200
- erroredTrack.error?.messages?.[0] ||
201
- erroredTrack.error?.type ||
202
- 'The track failed to download from the provided URL'
203
- toast.push({
204
- title: 'Caption track failed',
205
- status: 'error',
206
- description: errorMessage,
207
- })
208
- await refreshAssetData()
209
- onUpdate(erroredTrack, oldTrackId)
210
- setIsSubmitting(false)
211
- },
212
- })
213
-
214
- if (!result.found || !result.track) {
215
- toast.push({
216
- title: 'Caption track may have been updated',
217
- status: 'warning',
218
- description:
219
- 'The track was updated but its status could not be determined. It may still be processing. Please refresh the page to see if it appears.',
220
- })
221
- setIsSubmitting(false)
222
- return
223
- }
224
-
225
- if (result.status === 'errored') {
226
- return
227
- }
228
-
229
- await refreshAssetData()
230
-
231
- if (result.status === 'preparing') {
232
- toast.push({
233
- title: 'Caption track is processing',
234
- status: 'info',
235
- description:
236
- 'The track was updated and is being processed. It will appear in the list shortly.',
237
- })
238
- } else {
239
- toast.push({
240
- title: 'Caption track updated',
241
- status: 'success',
242
- description: 'Caption track updated successfully',
243
- })
244
- }
245
-
246
- onUpdate(result.track, oldTrackId)
247
- setIsSubmitting(false)
248
- }
249
-
250
- const handleSubmit = async () => {
251
- if (!name.trim()) {
252
- toast.push({
253
- title: 'Audio name required',
254
- status: 'error',
255
- description: 'Please enter an audio name for this caption track',
256
- })
257
- return
258
- }
259
-
260
- if (!languageCode.trim()) {
261
- toast.push({
262
- title: 'Language code required',
263
- status: 'error',
264
- description: 'Please enter a language code (e.g., en, es, fr)',
265
- })
266
- return
267
- }
268
-
269
- setIsSubmitting(true)
270
-
271
- try {
272
- if (!asset.assetId) {
273
- throw new Error('Asset ID is required')
274
- }
275
-
276
- const originalVttUrl = (() => {
277
- if (isAutogenerated || !track.id) return ''
278
- const playbackId = getPlaybackId(asset)
279
- if (!playbackId) return ''
280
- let url = `https://stream.mux.com/${playbackId}/text/${track.id}.vtt`
281
- if (getPlaybackPolicy(asset)?.policy === 'signed') {
282
- const token = generateJwt(client, playbackId, 'v')
283
- url += `?token=${token}`
284
- }
285
- return url
286
- })()
287
-
288
- const urlChanged =
289
- selectedFile !== null || (vttUrl.trim() && vttUrl.trim() !== originalVttUrl)
290
-
291
- if (!urlChanged) {
292
- toast.push({
293
- title: 'No changes',
294
- status: 'info',
295
- description:
296
- 'Please provide a new VTT file or URL using the "Replace" button or URL field to update the track.',
297
- })
298
- setIsSubmitting(false)
299
- return
300
- }
301
-
302
- if (urlChanged) {
303
- if (!selectedFile && vttUrl.trim()) {
304
- try {
305
- void new URL(vttUrl.trim())
306
- } catch {
307
- toast.push({
308
- title: 'Invalid URL',
309
- status: 'error',
310
- description: 'Please enter a valid URL (e.g., https://example.com/subtitles.vtt)',
311
- })
312
- setIsSubmitting(false)
313
- return
314
- }
315
- }
316
-
317
- if (!selectedFile && !vttUrl.trim()) {
318
- toast.push({
319
- title: 'VTT file or URL required',
320
- status: 'error',
321
- description: 'Please select a VTT file or enter a VTT file URL',
322
- })
323
- setIsSubmitting(false)
324
- return
325
- }
326
-
327
- await handleUpdateTrackWithNewUrl()
328
- }
329
-
330
- onClose()
331
- } catch (error) {
332
- toast.push({
333
- title: 'Failed to update caption track',
334
- status: 'error',
335
- description: error instanceof Error ? error.message : 'Please try again',
336
- })
337
- } finally {
338
- setIsSubmitting(false)
339
- }
340
- }
341
-
342
- return (
343
- <Dialog
344
- id={dialogId}
345
- header="Edit Caption Track"
346
- onClose={onClose}
347
- width={1}
348
- onClickOutside={onClose}
349
- >
350
- <Stack padding={4} space={4}>
351
- <Stack space={2}>
352
- <Card padding={3} marginBottom={2} tone="transparent" border radius={2}>
353
- <Flex align="center" justify="space-between">
354
- <Text>{getCurrentFileName()}</Text>
355
- <Flex gap={2}>
356
- {track.status !== 'errored' && (
357
- <Button
358
- icon={
359
- downloading ? (
360
- <Spinner
361
- style={{
362
- verticalAlign: 'middle',
363
- display: 'inline-block',
364
- marginTop: '-2px',
365
- width: '0.5em',
366
- height: '0.5em',
367
- }}
368
- />
369
- ) : (
370
- <DownloadIcon />
371
- )
372
- }
373
- text="Download"
374
- mode="ghost"
375
- tone="primary"
376
- fontSize={1}
377
- padding={2}
378
- onClick={handleDownloadCurrentFile}
379
- disabled={downloading || isSubmitting}
380
- />
381
- )}
382
- <Button
383
- icon={UploadIcon}
384
- text="Replace"
385
- mode="ghost"
386
- tone="primary"
387
- fontSize={1}
388
- padding={2}
389
- onClick={() => fileInputRef.current?.click()}
390
- disabled={isSubmitting}
391
- />
392
- </Flex>
393
- </Flex>
394
- <input
395
- ref={fileInputRef}
396
- type="file"
397
- accept=".vtt,text/vtt"
398
- style={{display: 'none'}}
399
- onChange={(e) => {
400
- if (e.target.files && e.target.files.length > 0 && !isSubmitting) {
401
- setSelectedFile(e.target.files[0])
402
- setVttUrl('')
403
- }
404
- }}
405
- />
406
- {selectedFile && (
407
- <Text size={1} muted style={{marginTop: 8}}>
408
- Selected: {selectedFile.name}
409
- </Text>
410
- )}
411
- </Card>
412
- <Stack space={2}>
413
- <Label htmlFor="vtt-url">VTT File URL</Label>
414
- <TextInput
415
- id="vtt-url"
416
- placeholder="https://example.com/subtitles.vtt"
417
- value={vttUrl}
418
- onChange={(e) => {
419
- setVttUrl(e.currentTarget.value)
420
- setSelectedFile(null)
421
- }}
422
- disabled={isSubmitting}
423
- />
424
- <Text size={1} muted>
425
- Add a URL to replace the existing VTT file with a new one
426
- </Text>
427
- </Stack>
428
- </Stack>
429
-
430
- <Stack space={2}>
431
- <Label htmlFor="caption-name">Audio name</Label>
432
- <Autocomplete
433
- id="caption-name"
434
- value={selectedLanguage?.value || ''}
435
- onChange={(newValue) => {
436
- const selected = LANGUAGE_OPTIONS.find((opt) => opt.value === newValue)
437
- if (selected) {
438
- setSelectedLanguage(selected)
439
- setLanguageCode(selected.value)
440
- setName(selected.label)
441
- }
442
- }}
443
- options={LANGUAGE_OPTIONS}
444
- icon={TranslateIcon}
445
- placeholder="Select language"
446
- filterOption={(query, option) =>
447
- option.label.toLowerCase().indexOf(query.toLowerCase()) > -1 ||
448
- option.value.toLowerCase().indexOf(query.toLowerCase()) > -1
449
- }
450
- openButton
451
- renderValue={(value) => LANGUAGE_OPTIONS.find((l) => l.value === value)?.label || value}
452
- renderOption={(option) => (
453
- <Card data-as="button" padding={3} radius={2} tone="inherit">
454
- <Text size={2} textOverflow="ellipsis">
455
- {option.label} ({option.value})
456
- </Text>
457
- </Card>
458
- )}
459
- disabled={isSubmitting}
460
- />
461
- </Stack>
462
-
463
- <Stack space={2}>
464
- <Label htmlFor="caption-language">Language Code</Label>
465
- <TextInput
466
- id="caption-language"
467
- placeholder="en-US"
468
- value={languageCode}
469
- onChange={(e) => {
470
- setLanguageCode(e.currentTarget.value)
471
- if (selectedLanguage && selectedLanguage.value !== e.currentTarget.value) {
472
- setSelectedLanguage(null)
473
- if (!name || name === selectedLanguage.label) {
474
- setName('')
475
- }
476
- }
477
- }}
478
- disabled={isSubmitting}
479
- />
480
- </Stack>
481
-
482
- <Flex gap={2} justify="flex-end" marginTop={2}>
483
- <Button text="Cancel" mode="ghost" onClick={onClose} disabled={isSubmitting} />
484
- <Button
485
- text="Update Caption Track"
486
- tone="primary"
487
- icon={
488
- isSubmitting ? (
489
- <Spinner
490
- style={{
491
- verticalAlign: 'middle',
492
- display: 'inline-block',
493
- marginBottom: '-3px',
494
- width: '1em',
495
- height: '1em',
496
- marginRight: '-6px',
497
- }}
498
- />
499
- ) : (
500
- UploadIcon
501
- )
502
- }
503
- onClick={handleSubmit}
504
- disabled={isSubmitting}
505
- />
506
- </Flex>
507
- </Stack>
508
- </Dialog>
509
- )
510
- }
@@ -1,122 +0,0 @@
1
- import {Button, Dialog, Flex, Stack, Text, TextInput} from '@sanity/ui'
2
- import React, {useId, useMemo, useState} from 'react'
3
- import {getDevicePixelRatio} from 'use-device-pixel-ratio'
4
-
5
- import {useDialogStateContext} from '../context/DialogStateContext'
6
- import {useClient} from '../hooks/useClient'
7
- import {
8
- formatSecondsToHHMMSS,
9
- getSecondsFromTimeFormat,
10
- isValidTimeFormat,
11
- } from '../util/formatSeconds'
12
- import type {VideoAssetDocument} from '../util/types'
13
- import VideoThumbnail from './VideoThumbnail'
14
-
15
- export interface Props {
16
- asset: VideoAssetDocument
17
- currentTime?: number
18
- }
19
-
20
- export default function EditThumbnailDialog({asset, currentTime = 0}: Props) {
21
- const client = useClient()
22
-
23
- const {setDialogState} = useDialogStateContext()
24
- const dialogId = `EditThumbnailDialog${useId()}`
25
-
26
- const [timeFormatted, setTimeFormatted] = useState<string>(() =>
27
- formatSecondsToHHMMSS(currentTime)
28
- )
29
- const [nextTime, setNextTime] = useState<number>(currentTime)
30
- const [inputError, setInputError] = useState<string>('')
31
-
32
- const assetWithNewThumbnail = useMemo(() => ({...asset, thumbTime: nextTime}), [asset, nextTime])
33
- const [saving, setSaving] = useState(false)
34
- const [saveThumbnailError, setSaveThumbnailError] = useState<Error | null>(null)
35
- const handleSave = () => {
36
- setSaving(true)
37
- client
38
- .patch(asset._id!)
39
- .set({thumbTime: nextTime})
40
- .commit({returnDocuments: false})
41
- .then(() => void setDialogState(false))
42
- .catch(setSaveThumbnailError)
43
- .finally(() => void setSaving(false))
44
- }
45
- const width = 300 * getDevicePixelRatio({maxDpr: 2})
46
-
47
- if (saveThumbnailError) {
48
- // eslint-disable-next-line no-warning-comments
49
- // @TODO handle errors more gracefully
50
- throw saveThumbnailError
51
- }
52
-
53
- const handleInputChange = (event: React.FormEvent<HTMLInputElement>) => {
54
- const value = event.currentTarget.value
55
- setTimeFormatted(value)
56
-
57
- if (isValidTimeFormat(value)) {
58
- setInputError('')
59
- const totalSeconds = getSecondsFromTimeFormat(value)
60
- setNextTime(totalSeconds)
61
- } else {
62
- setInputError('Invalid time format')
63
- }
64
- }
65
-
66
- return (
67
- <Dialog
68
- id={dialogId}
69
- header="Edit thumbnail"
70
- onClose={() => setDialogState(false)}
71
- footer={
72
- <Stack padding={3}>
73
- <Button
74
- key="thumbnail"
75
- disabled={inputError !== ''}
76
- mode="ghost"
77
- tone="primary"
78
- loading={saving}
79
- onClick={handleSave}
80
- text="Set new thumbnail"
81
- />
82
- </Stack>
83
- }
84
- >
85
- <Stack space={3} padding={3}>
86
- <Stack space={2}>
87
- <Text size={1} weight="semibold">
88
- Current:
89
- </Text>
90
- <VideoThumbnail asset={asset} width={width} staticImage />
91
- </Stack>
92
- <Stack space={2}>
93
- <Text size={1} weight="semibold">
94
- New:
95
- </Text>
96
- <VideoThumbnail asset={assetWithNewThumbnail} width={width} staticImage />
97
- </Stack>
98
-
99
- <Stack space={2}>
100
- <Flex align={'center'} justify={'center'}>
101
- <Text size={5} weight="semibold">
102
- Or
103
- </Text>
104
- </Flex>
105
- </Stack>
106
-
107
- <Stack space={2}>
108
- <Text size={1} weight="semibold">
109
- Selected time for thumbnail (hh:mm:ss):
110
- </Text>
111
- <TextInput
112
- size={1}
113
- value={timeFormatted}
114
- placeholder="hh:mm:ss"
115
- onChange={handleInputChange}
116
- customValidity={inputError}
117
- />
118
- </Stack>
119
- </Stack>
120
- </Dialog>
121
- )
122
- }