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.
Files changed (67) hide show
  1. package/lib/index.cjs +4037 -4
  2. package/lib/index.cjs.map +1 -1
  3. package/lib/index.d.ts +14 -1
  4. package/lib/index.js +4026 -2
  5. package/lib/index.js.map +1 -1
  6. package/package.json +27 -28
  7. package/src/actions/assets.ts +30 -2
  8. package/src/clients/upChunkObservable.ts +1 -1
  9. package/src/components/ConfigureApi.tsx +9 -1
  10. package/src/components/FormField.tsx +8 -10
  11. package/src/components/IconInfo.tsx +23 -0
  12. package/src/components/Input.styled.tsx +0 -8
  13. package/src/components/Input.tsx +4 -3
  14. package/src/components/InputBrowser.tsx +1 -8
  15. package/src/components/Player.styled.tsx +5 -144
  16. package/src/components/Player.tsx +23 -109
  17. package/src/components/PlayerActionsMenu.tsx +0 -4
  18. package/src/components/SelectAsset.tsx +18 -58
  19. package/src/components/SelectSortOptions.tsx +45 -0
  20. package/src/components/SpinnerBox.tsx +17 -0
  21. package/src/components/StudioTool.tsx +20 -0
  22. package/src/components/VideoDetails/DeleteDialog.tsx +156 -0
  23. package/src/components/VideoDetails/VideoDetails.tsx +298 -0
  24. package/src/components/VideoDetails/VideoReferences.tsx +70 -0
  25. package/src/components/VideoDetails/useVideoDetails.ts +85 -0
  26. package/src/components/VideoInBrowser.tsx +183 -0
  27. package/src/components/VideoMetadata.tsx +43 -0
  28. package/src/components/VideoPlayer.tsx +69 -0
  29. package/src/components/VideoThumbnail.tsx +106 -0
  30. package/src/components/VideosBrowser.tsx +83 -0
  31. package/src/components/__legacy__Uploader.tsx +2 -9
  32. package/src/components/documentPreview/DocumentPreview.tsx +107 -0
  33. package/src/components/documentPreview/DraftStatus.tsx +34 -0
  34. package/src/components/documentPreview/MissingSchemaType.tsx +33 -0
  35. package/src/components/documentPreview/PaneItemPreview.tsx +71 -0
  36. package/src/components/documentPreview/PublishedStatus.tsx +35 -0
  37. package/src/components/documentPreview/TimeAgo.tsx +13 -0
  38. package/src/components/documentPreview/paneItemTypes.ts +7 -0
  39. package/src/components/icons/Resolution.tsx +12 -0
  40. package/src/components/icons/StopWatch.tsx +20 -0
  41. package/src/components/icons/ToolIcon.tsx +21 -0
  42. package/src/hooks/useAssets.ts +61 -0
  43. package/src/hooks/useCancelUpload.ts +2 -2
  44. package/src/hooks/useClient.ts +3 -1
  45. package/src/hooks/useDocReferences.ts +21 -0
  46. package/src/hooks/useInView.ts +45 -0
  47. package/src/index.ts +2 -0
  48. package/src/plugin.tsx +1 -1
  49. package/src/util/constants.ts +7 -0
  50. package/src/util/createSearchFilter.ts +78 -0
  51. package/src/util/formatSeconds.ts +22 -0
  52. package/src/util/getAnimatedPosterSrc.ts +1 -1
  53. package/src/util/getPlaybackId.ts +1 -1
  54. package/src/util/getPlaybackPolicy.ts +1 -1
  55. package/src/util/getVideoMetadata.ts +18 -0
  56. package/src/util/types.ts +16 -1
  57. package/lib/_chunks/Player-d8f163ed.cjs +0 -474
  58. package/lib/_chunks/Player-d8f163ed.cjs.map +0 -1
  59. package/lib/_chunks/Player-fb9712c0.js +0 -465
  60. package/lib/_chunks/Player-fb9712c0.js.map +0 -1
  61. package/lib/_chunks/index-0656ede8.js +0 -3229
  62. package/lib/_chunks/index-0656ede8.js.map +0 -1
  63. package/lib/_chunks/index-9cd542f1.cjs +0 -3271
  64. package/lib/_chunks/index-9cd542f1.cjs.map +0 -1
  65. package/src/components/EditThumbnailDialog.tsx +0 -74
  66. package/src/components/VideoSource.styled.tsx +0 -235
  67. 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
- `