sanity-plugin-mux-input 2.1.0 → 2.2.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/lib/index.cjs +4037 -4
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.ts +14 -1
- package/lib/index.js +4026 -2
- package/lib/index.js.map +1 -1
- package/package.json +27 -28
- package/src/actions/assets.ts +30 -2
- package/src/clients/upChunkObservable.ts +1 -1
- package/src/components/ConfigureApi.tsx +9 -1
- package/src/components/FormField.tsx +8 -10
- package/src/components/IconInfo.tsx +23 -0
- package/src/components/Input.styled.tsx +0 -8
- package/src/components/Input.tsx +4 -3
- package/src/components/InputBrowser.tsx +1 -8
- package/src/components/Player.styled.tsx +5 -144
- package/src/components/Player.tsx +23 -109
- package/src/components/PlayerActionsMenu.tsx +0 -4
- package/src/components/SelectAsset.tsx +18 -58
- package/src/components/SelectSortOptions.tsx +45 -0
- package/src/components/SpinnerBox.tsx +17 -0
- package/src/components/StudioTool.tsx +20 -0
- package/src/components/VideoDetails/DeleteDialog.tsx +156 -0
- package/src/components/VideoDetails/VideoDetails.tsx +298 -0
- package/src/components/VideoDetails/VideoReferences.tsx +70 -0
- package/src/components/VideoDetails/useVideoDetails.ts +85 -0
- package/src/components/VideoInBrowser.tsx +183 -0
- package/src/components/VideoMetadata.tsx +43 -0
- package/src/components/VideoPlayer.tsx +69 -0
- package/src/components/VideoThumbnail.tsx +106 -0
- package/src/components/VideosBrowser.tsx +83 -0
- package/src/components/__legacy__Uploader.tsx +2 -9
- package/src/components/documentPreview/DocumentPreview.tsx +107 -0
- package/src/components/documentPreview/DraftStatus.tsx +34 -0
- package/src/components/documentPreview/MissingSchemaType.tsx +33 -0
- package/src/components/documentPreview/PaneItemPreview.tsx +71 -0
- package/src/components/documentPreview/PublishedStatus.tsx +35 -0
- package/src/components/documentPreview/TimeAgo.tsx +13 -0
- package/src/components/documentPreview/paneItemTypes.ts +7 -0
- package/src/components/icons/Resolution.tsx +12 -0
- package/src/components/icons/StopWatch.tsx +20 -0
- package/src/components/icons/ToolIcon.tsx +21 -0
- package/src/hooks/useAssets.ts +61 -0
- package/src/hooks/useCancelUpload.ts +2 -2
- package/src/hooks/useClient.ts +3 -1
- package/src/hooks/useDocReferences.ts +21 -0
- package/src/hooks/useInView.ts +45 -0
- package/src/index.ts +2 -0
- package/src/plugin.tsx +1 -1
- package/src/util/constants.ts +7 -0
- package/src/util/createSearchFilter.ts +78 -0
- package/src/util/formatSeconds.ts +22 -0
- package/src/util/getAnimatedPosterSrc.ts +1 -1
- package/src/util/getPlaybackId.ts +1 -1
- package/src/util/getPlaybackPolicy.ts +1 -1
- package/src/util/getVideoMetadata.ts +18 -0
- package/src/util/types.ts +16 -1
- package/lib/_chunks/Player-d8f163ed.cjs +0 -474
- package/lib/_chunks/Player-d8f163ed.cjs.map +0 -1
- package/lib/_chunks/Player-fb9712c0.js +0 -465
- package/lib/_chunks/Player-fb9712c0.js.map +0 -1
- package/lib/_chunks/index-0656ede8.js +0 -3229
- package/lib/_chunks/index-0656ede8.js.map +0 -1
- package/lib/_chunks/index-9cd542f1.cjs +0 -3271
- package/lib/_chunks/index-9cd542f1.cjs.map +0 -1
- package/src/components/EditThumbnailDialog.tsx +0 -74
- package/src/components/VideoSource.styled.tsx +0 -235
- package/src/components/VideoSource.tsx +0 -318
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import {Button, Dialog, Stack, Text} from '@sanity/ui'
|
|
2
|
-
import React, {useCallback, useId, useMemo, useState} from 'react'
|
|
3
|
-
import {getDevicePixelRatio} from 'use-device-pixel-ratio'
|
|
4
|
-
|
|
5
|
-
import {useClient} from '../hooks/useClient'
|
|
6
|
-
import type {SetDialogState} from '../hooks/useDialogState'
|
|
7
|
-
import type {VideoAssetDocument} from '../util/types'
|
|
8
|
-
import {VideoThumbnail} from './VideoSource.styled'
|
|
9
|
-
|
|
10
|
-
export interface Props {
|
|
11
|
-
asset: VideoAssetDocument
|
|
12
|
-
getCurrentTime: () => number
|
|
13
|
-
setDialogState: SetDialogState
|
|
14
|
-
}
|
|
15
|
-
export default function EditThumbnailDialog({asset, getCurrentTime, setDialogState}: Props) {
|
|
16
|
-
const client = useClient()
|
|
17
|
-
const dialogId = `EditThumbnailDialog${useId()}`
|
|
18
|
-
const nextTime = useMemo(() => getCurrentTime(), [getCurrentTime])
|
|
19
|
-
const assetWithNewThumbnail = useMemo(() => ({...asset, thumbTime: nextTime}), [asset, nextTime])
|
|
20
|
-
const [saving, setSaving] = useState(false)
|
|
21
|
-
const [error, setError] = useState<Error | null>(null)
|
|
22
|
-
const handleSave = useCallback(() => {
|
|
23
|
-
setSaving(true)
|
|
24
|
-
client
|
|
25
|
-
.patch(asset._id!)
|
|
26
|
-
.set({thumbTime: nextTime})
|
|
27
|
-
.commit({returnDocuments: false})
|
|
28
|
-
.then(() => void setDialogState(false))
|
|
29
|
-
.catch(setError)
|
|
30
|
-
.finally(() => void setSaving(false))
|
|
31
|
-
}, [client, asset._id, nextTime, setDialogState])
|
|
32
|
-
const width = 300 * getDevicePixelRatio({maxDpr: 2})
|
|
33
|
-
|
|
34
|
-
if (error) {
|
|
35
|
-
// eslint-disable-next-line no-warning-comments
|
|
36
|
-
// @TODO handle errors more gracefully
|
|
37
|
-
throw error
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return (
|
|
41
|
-
<Dialog
|
|
42
|
-
id={dialogId}
|
|
43
|
-
header="Edit thumbnail"
|
|
44
|
-
onClose={() => setDialogState(false)}
|
|
45
|
-
footer={
|
|
46
|
-
<Stack padding={3}>
|
|
47
|
-
<Button
|
|
48
|
-
key="thumbnail"
|
|
49
|
-
mode="ghost"
|
|
50
|
-
tone="primary"
|
|
51
|
-
loading={saving}
|
|
52
|
-
onClick={handleSave}
|
|
53
|
-
text="Set new thumbnail"
|
|
54
|
-
/>
|
|
55
|
-
</Stack>
|
|
56
|
-
}
|
|
57
|
-
>
|
|
58
|
-
<Stack space={3} padding={3}>
|
|
59
|
-
<Stack space={2}>
|
|
60
|
-
<Text size={1} weight="semibold">
|
|
61
|
-
Current:
|
|
62
|
-
</Text>
|
|
63
|
-
<VideoThumbnail asset={asset} width={width} />
|
|
64
|
-
</Stack>
|
|
65
|
-
<Stack space={2}>
|
|
66
|
-
<Text size={1} weight="semibold">
|
|
67
|
-
New:
|
|
68
|
-
</Text>
|
|
69
|
-
<VideoThumbnail asset={assetWithNewThumbnail} width={width} />
|
|
70
|
-
</Stack>
|
|
71
|
-
</Stack>
|
|
72
|
-
</Dialog>
|
|
73
|
-
)
|
|
74
|
-
}
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import {LockIcon, UnknownIcon} from '@sanity/icons'
|
|
2
|
-
import {Box, Card, Grid, Inline, Spinner} from '@sanity/ui'
|
|
3
|
-
import React, {memo, Suspense, useMemo} from 'react'
|
|
4
|
-
import {MediaPreview} from 'sanity'
|
|
5
|
-
import styled from 'styled-components'
|
|
6
|
-
import {suspend} from 'suspend-react'
|
|
7
|
-
import {useErrorBoundary} from 'use-error-boundary'
|
|
8
|
-
|
|
9
|
-
import {useClient} from '../hooks/useClient'
|
|
10
|
-
import {getAnimatedPosterSrc} from '../util/getAnimatedPosterSrc'
|
|
11
|
-
import {getPlaybackPolicy} from '../util/getPlaybackPolicy'
|
|
12
|
-
import {getPosterSrc} from '../util/getPosterSrc'
|
|
13
|
-
import type {VideoAssetDocument} from '../util/types'
|
|
14
|
-
|
|
15
|
-
const mediaDimensions = {aspect: 16 / 9}
|
|
16
|
-
|
|
17
|
-
interface ImageLoaderProps {
|
|
18
|
-
alt: string
|
|
19
|
-
src: string
|
|
20
|
-
height?: number
|
|
21
|
-
width: number
|
|
22
|
-
aspectRatio?: string
|
|
23
|
-
}
|
|
24
|
-
const ImageLoader = memo(function ImageLoader({
|
|
25
|
-
alt,
|
|
26
|
-
src,
|
|
27
|
-
height,
|
|
28
|
-
width,
|
|
29
|
-
aspectRatio,
|
|
30
|
-
}: ImageLoaderProps) {
|
|
31
|
-
suspend(async () => {
|
|
32
|
-
const img = new Image(width, height)
|
|
33
|
-
img.decoding = 'async'
|
|
34
|
-
img.src = src
|
|
35
|
-
await img.decode()
|
|
36
|
-
}, ['sanity-plugin-mux-input', 'image', src])
|
|
37
|
-
|
|
38
|
-
return <img alt={alt} src={src} height={height} width={width} style={{aspectRatio}} />
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
// @TODO fix typings errors due to props.renderDefault
|
|
42
|
-
const VideoMediaPreview = styled<any>(MediaPreview)`
|
|
43
|
-
img {
|
|
44
|
-
object-fit: cover;
|
|
45
|
-
}
|
|
46
|
-
`
|
|
47
|
-
|
|
48
|
-
interface VideoMediaPreviewSignedSubtitleProps {
|
|
49
|
-
solo?: boolean
|
|
50
|
-
}
|
|
51
|
-
const VideoMediaPreviewSignedSubtitle = ({solo}: VideoMediaPreviewSignedSubtitleProps) => {
|
|
52
|
-
return (
|
|
53
|
-
<Inline
|
|
54
|
-
space={1}
|
|
55
|
-
style={{
|
|
56
|
-
marginTop: solo ? '-1.35em' : undefined,
|
|
57
|
-
marginBottom: solo ? undefined : '0.35rem',
|
|
58
|
-
}}
|
|
59
|
-
>
|
|
60
|
-
<LockIcon />
|
|
61
|
-
Signed playback policy
|
|
62
|
-
</Inline>
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
interface PosterImageProps extends Omit<ImageLoaderProps, 'src' | 'alt'> {
|
|
67
|
-
asset: VideoAssetDocument
|
|
68
|
-
showTip?: boolean
|
|
69
|
-
}
|
|
70
|
-
const PosterImage = ({asset, height, width, showTip}: PosterImageProps) => {
|
|
71
|
-
const client = useClient()
|
|
72
|
-
const src = getPosterSrc({
|
|
73
|
-
asset,
|
|
74
|
-
client,
|
|
75
|
-
height,
|
|
76
|
-
width,
|
|
77
|
-
fit_mode: 'smartcrop',
|
|
78
|
-
})
|
|
79
|
-
const subtitle = useMemo(
|
|
80
|
-
() =>
|
|
81
|
-
showTip && getPlaybackPolicy(asset) === 'signed' ? (
|
|
82
|
-
<VideoMediaPreviewSignedSubtitle solo />
|
|
83
|
-
) : undefined,
|
|
84
|
-
[asset, showTip]
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
// eslint-disable-next-line no-warning-comments
|
|
88
|
-
// @TODO support setting the alt text in the schema, like how we deal with images
|
|
89
|
-
return (
|
|
90
|
-
<VideoMediaPreview
|
|
91
|
-
mediaDimensions={mediaDimensions}
|
|
92
|
-
subtitle={subtitle}
|
|
93
|
-
title={<>{null}</>}
|
|
94
|
-
media={<ImageLoader alt="" src={src} height={height} width={width} />}
|
|
95
|
-
/>
|
|
96
|
-
)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export interface VideoThumbnailProps extends Omit<PosterImageProps, 'height'> {
|
|
100
|
-
width: number
|
|
101
|
-
}
|
|
102
|
-
export const VideoThumbnail = memo(function VideoThumbnail({
|
|
103
|
-
asset,
|
|
104
|
-
width,
|
|
105
|
-
showTip,
|
|
106
|
-
}: VideoThumbnailProps) {
|
|
107
|
-
const {ErrorBoundary, didCatch, error} = useErrorBoundary()
|
|
108
|
-
const height = Math.round((width * 9) / 16)
|
|
109
|
-
const subtitle = useMemo(
|
|
110
|
-
() =>
|
|
111
|
-
showTip && getPlaybackPolicy(asset) === 'signed' ? (
|
|
112
|
-
<VideoMediaPreviewSignedSubtitle />
|
|
113
|
-
) : undefined,
|
|
114
|
-
[showTip, asset]
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
if (didCatch) {
|
|
118
|
-
return (
|
|
119
|
-
<VideoMediaPreview
|
|
120
|
-
subtitle={error.message}
|
|
121
|
-
mediaDimensions={mediaDimensions}
|
|
122
|
-
title="Error when loading thumbnail"
|
|
123
|
-
media={
|
|
124
|
-
<Card radius={2} height="fill" style={{position: 'relative', width: '100%'}}>
|
|
125
|
-
<Box
|
|
126
|
-
style={{
|
|
127
|
-
display: 'flex',
|
|
128
|
-
justifyContent: 'center',
|
|
129
|
-
alignItems: 'center',
|
|
130
|
-
position: 'absolute',
|
|
131
|
-
top: 0,
|
|
132
|
-
left: 0,
|
|
133
|
-
right: 0,
|
|
134
|
-
bottom: 0,
|
|
135
|
-
}}
|
|
136
|
-
>
|
|
137
|
-
<UnknownIcon />
|
|
138
|
-
</Box>
|
|
139
|
-
</Card>
|
|
140
|
-
}
|
|
141
|
-
/>
|
|
142
|
-
)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return (
|
|
146
|
-
<ErrorBoundary>
|
|
147
|
-
<Suspense
|
|
148
|
-
fallback={
|
|
149
|
-
<VideoMediaPreview
|
|
150
|
-
isPlaceholder
|
|
151
|
-
title="Loading thumbnail..."
|
|
152
|
-
subtitle={subtitle}
|
|
153
|
-
mediaDimensions={mediaDimensions}
|
|
154
|
-
/>
|
|
155
|
-
}
|
|
156
|
-
>
|
|
157
|
-
<PosterImage showTip={showTip} asset={asset} height={height} width={width} />
|
|
158
|
-
</Suspense>
|
|
159
|
-
</ErrorBoundary>
|
|
160
|
-
)
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
// @TODO fix typings errors due to props.renderDefault
|
|
164
|
-
const AnimatedVideoMediaPreview = styled<any>(MediaPreview)`
|
|
165
|
-
img {
|
|
166
|
-
object-fit: contain;
|
|
167
|
-
}
|
|
168
|
-
`
|
|
169
|
-
|
|
170
|
-
interface AnimatedPosterImageProps extends Omit<ImageLoaderProps, 'src' | 'alt' | 'height'> {
|
|
171
|
-
asset: VideoAssetDocument
|
|
172
|
-
}
|
|
173
|
-
const AnimatedPosterImage = ({asset, width}: AnimatedPosterImageProps) => {
|
|
174
|
-
const client = useClient()
|
|
175
|
-
const src = getAnimatedPosterSrc({asset, client, width})
|
|
176
|
-
|
|
177
|
-
// eslint-disable-next-line no-warning-comments
|
|
178
|
-
// @TODO support setting the alt text in the schema, like how we deal with images
|
|
179
|
-
return (
|
|
180
|
-
<AnimatedVideoMediaPreview
|
|
181
|
-
withBorder={false}
|
|
182
|
-
mediaDimensions={mediaDimensions}
|
|
183
|
-
media={<ImageLoader alt="" src={src} width={width} aspectRatio="16:9" />}
|
|
184
|
-
/>
|
|
185
|
-
)
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export interface AnimatedVideoThumbnailProps extends Omit<PosterImageProps, 'height'> {
|
|
189
|
-
width: number
|
|
190
|
-
}
|
|
191
|
-
export const AnimatedVideoThumbnail = memo(function AnimatedVideoThumbnail({
|
|
192
|
-
asset,
|
|
193
|
-
width,
|
|
194
|
-
}: AnimatedVideoThumbnailProps) {
|
|
195
|
-
const {ErrorBoundary, didCatch} = useErrorBoundary()
|
|
196
|
-
|
|
197
|
-
if (didCatch) {
|
|
198
|
-
return null
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return (
|
|
202
|
-
<ErrorBoundary>
|
|
203
|
-
<Suspense
|
|
204
|
-
fallback={
|
|
205
|
-
<FancyBackdrop>
|
|
206
|
-
<VideoMediaPreview
|
|
207
|
-
mediaDimensions={mediaDimensions}
|
|
208
|
-
withBorder={false}
|
|
209
|
-
media={<Spinner muted />}
|
|
210
|
-
/>
|
|
211
|
-
</FancyBackdrop>
|
|
212
|
-
}
|
|
213
|
-
>
|
|
214
|
-
<Card height="fill" tone="transparent">
|
|
215
|
-
<AnimatedPosterImage asset={asset} width={width} />
|
|
216
|
-
</Card>
|
|
217
|
-
</Suspense>
|
|
218
|
-
</ErrorBoundary>
|
|
219
|
-
)
|
|
220
|
-
})
|
|
221
|
-
const FancyBackdrop = styled(Box)`
|
|
222
|
-
backdrop-filter: blur(8px) brightness(0.5) saturate(2);
|
|
223
|
-
mix-blend-mode: color-dodge;
|
|
224
|
-
`
|
|
225
|
-
|
|
226
|
-
export const ThumbGrid = styled(Grid)`
|
|
227
|
-
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
|
228
|
-
`
|
|
229
|
-
|
|
230
|
-
export const CardLoadMore = styled(Card)`
|
|
231
|
-
border-top: 1px solid var(--card-border-color);
|
|
232
|
-
position: sticky;
|
|
233
|
-
bottom: 0;
|
|
234
|
-
z-index: 200;
|
|
235
|
-
`
|
|
@@ -1,318 +0,0 @@
|
|
|
1
|
-
import {EllipsisVerticalIcon, TrashIcon} from '@sanity/icons'
|
|
2
|
-
import {DownloadIcon} from '@sanity/icons'
|
|
3
|
-
import {
|
|
4
|
-
Box,
|
|
5
|
-
Button,
|
|
6
|
-
Card,
|
|
7
|
-
Checkbox,
|
|
8
|
-
Dialog,
|
|
9
|
-
Flex,
|
|
10
|
-
Grid,
|
|
11
|
-
Menu,
|
|
12
|
-
MenuButton,
|
|
13
|
-
MenuItem,
|
|
14
|
-
Spinner,
|
|
15
|
-
Stack,
|
|
16
|
-
Text,
|
|
17
|
-
useClickOutside,
|
|
18
|
-
useToast,
|
|
19
|
-
} from '@sanity/ui'
|
|
20
|
-
import {animate} from 'motion'
|
|
21
|
-
import React, {memo, useCallback, useEffect, useId, useLayoutEffect, useRef, useState} from 'react'
|
|
22
|
-
import styled from 'styled-components'
|
|
23
|
-
import {getDevicePixelRatio} from 'use-device-pixel-ratio'
|
|
24
|
-
|
|
25
|
-
import {deleteAsset} from '../actions/assets'
|
|
26
|
-
import {useClient} from '../hooks/useClient'
|
|
27
|
-
import type {VideoAssetDocument} from '../util/types'
|
|
28
|
-
import {AnimatedVideoThumbnail, CardLoadMore, ThumbGrid, VideoThumbnail} from './VideoSource.styled'
|
|
29
|
-
|
|
30
|
-
export interface AssetActionsMenuProps {
|
|
31
|
-
asset: VideoAssetDocument
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function AssetActionsMenu(props: AssetActionsMenuProps) {
|
|
35
|
-
const {asset} = props
|
|
36
|
-
const id = useId()
|
|
37
|
-
const [dialogState, setDialogState] = useState<false | 'show-uses' | 'confirm-delete'>()
|
|
38
|
-
const [open, setOpen] = useState(false)
|
|
39
|
-
const [menuElement, setMenuRef] = useState<HTMLDivElement | null>(null)
|
|
40
|
-
|
|
41
|
-
const handleDelete = useCallback(() => setDialogState('confirm-delete'), [])
|
|
42
|
-
const handleClick = useCallback(() => {
|
|
43
|
-
setDialogState(false)
|
|
44
|
-
setOpen(true)
|
|
45
|
-
}, [setDialogState])
|
|
46
|
-
const handleClose = useCallback(() => {
|
|
47
|
-
setDialogState(false)
|
|
48
|
-
setOpen(false)
|
|
49
|
-
}, [setDialogState])
|
|
50
|
-
|
|
51
|
-
useEffect(() => {
|
|
52
|
-
if (open && dialogState) {
|
|
53
|
-
setOpen(false)
|
|
54
|
-
}
|
|
55
|
-
}, [dialogState, open])
|
|
56
|
-
|
|
57
|
-
useClickOutside(
|
|
58
|
-
useCallback(() => setOpen(false), []),
|
|
59
|
-
[menuElement]
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<>
|
|
64
|
-
<MenuButton
|
|
65
|
-
id={`${id}-asset-menu`}
|
|
66
|
-
button={
|
|
67
|
-
<Button icon={EllipsisVerticalIcon} mode="ghost" onClick={handleClick} padding={2} />
|
|
68
|
-
}
|
|
69
|
-
menu={
|
|
70
|
-
<Menu ref={setMenuRef}>
|
|
71
|
-
<MenuItem tone="critical" icon={TrashIcon} text="Delete" onClick={handleDelete} />
|
|
72
|
-
</Menu>
|
|
73
|
-
}
|
|
74
|
-
portal
|
|
75
|
-
placement="right"
|
|
76
|
-
/>
|
|
77
|
-
{dialogState === 'confirm-delete' && <DeleteDialog asset={asset} onClose={handleClose} />}
|
|
78
|
-
</>
|
|
79
|
-
)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
interface DeleteDialogProps {
|
|
83
|
-
asset: VideoAssetDocument
|
|
84
|
-
onClose: () => void
|
|
85
|
-
}
|
|
86
|
-
function DeleteDialog(props: DeleteDialogProps) {
|
|
87
|
-
const {asset, onClose} = props
|
|
88
|
-
const client = useClient()
|
|
89
|
-
const {push: pushToast} = useToast()
|
|
90
|
-
const [deleting, setDeleting] = useState(false)
|
|
91
|
-
const [deleteOnMux, setDeleteOnMux] = useState(false)
|
|
92
|
-
const id = useId()
|
|
93
|
-
const noPaddingOnStack = true
|
|
94
|
-
const width = 200 * getDevicePixelRatio({maxDpr: 2})
|
|
95
|
-
|
|
96
|
-
const handleDelete = useCallback(async () => {
|
|
97
|
-
setDeleting(true)
|
|
98
|
-
try {
|
|
99
|
-
if (asset?._id) {
|
|
100
|
-
await client.delete(asset._id)
|
|
101
|
-
}
|
|
102
|
-
if (deleteOnMux && asset?.assetId) {
|
|
103
|
-
await deleteAsset(client, asset.assetId)
|
|
104
|
-
}
|
|
105
|
-
document
|
|
106
|
-
.querySelector(`[data-id="${asset._id}"]`)
|
|
107
|
-
?.parentElement?.setAttribute?.('hidden', 'true')
|
|
108
|
-
} catch (err: any) {
|
|
109
|
-
console.error('Failed during delete', err)
|
|
110
|
-
pushToast({
|
|
111
|
-
closable: true,
|
|
112
|
-
description: err?.message,
|
|
113
|
-
duration: 5000,
|
|
114
|
-
title: 'Uncaught error',
|
|
115
|
-
status: 'error',
|
|
116
|
-
})
|
|
117
|
-
} finally {
|
|
118
|
-
setDeleting(false)
|
|
119
|
-
onClose()
|
|
120
|
-
}
|
|
121
|
-
}, [asset._id, asset.assetId, client, deleteOnMux, onClose, pushToast])
|
|
122
|
-
|
|
123
|
-
return (
|
|
124
|
-
<Dialog
|
|
125
|
-
onClose={onClose}
|
|
126
|
-
id={`${id}-confirm-delete`}
|
|
127
|
-
header="Delete video"
|
|
128
|
-
footer={
|
|
129
|
-
<Grid padding={2} gap={2} columns={2}>
|
|
130
|
-
<Button mode="bleed" text="Cancel" onClick={onClose} />
|
|
131
|
-
<Button
|
|
132
|
-
text="Delete"
|
|
133
|
-
tone="critical"
|
|
134
|
-
icon={TrashIcon}
|
|
135
|
-
onClick={handleDelete}
|
|
136
|
-
loading={deleting}
|
|
137
|
-
// disabled={!canDelete}
|
|
138
|
-
/>
|
|
139
|
-
</Grid>
|
|
140
|
-
}
|
|
141
|
-
// __unstable_autoFocus
|
|
142
|
-
width={1}
|
|
143
|
-
>
|
|
144
|
-
<Stack
|
|
145
|
-
paddingX={noPaddingOnStack ? 0 : [2, 3, 4]}
|
|
146
|
-
paddingY={noPaddingOnStack ? 0 : [3, 3, 3, 4]}
|
|
147
|
-
space={1}
|
|
148
|
-
>
|
|
149
|
-
<Card paddingX={[2, 3, 4]} paddingY={[3, 3, 3, 4]}>
|
|
150
|
-
<Grid columns={3} gap={3}>
|
|
151
|
-
<Flex style={{gridColumn: 'span 2'}} align="center">
|
|
152
|
-
<Box padding={4}>
|
|
153
|
-
<Stack space={4}>
|
|
154
|
-
<Flex align="center" as="label">
|
|
155
|
-
<Checkbox
|
|
156
|
-
checked={deleteOnMux}
|
|
157
|
-
onChange={() => setDeleteOnMux((prev) => !prev)}
|
|
158
|
-
/>
|
|
159
|
-
<Text style={{margin: '0 10px'}}>Delete asset on Mux</Text>
|
|
160
|
-
</Flex>
|
|
161
|
-
<Flex align="center" as="label">
|
|
162
|
-
<Checkbox disabled checked />
|
|
163
|
-
<Text style={{margin: '0 10px'}}>Delete video from dataset</Text>
|
|
164
|
-
</Flex>
|
|
165
|
-
</Stack>
|
|
166
|
-
</Box>
|
|
167
|
-
</Flex>
|
|
168
|
-
<VideoThumbnail asset={asset} width={width} showTip />
|
|
169
|
-
</Grid>
|
|
170
|
-
</Card>
|
|
171
|
-
</Stack>
|
|
172
|
-
</Dialog>
|
|
173
|
-
)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export interface Props {
|
|
177
|
-
assets: VideoAssetDocument[]
|
|
178
|
-
isLoading: boolean
|
|
179
|
-
isLastPage: boolean
|
|
180
|
-
onSelect: (assetId: string) => void
|
|
181
|
-
onLoadMore: () => void
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export default function VideoSource({assets, isLoading, isLastPage, onSelect, onLoadMore}: Props) {
|
|
185
|
-
const handleClick = useCallback<React.MouseEventHandler<HTMLDivElement>>(
|
|
186
|
-
(event) => onSelect(event.currentTarget.dataset.id!),
|
|
187
|
-
[onSelect]
|
|
188
|
-
)
|
|
189
|
-
const handleKeyPress = useCallback<React.KeyboardEventHandler<HTMLDivElement>>(
|
|
190
|
-
(event) => {
|
|
191
|
-
if (event.key === 'Enter') {
|
|
192
|
-
onSelect(event.currentTarget.dataset.id!)
|
|
193
|
-
}
|
|
194
|
-
},
|
|
195
|
-
[onSelect]
|
|
196
|
-
)
|
|
197
|
-
const width = 200 * getDevicePixelRatio({maxDpr: 2})
|
|
198
|
-
|
|
199
|
-
return (
|
|
200
|
-
<>
|
|
201
|
-
<Box padding={4}>
|
|
202
|
-
<ThumbGrid gap={2}>
|
|
203
|
-
{assets.map((asset) => (
|
|
204
|
-
<VideoSourceItem
|
|
205
|
-
key={asset._id}
|
|
206
|
-
asset={asset}
|
|
207
|
-
onClick={handleClick}
|
|
208
|
-
onKeyPress={handleKeyPress}
|
|
209
|
-
width={width}
|
|
210
|
-
/>
|
|
211
|
-
))}
|
|
212
|
-
</ThumbGrid>
|
|
213
|
-
{isLoading && assets.length === 0 && (
|
|
214
|
-
<Flex justify="center">
|
|
215
|
-
<Spinner muted />
|
|
216
|
-
</Flex>
|
|
217
|
-
)}
|
|
218
|
-
|
|
219
|
-
{!isLoading && assets.length === 0 && (
|
|
220
|
-
<Text align="center" muted>
|
|
221
|
-
No videos
|
|
222
|
-
</Text>
|
|
223
|
-
)}
|
|
224
|
-
</Box>
|
|
225
|
-
{assets.length > 0 && !isLastPage && (
|
|
226
|
-
<CardLoadMore tone="default" padding={4}>
|
|
227
|
-
<Flex direction="column">
|
|
228
|
-
<Button
|
|
229
|
-
type="button"
|
|
230
|
-
icon={DownloadIcon}
|
|
231
|
-
loading={isLoading}
|
|
232
|
-
onClick={onLoadMore}
|
|
233
|
-
text="Load more"
|
|
234
|
-
tone="primary"
|
|
235
|
-
/>
|
|
236
|
-
</Flex>
|
|
237
|
-
</CardLoadMore>
|
|
238
|
-
)}
|
|
239
|
-
</>
|
|
240
|
-
)
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
interface VideoSourceItemProps {
|
|
244
|
-
asset: VideoAssetDocument
|
|
245
|
-
onClick: React.MouseEventHandler<HTMLDivElement>
|
|
246
|
-
onKeyPress: React.KeyboardEventHandler<HTMLDivElement>
|
|
247
|
-
width: number
|
|
248
|
-
}
|
|
249
|
-
const _VideoSourceItem = ({asset, onClick, onKeyPress, width}: VideoSourceItemProps) => {
|
|
250
|
-
const [hover, setHover] = useState<boolean | null>(null)
|
|
251
|
-
const ref = useRef<HTMLDivElement>(null)
|
|
252
|
-
useLayoutEffect(() => {
|
|
253
|
-
if (!ref.current || hover === null) {
|
|
254
|
-
return
|
|
255
|
-
}
|
|
256
|
-
if (hover) {
|
|
257
|
-
animate(ref.current, {opacity: 1})
|
|
258
|
-
} else {
|
|
259
|
-
animate(ref.current, {opacity: 0})
|
|
260
|
-
}
|
|
261
|
-
}, [hover])
|
|
262
|
-
return (
|
|
263
|
-
<Box height="fill" style={{position: 'relative'}}>
|
|
264
|
-
<Card
|
|
265
|
-
as="button"
|
|
266
|
-
data-id={asset._id}
|
|
267
|
-
onClick={onClick}
|
|
268
|
-
onKeyPress={onKeyPress}
|
|
269
|
-
tabIndex={0}
|
|
270
|
-
radius={2}
|
|
271
|
-
padding={1}
|
|
272
|
-
style={{lineHeight: 0, position: 'relative'}}
|
|
273
|
-
__unstable_focusRing
|
|
274
|
-
onMouseEnter={() => setHover(true)}
|
|
275
|
-
onMouseLeave={() => setHover(false)}
|
|
276
|
-
>
|
|
277
|
-
<VideoThumbnail asset={asset} width={width} showTip />
|
|
278
|
-
{asset?.playbackId && (
|
|
279
|
-
<AnimateWrapper tone="transparent" ref={ref} margin={1} radius={1}>
|
|
280
|
-
{hover !== null && <AnimatedVideoThumbnail asset={asset} width={width} />}
|
|
281
|
-
</AnimateWrapper>
|
|
282
|
-
)}
|
|
283
|
-
</Card>
|
|
284
|
-
<ActionsAssetsContainer>
|
|
285
|
-
<AssetActionsMenu asset={asset} />
|
|
286
|
-
</ActionsAssetsContainer>
|
|
287
|
-
</Box>
|
|
288
|
-
)
|
|
289
|
-
}
|
|
290
|
-
const VideoSourceItem = memo(_VideoSourceItem)
|
|
291
|
-
const AnimateWrapper = styled(Card)`
|
|
292
|
-
position: absolute;
|
|
293
|
-
top: 0;
|
|
294
|
-
left: 0;
|
|
295
|
-
right: 0;
|
|
296
|
-
bottom: 0;
|
|
297
|
-
will-change: opacity;
|
|
298
|
-
background: transparent;
|
|
299
|
-
background-color: hsl(0deg 0% 0% / 33%);
|
|
300
|
-
opacity: 0;
|
|
301
|
-
pointer-events: none;
|
|
302
|
-
`
|
|
303
|
-
|
|
304
|
-
const ActionsAssetsContainer = styled.div`
|
|
305
|
-
box-sizing: border-box;
|
|
306
|
-
position: absolute;
|
|
307
|
-
z-index: 300;
|
|
308
|
-
opacity: 0;
|
|
309
|
-
top: 7px;
|
|
310
|
-
right: 7px;
|
|
311
|
-
|
|
312
|
-
button:hover + &,
|
|
313
|
-
button:focus-visible + &,
|
|
314
|
-
&:hover,
|
|
315
|
-
&:focus-visible {
|
|
316
|
-
opacity: 1;
|
|
317
|
-
}
|
|
318
|
-
`
|