sanity-plugin-mux-input 2.1.1 → 2.2.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/README.md +1 -1
- package/lib/index.cjs +4038 -4
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.ts +14 -1
- package/lib/index.js +4027 -2
- package/lib/index.js.map +1 -1
- package/package.json +30 -30
- package/src/actions/assets.ts +30 -2
- 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-547f8e2a.cjs +0 -474
- package/lib/_chunks/Player-547f8e2a.cjs.map +0 -1
- package/lib/_chunks/Player-bfdb96f6.js +0 -465
- package/lib/_chunks/Player-bfdb96f6.js.map +0 -1
- package/lib/_chunks/index-39e38243.cjs +0 -3251
- package/lib/_chunks/index-39e38243.cjs.map +0 -1
- package/lib/_chunks/index-71899191.js +0 -3229
- package/lib/_chunks/index-71899191.js.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,73 +1,33 @@
|
|
|
1
|
-
import React, {useCallback
|
|
2
|
-
import {PatchEvent, set, setIfMissing} from 'sanity'
|
|
1
|
+
import React, {useCallback} from 'react'
|
|
2
|
+
import {PatchEvent, set, setIfMissing, unset} from 'sanity'
|
|
3
3
|
|
|
4
|
-
import {useClient} from '../hooks/useClient'
|
|
5
4
|
import type {SetDialogState} from '../hooks/useDialogState'
|
|
6
5
|
import type {MuxInputProps, VideoAssetDocument} from '../util/types'
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
const PER_PAGE = 200
|
|
10
|
-
|
|
11
|
-
function createQuery(start = 0, end = PER_PAGE) {
|
|
12
|
-
return /* groq */ `*[_type == "mux.videoAsset"] | order(_updatedAt desc) [${start}...${end}]`
|
|
13
|
-
}
|
|
6
|
+
import VideosBrowser, {type VideosBrowserProps} from './VideosBrowser'
|
|
14
7
|
|
|
15
8
|
export interface Props extends Pick<MuxInputProps, 'onChange'> {
|
|
16
9
|
asset?: VideoAssetDocument | null | undefined
|
|
17
10
|
setDialogState: SetDialogState
|
|
18
11
|
}
|
|
19
12
|
|
|
20
|
-
export default function SelectAssets({asset, onChange, setDialogState}: Props) {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
.fetch(createQuery(start, end))
|
|
34
|
-
.then((result: VideoAssetDocument[]) => {
|
|
35
|
-
setLastPage(result.length < PER_PAGE)
|
|
36
|
-
setAssets((prev) => prev.concat(result))
|
|
37
|
-
})
|
|
38
|
-
.finally(() => setLoading(false))
|
|
39
|
-
},
|
|
40
|
-
[client]
|
|
41
|
-
)
|
|
42
|
-
const handleSelect = useCallback<VideoSourceProps['onSelect']>(
|
|
43
|
-
(id) => {
|
|
44
|
-
const selected = assets.find((doc) => doc._id === id)
|
|
45
|
-
if (!selected) {
|
|
46
|
-
throw new TypeError(`Failed to find video asset with id: ${id}`)
|
|
13
|
+
export default function SelectAssets({asset: selectedAsset, onChange, setDialogState}: Props) {
|
|
14
|
+
const handleSelect = useCallback<Required<VideosBrowserProps>['onSelect']>(
|
|
15
|
+
(chosenAsset) => {
|
|
16
|
+
if (!chosenAsset?._id) {
|
|
17
|
+
onChange(PatchEvent.from([unset(['asset'])]))
|
|
18
|
+
}
|
|
19
|
+
if (chosenAsset._id !== selectedAsset?._id) {
|
|
20
|
+
onChange(
|
|
21
|
+
PatchEvent.from([
|
|
22
|
+
setIfMissing({asset: {}}),
|
|
23
|
+
set({_type: 'reference', _weak: true, _ref: chosenAsset._id}, ['asset']),
|
|
24
|
+
])
|
|
25
|
+
)
|
|
47
26
|
}
|
|
48
|
-
onChange(
|
|
49
|
-
PatchEvent.from([
|
|
50
|
-
setIfMissing({asset: {}}),
|
|
51
|
-
set({_type: 'reference', _weak: true, _ref: selected._id}, ['asset']),
|
|
52
|
-
])
|
|
53
|
-
)
|
|
54
27
|
setDialogState(false)
|
|
55
28
|
},
|
|
56
|
-
[
|
|
29
|
+
[onChange, setDialogState, selectedAsset]
|
|
57
30
|
)
|
|
58
|
-
const handleLoadMore = useCallback<VideoSourceProps['onLoadMore']>(() => {
|
|
59
|
-
fetchPage(++pageNoRef.current)
|
|
60
|
-
}, [fetchPage])
|
|
61
31
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return (
|
|
65
|
-
<VideoSource
|
|
66
|
-
onSelect={handleSelect}
|
|
67
|
-
assets={assets}
|
|
68
|
-
isLastPage={isLastPage}
|
|
69
|
-
isLoading={isLoading}
|
|
70
|
-
onLoadMore={handleLoadMore}
|
|
71
|
-
/>
|
|
72
|
-
)
|
|
32
|
+
return <VideosBrowser onSelect={handleSelect} />
|
|
73
33
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {SortIcon} from '@sanity/icons'
|
|
2
|
+
import {Button, Menu, MenuButton, MenuItem, PopoverProps} from '@sanity/ui'
|
|
3
|
+
import React, {useId} from 'react'
|
|
4
|
+
|
|
5
|
+
import {ASSET_SORT_OPTIONS, SortOption} from '../hooks/useAssets'
|
|
6
|
+
|
|
7
|
+
export const CONTEXT_MENU_POPOVER_PROPS: PopoverProps = {
|
|
8
|
+
constrainSize: true,
|
|
9
|
+
placement: 'bottom',
|
|
10
|
+
portal: true,
|
|
11
|
+
width: 0,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @sanity/ui components adapted from:
|
|
16
|
+
* https://github.com/sanity-io/sanity/blob/next/packages/sanity/src/desk/components/pane/PaneContextMenuButton.tsx#L19
|
|
17
|
+
*/
|
|
18
|
+
export function SelectSortOptions(props: {sort: SortOption; setSort: (s: SortOption) => void}) {
|
|
19
|
+
const id = useId()
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<MenuButton
|
|
23
|
+
button={
|
|
24
|
+
<Button text="Sort" icon={SortIcon} mode="bleed" padding={3} style={{cursor: 'pointer'}} />
|
|
25
|
+
}
|
|
26
|
+
id={id}
|
|
27
|
+
menu={
|
|
28
|
+
<Menu>
|
|
29
|
+
{Object.entries(ASSET_SORT_OPTIONS).map(([type, {label}]) => (
|
|
30
|
+
<MenuItem
|
|
31
|
+
key={type}
|
|
32
|
+
data-as="button"
|
|
33
|
+
onClick={() => props.setSort(type as SortOption)}
|
|
34
|
+
padding={3}
|
|
35
|
+
tone="default"
|
|
36
|
+
text={label}
|
|
37
|
+
pressed={type === props.sort}
|
|
38
|
+
/>
|
|
39
|
+
))}
|
|
40
|
+
</Menu>
|
|
41
|
+
}
|
|
42
|
+
popover={CONTEXT_MENU_POPOVER_PROPS}
|
|
43
|
+
/>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {Box, Spinner} from '@sanity/ui'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
const SpinnerBox: React.FC = () => (
|
|
5
|
+
<Box
|
|
6
|
+
style={{
|
|
7
|
+
display: 'flex',
|
|
8
|
+
alignItems: 'center',
|
|
9
|
+
justifyContent: 'center',
|
|
10
|
+
minHeight: '150px',
|
|
11
|
+
}}
|
|
12
|
+
>
|
|
13
|
+
<Spinner />
|
|
14
|
+
</Box>
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
export default SpinnerBox
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {Tool} from 'sanity'
|
|
3
|
+
|
|
4
|
+
import {Config} from '../util/types'
|
|
5
|
+
import ToolIcon from './icons/ToolIcon'
|
|
6
|
+
import VideosBrowser from './VideosBrowser'
|
|
7
|
+
|
|
8
|
+
const StudioTool: React.FC<Config> = () => {
|
|
9
|
+
return <VideosBrowser />
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function createStudioTool(config: Config): Tool {
|
|
13
|
+
const toolConfig = typeof config.tool === 'object' ? config.tool : {}
|
|
14
|
+
return {
|
|
15
|
+
name: 'mux',
|
|
16
|
+
title: toolConfig.title || 'Videos',
|
|
17
|
+
component: (props: any) => <StudioTool {...config} {...props} />,
|
|
18
|
+
icon: toolConfig.icon || ToolIcon,
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import {TrashIcon} from '@sanity/icons'
|
|
2
|
+
import {Button, Card, Checkbox, Dialog, Flex, Heading, Stack, Text, useToast} from '@sanity/ui'
|
|
3
|
+
import React, {useEffect, useState} from 'react'
|
|
4
|
+
import {SanityDocument} from 'sanity'
|
|
5
|
+
|
|
6
|
+
import {deleteAsset} from '../../actions/assets'
|
|
7
|
+
import {useClient} from '../../hooks/useClient'
|
|
8
|
+
import {DIALOGS_Z_INDEX} from '../../util/constants'
|
|
9
|
+
import {PluginPlacement, VideoAssetDocument} from '../../util/types'
|
|
10
|
+
import SpinnerBox from '../SpinnerBox'
|
|
11
|
+
import FileReferences from './VideoReferences'
|
|
12
|
+
|
|
13
|
+
export default function DeleteDialog({
|
|
14
|
+
asset,
|
|
15
|
+
references,
|
|
16
|
+
referencesLoading,
|
|
17
|
+
cancelDelete,
|
|
18
|
+
placement,
|
|
19
|
+
succeededDeleting,
|
|
20
|
+
}: {
|
|
21
|
+
asset: VideoAssetDocument
|
|
22
|
+
placement: PluginPlacement
|
|
23
|
+
references?: SanityDocument[]
|
|
24
|
+
referencesLoading: boolean
|
|
25
|
+
cancelDelete: () => void
|
|
26
|
+
succeededDeleting: () => void
|
|
27
|
+
}) {
|
|
28
|
+
const client = useClient()
|
|
29
|
+
const [state, setState] = useState<
|
|
30
|
+
'processing_deletion' | 'checkingReferences' | 'error_deleting' | 'cantDelete' | 'confirm'
|
|
31
|
+
>('checkingReferences')
|
|
32
|
+
const [deleteOnMux, setDeleteOnMux] = useState(true)
|
|
33
|
+
const toast = useToast()
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (state !== 'checkingReferences' || referencesLoading) return
|
|
37
|
+
|
|
38
|
+
setState(references?.length ? 'cantDelete' : 'confirm')
|
|
39
|
+
}, [state, references, referencesLoading])
|
|
40
|
+
|
|
41
|
+
async function confirmDelete() {
|
|
42
|
+
if (state !== 'confirm') return
|
|
43
|
+
|
|
44
|
+
setState('processing_deletion')
|
|
45
|
+
const worked = await deleteAsset({client, asset, deleteOnMux})
|
|
46
|
+
if (worked === true) {
|
|
47
|
+
toast.push({title: 'Successfully deleted video', status: 'success'})
|
|
48
|
+
succeededDeleting()
|
|
49
|
+
} else if (worked === 'failed-mux') {
|
|
50
|
+
toast.push({
|
|
51
|
+
title: 'Deleted video in Sanity',
|
|
52
|
+
description: "But it wasn't deleted in Mux",
|
|
53
|
+
status: 'warning',
|
|
54
|
+
})
|
|
55
|
+
succeededDeleting()
|
|
56
|
+
} else {
|
|
57
|
+
toast.push({title: 'Failed deleting video', status: 'error'})
|
|
58
|
+
|
|
59
|
+
setState('error_deleting')
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<Dialog
|
|
65
|
+
header={'Delete file'}
|
|
66
|
+
zOffset={DIALOGS_Z_INDEX}
|
|
67
|
+
id="deleting-file-details-dialog"
|
|
68
|
+
onClose={cancelDelete}
|
|
69
|
+
onClickOutside={cancelDelete}
|
|
70
|
+
width={1}
|
|
71
|
+
position="fixed"
|
|
72
|
+
footer={
|
|
73
|
+
<Card padding={3}>
|
|
74
|
+
<Flex justify="space-between" align="center">
|
|
75
|
+
<Button
|
|
76
|
+
icon={TrashIcon}
|
|
77
|
+
fontSize={2}
|
|
78
|
+
padding={3}
|
|
79
|
+
text="Delete file"
|
|
80
|
+
tone="critical"
|
|
81
|
+
onClick={confirmDelete}
|
|
82
|
+
disabled={['processing_deletion', 'checkingReferences', 'cantDelete'].some(
|
|
83
|
+
(s) => s === state
|
|
84
|
+
)}
|
|
85
|
+
/>
|
|
86
|
+
</Flex>
|
|
87
|
+
</Card>
|
|
88
|
+
}
|
|
89
|
+
>
|
|
90
|
+
<Card
|
|
91
|
+
padding={5}
|
|
92
|
+
style={{
|
|
93
|
+
minHeight: '150px',
|
|
94
|
+
display: 'flex',
|
|
95
|
+
alignItems: 'center',
|
|
96
|
+
justifyContent: 'center',
|
|
97
|
+
}}
|
|
98
|
+
>
|
|
99
|
+
<Stack space={3}>
|
|
100
|
+
{state === 'checkingReferences' && (
|
|
101
|
+
<>
|
|
102
|
+
<Heading size={2}>Checking if file can be deleted</Heading>
|
|
103
|
+
<SpinnerBox />
|
|
104
|
+
</>
|
|
105
|
+
)}
|
|
106
|
+
{state === 'cantDelete' && (
|
|
107
|
+
<>
|
|
108
|
+
<Heading size={2}>Video can't be deleted</Heading>
|
|
109
|
+
<Text size={2} style={{marginBottom: '2rem'}}>
|
|
110
|
+
There are {references?.length} document{references && references.length > 0 && 's'}{' '}
|
|
111
|
+
pointing to this file. Remove their references to this file or delete them before
|
|
112
|
+
proceeding.
|
|
113
|
+
</Text>
|
|
114
|
+
<FileReferences
|
|
115
|
+
references={references}
|
|
116
|
+
isLoaded={!referencesLoading}
|
|
117
|
+
placement={placement}
|
|
118
|
+
/>
|
|
119
|
+
</>
|
|
120
|
+
)}
|
|
121
|
+
{state === 'confirm' && (
|
|
122
|
+
<>
|
|
123
|
+
<Heading size={2}>Are you sure you want to delete this file?</Heading>
|
|
124
|
+
<Text size={2}>This action is irreversible</Text>
|
|
125
|
+
<Stack space={4} marginTop={4}>
|
|
126
|
+
<Flex align="center" as="label">
|
|
127
|
+
<Checkbox
|
|
128
|
+
checked={deleteOnMux}
|
|
129
|
+
onChange={() => setDeleteOnMux((prev) => !prev)}
|
|
130
|
+
/>
|
|
131
|
+
<Text style={{margin: '0 10px'}}>Delete asset on Mux</Text>
|
|
132
|
+
</Flex>
|
|
133
|
+
<Flex align="center" as="label">
|
|
134
|
+
<Checkbox disabled checked />
|
|
135
|
+
<Text style={{margin: '0 10px'}}>Delete video from dataset</Text>
|
|
136
|
+
</Flex>
|
|
137
|
+
</Stack>
|
|
138
|
+
</>
|
|
139
|
+
)}
|
|
140
|
+
{state === 'processing_deletion' && (
|
|
141
|
+
<>
|
|
142
|
+
<Heading size={2}>Deleting file...</Heading>
|
|
143
|
+
<SpinnerBox />
|
|
144
|
+
</>
|
|
145
|
+
)}
|
|
146
|
+
{state === 'error_deleting' && (
|
|
147
|
+
<>
|
|
148
|
+
<Heading size={2}>Something went wrong!</Heading>
|
|
149
|
+
<Text size={2}>Try deleting the file again by clicking the button below</Text>
|
|
150
|
+
</>
|
|
151
|
+
)}
|
|
152
|
+
</Stack>
|
|
153
|
+
</Card>
|
|
154
|
+
</Dialog>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CalendarIcon,
|
|
3
|
+
CheckmarkIcon,
|
|
4
|
+
ClockIcon,
|
|
5
|
+
CropIcon,
|
|
6
|
+
EditIcon,
|
|
7
|
+
ErrorOutlineIcon,
|
|
8
|
+
RevertIcon,
|
|
9
|
+
SearchIcon,
|
|
10
|
+
TrashIcon,
|
|
11
|
+
} from '@sanity/icons'
|
|
12
|
+
import {
|
|
13
|
+
Button,
|
|
14
|
+
Card,
|
|
15
|
+
Dialog,
|
|
16
|
+
Flex,
|
|
17
|
+
Heading,
|
|
18
|
+
Spinner,
|
|
19
|
+
Stack,
|
|
20
|
+
Tab,
|
|
21
|
+
TabList,
|
|
22
|
+
TabPanel,
|
|
23
|
+
Text,
|
|
24
|
+
TextInput,
|
|
25
|
+
} from '@sanity/ui'
|
|
26
|
+
import React, {useEffect, useState} from 'react'
|
|
27
|
+
|
|
28
|
+
import {DIALOGS_Z_INDEX} from '../../util/constants'
|
|
29
|
+
import FormField from '../FormField'
|
|
30
|
+
import IconInfo from '../IconInfo'
|
|
31
|
+
import {ResolutionIcon} from '../icons/Resolution'
|
|
32
|
+
import {StopWatchIcon} from '../icons/StopWatch'
|
|
33
|
+
import VideoPlayer from '../VideoPlayer'
|
|
34
|
+
import DeleteDialog from './DeleteDialog'
|
|
35
|
+
import useFileDetails, {FileDetailsProps} from './useVideoDetails'
|
|
36
|
+
import FileReferences from './VideoReferences'
|
|
37
|
+
|
|
38
|
+
const AssetInput: React.FC<{
|
|
39
|
+
label: string
|
|
40
|
+
description?: string
|
|
41
|
+
placeholder?: string
|
|
42
|
+
value: string
|
|
43
|
+
onInput: (e: React.FormEvent<HTMLInputElement>) => void
|
|
44
|
+
disabled?: boolean
|
|
45
|
+
}> = (props) => (
|
|
46
|
+
<FormField title={props.label} description={props.description} inputId={props.label}>
|
|
47
|
+
<TextInput
|
|
48
|
+
id={props.label}
|
|
49
|
+
value={props.value}
|
|
50
|
+
placeholder={props.placeholder}
|
|
51
|
+
onInput={props.onInput}
|
|
52
|
+
disabled={props.disabled}
|
|
53
|
+
/>
|
|
54
|
+
</FormField>
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
const VideoDetails: React.FC<FileDetailsProps> = (props) => {
|
|
58
|
+
const [tab, setTab] = useState<'details' | 'references'>('details')
|
|
59
|
+
const {
|
|
60
|
+
displayInfo,
|
|
61
|
+
filename,
|
|
62
|
+
modified,
|
|
63
|
+
references,
|
|
64
|
+
referencesLoading,
|
|
65
|
+
setFilename,
|
|
66
|
+
state,
|
|
67
|
+
setState,
|
|
68
|
+
handleClose,
|
|
69
|
+
confirmClose,
|
|
70
|
+
saveChanges,
|
|
71
|
+
} = useFileDetails(props)
|
|
72
|
+
|
|
73
|
+
const isSaving = state === 'saving'
|
|
74
|
+
|
|
75
|
+
// Avoid layout shifts in large screens' 2-column dialog by setting their `minHeight` to the container's
|
|
76
|
+
const [containerHeight, setContainerHeight] = useState<number | null>(null)
|
|
77
|
+
const contentsRef = React.useRef<HTMLDivElement>(null)
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!contentsRef.current || !('getBoundingClientRect' in contentsRef.current)) return
|
|
80
|
+
|
|
81
|
+
setContainerHeight(contentsRef.current.getBoundingClientRect().height)
|
|
82
|
+
}, [])
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<Dialog
|
|
86
|
+
header={displayInfo.title}
|
|
87
|
+
zOffset={DIALOGS_Z_INDEX}
|
|
88
|
+
id="file-details-dialog"
|
|
89
|
+
onClose={handleClose}
|
|
90
|
+
onClickOutside={handleClose}
|
|
91
|
+
width={2}
|
|
92
|
+
style={{minHeight: '50vh'}}
|
|
93
|
+
position="fixed"
|
|
94
|
+
footer={
|
|
95
|
+
<Card padding={3}>
|
|
96
|
+
<Flex justify="space-between" align="center">
|
|
97
|
+
<Button
|
|
98
|
+
icon={TrashIcon}
|
|
99
|
+
fontSize={2}
|
|
100
|
+
padding={3}
|
|
101
|
+
mode="bleed"
|
|
102
|
+
text="Delete"
|
|
103
|
+
tone="critical"
|
|
104
|
+
onClick={() => setState('deleting')}
|
|
105
|
+
disabled={isSaving}
|
|
106
|
+
/>
|
|
107
|
+
{modified && (
|
|
108
|
+
<Button
|
|
109
|
+
icon={CheckmarkIcon}
|
|
110
|
+
fontSize={2}
|
|
111
|
+
padding={3}
|
|
112
|
+
mode="ghost"
|
|
113
|
+
text="Save and close"
|
|
114
|
+
tone="positive"
|
|
115
|
+
onClick={saveChanges}
|
|
116
|
+
iconRight={isSaving && Spinner}
|
|
117
|
+
disabled={isSaving}
|
|
118
|
+
/>
|
|
119
|
+
)}
|
|
120
|
+
</Flex>
|
|
121
|
+
</Card>
|
|
122
|
+
}
|
|
123
|
+
>
|
|
124
|
+
{/* DELETION DIALOG */}
|
|
125
|
+
{state === 'deleting' && (
|
|
126
|
+
<DeleteDialog
|
|
127
|
+
asset={props.asset}
|
|
128
|
+
cancelDelete={() => setState('idle')}
|
|
129
|
+
placement={props.placement}
|
|
130
|
+
referencesLoading={referencesLoading}
|
|
131
|
+
references={references}
|
|
132
|
+
succeededDeleting={() => {
|
|
133
|
+
props.closeDialog()
|
|
134
|
+
}}
|
|
135
|
+
/>
|
|
136
|
+
)}
|
|
137
|
+
|
|
138
|
+
{/* CONFIRM CLOSING DIALOG */}
|
|
139
|
+
{state === 'closing' && (
|
|
140
|
+
<Dialog
|
|
141
|
+
header={'You have unsaved changes'}
|
|
142
|
+
zOffset={DIALOGS_Z_INDEX}
|
|
143
|
+
id="closing-file-details-dialog"
|
|
144
|
+
onClose={() => confirmClose(false)}
|
|
145
|
+
onClickOutside={() => confirmClose(false)}
|
|
146
|
+
width={1}
|
|
147
|
+
position="fixed"
|
|
148
|
+
footer={
|
|
149
|
+
<Card padding={3}>
|
|
150
|
+
<Flex justify="space-between" align="center">
|
|
151
|
+
<Button
|
|
152
|
+
icon={ErrorOutlineIcon}
|
|
153
|
+
fontSize={2}
|
|
154
|
+
padding={3}
|
|
155
|
+
text="Discard changes"
|
|
156
|
+
tone="critical"
|
|
157
|
+
onClick={() => confirmClose(true)}
|
|
158
|
+
/>
|
|
159
|
+
{modified && (
|
|
160
|
+
<Button
|
|
161
|
+
icon={RevertIcon}
|
|
162
|
+
fontSize={2}
|
|
163
|
+
padding={3}
|
|
164
|
+
mode="ghost"
|
|
165
|
+
text="Keep editing"
|
|
166
|
+
tone="primary"
|
|
167
|
+
onClick={() => confirmClose(false)}
|
|
168
|
+
/>
|
|
169
|
+
)}
|
|
170
|
+
</Flex>
|
|
171
|
+
</Card>
|
|
172
|
+
}
|
|
173
|
+
>
|
|
174
|
+
<Card padding={5}>
|
|
175
|
+
<Stack style={{textAlign: 'center'}} space={3}>
|
|
176
|
+
<Heading size={2}>Unsaved changes will be lost</Heading>
|
|
177
|
+
<Text size={2}>Are you sure you want to discard them?</Text>
|
|
178
|
+
</Stack>
|
|
179
|
+
</Card>
|
|
180
|
+
</Dialog>
|
|
181
|
+
)}
|
|
182
|
+
<Card
|
|
183
|
+
padding={4}
|
|
184
|
+
sizing="border"
|
|
185
|
+
style={{
|
|
186
|
+
containerType: 'inline-size',
|
|
187
|
+
}}
|
|
188
|
+
>
|
|
189
|
+
<Flex
|
|
190
|
+
sizing="border"
|
|
191
|
+
gap={4}
|
|
192
|
+
direction={['column', 'column', 'row']}
|
|
193
|
+
align="flex-start"
|
|
194
|
+
ref={contentsRef}
|
|
195
|
+
style={
|
|
196
|
+
typeof containerHeight === 'number'
|
|
197
|
+
? {
|
|
198
|
+
minHeight: containerHeight,
|
|
199
|
+
}
|
|
200
|
+
: undefined
|
|
201
|
+
}
|
|
202
|
+
>
|
|
203
|
+
<Stack space={4} flex={1} sizing="border">
|
|
204
|
+
<VideoPlayer asset={props.asset} autoPlay={props.asset.autoPlay || false} />
|
|
205
|
+
</Stack>
|
|
206
|
+
<Stack space={4} flex={1} sizing="border">
|
|
207
|
+
<TabList space={2}>
|
|
208
|
+
<Tab
|
|
209
|
+
aria-controls="details-panel"
|
|
210
|
+
icon={EditIcon}
|
|
211
|
+
id="details-tab"
|
|
212
|
+
label="Details"
|
|
213
|
+
onClick={() => setTab('details')}
|
|
214
|
+
selected={tab === 'details'}
|
|
215
|
+
/>
|
|
216
|
+
{references && references.length > 0 && (
|
|
217
|
+
<Tab
|
|
218
|
+
aria-controls="references-panel"
|
|
219
|
+
icon={SearchIcon}
|
|
220
|
+
id="references-tab"
|
|
221
|
+
label={`Used by (${references.length})`}
|
|
222
|
+
onClick={() => setTab('references')}
|
|
223
|
+
selected={tab === 'references'}
|
|
224
|
+
/>
|
|
225
|
+
)}
|
|
226
|
+
</TabList>
|
|
227
|
+
<TabPanel aria-labelledby="details-tab" id="details-panel" hidden={tab !== 'details'}>
|
|
228
|
+
<Stack space={4}>
|
|
229
|
+
<AssetInput
|
|
230
|
+
label="File name"
|
|
231
|
+
description="Not visible to users. Useful for finding files later."
|
|
232
|
+
value={filename || ''}
|
|
233
|
+
onInput={(e) => setFilename(e.currentTarget.value)}
|
|
234
|
+
disabled={state !== 'idle'}
|
|
235
|
+
/>
|
|
236
|
+
<Stack space={3}>
|
|
237
|
+
{displayInfo?.duration && (
|
|
238
|
+
<IconInfo
|
|
239
|
+
text={`Duration: ${displayInfo.duration}`}
|
|
240
|
+
icon={ClockIcon}
|
|
241
|
+
size={2}
|
|
242
|
+
/>
|
|
243
|
+
)}
|
|
244
|
+
{displayInfo?.max_stored_resolution && (
|
|
245
|
+
<IconInfo
|
|
246
|
+
text={`Max Resolution: ${displayInfo.max_stored_resolution}`}
|
|
247
|
+
icon={ResolutionIcon}
|
|
248
|
+
size={2}
|
|
249
|
+
/>
|
|
250
|
+
)}
|
|
251
|
+
{displayInfo?.max_stored_frame_rate && (
|
|
252
|
+
<IconInfo
|
|
253
|
+
text={`Frame rate: ${displayInfo.max_stored_frame_rate}`}
|
|
254
|
+
icon={StopWatchIcon}
|
|
255
|
+
size={2}
|
|
256
|
+
/>
|
|
257
|
+
)}
|
|
258
|
+
{displayInfo?.aspect_ratio && (
|
|
259
|
+
<IconInfo
|
|
260
|
+
text={`Aspect Ratio: ${displayInfo.aspect_ratio}`}
|
|
261
|
+
icon={CropIcon}
|
|
262
|
+
size={2}
|
|
263
|
+
/>
|
|
264
|
+
)}
|
|
265
|
+
<IconInfo
|
|
266
|
+
text={`Uploaded on: ${displayInfo.createdAt.toLocaleDateString('en', {
|
|
267
|
+
year: 'numeric',
|
|
268
|
+
month: '2-digit',
|
|
269
|
+
day: '2-digit',
|
|
270
|
+
hour: '2-digit',
|
|
271
|
+
minute: '2-digit',
|
|
272
|
+
hour12: true,
|
|
273
|
+
})}`}
|
|
274
|
+
icon={CalendarIcon}
|
|
275
|
+
size={2}
|
|
276
|
+
/>
|
|
277
|
+
</Stack>
|
|
278
|
+
</Stack>
|
|
279
|
+
</TabPanel>
|
|
280
|
+
<TabPanel
|
|
281
|
+
aria-labelledby="references-tab"
|
|
282
|
+
id="references-panel"
|
|
283
|
+
hidden={tab !== 'references'}
|
|
284
|
+
>
|
|
285
|
+
<FileReferences
|
|
286
|
+
references={references}
|
|
287
|
+
isLoaded={!referencesLoading}
|
|
288
|
+
placement={props.placement}
|
|
289
|
+
/>
|
|
290
|
+
</TabPanel>
|
|
291
|
+
</Stack>
|
|
292
|
+
</Flex>
|
|
293
|
+
</Card>
|
|
294
|
+
</Dialog>
|
|
295
|
+
)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export default VideoDetails
|