sanity-plugin-mux-input 3.0.5 → 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 (123) hide show
  1. package/dist/index.js +28 -28
  2. package/dist/index.js.map +1 -1
  3. package/package.json +5 -15
  4. package/dist/index.cjs +0 -5746
  5. package/dist/index.cjs.map +0 -1
  6. package/dist/index.d.cts +0 -288
  7. package/dist/index.d.cts.map +0 -1
  8. package/sanity.json +0 -8
  9. package/src/_exports/index.ts +0 -73
  10. package/src/actions/assets.ts +0 -152
  11. package/src/actions/secrets.ts +0 -110
  12. package/src/actions/upload.ts +0 -308
  13. package/src/clients/upChunkObservable.ts +0 -54
  14. package/src/components/AddCaptionDialog.tsx +0 -440
  15. package/src/components/CaptionsDialog.tsx +0 -23
  16. package/src/components/ConfigureApi.styled.tsx +0 -19
  17. package/src/components/ConfigureApi.tsx +0 -296
  18. package/src/components/DraggableWatermark.tsx +0 -885
  19. package/src/components/EditCaptionDialog.tsx +0 -511
  20. package/src/components/EditThumbnailDialog.tsx +0 -121
  21. package/src/components/ErrorBoundaryCard.tsx +0 -97
  22. package/src/components/FileInputButton.tsx +0 -54
  23. package/src/components/FileInputMenuItem.styled.tsx +0 -36
  24. package/src/components/FileInputMenuItem.tsx +0 -85
  25. package/src/components/FormField.tsx +0 -38
  26. package/src/components/IconInfo.tsx +0 -22
  27. package/src/components/ImportVideosFromMux.tsx +0 -339
  28. package/src/components/Input.styled.tsx +0 -22
  29. package/src/components/Input.tsx +0 -78
  30. package/src/components/InputBrowser.tsx +0 -41
  31. package/src/components/MuxLogo.tsx +0 -42
  32. package/src/components/Onboard.tsx +0 -65
  33. package/src/components/PageSelector.tsx +0 -54
  34. package/src/components/Player.styled.tsx +0 -11
  35. package/src/components/Player.tsx +0 -117
  36. package/src/components/PlayerActionsMenu.tsx +0 -191
  37. package/src/components/ResyncMetadata.tsx +0 -278
  38. package/src/components/SelectAsset.tsx +0 -39
  39. package/src/components/SelectSortOptions.tsx +0 -45
  40. package/src/components/SpinnerBox.tsx +0 -16
  41. package/src/components/StudioTool.tsx +0 -24
  42. package/src/components/TextTracksEditor.tsx +0 -117
  43. package/src/components/TextTracksManager.tsx +0 -738
  44. package/src/components/UploadConfiguration.tsx +0 -696
  45. package/src/components/UploadPlaceholder.tsx +0 -88
  46. package/src/components/UploadProgress.tsx +0 -80
  47. package/src/components/Uploader.styled.tsx +0 -65
  48. package/src/components/Uploader.tsx +0 -499
  49. package/src/components/VideoDetails/DeleteDialog.tsx +0 -148
  50. package/src/components/VideoDetails/VideoDetails.tsx +0 -358
  51. package/src/components/VideoDetails/VideoReferences.tsx +0 -63
  52. package/src/components/VideoDetails/useVideoDetails.ts +0 -103
  53. package/src/components/VideoInBrowser.tsx +0 -245
  54. package/src/components/VideoMetadata.tsx +0 -45
  55. package/src/components/VideoPlayer.tsx +0 -241
  56. package/src/components/VideoThumbnail.tsx +0 -139
  57. package/src/components/VideosBrowser.tsx +0 -100
  58. package/src/components/documentPreview/DocumentPreview.tsx +0 -84
  59. package/src/components/documentPreview/DraftStatus.tsx +0 -34
  60. package/src/components/documentPreview/MissingSchemaType.tsx +0 -32
  61. package/src/components/documentPreview/PaneItemPreview.tsx +0 -67
  62. package/src/components/documentPreview/PublishedStatus.tsx +0 -35
  63. package/src/components/documentPreview/TimeAgo.tsx +0 -12
  64. package/src/components/icons/Audio.tsx +0 -13
  65. package/src/components/icons/Resolution.tsx +0 -12
  66. package/src/components/icons/StopWatch.tsx +0 -20
  67. package/src/components/icons/ToolIcon.tsx +0 -19
  68. package/src/components/uploadConfiguration/PlaybackPolicy.tsx +0 -133
  69. package/src/components/uploadConfiguration/PlaybackPolicyOption.tsx +0 -76
  70. package/src/components/uploadConfiguration/PlaybackPolicyWarning.tsx +0 -29
  71. package/src/components/uploadConfiguration/ResolutionTierSelector.tsx +0 -72
  72. package/src/components/uploadConfiguration/StaticRenditionSelector.tsx +0 -180
  73. package/src/components/withFocusRing/helpers.ts +0 -24
  74. package/src/components/withFocusRing/index.ts +0 -1
  75. package/src/components/withFocusRing/withFocusRing.ts +0 -30
  76. package/src/context/DialogStateContext.tsx +0 -33
  77. package/src/context/DrmPlaybackWarningContext.tsx +0 -97
  78. package/src/hooks/useAccessControl.ts +0 -13
  79. package/src/hooks/useAssetDocumentValues.ts +0 -11
  80. package/src/hooks/useAssets.ts +0 -73
  81. package/src/hooks/useCancelUpload.ts +0 -22
  82. package/src/hooks/useClient.ts +0 -8
  83. package/src/hooks/useDialogState.ts +0 -11
  84. package/src/hooks/useDocReferences.ts +0 -21
  85. package/src/hooks/useFetchFileSize.ts +0 -55
  86. package/src/hooks/useImportMuxAssets.ts +0 -132
  87. package/src/hooks/useInView.ts +0 -41
  88. package/src/hooks/useMediaMetadata.ts +0 -104
  89. package/src/hooks/useMuxAssets.ts +0 -179
  90. package/src/hooks/useMuxPolling.ts +0 -52
  91. package/src/hooks/useResyncAsset.ts +0 -110
  92. package/src/hooks/useResyncMuxMetadata.ts +0 -169
  93. package/src/hooks/useSaveSecrets.ts +0 -78
  94. package/src/hooks/useSecretsDocumentValues.ts +0 -38
  95. package/src/hooks/useSecretsFormState.ts +0 -47
  96. package/src/plugin.tsx +0 -31
  97. package/src/sanity-ui.d.ts +0 -5
  98. package/src/schema.ts +0 -196
  99. package/src/util/addKeysToMuxData.ts +0 -30
  100. package/src/util/asserters.ts +0 -23
  101. package/src/util/assetTitlePlaceholder.ts +0 -31
  102. package/src/util/constants.ts +0 -15
  103. package/src/util/convertWatermarkToMux.ts +0 -160
  104. package/src/util/createSearchFilter.ts +0 -76
  105. package/src/util/createUrlParamsObject.ts +0 -29
  106. package/src/util/extractFiles.ts +0 -67
  107. package/src/util/formatBytes.ts +0 -31
  108. package/src/util/formatDriveShareLink.ts +0 -64
  109. package/src/util/formatSeconds.ts +0 -48
  110. package/src/util/generateJwt.ts +0 -57
  111. package/src/util/getAnimatedPosterSrc.ts +0 -26
  112. package/src/util/getPlaybackPolicy.ts +0 -69
  113. package/src/util/getPosterSrc.ts +0 -28
  114. package/src/util/getVideoMetadata.ts +0 -23
  115. package/src/util/getVideoSrc.ts +0 -23
  116. package/src/util/parsers.ts +0 -5
  117. package/src/util/pluginVersion.ts +0 -5
  118. package/src/util/readSecrets.ts +0 -38
  119. package/src/util/roundPxString.ts +0 -16
  120. package/src/util/textTracks.ts +0 -222
  121. package/src/util/tryWithSuspend.ts +0 -22
  122. package/src/util/types.ts +0 -566
  123. package/v2-incompatible.js +0 -11
@@ -1,245 +0,0 @@
1
- import {CheckmarkIcon, EditIcon, LockIcon, PlayIcon} from '@sanity/icons'
2
- import {Button, Card, Stack, Text, Tooltip} from '@sanity/ui'
3
- import React, {useState} from 'react'
4
- import {styled} from 'styled-components'
5
-
6
- import {DRMWarningDialog, useDrmPlaybackWarningContext} from '../context/DrmPlaybackWarningContext'
7
- import {THUMBNAIL_ASPECT_RATIO} from '../util/constants'
8
- import {getPlaybackPolicy} from '../util/getPlaybackPolicy'
9
- import {type VideoAssetDocument} from '../util/types'
10
- import IconInfo from './IconInfo'
11
- import {AudioIcon} from './icons/Audio'
12
- import VideoMetadata from './VideoMetadata'
13
- import VideoPlayer, {assetIsAudio} from './VideoPlayer'
14
- import VideoThumbnail from './VideoThumbnail'
15
-
16
- const PlayButton = styled.button`
17
- display: block;
18
- padding: 0;
19
- margin: 0;
20
- border: none;
21
- border-radius: 0.1875rem;
22
- position: relative;
23
- cursor: pointer;
24
-
25
- &::after {
26
- content: '';
27
- background: var(--card-fg-color);
28
- opacity: 0;
29
- display: block;
30
- position: absolute;
31
- inset: 0;
32
- z-index: 10;
33
- transition: 0.15s ease-out;
34
- border-radius: inherit;
35
- }
36
-
37
- > div[data-play] {
38
- z-index: 11;
39
- opacity: 0;
40
- transition: 0.15s 0.05s ease-out;
41
- position: absolute;
42
- left: 50%;
43
- top: 50%;
44
- transform: translate(-50%, -50%);
45
- color: var(--card-fg-color);
46
- background: var(--card-bg-color);
47
- width: auto;
48
- height: 30%;
49
- aspect-ratio: 1;
50
- border-radius: 100%;
51
- display: flex;
52
- justify-content: center;
53
- align-items: center;
54
- box-sizing: border-box;
55
- > svg {
56
- display: block;
57
- width: 70%;
58
- height: auto;
59
- // Visual balance to center-align the icon
60
- transform: translateX(5%);
61
- }
62
- }
63
-
64
- &:hover,
65
- &:focus {
66
- &::after {
67
- opacity: 0.3;
68
- }
69
- > div[data-play] {
70
- opacity: 1;
71
- }
72
- }
73
- `
74
-
75
- type RenderState = 'render-video' | 'pre-render-warn' | false
76
-
77
- export default function VideoInBrowser({
78
- onSelect,
79
- onEdit,
80
- asset,
81
- }: {
82
- onSelect?: (asset: VideoAssetDocument) => void
83
- onEdit?: (asset: VideoAssetDocument) => void
84
- asset: VideoAssetDocument
85
- }) {
86
- const [renderVideo, setRenderVideo] = useState<RenderState>(false)
87
- const select = React.useCallback(() => onSelect?.(asset), [onSelect, asset])
88
- const edit = React.useCallback(() => onEdit?.(asset), [onEdit, asset])
89
- const {hasShownWarning} = useDrmPlaybackWarningContext()
90
-
91
- if (!asset) {
92
- return null
93
- }
94
-
95
- const playbackPolicy = getPlaybackPolicy(asset)
96
- const onClickPlay = () => {
97
- if (playbackPolicy?.policy === 'drm' && !hasShownWarning) {
98
- setRenderVideo('pre-render-warn')
99
- } else {
100
- setRenderVideo('render-video')
101
- }
102
- }
103
- return (
104
- <Card
105
- border
106
- padding={2}
107
- sizing="border"
108
- radius={2}
109
- style={{
110
- position: 'relative',
111
- }}
112
- >
113
- {playbackPolicy?.policy === 'signed' && (
114
- <Tooltip
115
- animate
116
- content={
117
- <Card padding={2} radius={2}>
118
- <IconInfo icon={LockIcon} text="Signed playback policy" size={2} />
119
- </Card>
120
- }
121
- placement="right"
122
- fallbackPlacements={['top', 'bottom']}
123
- portal
124
- >
125
- <Card
126
- tone="caution"
127
- style={{
128
- borderRadius: '100%',
129
- position: 'absolute',
130
- left: '1em',
131
- top: '1em',
132
- zIndex: 11,
133
- }}
134
- padding={2}
135
- border
136
- >
137
- <Text muted size={1}>
138
- <LockIcon />
139
- </Text>
140
- </Card>
141
- </Tooltip>
142
- )}
143
- {playbackPolicy?.policy === 'drm' && (
144
- <Tooltip
145
- animate
146
- content={
147
- <Card padding={2} radius={2}>
148
- <IconInfo icon={LockIcon} text="DRM playback policy" size={2} />
149
- </Card>
150
- }
151
- placement="right"
152
- fallbackPlacements={['top', 'bottom']}
153
- portal
154
- >
155
- <Card
156
- tone="caution"
157
- style={{
158
- borderRadius: '0.25rem',
159
- position: 'absolute',
160
- left: '1em',
161
- top: '1em',
162
- zIndex: 11,
163
- }}
164
- padding={2}
165
- border
166
- >
167
- <Text muted size={1} weight="semibold" style={{color: 'var(--card-icon-color)'}}>
168
- DRM
169
- </Text>
170
- </Card>
171
- </Tooltip>
172
- )}
173
- <Stack
174
- space={3}
175
- height="fill"
176
- style={{
177
- gridTemplateRows: 'min-content min-content 1fr',
178
- }}
179
- >
180
- {renderVideo === 'pre-render-warn' && (
181
- <DRMWarningDialog
182
- onClose={() => {
183
- setRenderVideo('render-video')
184
- }}
185
- />
186
- )}
187
- {renderVideo === 'render-video' ? (
188
- <VideoPlayer asset={asset} autoPlay forceAspectRatio={THUMBNAIL_ASPECT_RATIO} />
189
- ) : (
190
- <PlayButton onClick={onClickPlay}>
191
- <div data-play>
192
- <PlayIcon />
193
- </div>
194
- {assetIsAudio(asset) ? (
195
- <div
196
- style={{
197
- aspectRatio: THUMBNAIL_ASPECT_RATIO,
198
- display: 'flex',
199
- alignItems: 'center',
200
- justifyContent: 'center',
201
- }}
202
- >
203
- <AudioIcon width="3em" height="3em" />
204
- </div>
205
- ) : (
206
- <VideoThumbnail asset={asset} />
207
- )}
208
- </PlayButton>
209
- )}
210
- <VideoMetadata asset={asset} />
211
- <div
212
- style={{
213
- display: 'flex',
214
- width: '100%',
215
- alignItems: 'flex-end',
216
- justifyContent: 'flex-start',
217
- gap: '.35rem',
218
- }}
219
- >
220
- {onSelect && (
221
- <Button
222
- icon={CheckmarkIcon}
223
- fontSize={2}
224
- padding={2}
225
- mode="ghost"
226
- text="Select"
227
- style={{flex: 1}}
228
- tone="positive"
229
- onClick={select}
230
- />
231
- )}
232
- <Button
233
- icon={EditIcon}
234
- fontSize={2}
235
- padding={2}
236
- mode="ghost"
237
- text="Details"
238
- style={{flex: 1}}
239
- onClick={edit}
240
- />
241
- </div>
242
- </Stack>
243
- </Card>
244
- )
245
- }
@@ -1,45 +0,0 @@
1
- import {CalendarIcon, ClockIcon, TagIcon} from '@sanity/icons'
2
- import {Inline, Stack, Text} from '@sanity/ui'
3
-
4
- import getVideoMetadata from '../util/getVideoMetadata'
5
- import type {VideoAssetDocument} from '../util/types'
6
- import IconInfo from './IconInfo'
7
-
8
- const VideoMetadata = (props: {asset: VideoAssetDocument}) => {
9
- if (!props.asset) {
10
- return null
11
- }
12
-
13
- const displayInfo = getVideoMetadata(props.asset)
14
- return (
15
- <Stack space={2}>
16
- {displayInfo.title && (
17
- <Text
18
- size={1}
19
- weight="semibold"
20
- style={{
21
- wordWrap: 'break-word',
22
- }}
23
- >
24
- {displayInfo.title}
25
- </Text>
26
- )}
27
- <Inline space={3}>
28
- {displayInfo?.duration && (
29
- <IconInfo text={displayInfo.duration} icon={ClockIcon} size={1} muted />
30
- )}
31
- <IconInfo
32
- text={displayInfo.createdAt.toISOString().split('T')[0]!}
33
- icon={CalendarIcon}
34
- size={1}
35
- muted
36
- />
37
- {displayInfo.title != displayInfo.id.slice(0, 12) && (
38
- <IconInfo text={displayInfo.id.slice(0, 12)} icon={TagIcon} size={1} muted />
39
- )}
40
- </Inline>
41
- </Stack>
42
- )
43
- }
44
-
45
- export default VideoMetadata
@@ -1,241 +0,0 @@
1
- import {type MuxPlayerProps, type MuxPlayerRefAttributes} from '@mux/mux-player-react'
2
- import MuxPlayer from '@mux/mux-player-react/lazy'
3
- import {ErrorOutlineIcon} from '@sanity/icons'
4
- import {Card, Text} from '@sanity/ui'
5
- import {type PropsWithChildren, Suspense, useMemo, useRef, useState} from 'react'
6
-
7
- import {useDialogStateContext} from '../context/DialogStateContext'
8
- import {useClient} from '../hooks/useClient'
9
- import {AUDIO_ASPECT_RATIO, MIN_ASPECT_RATIO} from '../util/constants'
10
- import {generateJwt} from '../util/generateJwt'
11
- import {getPlaybackId} from '../util/getPlaybackPolicy'
12
- import {getPlaybackPolicyById} from '../util/getPlaybackPolicy'
13
- import {getPosterSrc} from '../util/getPosterSrc'
14
- import {getVideoSrc} from '../util/getVideoSrc'
15
- import {tryWithSuspend} from '../util/tryWithSuspend'
16
- import type {VideoAssetDocument} from '../util/types'
17
- import CaptionsDialog from './CaptionsDialog'
18
- import EditThumbnailDialog from './EditThumbnailDialog'
19
- import {AudioIcon} from './icons/Audio'
20
-
21
- export default function VideoPlayer({
22
- asset,
23
- thumbnailWidth = 250,
24
- children,
25
- hlsConfig,
26
- ...props
27
- }: PropsWithChildren<
28
- {
29
- asset: VideoAssetDocument
30
- thumbnailWidth?: number
31
- forceAspectRatio?: number
32
- hlsConfig?: MuxPlayerProps['_hlsConfig']
33
- } & Partial<Pick<MuxPlayerProps, 'autoPlay'>>
34
- >) {
35
- const client = useClient()
36
- const {dialogState} = useDialogStateContext()
37
-
38
- const isAudio = assetIsAudio(asset)
39
- const muxPlayer = useRef<MuxPlayerRefAttributes>(null)
40
- const playerContainerRef = useRef<HTMLDivElement>(null)
41
- const [error, setError] = useState<Error>()
42
-
43
- /* Playback ID that will be used to play the video */
44
- const playbackId = useMemo(() => {
45
- try {
46
- return getPlaybackId(asset, ['public', 'signed', 'drm'])
47
- } catch (e) {
48
- // oxlint-disable-next-line react/react-compiler
49
- setError(new TypeError('Asset has no playback ID', {cause: e}))
50
- return undefined
51
- }
52
- }, [asset])
53
-
54
- const muxPlaybackId = useMemo(() => {
55
- if (!playbackId) return undefined
56
- return getPlaybackPolicyById(asset, playbackId)
57
- }, [asset, playbackId])
58
-
59
- const src = useMemo(() => {
60
- if (!playbackId) return undefined
61
- if (!muxPlaybackId) return undefined
62
- return tryWithSuspend(
63
- () => getVideoSrc({muxPlaybackId, client}),
64
- (e: Error) => {
65
- setError(e)
66
- return undefined
67
- },
68
- )
69
- }, [muxPlaybackId, playbackId, client])
70
-
71
- const poster = useMemo(() => {
72
- return tryWithSuspend(
73
- () => getPosterSrc({asset, client, width: thumbnailWidth}),
74
- (e: Error) => {
75
- setError(e)
76
- return undefined
77
- },
78
- )
79
- }, [asset, client, thumbnailWidth])
80
-
81
- const signedToken = useMemo(() => {
82
- try {
83
- const url = new URL(src!)
84
- return url.searchParams.get('token')
85
- } catch {
86
- return undefined
87
- }
88
- }, [src])
89
- const drmToken = useMemo(() => {
90
- if (!playbackId) return undefined
91
- if (muxPlaybackId?.policy !== 'drm') return undefined
92
-
93
- return tryWithSuspend(
94
- () => generateJwt(client, playbackId, 'd'),
95
- (e: Error) => {
96
- setError(e)
97
- return undefined
98
- },
99
- )
100
- }, [client, muxPlaybackId?.policy, playbackId])
101
- const tokens:
102
- | Partial<{
103
- playback?: string
104
- thumbnail?: string
105
- storyboard?: string
106
- drm?: string
107
- }>
108
- | undefined = useMemo(() => {
109
- try {
110
- const partialTokens: {
111
- playback?: string
112
- thumbnail?: string
113
- storyboard?: string
114
- drm?: string
115
- } = {
116
- playback: undefined,
117
- thumbnail: undefined,
118
- storyboard: undefined,
119
- drm: undefined,
120
- }
121
-
122
- if (signedToken) {
123
- partialTokens.playback = signedToken
124
- partialTokens.thumbnail = signedToken
125
- partialTokens.storyboard = signedToken
126
- }
127
-
128
- if (drmToken) {
129
- partialTokens.drm = drmToken
130
- }
131
-
132
- return {...partialTokens}
133
- } catch {
134
- return undefined
135
- }
136
- }, [signedToken, drmToken])
137
-
138
- const [width, height] = (asset?.data?.aspect_ratio ?? '16:9').split(':').map(Number)
139
- const targetAspectRatio =
140
- props.forceAspectRatio || (Number.isNaN(width) ? 16 / 9 : width! / height!)
141
- let aspectRatio = Math.max(MIN_ASPECT_RATIO, targetAspectRatio)
142
- if (isAudio) {
143
- aspectRatio = props.forceAspectRatio
144
- ? // Make it wider when forcing aspect ratio to balance with videos' rendering height (audio players overflow a bit)
145
- props.forceAspectRatio * 1.2
146
- : AUDIO_ASPECT_RATIO
147
- }
148
-
149
- /* We use Suspense here because `generateJwt` and related functions use suspend()
150
- under the hood */
151
- return (
152
- <>
153
- <Card
154
- ref={playerContainerRef}
155
- tone="transparent"
156
- style={{
157
- aspectRatio: aspectRatio,
158
- position: 'relative',
159
- ...(isAudio && {display: 'flex', alignItems: 'flex-end'}),
160
- }}
161
- >
162
- {src && poster && (
163
- <>
164
- {isAudio && (
165
- <AudioIcon
166
- style={{
167
- padding: '0.5em',
168
- width: '2.2em',
169
- height: '2.2em',
170
- position: 'absolute',
171
- top: 0,
172
- left: 0,
173
- zIndex: 1,
174
- }}
175
- />
176
- )}
177
- <Suspense fallback={null}>
178
- <MuxPlayer
179
- poster={isAudio ? undefined : poster}
180
- ref={muxPlayer}
181
- {...props}
182
- playsInline
183
- playbackId={playbackId}
184
- tokens={tokens}
185
- preload="metadata"
186
- crossOrigin="anonymous"
187
- metadata={{
188
- player_name: 'Sanity Admin Dashboard',
189
- // @ts-expect-error - this constant is search/replaced so must be exact, not accessed with an index signature
190
- player_version: process.env.PKG_VERSION,
191
- page_type: 'Preview Player',
192
- }}
193
- audio={isAudio}
194
- _hlsConfig={hlsConfig}
195
- style={{
196
- ...(!isAudio && {height: '100%'}),
197
- width: '100%',
198
- display: 'block',
199
- objectFit: 'contain',
200
- ...(isAudio && {alignSelf: 'end'}),
201
- }}
202
- />
203
- {children}
204
- </Suspense>
205
- </>
206
- )}
207
- {error ? (
208
- <div
209
- style={{
210
- position: 'absolute',
211
- top: '50%',
212
- left: '50%',
213
- transform: 'translate(-50%, -50%)',
214
- }}
215
- >
216
- <Text muted>
217
- <ErrorOutlineIcon style={{marginRight: '0.15em'}} />
218
- {typeof error === 'object' && 'message' in error && typeof error.message === 'string'
219
- ? error.message
220
- : 'Error loading video'}
221
- </Text>
222
- </div>
223
- ) : null}
224
- {children}
225
- </Card>
226
-
227
- {dialogState === 'edit-thumbnail' && (
228
- <EditThumbnailDialog
229
- asset={asset}
230
- // oxlint-disable-next-line react/react-compiler
231
- currentTime={muxPlayer?.current?.currentTime}
232
- />
233
- )}
234
- {dialogState === 'edit-captions' && <CaptionsDialog asset={asset} />}
235
- </>
236
- )
237
- }
238
-
239
- export function assetIsAudio(asset: VideoAssetDocument) {
240
- return asset.data?.max_stored_resolution === 'Audio only'
241
- }
@@ -1,139 +0,0 @@
1
- import {ErrorOutlineIcon} from '@sanity/icons'
2
- import {Box, Card, type CardTone, Spinner, Stack, Text} from '@sanity/ui'
3
- import {Suspense, useMemo, useRef, useState} from 'react'
4
- import {styled} from 'styled-components'
5
-
6
- import {useClient} from '../hooks/useClient'
7
- import {useInView} from '../hooks/useInView'
8
- import {THUMBNAIL_ASPECT_RATIO} from '../util/constants'
9
- import {getAnimatedPosterSrc} from '../util/getAnimatedPosterSrc'
10
- import {getPosterSrc} from '../util/getPosterSrc'
11
- import {tryWithSuspend} from '../util/tryWithSuspend'
12
- import type {AssetThumbnailOptions, MuxAnimatedThumbnailUrl, MuxThumbnailUrl} from '../util/types'
13
-
14
- const Image = styled.img`
15
- transition: opacity 0.175s ease-out 0s;
16
- display: block;
17
- width: 100%;
18
- height: 100%;
19
- object-fit: contain;
20
- object-position: center center;
21
- `
22
-
23
- type ImageStatus = 'loading' | 'error' | 'loaded'
24
-
25
- const STATUS_TO_TONE: Record<ImageStatus, CardTone> = {
26
- loading: 'transparent',
27
- error: 'critical',
28
- loaded: 'default',
29
- }
30
-
31
- // oxlint-disable-next-line react/react-compiler
32
- export default function VideoThumbnail({
33
- asset,
34
- width,
35
- staticImage = false,
36
- }: {
37
- asset: AssetThumbnailOptions['asset']
38
- width?: number
39
- staticImage?: boolean
40
- }) {
41
- const posterWidth = width || 250
42
- const client = useClient()
43
- const ref = useRef<HTMLDivElement | null>(null)
44
- const inView = useInView(ref)
45
-
46
- const [status, setStatus] = useState<ImageStatus>('loading')
47
- const [error, setError] = useState<string | null>(null)
48
-
49
- const thumbnailSrc = useMemo(() => {
50
- return tryWithSuspend(
51
- () => {
52
- let thumbnail: MuxAnimatedThumbnailUrl | MuxThumbnailUrl | undefined
53
-
54
- if (staticImage) thumbnail = getPosterSrc({asset, client, width: posterWidth})
55
- else thumbnail = getAnimatedPosterSrc({asset, client, width: posterWidth})
56
- return thumbnail
57
- },
58
- (err: Error) => {
59
- handleError(err.message)
60
- return undefined
61
- },
62
- )
63
- }, [asset, client, posterWidth, staticImage])
64
-
65
- function handleLoad() {
66
- setStatus('loaded')
67
- }
68
-
69
- function handleError(err?: string) {
70
- setStatus('error')
71
- if (err) {
72
- setError(err)
73
- } else {
74
- setError('Failed loading thumbnail')
75
- }
76
- }
77
-
78
- return (
79
- <Suspense fallback={<span>Preparing thumbnail</span>}>
80
- <Card
81
- style={{
82
- aspectRatio: THUMBNAIL_ASPECT_RATIO,
83
- position: 'relative',
84
- maxWidth: width ? `${width}px` : undefined,
85
- width: '100%',
86
- flex: 1,
87
- }}
88
- border
89
- radius={2}
90
- ref={ref}
91
- tone={STATUS_TO_TONE[status]}
92
- >
93
- {inView ? (
94
- <>
95
- {status === 'loading' && (
96
- <Box
97
- style={{
98
- position: 'absolute',
99
- left: '50%',
100
- top: '50%',
101
- transform: 'translate(-50%, -50%)',
102
- }}
103
- >
104
- <Spinner />
105
- </Box>
106
- )}
107
- {status === 'error' && (
108
- <Stack
109
- space={4}
110
- style={{
111
- position: 'absolute',
112
- width: '100%',
113
- left: 0,
114
- top: '50%',
115
- transform: 'translateY(-50%)',
116
- justifyItems: 'center',
117
- }}
118
- >
119
- <Text size={4} muted>
120
- <ErrorOutlineIcon style={{fontSize: '1.75em'}} />
121
- </Text>
122
- <Text muted align="center">
123
- {error}
124
- </Text>
125
- </Stack>
126
- )}
127
- <Image
128
- src={thumbnailSrc ?? undefined}
129
- alt={`Preview for ${staticImage ? 'image' : 'video'} ${asset.filename || asset.assetId}`}
130
- onLoad={handleLoad}
131
- onError={() => handleError()}
132
- style={{opacity: status === 'loaded' ? 1 : 0}}
133
- />
134
- </>
135
- ) : null}
136
- </Card>
137
- </Suspense>
138
- )
139
- }