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,54 +0,0 @@
1
- import {useEffect, useState} from 'react'
2
-
3
- import {StagedUpload} from '../components/Uploader'
4
-
5
- export function useFetchFileSize(stagedUpload: StagedUpload, maxFileSize?: number) {
6
- const [fileSize, setFileSize] = useState<number | null>(null)
7
- const [isLoadingFileSize, setIsLoadingFileSize] = useState(false)
8
- const [canSkipFileSizeValidation, setCanSkipFileSizeValidation] = useState(false)
9
-
10
- useEffect(() => {
11
- // Fetch URL Upload file size
12
- if (stagedUpload.type === 'url') {
13
- setIsLoadingFileSize(false)
14
- setCanSkipFileSizeValidation(false)
15
- setFileSize(null)
16
- const url = stagedUpload.url
17
-
18
- // Get file size from URL
19
- const fetchFileSize = async () => {
20
- setIsLoadingFileSize(true)
21
- try {
22
- const response = await fetch(url, {method: 'HEAD'})
23
- const contentLength = response.headers.get('content-length')
24
- const newFileSize = contentLength ? parseInt(contentLength, 10) : null
25
-
26
- setIsLoadingFileSize(false)
27
- if (newFileSize) {
28
- setFileSize(newFileSize)
29
- }
30
- if (newFileSize === null && maxFileSize !== undefined) {
31
- // Size unknown but size limit is configured - skip file size validation
32
- setCanSkipFileSizeValidation(true)
33
- }
34
- } catch {
35
- console.warn('Could not validate file size from URL')
36
- // Skip validation of file size, but still validate duration
37
- setCanSkipFileSizeValidation(true)
38
- setIsLoadingFileSize(false)
39
- }
40
- }
41
-
42
- fetchFileSize()
43
- }
44
- if (stagedUpload.type === 'file') {
45
- setFileSize(stagedUpload.files[0].size)
46
- }
47
- }, [maxFileSize, stagedUpload, stagedUpload.type])
48
-
49
- return {
50
- fileSize,
51
- isLoadingFileSize,
52
- canSkipFileSizeValidation,
53
- }
54
- }
@@ -1,132 +0,0 @@
1
- import {uuid} from '@sanity/uuid'
2
- import {useMemo, useState} from 'react'
3
- import {
4
- createHookFromObservableFactory,
5
- type DocumentStore,
6
- useClient,
7
- useDocumentStore,
8
- } from 'sanity'
9
-
10
- import {generateAssetPlaceholder} from '../util/assetTitlePlaceholder'
11
- import {parseMuxDate} from '../util/parsers'
12
- import type {MuxAsset, VideoAssetDocument} from '../util/types'
13
- import {SANITY_API_VERSION} from './useClient'
14
- import useMuxAssets from './useMuxAssets'
15
- import {useSecretsDocumentValues} from './useSecretsDocumentValues'
16
-
17
- type ImportState = 'closed' | 'idle' | 'importing' | 'done' | 'error'
18
-
19
- export type AssetInSanity = {
20
- uploadId: string
21
- assetId: string
22
- }
23
-
24
- export default function useImportMuxAssets() {
25
- const documentStore = useDocumentStore()
26
- const client = useClient({
27
- apiVersion: SANITY_API_VERSION,
28
- })
29
-
30
- const [assetsInSanity, assetsInSanityLoading] = useAssetsInSanity(documentStore)
31
-
32
- const secretDocumentValues = useSecretsDocumentValues()
33
- const hasSecrets = !!secretDocumentValues.value.secrets?.secretKey
34
-
35
- const [importError, setImportError] = useState<unknown>()
36
- const [importState, setImportState] = useState<ImportState>('closed')
37
- const dialogOpen = importState !== 'closed'
38
-
39
- const muxAssets = useMuxAssets({
40
- client,
41
- enabled: hasSecrets && dialogOpen,
42
- })
43
-
44
- const missingAssets = useMemo(() => {
45
- return assetsInSanity && muxAssets.data
46
- ? muxAssets.data.filter((a) => !assetExistsInSanity(a, assetsInSanity))
47
- : undefined
48
- }, [assetsInSanity, muxAssets.data])
49
-
50
- const [selectedAssets, setSelectedAssets] = useState<MuxAsset[]>([])
51
-
52
- const closeDialog = () => {
53
- if (importState !== 'importing') setImportState('closed')
54
- }
55
- const openDialog = () => {
56
- if (importState === 'closed') setImportState('idle')
57
- }
58
-
59
- async function importAssets() {
60
- setImportState('importing')
61
- const documents = selectedAssets.flatMap((asset) => muxAssetToSanityDocument(asset) || [])
62
-
63
- const tx = client.transaction()
64
- documents.forEach((doc) => tx.create(doc))
65
-
66
- try {
67
- await tx.commit({returnDocuments: false})
68
- setSelectedAssets([])
69
- setImportState('done')
70
- } catch (error) {
71
- setImportState('error')
72
- setImportError(error)
73
- }
74
- }
75
-
76
- return {
77
- assetsInSanityLoading,
78
- closeDialog,
79
- dialogOpen,
80
- importState,
81
- importError,
82
- hasSecrets,
83
- importAssets,
84
- missingAssets,
85
- muxAssets,
86
- openDialog,
87
- selectedAssets,
88
- setSelectedAssets,
89
- }
90
- }
91
-
92
- function muxAssetToSanityDocument(asset: MuxAsset): VideoAssetDocument | undefined {
93
- const playbackId = (asset.playback_ids || []).find((p) => p.id)?.id
94
-
95
- if (!playbackId) return undefined
96
-
97
- return {
98
- _id: uuid(),
99
- _type: 'mux.videoAsset',
100
- _updatedAt: new Date().toISOString(),
101
- _createdAt: parseMuxDate(asset.created_at).toISOString(),
102
- assetId: asset.id,
103
- playbackId,
104
- filename: asset.meta?.title ?? generateAssetPlaceholder(asset.id),
105
- status: asset.status,
106
- data: asset,
107
- }
108
- }
109
-
110
- const useAssetsInSanity = createHookFromObservableFactory<AssetInSanity[], DocumentStore>(
111
- (documentStore) => {
112
- return documentStore.listenQuery(
113
- /* groq */ `*[_type == "mux.videoAsset"] {
114
- "uploadId": coalesce(uploadId, data.upload_id),
115
- "assetId": coalesce(assetId, data.id),
116
- }`,
117
- {},
118
- {
119
- apiVersion: SANITY_API_VERSION,
120
- }
121
- )
122
- }
123
- )
124
-
125
- function assetExistsInSanity(asset: MuxAsset, existingAssets: AssetInSanity[]) {
126
- // Don't allow importing assets that are not ready
127
- if (asset.status !== 'ready') return false
128
-
129
- return existingAssets.some(
130
- (existing) => existing.assetId === asset.id || existing.uploadId === asset.upload_id
131
- )
132
- }
@@ -1,42 +0,0 @@
1
- import {useEffect, useState} from 'react'
2
-
3
- type IntersectionOptions = {
4
- root?: Element | null
5
- rootMargin?: string
6
- threshold?: number
7
- onChange?: (inView: boolean) => void
8
- }
9
-
10
- export function useInView(
11
- ref: React.RefObject<HTMLDivElement | null>,
12
- options: IntersectionOptions = {}
13
- ) {
14
- const [inView, setInView] = useState(false)
15
-
16
- useEffect(() => {
17
- if (!ref.current) return
18
-
19
- const observer = new IntersectionObserver(([entry], obs) => {
20
- // ==== from react-intersection-observer ====
21
- // While it would be nice if you could just look at isIntersecting to determine if the component is inside the viewport, browsers can't agree on how to use it.
22
- // -Firefox ignores `threshold` when considering `isIntersecting`, so it will never be false again if `threshold` is > 0
23
- const nowInView =
24
- entry.isIntersecting &&
25
- obs.thresholds.some((threshold) => entry.intersectionRatio >= threshold)
26
-
27
- // Update our state when observer callback fires
28
- setInView(nowInView)
29
- options?.onChange?.(nowInView)
30
- }, options)
31
-
32
- const toObserve = ref.current
33
- observer.observe(toObserve)
34
-
35
- // eslint-disable-next-line
36
- return () => {
37
- if (toObserve) observer.unobserve(toObserve)
38
- }
39
- }, [options, ref])
40
-
41
- return inView
42
- }
@@ -1,103 +0,0 @@
1
- import {useEffect, useState} from 'react'
2
-
3
- import {StagedUpload} from '../components/Uploader'
4
-
5
- export interface VideoAssetMetadata {
6
- width?: number
7
- height?: number
8
- isAudioOnly?: boolean
9
- duration?: number
10
- size?: number
11
- aspectRatio?: number
12
- }
13
-
14
- export function useMediaMetadata(stagedUpload: StagedUpload) {
15
- const [videoAssetMetadata, setVideoAssetMetadata] = useState<VideoAssetMetadata | null>(null)
16
- const [isLoadingMetadata, setIsLoadingMetadata] = useState(false)
17
- useEffect(() => {
18
- let videoSrc = null
19
- // Validate file uploads
20
- if (stagedUpload.type === 'file') {
21
- const file = stagedUpload.files[0]
22
- videoSrc = URL.createObjectURL(file)
23
- }
24
-
25
- // Validate URL uploads
26
- if (stagedUpload.type === 'url') {
27
- videoSrc = stagedUpload.url
28
- }
29
-
30
- setVideoAssetMetadata((old) => ({
31
- ...old,
32
- duration: undefined,
33
- width: undefined,
34
- height: undefined,
35
- }))
36
-
37
- if (!videoSrc) return () => null
38
-
39
- setIsLoadingMetadata(true)
40
- const videoElement = document.createElement('video')
41
- videoElement.preload = 'metadata'
42
-
43
- const metadataListeners = [
44
- () => {
45
- setIsLoadingMetadata(false)
46
- },
47
- () => {
48
- const duration = videoElement.duration
49
- const width = videoElement.videoWidth
50
- const height = videoElement.videoHeight
51
- const isAudioOnly = width <= 0 && height <= 0
52
- const aspectRatio = width / height
53
- setVideoAssetMetadata((old) => {
54
- return {
55
- ...old,
56
- duration: duration,
57
- width: width,
58
- height: height,
59
- isAudioOnly: isAudioOnly,
60
- aspectRatio: aspectRatio,
61
- }
62
- })
63
- },
64
- ]
65
-
66
- const cleanupVideo = (videoEl: HTMLVideoElement) => {
67
- const currentVideoSrc = videoEl?.src
68
- if (videoEl) {
69
- metadataListeners.forEach((listener) =>
70
- videoEl.removeEventListener('loadedmetadata', listener)
71
- )
72
- videoEl.onerror = null
73
- videoEl.src = ''
74
- videoEl.load()
75
- }
76
- if (currentVideoSrc?.startsWith('blob:')) {
77
- URL.revokeObjectURL(currentVideoSrc)
78
- }
79
- }
80
- metadataListeners.push(() => setTimeout(() => cleanupVideo(videoElement), 0))
81
-
82
- videoElement.onerror = () => {
83
- setIsLoadingMetadata(false)
84
- console.warn('Could not read video metadata for validation')
85
- cleanupVideo(videoElement)
86
- }
87
-
88
- metadataListeners.forEach((listener) =>
89
- videoElement.addEventListener('loadedmetadata', listener)
90
- )
91
- videoElement.src = videoSrc
92
-
93
- return () => {
94
- cleanupVideo(videoElement)
95
- }
96
- }, [stagedUpload.type, stagedUpload])
97
-
98
- return {
99
- videoAssetMetadata,
100
- setVideoAssetMetadata,
101
- isLoadingMetadata,
102
- }
103
- }
@@ -1,179 +0,0 @@
1
- import {useEffect, useState} from 'react'
2
- import {defer, of, timer} from 'rxjs'
3
- import {concatMap, expand, tap} from 'rxjs/operators'
4
- import type {SanityClient} from 'sanity'
5
-
6
- import {listAssets} from '../actions/assets'
7
- import type {MuxAsset} from '../util/types'
8
-
9
- const ASSETS_PER_PAGE = 100
10
-
11
- type MuxAssetsState = {
12
- cursor: string | null
13
- loading: boolean
14
- data?: MuxAsset[]
15
- error?: FetchError
16
- hasSkippedAssetsWithoutPlayback?: boolean
17
- }
18
-
19
- type FetchError =
20
- | {
21
- _tag: 'FetchError'
22
- }
23
- | {_tag: 'MuxError'; error: unknown}
24
-
25
- type PageResult = (
26
- | {
27
- data: MuxAsset[]
28
- next_cursor: string | null
29
- }
30
- | {
31
- error: FetchError
32
- }
33
- ) & {
34
- cursor: string | null
35
- }
36
-
37
- /**
38
- * @docs {@link https://docs.mux.com/api-reference#video/operation/list-assets}
39
- */
40
- async function fetchMuxAssetsPage(
41
- client: SanityClient,
42
- cursor: string | null
43
- ): Promise<PageResult> {
44
- try {
45
- const response = await listAssets(client, {
46
- limit: ASSETS_PER_PAGE,
47
- cursor,
48
- })
49
-
50
- return {
51
- cursor,
52
- data: response.data as MuxAsset[],
53
- next_cursor: response.next_cursor || null,
54
- }
55
- } catch (error) {
56
- return {
57
- cursor,
58
- error: {_tag: 'FetchError'},
59
- }
60
- }
61
- }
62
-
63
- function accumulateIntermediateState(
64
- currentState: MuxAssetsState,
65
- pageResult: PageResult
66
- ): MuxAssetsState {
67
- const currentData = ('data' in currentState && currentState.data) || []
68
- const newAssets = ('data' in pageResult && pageResult.data) || []
69
-
70
- // Filter assets and check for skipped items
71
- const {validAssets, skippedInThisPage} = newAssets.reduce<{
72
- validAssets: MuxAsset[]
73
- skippedInThisPage: boolean
74
- }>(
75
- (acc, asset) => {
76
- const hasPlaybackIds = asset.playback_ids && asset.playback_ids.length > 0
77
- const isDuplicate = currentData.some((a) => a.id === asset.id)
78
-
79
- if (!hasPlaybackIds) {
80
- acc.skippedInThisPage = true
81
- }
82
-
83
- if (hasPlaybackIds && !isDuplicate) {
84
- acc.validAssets.push(asset)
85
- }
86
-
87
- return acc
88
- },
89
- {validAssets: [], skippedInThisPage: false}
90
- )
91
-
92
- return {
93
- ...currentState,
94
- data: [...currentData, ...validAssets],
95
- error:
96
- 'error' in pageResult
97
- ? pageResult.error
98
- : // Reset error if current page is successful
99
- undefined,
100
- cursor: 'next_cursor' in pageResult ? pageResult.next_cursor : pageResult.cursor,
101
- loading: true,
102
- hasSkippedAssetsWithoutPlayback:
103
- currentState.hasSkippedAssetsWithoutPlayback || skippedInThisPage,
104
- }
105
- }
106
-
107
- function hasMorePages(pageResult: PageResult) {
108
- return (
109
- typeof pageResult === 'object' && 'next_cursor' in pageResult && pageResult.next_cursor !== null
110
- )
111
- }
112
-
113
- /**
114
- * Fetches all assets from a Mux environment. Rules:
115
- * - One page at a time
116
- * - Uses cursor-based pagination
117
- * - We've finished fetching when `next_cursor` is null
118
- * - Rate limiting to one request per 2 seconds
119
- * - Update state while still fetching to give feedback to users
120
- */
121
- export default function useMuxAssets({client, enabled}: {client: SanityClient; enabled: boolean}) {
122
- const [state, setState] = useState<MuxAssetsState>({loading: true, cursor: null})
123
-
124
- useEffect(() => {
125
- if (!enabled) return
126
-
127
- const subscription = defer(() =>
128
- fetchMuxAssetsPage(
129
- client,
130
- // When we've already successfully loaded before (fully or partially), we start from the next cursor to avoid re-fetching
131
- 'data' in state && state.data && state.data.length > 0 && !state.error ? state.cursor : null
132
- )
133
- )
134
- .pipe(
135
- // Here we use "expand" to recursively fetch next pages
136
- expand((pageResult) => {
137
- // if fetched page has next_cursor, we continue emitting, requesting the next page
138
- // after 2s to avoid rate limiting
139
- if (hasMorePages(pageResult)) {
140
- return timer(2000).pipe(
141
- concatMap(() =>
142
- // eslint-disable-next-line max-nested-callbacks
143
- defer(() =>
144
- fetchMuxAssetsPage(
145
- client,
146
- 'next_cursor' in pageResult ? pageResult.next_cursor : null
147
- )
148
- )
149
- )
150
- )
151
- }
152
-
153
- // Else, we stop emitting
154
- return of()
155
- }),
156
-
157
- // On each iteration, persist intermediate states to give feedback to users
158
- tap((pageResult) =>
159
- setState((prevState) => accumulateIntermediateState(prevState, pageResult))
160
- )
161
- )
162
- .subscribe({
163
- // Once done, let the user know we've stopped loading
164
- complete: () => {
165
- setState((prev) => ({
166
- ...prev,
167
- loading: false,
168
- }))
169
- },
170
- })
171
-
172
- // Unsubscribe on component unmount to prevent memory leaks or fetching unnecessarily
173
- // eslint-disable-next-line consistent-return
174
- return () => subscription.unsubscribe()
175
- // eslint-disable-next-line react-hooks/exhaustive-deps
176
- }, [enabled])
177
-
178
- return state
179
- }
@@ -1,49 +0,0 @@
1
- import {useMemo} from 'react'
2
- import {useDataset, useProjectId} from 'sanity'
3
- import useSWR from 'swr'
4
-
5
- import {useClient} from '../hooks/useClient'
6
- import {PLUGIN_VERSION_QUERY} from '../util/pluginVersion'
7
- import type {MuxAsset, VideoAssetDocument} from '../util/types'
8
-
9
- // Poll MUX if it's preparing the main document or its own static renditions
10
- export const useMuxPolling = (asset?: VideoAssetDocument) => {
11
- const client = useClient()
12
- const projectId = useProjectId()
13
- const dataset = useDataset()
14
- const isPreparingStaticRenditions = useMemo(() => {
15
- // Legacy: If static_renditions has a status field, it was created with mp4_support (deprecated)
16
- // We don't process this old format, just return false
17
- // Note: 'disabled' status is valid in the new format when no renditions were requested
18
- if (
19
- asset?.data?.static_renditions?.status &&
20
- asset?.data?.static_renditions?.status !== 'disabled'
21
- ) {
22
- return false
23
- }
24
-
25
- const files = asset?.data?.static_renditions?.files
26
- if (!files || files.length === 0) {
27
- return false
28
- }
29
- return files.some((file) => file.status === 'preparing')
30
- }, [asset?.data?.static_renditions?.status, asset?.data?.static_renditions?.files])
31
-
32
- const shouldFetch = useMemo(
33
- () => !!asset?.assetId && (asset?.status === 'preparing' || isPreparingStaticRenditions),
34
- [asset?.assetId, asset?.status, isPreparingStaticRenditions]
35
- )
36
- return useSWR(
37
- shouldFetch ? `/${projectId}/addons/mux/assets/${dataset}/data/${asset?.assetId}` : null,
38
- async () => {
39
- const {data} = await client.request<{data: MuxAsset}>({
40
- url: `/addons/mux/assets/${dataset}/data/${asset!.assetId}`,
41
- withCredentials: true,
42
- method: 'GET',
43
- query: PLUGIN_VERSION_QUERY,
44
- })
45
- client.patch(asset!._id!).set({status: data.status, data}).commit({returnDocuments: false})
46
- },
47
- {refreshInterval: 2000, refreshWhenHidden: true, dedupingInterval: 1000}
48
- )
49
- }
@@ -1,110 +0,0 @@
1
- import {useToast} from '@sanity/ui'
2
- import {useCallback, useState} from 'react'
3
-
4
- import {getAsset} from '../actions/assets'
5
- import {addKeysToMuxData} from '../util/addKeysToMuxData'
6
- import type {MuxAsset, VideoAssetDocument} from '../util/types'
7
- import {useClient} from './useClient'
8
-
9
- type ResyncAssetState = 'idle' | 'syncing' | 'success' | 'error'
10
-
11
- interface UseResyncAssetOptions {
12
- showToast?: boolean
13
- onSuccess?: (updatedData: MuxAsset) => void
14
- onError?: (error: unknown) => void
15
- }
16
-
17
- interface UseResyncAssetReturn {
18
- resyncState: ResyncAssetState
19
- resyncError: unknown
20
- resyncAsset: (asset: VideoAssetDocument) => Promise<MuxAsset | undefined>
21
- isResyncing: boolean
22
- }
23
-
24
- export function useResyncAsset(options?: UseResyncAssetOptions): UseResyncAssetReturn {
25
- const client = useClient()
26
- const toast = useToast()
27
- const [resyncState, setResyncState] = useState<ResyncAssetState>('idle')
28
- const [resyncError, setResyncError] = useState<unknown>(null)
29
-
30
- const showToast = options?.showToast ?? false
31
-
32
- const resyncAsset = useCallback(
33
- async (asset: VideoAssetDocument) => {
34
- if (!asset.assetId) {
35
- if (showToast) {
36
- toast.push({
37
- title: 'Cannot resync',
38
- description: 'Asset has no Mux ID',
39
- status: 'error',
40
- })
41
- }
42
- options?.onError?.(new Error('Asset has no Mux ID'))
43
- return undefined
44
- }
45
-
46
- if (!asset._id) {
47
- if (showToast) {
48
- toast.push({
49
- title: 'Cannot resync',
50
- description: 'Asset has no document ID',
51
- status: 'error',
52
- })
53
- }
54
- options?.onError?.(new Error('Asset has no document ID'))
55
- return undefined
56
- }
57
-
58
- setResyncState('syncing')
59
- setResyncError(null)
60
-
61
- try {
62
- const response = await getAsset(client, asset.assetId)
63
- const muxData = response.data
64
- const dataWithKeys = addKeysToMuxData(muxData)
65
-
66
- await client
67
- .patch(asset._id)
68
- .set({
69
- status: muxData.status,
70
- data: dataWithKeys,
71
- ...(muxData.meta?.title && {filename: muxData.meta.title}),
72
- })
73
- .commit({returnDocuments: false})
74
-
75
- setResyncState('success')
76
- if (showToast) {
77
- toast.push({
78
- title: 'Asset synced',
79
- description: 'Data has been updated from Mux',
80
- status: 'success',
81
- })
82
- }
83
-
84
- options?.onSuccess?.(muxData)
85
- return muxData
86
- } catch (error) {
87
- setResyncState('error')
88
- setResyncError(error)
89
- console.error('Failed to refresh asset data:', error)
90
- if (showToast) {
91
- toast.push({
92
- title: 'Sync failed',
93
- description: 'Could not sync asset from Mux',
94
- status: 'error',
95
- })
96
- }
97
- options?.onError?.(error)
98
- return undefined
99
- }
100
- },
101
- [client, toast, options, showToast]
102
- )
103
-
104
- return {
105
- resyncState,
106
- resyncError,
107
- resyncAsset,
108
- isResyncing: resyncState === 'syncing',
109
- }
110
- }