sanity-plugin-mux-input 2.15.0 → 2.16.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 +285 -118
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +286 -119
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/PlayerActionsMenu.tsx +14 -0
- package/src/components/ResyncMetadata.tsx +152 -73
- package/src/components/TextTracksManager.tsx +11 -55
- package/src/components/VideoDetails/VideoDetails.tsx +27 -11
- package/src/components/VideoDetails/useVideoDetails.ts +15 -1
- package/src/hooks/useResyncAsset.ts +110 -0
- package/src/hooks/useResyncMuxMetadata.ts +33 -0
- package/src/schema.ts +5 -0
- package/src/util/addKeysToMuxData.ts +30 -0
package/package.json
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
PlugIcon,
|
|
6
6
|
ResetIcon,
|
|
7
7
|
SearchIcon,
|
|
8
|
+
SyncIcon,
|
|
8
9
|
TranslateIcon,
|
|
9
10
|
UploadIcon,
|
|
10
11
|
} from '@sanity/icons'
|
|
@@ -28,6 +29,7 @@ import {styled} from 'styled-components'
|
|
|
28
29
|
|
|
29
30
|
import {useAccessControl} from '../hooks/useAccessControl'
|
|
30
31
|
import {type DialogState, type SetDialogState} from '../hooks/useDialogState'
|
|
32
|
+
import {useResyncAsset} from '../hooks/useResyncAsset'
|
|
31
33
|
import {getPlaybackPolicy} from '../util/getPlaybackPolicy'
|
|
32
34
|
import type {MuxInputProps, PluginConfig, VideoAssetDocument} from '../util/types'
|
|
33
35
|
import {FileInputMenuItem} from './FileInputMenuItem'
|
|
@@ -66,9 +68,15 @@ function PlayerActionsMenu(
|
|
|
66
68
|
const [menuElement, setMenuRef] = useState<HTMLDivElement | null>(null)
|
|
67
69
|
const isSigned = useMemo(() => getPlaybackPolicy(asset)?.policy === 'signed', [asset])
|
|
68
70
|
const {hasConfigAccess} = useAccessControl(props.config)
|
|
71
|
+
const {resyncAsset, isResyncing} = useResyncAsset({showToast: true})
|
|
69
72
|
|
|
70
73
|
const onReset = useCallback(() => onChange(PatchEvent.from(unset([]))), [onChange])
|
|
71
74
|
|
|
75
|
+
const handleResync = useCallback(async () => {
|
|
76
|
+
setOpen(false)
|
|
77
|
+
await resyncAsset(asset)
|
|
78
|
+
}, [resyncAsset, asset])
|
|
79
|
+
|
|
72
80
|
useEffect(() => {
|
|
73
81
|
if (open && dialogState) {
|
|
74
82
|
setOpen(false)
|
|
@@ -134,6 +142,12 @@ function PlayerActionsMenu(
|
|
|
134
142
|
text="Captions"
|
|
135
143
|
onClick={() => setDialogState('edit-captions')}
|
|
136
144
|
/>
|
|
145
|
+
<MenuItem
|
|
146
|
+
icon={SyncIcon}
|
|
147
|
+
text="Resync from Mux"
|
|
148
|
+
onClick={handleResync}
|
|
149
|
+
disabled={readOnly || isResyncing}
|
|
150
|
+
/>
|
|
137
151
|
</>
|
|
138
152
|
)}
|
|
139
153
|
<MenuDivider />
|
|
@@ -1,28 +1,106 @@
|
|
|
1
1
|
import {CheckmarkCircleIcon, ErrorOutlineIcon, SyncIcon} from '@sanity/icons'
|
|
2
|
-
import {Box, Button, Card, Dialog, Flex, Heading, Spinner, Stack, Text} from '@sanity/ui'
|
|
2
|
+
import {Box, Button, Card, Dialog, Flex, Heading, Radio, Spinner, Stack, Text} from '@sanity/ui'
|
|
3
|
+
import {useState} from 'react'
|
|
3
4
|
|
|
4
5
|
import useResyncMuxMetadata from '../hooks/useResyncMuxMetadata'
|
|
5
6
|
import {isEmptyOrPlaceholderTitle} from '../util/assetTitlePlaceholder'
|
|
6
7
|
import {DIALOGS_Z_INDEX} from '../util/constants'
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
type SyncOption = 'fillEmpty' | 'syncTitles' | 'fullResync'
|
|
10
|
+
|
|
11
|
+
interface OptionCardProps {
|
|
12
|
+
id: SyncOption
|
|
13
|
+
selected: boolean
|
|
14
|
+
onSelect: (id: SyncOption) => void
|
|
15
|
+
title: string
|
|
16
|
+
count: number
|
|
17
|
+
description: string
|
|
18
|
+
disabled?: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function OptionCard({
|
|
22
|
+
id,
|
|
23
|
+
selected,
|
|
24
|
+
onSelect,
|
|
25
|
+
title,
|
|
26
|
+
count,
|
|
27
|
+
description,
|
|
28
|
+
disabled,
|
|
29
|
+
}: OptionCardProps) {
|
|
30
|
+
return (
|
|
31
|
+
<Card
|
|
32
|
+
as="label"
|
|
33
|
+
padding={3}
|
|
34
|
+
radius={2}
|
|
35
|
+
border
|
|
36
|
+
tone={selected ? 'primary' : 'default'}
|
|
37
|
+
style={{
|
|
38
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
39
|
+
opacity: disabled ? 0.5 : 1,
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
<Flex gap={3} align="flex-start">
|
|
43
|
+
<Box paddingTop={1}>
|
|
44
|
+
<Radio
|
|
45
|
+
checked={selected}
|
|
46
|
+
onChange={() => onSelect(id)}
|
|
47
|
+
disabled={disabled}
|
|
48
|
+
name="sync-option"
|
|
49
|
+
/>
|
|
50
|
+
</Box>
|
|
51
|
+
<Stack space={2} flex={1}>
|
|
52
|
+
<Flex align="center" gap={2}>
|
|
53
|
+
<Text size={2} weight="semibold">
|
|
54
|
+
{title} ({count})
|
|
55
|
+
</Text>
|
|
56
|
+
</Flex>
|
|
57
|
+
<Text size={1} muted>
|
|
58
|
+
{description}
|
|
59
|
+
</Text>
|
|
60
|
+
</Stack>
|
|
61
|
+
</Flex>
|
|
62
|
+
</Card>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
9
66
|
function ResyncMetadataDialog(props: ReturnType<typeof useResyncMuxMetadata>) {
|
|
10
67
|
const {resyncState} = props
|
|
11
68
|
|
|
12
|
-
const canTriggerResync = resyncState === 'idle' || resyncState === 'error'
|
|
13
|
-
const isResyncing = resyncState === 'syncing'
|
|
14
|
-
const isDone = resyncState === 'done'
|
|
15
|
-
|
|
16
69
|
const videosToUpdate = props.matchedAssets?.filter((m) => m.muxAsset).length || 0
|
|
17
70
|
const videosWithEmptyOrPlaceholder =
|
|
18
71
|
props.matchedAssets?.filter(
|
|
19
72
|
(m) => m.muxAsset && m.muxTitle && isEmptyOrPlaceholderTitle(m.currentTitle, m.muxAsset.id)
|
|
20
73
|
).length || 0
|
|
21
74
|
|
|
75
|
+
const hasEmptyTitles = videosWithEmptyOrPlaceholder > 0
|
|
76
|
+
const defaultOption: SyncOption = hasEmptyTitles ? 'fillEmpty' : 'syncTitles'
|
|
77
|
+
const [selectedOption, setSelectedOption] = useState<SyncOption>(defaultOption)
|
|
78
|
+
|
|
79
|
+
const canTriggerResync = resyncState === 'idle' || resyncState === 'error'
|
|
80
|
+
const isResyncing = resyncState === 'syncing'
|
|
81
|
+
const isDone = resyncState === 'done'
|
|
82
|
+
const isLoading = props.muxAssets.loading || props.sanityAssetsLoading
|
|
83
|
+
|
|
84
|
+
const handleSync = () => {
|
|
85
|
+
switch (selectedOption) {
|
|
86
|
+
case 'fillEmpty':
|
|
87
|
+
props.syncOnlyEmpty()
|
|
88
|
+
break
|
|
89
|
+
case 'syncTitles':
|
|
90
|
+
props.syncAllVideos()
|
|
91
|
+
break
|
|
92
|
+
case 'fullResync':
|
|
93
|
+
props.syncFullData()
|
|
94
|
+
break
|
|
95
|
+
default:
|
|
96
|
+
break
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
22
100
|
return (
|
|
23
101
|
<Dialog
|
|
24
102
|
animate
|
|
25
|
-
header=
|
|
103
|
+
header="Sync with Mux"
|
|
26
104
|
zOffset={DIALOGS_Z_INDEX}
|
|
27
105
|
id="resync-metadata-dialog"
|
|
28
106
|
onClose={props.closeDialog}
|
|
@@ -32,40 +110,25 @@ function ResyncMetadataDialog(props: ReturnType<typeof useResyncMuxMetadata>) {
|
|
|
32
110
|
footer={
|
|
33
111
|
!isDone && (
|
|
34
112
|
<Card padding={3}>
|
|
35
|
-
<Flex justify="
|
|
113
|
+
<Flex justify="flex-end" gap={2}>
|
|
36
114
|
<Button
|
|
37
115
|
fontSize={2}
|
|
38
116
|
padding={3}
|
|
39
117
|
mode="ghost"
|
|
40
118
|
text="Cancel"
|
|
41
|
-
tone="critical"
|
|
42
119
|
onClick={props.closeDialog}
|
|
43
120
|
disabled={isResyncing}
|
|
44
121
|
/>
|
|
45
|
-
<
|
|
46
|
-
{
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
/>
|
|
56
|
-
)}
|
|
57
|
-
<Button
|
|
58
|
-
icon={SyncIcon}
|
|
59
|
-
fontSize={2}
|
|
60
|
-
padding={3}
|
|
61
|
-
mode="ghost"
|
|
62
|
-
text={`Update all (${videosToUpdate})`}
|
|
63
|
-
tone="positive"
|
|
64
|
-
onClick={props.syncAllVideos}
|
|
65
|
-
iconRight={isResyncing && Spinner}
|
|
66
|
-
disabled={!canTriggerResync}
|
|
67
|
-
/>
|
|
68
|
-
</Flex>
|
|
122
|
+
<Button
|
|
123
|
+
icon={SyncIcon}
|
|
124
|
+
fontSize={2}
|
|
125
|
+
padding={3}
|
|
126
|
+
text="Run sync"
|
|
127
|
+
tone="primary"
|
|
128
|
+
onClick={handleSync}
|
|
129
|
+
iconRight={isResyncing && Spinner}
|
|
130
|
+
disabled={!canTriggerResync || isLoading}
|
|
131
|
+
/>
|
|
69
132
|
</Flex>
|
|
70
133
|
</Card>
|
|
71
134
|
)
|
|
@@ -73,15 +136,17 @@ function ResyncMetadataDialog(props: ReturnType<typeof useResyncMuxMetadata>) {
|
|
|
73
136
|
>
|
|
74
137
|
<Box padding={4}>
|
|
75
138
|
{/* LOADING ASSETS STATE */}
|
|
76
|
-
{
|
|
77
|
-
<Card tone="primary" marginBottom={
|
|
139
|
+
{isLoading && (
|
|
140
|
+
<Card tone="primary" marginBottom={4} padding={3} border radius={2}>
|
|
78
141
|
<Flex align="center" gap={4}>
|
|
79
142
|
<Spinner muted size={4} />
|
|
80
143
|
<Stack space={2}>
|
|
81
144
|
<Text size={2} weight="semibold">
|
|
82
145
|
Loading assets from Mux
|
|
83
146
|
</Text>
|
|
84
|
-
<Text size={1}
|
|
147
|
+
<Text size={1} muted>
|
|
148
|
+
This may take a while.
|
|
149
|
+
</Text>
|
|
85
150
|
</Stack>
|
|
86
151
|
</Flex>
|
|
87
152
|
</Card>
|
|
@@ -89,7 +154,7 @@ function ResyncMetadataDialog(props: ReturnType<typeof useResyncMuxMetadata>) {
|
|
|
89
154
|
|
|
90
155
|
{/* ERROR LOADING MUX */}
|
|
91
156
|
{props.muxAssets.error && (
|
|
92
|
-
<Card tone="critical" marginBottom={
|
|
157
|
+
<Card tone="critical" marginBottom={4} padding={3} border radius={2}>
|
|
93
158
|
<Flex align="center" gap={2}>
|
|
94
159
|
<ErrorOutlineIcon fontSize={36} />
|
|
95
160
|
<Stack space={2}>
|
|
@@ -104,14 +169,16 @@ function ResyncMetadataDialog(props: ReturnType<typeof useResyncMuxMetadata>) {
|
|
|
104
169
|
|
|
105
170
|
{/* SYNCING STATE */}
|
|
106
171
|
{resyncState === 'syncing' && (
|
|
107
|
-
<Card tone="primary" marginBottom={
|
|
172
|
+
<Card tone="primary" marginBottom={4} padding={3} border radius={2}>
|
|
108
173
|
<Flex align="center" gap={4}>
|
|
109
174
|
<Spinner muted size={4} />
|
|
110
175
|
<Stack space={2}>
|
|
111
176
|
<Text size={2} weight="semibold">
|
|
112
|
-
|
|
177
|
+
Syncing metadata
|
|
178
|
+
</Text>
|
|
179
|
+
<Text size={1} muted>
|
|
180
|
+
Updating videos from Mux...
|
|
113
181
|
</Text>
|
|
114
|
-
<Text size={1}>Syncing titles from Mux...</Text>
|
|
115
182
|
</Stack>
|
|
116
183
|
</Flex>
|
|
117
184
|
</Card>
|
|
@@ -119,7 +186,7 @@ function ResyncMetadataDialog(props: ReturnType<typeof useResyncMuxMetadata>) {
|
|
|
119
186
|
|
|
120
187
|
{/* ERROR SYNCING */}
|
|
121
188
|
{resyncState === 'error' && (
|
|
122
|
-
<Card tone="critical" marginBottom={
|
|
189
|
+
<Card tone="critical" marginBottom={4} padding={3} border radius={2}>
|
|
123
190
|
<Flex align="center" gap={2}>
|
|
124
191
|
<ErrorOutlineIcon fontSize={36} />
|
|
125
192
|
<Stack space={2}>
|
|
@@ -138,45 +205,57 @@ function ResyncMetadataDialog(props: ReturnType<typeof useResyncMuxMetadata>) {
|
|
|
138
205
|
|
|
139
206
|
{/* SUCCESS STATE */}
|
|
140
207
|
{resyncState === 'done' && (
|
|
141
|
-
<Stack paddingY={5}
|
|
208
|
+
<Stack paddingY={5} space={3} style={{textAlign: 'center'}}>
|
|
142
209
|
<Box>
|
|
143
210
|
<CheckmarkCircleIcon fontSize={48} />
|
|
144
211
|
</Box>
|
|
145
|
-
<Heading size={2}>
|
|
146
|
-
<Text size={2}
|
|
212
|
+
<Heading size={2}>Sync completed</Heading>
|
|
213
|
+
<Text size={2} muted>
|
|
214
|
+
Videos have been updated from Mux.
|
|
215
|
+
</Text>
|
|
147
216
|
</Stack>
|
|
148
217
|
)}
|
|
149
218
|
|
|
150
|
-
{/*
|
|
151
|
-
{
|
|
219
|
+
{/* OPTIONS */}
|
|
220
|
+
{!isDone && !isLoading && !props.muxAssets.error && (
|
|
152
221
|
<Stack space={4}>
|
|
153
|
-
<
|
|
154
|
-
|
|
155
|
-
{videosToUpdate === 1 ? '' : 's'} with Mux metadata
|
|
156
|
-
</Heading>
|
|
157
|
-
<Text size={2}>
|
|
158
|
-
This will update video titles in Sanity to match those in Mux. No new videos will be
|
|
159
|
-
created.
|
|
222
|
+
<Text size={1} muted>
|
|
223
|
+
Found {videosToUpdate} video{videosToUpdate === 1 ? '' : 's'} linked to Mux.
|
|
160
224
|
</Text>
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
225
|
+
|
|
226
|
+
<Stack space={3}>
|
|
227
|
+
{hasEmptyTitles && (
|
|
228
|
+
<OptionCard
|
|
229
|
+
id="fillEmpty"
|
|
230
|
+
selected={selectedOption === 'fillEmpty'}
|
|
231
|
+
onSelect={setSelectedOption}
|
|
232
|
+
title="Fill missing titles only"
|
|
233
|
+
count={videosWithEmptyOrPlaceholder}
|
|
234
|
+
description="Updates only videos without a title or with placeholder titles (e.g., 'Asset #123') using the title from Mux."
|
|
235
|
+
disabled={isResyncing}
|
|
236
|
+
/>
|
|
237
|
+
)}
|
|
238
|
+
|
|
239
|
+
<OptionCard
|
|
240
|
+
id="syncTitles"
|
|
241
|
+
selected={selectedOption === 'syncTitles'}
|
|
242
|
+
onSelect={setSelectedOption}
|
|
243
|
+
title="Sync all titles"
|
|
244
|
+
count={videosToUpdate}
|
|
245
|
+
description="Replaces the title in Sanity with the title from Mux for all videos."
|
|
246
|
+
disabled={isResyncing}
|
|
247
|
+
/>
|
|
248
|
+
|
|
249
|
+
<OptionCard
|
|
250
|
+
id="fullResync"
|
|
251
|
+
selected={selectedOption === 'fullResync'}
|
|
252
|
+
onSelect={setSelectedOption}
|
|
253
|
+
title="Full resync"
|
|
254
|
+
count={videosToUpdate}
|
|
255
|
+
description="Updates all fields from Mux including status, duration, tracks, captions, and renditions."
|
|
256
|
+
disabled={isResyncing}
|
|
257
|
+
/>
|
|
258
|
+
</Stack>
|
|
180
259
|
</Stack>
|
|
181
260
|
)}
|
|
182
261
|
</Box>
|
|
@@ -197,5 +276,5 @@ export default function ResyncMetadata() {
|
|
|
197
276
|
}
|
|
198
277
|
|
|
199
278
|
// eslint-disable-next-line consistent-return
|
|
200
|
-
return <Button mode="bleed" text="
|
|
279
|
+
return <Button mode="bleed" text="Sync with Mux" onClick={resyncMetadata.openDialog} />
|
|
201
280
|
}
|
|
@@ -10,8 +10,9 @@ import {
|
|
|
10
10
|
import {Box, Button, Card, Dialog, Flex, Heading, Spinner, Stack, Text, useToast} from '@sanity/ui'
|
|
11
11
|
import {useEffect, useId, useMemo, useState} from 'react'
|
|
12
12
|
|
|
13
|
-
import {deleteTextTrack
|
|
13
|
+
import {deleteTextTrack} from '../actions/assets'
|
|
14
14
|
import {useClient} from '../hooks/useClient'
|
|
15
|
+
import {useResyncAsset} from '../hooks/useResyncAsset'
|
|
15
16
|
import {downloadVttFile} from '../util/textTracks'
|
|
16
17
|
import type {MuxTextTrack, VideoAssetDocument} from '../util/types'
|
|
17
18
|
import AddCaptionDialog from './AddCaptionDialog'
|
|
@@ -203,6 +204,7 @@ export default function TextTracksManager({
|
|
|
203
204
|
const client = useClient()
|
|
204
205
|
const toast = useToast()
|
|
205
206
|
const dialogId = `DeleteCaptionDialog${useId()}`
|
|
207
|
+
const {resyncAsset} = useResyncAsset()
|
|
206
208
|
const [downloadingTrackId, setDownloadingTrackId] = useState<string | null>(null)
|
|
207
209
|
const [deletingTrackId, setDeletingTrackId] = useState<string | null>(null)
|
|
208
210
|
const [addedTracks, setAddedTracks] = useState<MuxTextTrack[]>([])
|
|
@@ -216,27 +218,6 @@ export default function TextTracksManager({
|
|
|
216
218
|
|
|
217
219
|
const MAX_VISIBLE_TRACKS = 4
|
|
218
220
|
|
|
219
|
-
useEffect(() => {
|
|
220
|
-
if (!asset.assetId || !asset._id) return
|
|
221
|
-
|
|
222
|
-
const assetId = asset.assetId
|
|
223
|
-
const documentId = asset._id
|
|
224
|
-
|
|
225
|
-
const refreshAsset = async () => {
|
|
226
|
-
try {
|
|
227
|
-
const response = await getAsset(client, assetId)
|
|
228
|
-
await client
|
|
229
|
-
.patch(documentId)
|
|
230
|
-
.set({data: response.data, status: response.data.status})
|
|
231
|
-
.commit()
|
|
232
|
-
} catch (error) {
|
|
233
|
-
console.error('Failed to refresh asset data:', error)
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
refreshAsset()
|
|
238
|
-
}, [asset.assetId, asset._id, client])
|
|
239
|
-
|
|
240
221
|
const realTracks: MuxTextTrack[] = propTracks
|
|
241
222
|
? propTracks
|
|
242
223
|
: asset.data?.tracks?.filter((track): track is MuxTextTrack => track.type === 'text') || []
|
|
@@ -344,20 +325,13 @@ export default function TextTracksManager({
|
|
|
344
325
|
return undefined
|
|
345
326
|
}
|
|
346
327
|
|
|
347
|
-
const assetId = asset.assetId
|
|
348
|
-
const documentId = asset._id
|
|
349
|
-
|
|
350
328
|
const interval = setInterval(async () => {
|
|
351
329
|
try {
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
.patch(documentId)
|
|
355
|
-
.set({data: response.data, status: response.data.status})
|
|
356
|
-
.commit()
|
|
330
|
+
const muxData = await resyncAsset(asset)
|
|
331
|
+
if (!muxData) return
|
|
357
332
|
|
|
358
333
|
const fetchedTracks =
|
|
359
|
-
|
|
360
|
-
[]
|
|
334
|
+
muxData.tracks?.filter((track): track is MuxTextTrack => track.type === 'text') || []
|
|
361
335
|
|
|
362
336
|
const isMockTrackReplaced = (
|
|
363
337
|
mockTrack: MuxTextTrack,
|
|
@@ -444,7 +418,7 @@ export default function TextTracksManager({
|
|
|
444
418
|
}, 3000) // Poll every 3 seconds
|
|
445
419
|
|
|
446
420
|
return () => clearInterval(interval)
|
|
447
|
-
}, [allTracks, asset
|
|
421
|
+
}, [allTracks, asset, resyncAsset])
|
|
448
422
|
|
|
449
423
|
const visibleTracks = allTracks
|
|
450
424
|
.filter(
|
|
@@ -506,17 +480,8 @@ export default function TextTracksManager({
|
|
|
506
480
|
}
|
|
507
481
|
await deleteTextTrack(client, asset.assetId, track.id)
|
|
508
482
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const response = await getAsset(client, asset.assetId)
|
|
512
|
-
await client
|
|
513
|
-
.patch(asset._id)
|
|
514
|
-
.set({data: response.data, status: response.data.status})
|
|
515
|
-
.commit()
|
|
516
|
-
} catch (refreshError) {
|
|
517
|
-
console.error('Failed to refresh asset data:', refreshError)
|
|
518
|
-
}
|
|
519
|
-
}
|
|
483
|
+
// Refresh asset data after deletion
|
|
484
|
+
await resyncAsset(asset)
|
|
520
485
|
|
|
521
486
|
toast.push({
|
|
522
487
|
title: 'Successfully deleted caption track',
|
|
@@ -600,17 +565,8 @@ export default function TextTracksManager({
|
|
|
600
565
|
|
|
601
566
|
setTrackToEdit(null)
|
|
602
567
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
const response = await getAsset(client, asset.assetId)
|
|
606
|
-
await client
|
|
607
|
-
.patch(asset._id)
|
|
608
|
-
.set({data: response.data, status: response.data.status})
|
|
609
|
-
.commit()
|
|
610
|
-
} catch (refreshError) {
|
|
611
|
-
console.error('Failed to refresh asset data:', refreshError)
|
|
612
|
-
}
|
|
613
|
-
}
|
|
568
|
+
// Refresh asset data after update
|
|
569
|
+
await resyncAsset(asset)
|
|
614
570
|
}
|
|
615
571
|
|
|
616
572
|
const getTrackSourceLabel = (track: MuxTextTrack) => {
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
ErrorOutlineIcon,
|
|
8
8
|
RevertIcon,
|
|
9
9
|
SearchIcon,
|
|
10
|
+
SyncIcon,
|
|
10
11
|
TagIcon,
|
|
11
12
|
TrashIcon,
|
|
12
13
|
} from '@sanity/icons'
|
|
@@ -71,6 +72,8 @@ const VideoDetails: React.FC<VideoDetailsProps> = (props) => {
|
|
|
71
72
|
handleClose,
|
|
72
73
|
confirmClose,
|
|
73
74
|
saveChanges,
|
|
75
|
+
handleResync,
|
|
76
|
+
isResyncing,
|
|
74
77
|
} = useVideoDetails(props)
|
|
75
78
|
|
|
76
79
|
const isSaving = state === 'saving'
|
|
@@ -97,16 +100,29 @@ const VideoDetails: React.FC<VideoDetailsProps> = (props) => {
|
|
|
97
100
|
footer={
|
|
98
101
|
<Card padding={3}>
|
|
99
102
|
<Flex justify="space-between" align="center">
|
|
100
|
-
<
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
103
|
+
<Flex gap={2}>
|
|
104
|
+
<Button
|
|
105
|
+
icon={TrashIcon}
|
|
106
|
+
fontSize={2}
|
|
107
|
+
padding={3}
|
|
108
|
+
mode="bleed"
|
|
109
|
+
text="Delete"
|
|
110
|
+
tone="critical"
|
|
111
|
+
onClick={() => setState('deleting')}
|
|
112
|
+
disabled={isSaving || isResyncing}
|
|
113
|
+
/>
|
|
114
|
+
<Button
|
|
115
|
+
icon={SyncIcon}
|
|
116
|
+
fontSize={2}
|
|
117
|
+
padding={3}
|
|
118
|
+
mode="bleed"
|
|
119
|
+
text="Resync"
|
|
120
|
+
tone="primary"
|
|
121
|
+
onClick={handleResync}
|
|
122
|
+
disabled={isSaving || isResyncing}
|
|
123
|
+
iconRight={isResyncing && Spinner}
|
|
124
|
+
/>
|
|
125
|
+
</Flex>
|
|
110
126
|
{modified && (
|
|
111
127
|
<Button
|
|
112
128
|
icon={CheckmarkIcon}
|
|
@@ -117,7 +133,7 @@ const VideoDetails: React.FC<VideoDetailsProps> = (props) => {
|
|
|
117
133
|
tone="positive"
|
|
118
134
|
onClick={saveChanges}
|
|
119
135
|
iconRight={isSaving && Spinner}
|
|
120
|
-
disabled={isSaving}
|
|
136
|
+
disabled={isSaving || isResyncing}
|
|
121
137
|
/>
|
|
122
138
|
)}
|
|
123
139
|
</Flex>
|
|
@@ -4,9 +4,12 @@ import {useDocumentStore} from 'sanity'
|
|
|
4
4
|
|
|
5
5
|
import {useClient} from '../../hooks/useClient'
|
|
6
6
|
import useDocReferences from '../../hooks/useDocReferences'
|
|
7
|
+
import {useResyncAsset} from '../../hooks/useResyncAsset'
|
|
7
8
|
import getVideoMetadata from '../../util/getVideoMetadata'
|
|
8
9
|
import {VideoAssetDocument} from '../../util/types'
|
|
9
10
|
|
|
11
|
+
type VideoDetailsState = 'idle' | 'saving' | 'deleting' | 'closing' | 'resyncing'
|
|
12
|
+
|
|
10
13
|
export interface VideoDetailsProps {
|
|
11
14
|
closeDialog: () => void
|
|
12
15
|
asset: VideoAssetDocument & {autoPlay?: boolean}
|
|
@@ -27,7 +30,16 @@ export default function useVideoDetails(props: VideoDetailsProps) {
|
|
|
27
30
|
|
|
28
31
|
const displayInfo = getVideoMetadata({...props.asset, filename})
|
|
29
32
|
|
|
30
|
-
const [state, setState] = useState<
|
|
33
|
+
const [state, setState] = useState<VideoDetailsState>('idle')
|
|
34
|
+
|
|
35
|
+
const {resyncAsset, isResyncing} = useResyncAsset({showToast: true})
|
|
36
|
+
|
|
37
|
+
async function handleResync() {
|
|
38
|
+
if (state !== 'idle') return
|
|
39
|
+
setState('resyncing')
|
|
40
|
+
await resyncAsset(props.asset)
|
|
41
|
+
setState('idle')
|
|
42
|
+
}
|
|
31
43
|
|
|
32
44
|
function handleClose() {
|
|
33
45
|
if (state !== 'idle') return
|
|
@@ -85,5 +97,7 @@ export default function useVideoDetails(props: VideoDetailsProps) {
|
|
|
85
97
|
handleClose,
|
|
86
98
|
confirmClose,
|
|
87
99
|
saveChanges,
|
|
100
|
+
handleResync,
|
|
101
|
+
isResyncing,
|
|
88
102
|
}
|
|
89
103
|
}
|