sanity-plugin-mux-input 2.9.1 → 2.10.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.
- package/dist/index.js +2144 -1911
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2148 -1915
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/actions/assets.ts +22 -0
- package/src/components/ConfigureApi.tsx +32 -9
- package/src/components/ImportVideosFromMux.tsx +26 -2
- package/src/components/Input.tsx +3 -3
- package/src/components/ResyncMetadata.tsx +201 -0
- package/src/components/VideosBrowser.tsx +10 -2
- package/src/hooks/useImportMuxAssets.ts +3 -3
- package/src/hooks/useMuxAssets.ts +64 -53
- package/src/hooks/useResyncMuxMetadata.ts +143 -0
- package/src/util/assetTitlePlaceholder.ts +31 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import {useMemo, useState} from 'react'
|
|
2
|
+
import {
|
|
3
|
+
createHookFromObservableFactory,
|
|
4
|
+
type DocumentStore,
|
|
5
|
+
useClient,
|
|
6
|
+
useDocumentStore,
|
|
7
|
+
} from 'sanity'
|
|
8
|
+
|
|
9
|
+
import {isEmptyOrPlaceholderTitle} from '../util/assetTitlePlaceholder'
|
|
10
|
+
import type {MuxAsset, VideoAssetDocument} from '../util/types'
|
|
11
|
+
import {SANITY_API_VERSION} from './useClient'
|
|
12
|
+
import useMuxAssets from './useMuxAssets'
|
|
13
|
+
import {useSecretsDocumentValues} from './useSecretsDocumentValues'
|
|
14
|
+
|
|
15
|
+
type ResyncState = 'closed' | 'idle' | 'syncing' | 'done' | 'error'
|
|
16
|
+
|
|
17
|
+
export type MatchedAsset = {
|
|
18
|
+
sanityDoc: VideoAssetDocument
|
|
19
|
+
muxAsset: MuxAsset | undefined
|
|
20
|
+
muxTitle: string | undefined
|
|
21
|
+
currentTitle: string | undefined
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default function useResyncMuxMetadata() {
|
|
25
|
+
const documentStore = useDocumentStore()
|
|
26
|
+
const client = useClient({
|
|
27
|
+
apiVersion: SANITY_API_VERSION,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const [sanityAssets, sanityAssetsLoading] = useSanityAssets(documentStore)
|
|
31
|
+
|
|
32
|
+
const secretDocumentValues = useSecretsDocumentValues()
|
|
33
|
+
const hasSecrets = !!secretDocumentValues.value.secrets?.secretKey
|
|
34
|
+
|
|
35
|
+
const [resyncError, setResyncError] = useState<unknown>()
|
|
36
|
+
const [resyncState, setResyncState] = useState<ResyncState>('closed')
|
|
37
|
+
const dialogOpen = resyncState !== 'closed'
|
|
38
|
+
|
|
39
|
+
const muxAssets = useMuxAssets({
|
|
40
|
+
client,
|
|
41
|
+
enabled: hasSecrets && dialogOpen,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const matchedAssets = useMemo(() => {
|
|
45
|
+
return sanityAssets && muxAssets.data
|
|
46
|
+
? sanityAssets.map((sanityDoc) => {
|
|
47
|
+
const muxAsset = muxAssets.data?.find(
|
|
48
|
+
(m) => m.id === sanityDoc.assetId || m.id === sanityDoc.data?.id
|
|
49
|
+
)
|
|
50
|
+
return {
|
|
51
|
+
sanityDoc,
|
|
52
|
+
muxAsset,
|
|
53
|
+
muxTitle: muxAsset?.meta?.title,
|
|
54
|
+
currentTitle: sanityDoc.filename,
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
: undefined
|
|
58
|
+
}, [sanityAssets, muxAssets.data])
|
|
59
|
+
|
|
60
|
+
const closeDialog = () => {
|
|
61
|
+
if (resyncState !== 'syncing') setResyncState('closed')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const openDialog = () => {
|
|
65
|
+
if (resyncState === 'closed') setResyncState('idle')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function syncAllVideos() {
|
|
69
|
+
if (!matchedAssets) return
|
|
70
|
+
|
|
71
|
+
setResyncState('syncing')
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const tx = client.transaction()
|
|
75
|
+
|
|
76
|
+
matchedAssets.forEach((matched) => {
|
|
77
|
+
// Update all videos with the Mux title, even if it's undefined/empty
|
|
78
|
+
const newTitle = matched.muxTitle || ''
|
|
79
|
+
tx.patch(matched.sanityDoc._id, {set: {filename: newTitle}})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
await tx.commit({returnDocuments: false})
|
|
83
|
+
setResyncState('done')
|
|
84
|
+
} catch (error) {
|
|
85
|
+
setResyncState('error')
|
|
86
|
+
setResyncError(error)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function syncOnlyEmpty() {
|
|
91
|
+
if (!matchedAssets) return
|
|
92
|
+
|
|
93
|
+
setResyncState('syncing')
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const tx = client.transaction()
|
|
97
|
+
|
|
98
|
+
matchedAssets.forEach((matched) => {
|
|
99
|
+
// Only update if the current title is empty or has the placeholder format
|
|
100
|
+
// AND there's a new title available from Mux
|
|
101
|
+
if (
|
|
102
|
+
matched.muxAsset &&
|
|
103
|
+
matched.muxTitle &&
|
|
104
|
+
isEmptyOrPlaceholderTitle(matched.currentTitle, matched.muxAsset.id)
|
|
105
|
+
) {
|
|
106
|
+
tx.patch(matched.sanityDoc._id, {set: {filename: matched.muxTitle}})
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
await tx.commit({returnDocuments: false})
|
|
111
|
+
setResyncState('done')
|
|
112
|
+
} catch (error) {
|
|
113
|
+
setResyncState('error')
|
|
114
|
+
setResyncError(error)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
sanityAssetsLoading,
|
|
120
|
+
closeDialog,
|
|
121
|
+
dialogOpen,
|
|
122
|
+
resyncState,
|
|
123
|
+
resyncError,
|
|
124
|
+
hasSecrets,
|
|
125
|
+
syncAllVideos,
|
|
126
|
+
syncOnlyEmpty,
|
|
127
|
+
matchedAssets,
|
|
128
|
+
muxAssets,
|
|
129
|
+
openDialog,
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const useSanityAssets = createHookFromObservableFactory<VideoAssetDocument[], DocumentStore>(
|
|
134
|
+
(documentStore) => {
|
|
135
|
+
return documentStore.listenQuery(
|
|
136
|
+
/* groq */ `*[_type == "mux.videoAsset"]`,
|
|
137
|
+
{},
|
|
138
|
+
{
|
|
139
|
+
apiVersion: SANITY_API_VERSION,
|
|
140
|
+
}
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {truncateString} from 'sanity'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generates a placeholder title for a Mux asset when no title is available.
|
|
5
|
+
* This format is used when importing assets that don't have a title in Mux.
|
|
6
|
+
*
|
|
7
|
+
* @param assetId - The Mux asset ID
|
|
8
|
+
* @returns A placeholder title in the format "Asset #[truncated-id]"
|
|
9
|
+
*/
|
|
10
|
+
export function generateAssetPlaceholder(assetId: string): string {
|
|
11
|
+
return `Asset #${truncateString(assetId, 15)}`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Checks if a filename is empty or has the placeholder format.
|
|
16
|
+
* This is used to determine if a video title should be updated during metadata sync.
|
|
17
|
+
*
|
|
18
|
+
* @param filename - The current filename/title of the video
|
|
19
|
+
* @param assetId - The Mux asset ID to check against
|
|
20
|
+
* @returns true if the filename is empty or matches the placeholder format
|
|
21
|
+
*/
|
|
22
|
+
export function isEmptyOrPlaceholderTitle(filename: string | undefined, assetId: string): boolean {
|
|
23
|
+
// Check if filename is empty/undefined
|
|
24
|
+
if (!filename || filename.trim() === '') {
|
|
25
|
+
return true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Check if filename matches the placeholder format for this asset
|
|
29
|
+
const placeholder = generateAssetPlaceholder(assetId)
|
|
30
|
+
return filename === placeholder
|
|
31
|
+
}
|