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,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
|
-
}
|
package/src/hooks/useInView.ts
DELETED
|
@@ -1,41 +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
|
-
return () => {
|
|
36
|
-
if (toObserve) observer.unobserve(toObserve)
|
|
37
|
-
}
|
|
38
|
-
}, [options, ref])
|
|
39
|
-
|
|
40
|
-
return inView
|
|
41
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import {useEffect, useState} from 'react'
|
|
2
|
-
|
|
3
|
-
import {type 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
|
-
// oxlint-disable-next-line react/react-compiler
|
|
31
|
-
setVideoAssetMetadata((old) => ({
|
|
32
|
-
...old,
|
|
33
|
-
duration: undefined,
|
|
34
|
-
width: undefined,
|
|
35
|
-
height: undefined,
|
|
36
|
-
}))
|
|
37
|
-
|
|
38
|
-
if (!videoSrc) return () => null
|
|
39
|
-
|
|
40
|
-
setIsLoadingMetadata(true)
|
|
41
|
-
const videoElement = document.createElement('video')
|
|
42
|
-
videoElement.preload = 'metadata'
|
|
43
|
-
|
|
44
|
-
const metadataListeners = [
|
|
45
|
-
() => {
|
|
46
|
-
setIsLoadingMetadata(false)
|
|
47
|
-
},
|
|
48
|
-
() => {
|
|
49
|
-
const duration = videoElement.duration
|
|
50
|
-
const width = videoElement.videoWidth
|
|
51
|
-
const height = videoElement.videoHeight
|
|
52
|
-
const isAudioOnly = width <= 0 && height <= 0
|
|
53
|
-
const aspectRatio = width / height
|
|
54
|
-
setVideoAssetMetadata((old) => {
|
|
55
|
-
return {
|
|
56
|
-
...old,
|
|
57
|
-
duration: duration,
|
|
58
|
-
width: width,
|
|
59
|
-
height: height,
|
|
60
|
-
isAudioOnly: isAudioOnly,
|
|
61
|
-
aspectRatio: aspectRatio,
|
|
62
|
-
}
|
|
63
|
-
})
|
|
64
|
-
},
|
|
65
|
-
]
|
|
66
|
-
|
|
67
|
-
const cleanupVideo = (videoEl: HTMLVideoElement) => {
|
|
68
|
-
const currentVideoSrc = videoEl?.src
|
|
69
|
-
if (videoEl) {
|
|
70
|
-
metadataListeners.forEach((listener) =>
|
|
71
|
-
videoEl.removeEventListener('loadedmetadata', listener),
|
|
72
|
-
)
|
|
73
|
-
videoEl.onerror = null
|
|
74
|
-
videoEl.src = ''
|
|
75
|
-
videoEl.load()
|
|
76
|
-
}
|
|
77
|
-
if (currentVideoSrc?.startsWith('blob:')) {
|
|
78
|
-
URL.revokeObjectURL(currentVideoSrc)
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
metadataListeners.push(() => setTimeout(() => cleanupVideo(videoElement), 0))
|
|
82
|
-
|
|
83
|
-
videoElement.onerror = () => {
|
|
84
|
-
setIsLoadingMetadata(false)
|
|
85
|
-
console.warn('Could not read video metadata for validation')
|
|
86
|
-
cleanupVideo(videoElement)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
metadataListeners.forEach((listener) =>
|
|
90
|
-
videoElement.addEventListener('loadedmetadata', listener),
|
|
91
|
-
)
|
|
92
|
-
videoElement.src = videoSrc
|
|
93
|
-
|
|
94
|
-
return () => {
|
|
95
|
-
cleanupVideo(videoElement)
|
|
96
|
-
}
|
|
97
|
-
}, [stagedUpload.type, stagedUpload])
|
|
98
|
-
|
|
99
|
-
return {
|
|
100
|
-
videoAssetMetadata,
|
|
101
|
-
setVideoAssetMetadata,
|
|
102
|
-
isLoadingMetadata,
|
|
103
|
-
}
|
|
104
|
-
}
|
|
@@ -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,
|
|
53
|
-
next_cursor: response.next_cursor || null,
|
|
54
|
-
}
|
|
55
|
-
} catch {
|
|
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
|
|
132
|
-
? state.cursor
|
|
133
|
-
: null,
|
|
134
|
-
),
|
|
135
|
-
)
|
|
136
|
-
.pipe(
|
|
137
|
-
// Here we use "expand" to recursively fetch next pages
|
|
138
|
-
expand((pageResult) => {
|
|
139
|
-
// if fetched page has next_cursor, we continue emitting, requesting the next page
|
|
140
|
-
// after 2s to avoid rate limiting
|
|
141
|
-
if (hasMorePages(pageResult)) {
|
|
142
|
-
return timer(2000).pipe(
|
|
143
|
-
concatMap(() =>
|
|
144
|
-
defer(() =>
|
|
145
|
-
fetchMuxAssetsPage(
|
|
146
|
-
client,
|
|
147
|
-
'next_cursor' in pageResult ? pageResult.next_cursor : null,
|
|
148
|
-
),
|
|
149
|
-
),
|
|
150
|
-
),
|
|
151
|
-
)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Else, we stop emitting
|
|
155
|
-
return of()
|
|
156
|
-
}),
|
|
157
|
-
|
|
158
|
-
// On each iteration, persist intermediate states to give feedback to users
|
|
159
|
-
tap((pageResult) =>
|
|
160
|
-
setState((prevState) => accumulateIntermediateState(prevState, pageResult)),
|
|
161
|
-
),
|
|
162
|
-
)
|
|
163
|
-
.subscribe({
|
|
164
|
-
// Once done, let the user know we've stopped loading
|
|
165
|
-
complete: () => {
|
|
166
|
-
setState((prev) => ({
|
|
167
|
-
...prev,
|
|
168
|
-
loading: false,
|
|
169
|
-
}))
|
|
170
|
-
},
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
// Unsubscribe on component unmount to prevent memory leaks or fetching unnecessarily
|
|
174
|
-
return () => subscription.unsubscribe()
|
|
175
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
176
|
-
}, [enabled])
|
|
177
|
-
|
|
178
|
-
return state
|
|
179
|
-
}
|
|
@@ -1,52 +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
|
-
await client
|
|
46
|
-
.patch(asset!._id)
|
|
47
|
-
.set({status: data.status, data})
|
|
48
|
-
.commit({returnDocuments: false})
|
|
49
|
-
},
|
|
50
|
-
{refreshInterval: 2000, refreshWhenHidden: true, dedupingInterval: 1000},
|
|
51
|
-
)
|
|
52
|
-
}
|
|
@@ -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
|
-
}
|