sanity-plugin-mux-input 3.0.4 → 4.0.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/README.md +0 -2
- package/dist/index.d.ts +134 -193
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2893 -4417
- package/dist/index.js.map +1 -1
- package/package.json +33 -43
- package/dist/index.cjs +0 -7270
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -347
- package/sanity.json +0 -8
- package/src/_exports/index.ts +0 -73
- package/src/actions/assets.ts +0 -152
- package/src/actions/secrets.ts +0 -111
- package/src/actions/upload.ts +0 -310
- package/src/clients/upChunkObservable.ts +0 -54
- package/src/components/AddCaptionDialog.tsx +0 -440
- package/src/components/CaptionsDialog.tsx +0 -23
- package/src/components/ConfigureApi.styled.tsx +0 -19
- package/src/components/ConfigureApi.tsx +0 -296
- package/src/components/DraggableWatermark.tsx +0 -877
- package/src/components/EditCaptionDialog.tsx +0 -510
- package/src/components/EditThumbnailDialog.tsx +0 -122
- package/src/components/ErrorBoundaryCard.tsx +0 -96
- package/src/components/FileInputButton.tsx +0 -54
- package/src/components/FileInputMenuItem.styled.tsx +0 -36
- package/src/components/FileInputMenuItem.tsx +0 -85
- package/src/components/FormField.tsx +0 -38
- package/src/components/IconInfo.tsx +0 -22
- package/src/components/ImportVideosFromMux.tsx +0 -342
- package/src/components/Input.styled.tsx +0 -22
- package/src/components/Input.tsx +0 -78
- package/src/components/InputBrowser.tsx +0 -41
- package/src/components/InputError.tsx +0 -17
- package/src/components/MuxLogo.tsx +0 -42
- package/src/components/Onboard.tsx +0 -65
- package/src/components/PageSelector.tsx +0 -54
- package/src/components/Player.styled.tsx +0 -55
- package/src/components/Player.tsx +0 -117
- package/src/components/PlayerActionsMenu.tsx +0 -190
- package/src/components/ResyncMetadata.tsx +0 -280
- package/src/components/SelectAsset.tsx +0 -39
- package/src/components/SelectSortOptions.tsx +0 -45
- package/src/components/SpinnerBox.tsx +0 -16
- package/src/components/StudioTool.tsx +0 -24
- package/src/components/TextTracksEditor.tsx +0 -117
- package/src/components/TextTracksManager.tsx +0 -737
- package/src/components/UploadConfiguration.tsx +0 -694
- package/src/components/UploadPlaceholder.tsx +0 -88
- package/src/components/UploadProgress.tsx +0 -80
- package/src/components/Uploader.styled.tsx +0 -66
- package/src/components/Uploader.tsx +0 -498
- package/src/components/VideoDetails/DeleteDialog.tsx +0 -147
- package/src/components/VideoDetails/VideoDetails.tsx +0 -358
- package/src/components/VideoDetails/VideoReferences.tsx +0 -63
- package/src/components/VideoDetails/useVideoDetails.ts +0 -103
- package/src/components/VideoInBrowser.tsx +0 -245
- package/src/components/VideoMetadata.tsx +0 -45
- package/src/components/VideoPlayer.tsx +0 -235
- package/src/components/VideoThumbnail.tsx +0 -138
- package/src/components/VideosBrowser.tsx +0 -100
- package/src/components/documentPreview/DocumentPreview.tsx +0 -83
- package/src/components/documentPreview/DraftStatus.tsx +0 -34
- package/src/components/documentPreview/MissingSchemaType.tsx +0 -32
- package/src/components/documentPreview/PaneItemPreview.tsx +0 -74
- package/src/components/documentPreview/PublishedStatus.tsx +0 -35
- package/src/components/documentPreview/TimeAgo.tsx +0 -12
- package/src/components/documentPreview/paneItemTypes.ts +0 -7
- package/src/components/icons/Audio.tsx +0 -13
- package/src/components/icons/Resolution.tsx +0 -12
- package/src/components/icons/StopWatch.tsx +0 -20
- package/src/components/icons/ToolIcon.tsx +0 -19
- package/src/components/uploadConfiguration/PlaybackPolicy.tsx +0 -133
- package/src/components/uploadConfiguration/PlaybackPolicyOption.tsx +0 -76
- package/src/components/uploadConfiguration/PlaybackPolicyWarning.tsx +0 -29
- package/src/components/uploadConfiguration/ResolutionTierSelector.tsx +0 -72
- package/src/components/uploadConfiguration/StaticRenditionSelector.tsx +0 -180
- package/src/components/withFocusRing/helpers.ts +0 -24
- package/src/components/withFocusRing/index.ts +0 -1
- package/src/components/withFocusRing/withFocusRing.ts +0 -30
- package/src/context/DialogStateContext.tsx +0 -36
- package/src/context/DrmPlaybackWarningContext.tsx +0 -93
- package/src/hooks/useAccessControl.ts +0 -13
- package/src/hooks/useAssetDocumentValues.ts +0 -11
- package/src/hooks/useAssets.ts +0 -68
- package/src/hooks/useCancelUpload.ts +0 -22
- package/src/hooks/useClient.ts +0 -8
- package/src/hooks/useDialogState.ts +0 -11
- package/src/hooks/useDocReferences.ts +0 -21
- package/src/hooks/useFetchFileSize.ts +0 -54
- package/src/hooks/useImportMuxAssets.ts +0 -132
- package/src/hooks/useInView.ts +0 -42
- package/src/hooks/useMediaMetadata.ts +0 -103
- package/src/hooks/useMuxAssets.ts +0 -179
- package/src/hooks/useMuxPolling.ts +0 -49
- package/src/hooks/useResyncAsset.ts +0 -110
- package/src/hooks/useResyncMuxMetadata.ts +0 -176
- package/src/hooks/useSaveSecrets.ts +0 -78
- package/src/hooks/useSecretsDocumentValues.ts +0 -38
- package/src/hooks/useSecretsFormState.ts +0 -47
- package/src/plugin.tsx +0 -31
- package/src/sanity-ui.d.ts +0 -5
- package/src/schema.ts +0 -196
- package/src/util/addKeysToMuxData.ts +0 -30
- package/src/util/areSecretsSignable.ts +0 -5
- package/src/util/asserters.ts +0 -36
- package/src/util/assetTitlePlaceholder.ts +0 -31
- package/src/util/constants.ts +0 -15
- package/src/util/convertWatermarkToMux.ts +0 -160
- package/src/util/createSearchFilter.ts +0 -76
- package/src/util/createUrlParamsObject.ts +0 -29
- package/src/util/extractFiles.ts +0 -67
- package/src/util/formatBytes.ts +0 -32
- package/src/util/formatDriveShareLink.ts +0 -64
- package/src/util/formatSeconds.ts +0 -49
- package/src/util/generateJwt.ts +0 -57
- package/src/util/getAnimatedPosterSrc.ts +0 -26
- package/src/util/getPlaybackPolicy.ts +0 -69
- package/src/util/getPosterSrc.ts +0 -28
- package/src/util/getStoryboardSrc.ts +0 -27
- package/src/util/getVideoMetadata.ts +0 -23
- package/src/util/getVideoSrc.ts +0 -23
- package/src/util/isSigned.ts +0 -20
- package/src/util/parsers.ts +0 -5
- package/src/util/pluginVersion.ts +0 -1
- package/src/util/readSecrets.ts +0 -38
- package/src/util/roundPxString.ts +0 -16
- package/src/util/textTracks.ts +0 -222
- package/src/util/tryWithSuspend.ts +0 -22
- package/src/util/types.ts +0 -596
- package/v2-incompatible.js +0 -11
package/src/components/Input.tsx
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import {Card} from '@sanity/ui'
|
|
2
|
-
import {memo, Suspense} from 'react'
|
|
3
|
-
|
|
4
|
-
import {useAccessControl} from '../hooks/useAccessControl'
|
|
5
|
-
import {useAssetDocumentValues} from '../hooks/useAssetDocumentValues'
|
|
6
|
-
import {useClient} from '../hooks/useClient'
|
|
7
|
-
import {useDialogState} from '../hooks/useDialogState'
|
|
8
|
-
import {useMuxPolling} from '../hooks/useMuxPolling'
|
|
9
|
-
import {useSecretsDocumentValues} from '../hooks/useSecretsDocumentValues'
|
|
10
|
-
import type {MuxInputProps, PluginConfig} from '../util/types'
|
|
11
|
-
import {ConfigureApiDialog} from './ConfigureApi'
|
|
12
|
-
import ErrorBoundaryCard from './ErrorBoundaryCard'
|
|
13
|
-
import {InputFallback} from './Input.styled'
|
|
14
|
-
import Onboard from './Onboard'
|
|
15
|
-
import Uploader from './Uploader'
|
|
16
|
-
|
|
17
|
-
export interface InputProps extends MuxInputProps {
|
|
18
|
-
config: PluginConfig
|
|
19
|
-
}
|
|
20
|
-
const Input = (props: InputProps) => {
|
|
21
|
-
const client = useClient()
|
|
22
|
-
const secretDocumentValues = useSecretsDocumentValues()
|
|
23
|
-
const assetDocumentValues = useAssetDocumentValues(props.value?.asset)
|
|
24
|
-
const poll = useMuxPolling(props.readOnly ? undefined : assetDocumentValues?.value || undefined)
|
|
25
|
-
const [dialogState, setDialogState] = useDialogState()
|
|
26
|
-
const {hasConfigAccess} = useAccessControl(props.config)
|
|
27
|
-
|
|
28
|
-
const error = secretDocumentValues.error || assetDocumentValues.error || poll.error /*||
|
|
29
|
-
// @TODO move errored logic to Uploader, where handleRemoveVideo can be called
|
|
30
|
-
(assetDocumentValues.value?.status === 'errored'
|
|
31
|
-
? new Error(assetDocumentValues.value.data?.errors?.messages?.join(' '))
|
|
32
|
-
: undefined)
|
|
33
|
-
// */
|
|
34
|
-
if (error) {
|
|
35
|
-
// @TODO deal with it more gracefully
|
|
36
|
-
throw error
|
|
37
|
-
}
|
|
38
|
-
const isLoading = secretDocumentValues.isLoading || assetDocumentValues.isLoading
|
|
39
|
-
|
|
40
|
-
return (
|
|
41
|
-
<Card>
|
|
42
|
-
<ErrorBoundaryCard schemaType={props.schemaType}>
|
|
43
|
-
<Suspense fallback={<InputFallback />}>
|
|
44
|
-
{isLoading ? (
|
|
45
|
-
<InputFallback />
|
|
46
|
-
) : (
|
|
47
|
-
<>
|
|
48
|
-
{secretDocumentValues.value.needsSetup && !assetDocumentValues.value ? (
|
|
49
|
-
<Onboard setDialogState={setDialogState} config={props.config} />
|
|
50
|
-
) : (
|
|
51
|
-
<Uploader
|
|
52
|
-
{...props}
|
|
53
|
-
config={props.config}
|
|
54
|
-
onChange={props.onChange}
|
|
55
|
-
client={client}
|
|
56
|
-
secrets={secretDocumentValues.value.secrets}
|
|
57
|
-
asset={assetDocumentValues.value}
|
|
58
|
-
dialogState={dialogState}
|
|
59
|
-
setDialogState={setDialogState}
|
|
60
|
-
needsSetup={secretDocumentValues.value.needsSetup}
|
|
61
|
-
/>
|
|
62
|
-
)}
|
|
63
|
-
|
|
64
|
-
{dialogState === 'secrets' && hasConfigAccess && (
|
|
65
|
-
<ConfigureApiDialog
|
|
66
|
-
setDialogState={setDialogState}
|
|
67
|
-
secrets={secretDocumentValues.value.secrets}
|
|
68
|
-
/>
|
|
69
|
-
)}
|
|
70
|
-
</>
|
|
71
|
-
)}
|
|
72
|
-
</Suspense>
|
|
73
|
-
</ErrorBoundaryCard>
|
|
74
|
-
</Card>
|
|
75
|
-
)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export default memo(Input)
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import {Dialog} from '@sanity/ui'
|
|
2
|
-
import {useCallback, useId} from 'react'
|
|
3
|
-
import {styled} from 'styled-components'
|
|
4
|
-
|
|
5
|
-
import type {SetDialogState} from '../hooks/useDialogState'
|
|
6
|
-
import SelectAsset, {type Props as SelectAssetProps} from './SelectAsset'
|
|
7
|
-
|
|
8
|
-
/** To prevent Content Layout Shift (CLS), ensure that the dialog always occupies the entire available height. */
|
|
9
|
-
const StyledDialog = styled(Dialog)`
|
|
10
|
-
> div[data-ui='DialogCard'] > div[data-ui='Card'] {
|
|
11
|
-
height: 100%;
|
|
12
|
-
}
|
|
13
|
-
`
|
|
14
|
-
|
|
15
|
-
export default function InputBrowser({
|
|
16
|
-
setDialogState,
|
|
17
|
-
asset,
|
|
18
|
-
onChange,
|
|
19
|
-
config,
|
|
20
|
-
}: Pick<SelectAssetProps, 'onChange' | 'asset' | 'config'> & {
|
|
21
|
-
setDialogState: SetDialogState
|
|
22
|
-
}) {
|
|
23
|
-
const id = `InputBrowser${useId()}`
|
|
24
|
-
const handleClose = useCallback(() => setDialogState(false), [setDialogState])
|
|
25
|
-
return (
|
|
26
|
-
<StyledDialog
|
|
27
|
-
__unstable_autoFocus
|
|
28
|
-
header="Select video"
|
|
29
|
-
id={id}
|
|
30
|
-
onClose={handleClose}
|
|
31
|
-
width={2}
|
|
32
|
-
>
|
|
33
|
-
<SelectAsset
|
|
34
|
-
config={config}
|
|
35
|
-
asset={asset}
|
|
36
|
-
onChange={onChange}
|
|
37
|
-
setDialogState={setDialogState}
|
|
38
|
-
/>
|
|
39
|
-
</StyledDialog>
|
|
40
|
-
)
|
|
41
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import {Box, Dialog, Text} from '@sanity/ui'
|
|
2
|
-
import {useId} from 'react'
|
|
3
|
-
|
|
4
|
-
interface Props {
|
|
5
|
-
error: Error
|
|
6
|
-
onClose: () => void
|
|
7
|
-
}
|
|
8
|
-
export default function InputError({onClose, error}: Props) {
|
|
9
|
-
const id = `InputError${useId()}`
|
|
10
|
-
return (
|
|
11
|
-
<Dialog animate header={error.name} id={id} onClose={onClose}>
|
|
12
|
-
<Box padding={4}>
|
|
13
|
-
<Text>{error.message}</Text>
|
|
14
|
-
</Box>
|
|
15
|
-
</Dialog>
|
|
16
|
-
)
|
|
17
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import {useTheme_v2} from '@sanity/ui'
|
|
2
|
-
import {CSSProperties, useId, useMemo} from 'react'
|
|
3
|
-
|
|
4
|
-
export interface Props {
|
|
5
|
-
height?: number
|
|
6
|
-
}
|
|
7
|
-
export default function MuxLogo({height = 26}: Props) {
|
|
8
|
-
const id = useId()
|
|
9
|
-
const theme = useTheme_v2()
|
|
10
|
-
const fillColor = theme.color._dark ? 'white' : 'black'
|
|
11
|
-
const titleId = useMemo(() => `${id}-title`, [id])
|
|
12
|
-
|
|
13
|
-
const pathStyle: CSSProperties = {
|
|
14
|
-
fillRule: 'nonzero',
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return (
|
|
18
|
-
<svg
|
|
19
|
-
aria-labelledby={titleId}
|
|
20
|
-
style={{height: `${height}px`}}
|
|
21
|
-
viewBox="0 0 1600 500"
|
|
22
|
-
version="1.1"
|
|
23
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
24
|
-
xmlSpace="preserve"
|
|
25
|
-
>
|
|
26
|
-
<g id="Layer-1" fill={fillColor}>
|
|
27
|
-
<path
|
|
28
|
-
d="M994.287,93.486c-17.121,-0 -31,-13.879 -31,-31c0,-17.121 13.879,-31 31,-31c17.121,-0 31,13.879 31,31c0,17.121 -13.879,31 -31,31m0,-93.486c-34.509,-0 -62.484,27.976 -62.484,62.486l0,187.511c0,68.943 -56.09,125.033 -125.032,125.033c-68.942,-0 -125.03,-56.09 -125.03,-125.033l0,-187.511c0,-34.51 -27.976,-62.486 -62.485,-62.486c-34.509,-0 -62.484,27.976 -62.484,62.486l0,187.511c0,137.853 112.149,250.003 249.999,250.003c137.851,-0 250.001,-112.15 250.001,-250.003l0,-187.511c0,-34.51 -27.976,-62.486 -62.485,-62.486"
|
|
29
|
-
style={pathStyle}
|
|
30
|
-
/>
|
|
31
|
-
<path
|
|
32
|
-
d="M1537.51,468.511c-17.121,-0 -31,-13.879 -31,-31c0,-17.121 13.879,-31 31,-31c17.121,-0 31,13.879 31,31c0,17.121 -13.879,31 -31,31m-275.883,-218.509l-143.33,143.329c-24.402,24.402 -24.402,63.966 0,88.368c24.402,24.402 63.967,24.402 88.369,-0l143.33,-143.329l143.328,143.329c24.402,24.4 63.967,24.402 88.369,-0c24.403,-24.402 24.403,-63.966 0.001,-88.368l-143.33,-143.329l0.001,-0.004l143.329,-143.329c24.402,-24.402 24.402,-63.965 0,-88.367c-24.402,-24.402 -63.967,-24.402 -88.369,-0l-143.329,143.328l-143.329,-143.328c-24.402,-24.401 -63.967,-24.402 -88.369,-0c-24.402,24.402 -24.402,63.965 0,88.367l143.329,143.329l0,0.004Z"
|
|
33
|
-
style={pathStyle}
|
|
34
|
-
/>
|
|
35
|
-
<path
|
|
36
|
-
d="M437.511,468.521c-17.121,-0 -31,-13.879 -31,-31c0,-17.121 13.879,-31 31,-31c17.121,-0 31,13.879 31,31c0,17.121 -13.879,31 -31,31m23.915,-463.762c-23.348,-9.672 -50.226,-4.327 -68.096,13.544l-143.331,143.329l-143.33,-143.329c-17.871,-17.871 -44.747,-23.216 -68.096,-13.544c-23.349,9.671 -38.574,32.455 -38.574,57.729l0,375.026c0,34.51 27.977,62.486 62.487,62.486c34.51,-0 62.486,-27.976 62.486,-62.486l0,-224.173l80.843,80.844c24.404,24.402 63.965,24.402 88.369,-0l80.843,-80.844l0,224.173c0,34.51 27.976,62.486 62.486,62.486c34.51,-0 62.486,-27.976 62.486,-62.486l0,-375.026c0,-25.274 -15.224,-48.058 -38.573,-57.729"
|
|
37
|
-
style={pathStyle}
|
|
38
|
-
/>
|
|
39
|
-
</g>
|
|
40
|
-
</svg>
|
|
41
|
-
)
|
|
42
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import {PlugIcon} from '@sanity/icons'
|
|
2
|
-
import {Button, Card, Flex, Grid, Heading, Inline, Text} from '@sanity/ui'
|
|
3
|
-
import {useCallback} from 'react'
|
|
4
|
-
|
|
5
|
-
import {useAccessControl} from '../hooks/useAccessControl'
|
|
6
|
-
import type {SetDialogState} from '../hooks/useDialogState'
|
|
7
|
-
import {PluginConfig} from '../util/types'
|
|
8
|
-
import MuxLogo from './MuxLogo'
|
|
9
|
-
|
|
10
|
-
interface OnboardProps {
|
|
11
|
-
setDialogState: SetDialogState
|
|
12
|
-
config: PluginConfig
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export default function Onboard(props: OnboardProps) {
|
|
16
|
-
const {setDialogState} = props
|
|
17
|
-
const handleOpen = useCallback(() => setDialogState('secrets'), [setDialogState])
|
|
18
|
-
const {hasConfigAccess} = useAccessControl(props.config)
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<>
|
|
22
|
-
<div style={{padding: 2}}>
|
|
23
|
-
<Card
|
|
24
|
-
display="flex"
|
|
25
|
-
sizing="border"
|
|
26
|
-
style={{
|
|
27
|
-
aspectRatio: '16/9',
|
|
28
|
-
width: '100%',
|
|
29
|
-
boxShadow: 'var(--card-bg-color) 0 0 0 2px',
|
|
30
|
-
}}
|
|
31
|
-
paddingX={[2, 3, 4, 4]}
|
|
32
|
-
radius={1}
|
|
33
|
-
tone="transparent"
|
|
34
|
-
>
|
|
35
|
-
<Flex justify="flex-start" align="center">
|
|
36
|
-
<Grid columns={1} gap={[2, 3, 4, 4]}>
|
|
37
|
-
<Inline paddingY={1}>
|
|
38
|
-
<div style={{height: '32px'}}>
|
|
39
|
-
<MuxLogo />
|
|
40
|
-
</div>
|
|
41
|
-
</Inline>
|
|
42
|
-
<Inline paddingY={1}>
|
|
43
|
-
<Heading size={[0, 1, 2, 2]}>
|
|
44
|
-
Upload and preview videos directly from your studio.
|
|
45
|
-
</Heading>
|
|
46
|
-
</Inline>
|
|
47
|
-
<Inline paddingY={1}>
|
|
48
|
-
{hasConfigAccess ? (
|
|
49
|
-
<Button mode="ghost" icon={PlugIcon} text="Configure API" onClick={handleOpen} />
|
|
50
|
-
) : (
|
|
51
|
-
<Card padding={[3, 3, 3]} radius={2} shadow={1} tone="critical">
|
|
52
|
-
<Text>
|
|
53
|
-
You do not have access to configure the Mux API. Please contact your
|
|
54
|
-
administrator.
|
|
55
|
-
</Text>
|
|
56
|
-
</Card>
|
|
57
|
-
)}
|
|
58
|
-
</Inline>
|
|
59
|
-
</Grid>
|
|
60
|
-
</Flex>
|
|
61
|
-
</Card>
|
|
62
|
-
</div>
|
|
63
|
-
</>
|
|
64
|
-
)
|
|
65
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import {ChevronLeftIcon, ChevronRightIcon} from '@sanity/icons'
|
|
2
|
-
import {Button, Label} from '@sanity/ui'
|
|
3
|
-
import {Dispatch, SetStateAction, useEffect} from 'react'
|
|
4
|
-
|
|
5
|
-
const PageSelector = (props: {
|
|
6
|
-
page: number
|
|
7
|
-
setPage: Dispatch<SetStateAction<number>>
|
|
8
|
-
total: number
|
|
9
|
-
}) => {
|
|
10
|
-
const page = props.page
|
|
11
|
-
const setPage = props.setPage
|
|
12
|
-
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
// Constraint in bounds.
|
|
15
|
-
const clamped = Math.min(props.total - 1, Math.max(0, page))
|
|
16
|
-
if (page !== clamped) {
|
|
17
|
-
setPage(clamped)
|
|
18
|
-
}
|
|
19
|
-
}, [page, props.total, setPage])
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<>
|
|
23
|
-
<Button
|
|
24
|
-
icon={ChevronLeftIcon}
|
|
25
|
-
mode="bleed"
|
|
26
|
-
padding={3}
|
|
27
|
-
style={{cursor: 'pointer'}}
|
|
28
|
-
disabled={page <= 0}
|
|
29
|
-
onClick={() => {
|
|
30
|
-
setPage((p) => {
|
|
31
|
-
return Math.min(props.total - 1, Math.max(0, p - 1))
|
|
32
|
-
})
|
|
33
|
-
}}
|
|
34
|
-
/>
|
|
35
|
-
<Label muted>
|
|
36
|
-
Page {page + 1}/{props.total}
|
|
37
|
-
</Label>
|
|
38
|
-
<Button
|
|
39
|
-
icon={ChevronRightIcon}
|
|
40
|
-
mode="bleed"
|
|
41
|
-
padding={3}
|
|
42
|
-
style={{cursor: 'pointer'}}
|
|
43
|
-
disabled={page >= props.total - 1}
|
|
44
|
-
onClick={() => {
|
|
45
|
-
setPage((p) => {
|
|
46
|
-
return Math.min(props.total - 1, Math.max(0, p + 1))
|
|
47
|
-
})
|
|
48
|
-
}}
|
|
49
|
-
/>
|
|
50
|
-
</>
|
|
51
|
-
)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export default PageSelector
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import {Suspense, useState} from 'react'
|
|
2
|
-
import {styled} from 'styled-components'
|
|
3
|
-
|
|
4
|
-
import {useClient} from '../hooks/useClient'
|
|
5
|
-
import {getStoryboardSrc} from '../util/getStoryboardSrc'
|
|
6
|
-
import type {VideoAssetDocument} from '../util/types'
|
|
7
|
-
|
|
8
|
-
export const StyledCenterControls = styled.div`
|
|
9
|
-
&& {
|
|
10
|
-
--media-background-color: transparent;
|
|
11
|
-
--media-button-icon-width: 100%;
|
|
12
|
-
--media-button-icon-height: auto;
|
|
13
|
-
pointer-events: none;
|
|
14
|
-
width: 100%;
|
|
15
|
-
display: flex;
|
|
16
|
-
flex-flow: row;
|
|
17
|
-
align-items: center;
|
|
18
|
-
justify-content: center;
|
|
19
|
-
media-play-button {
|
|
20
|
-
--media-control-background: transparent;
|
|
21
|
-
--media-control-hover-background: transparent;
|
|
22
|
-
padding: 0;
|
|
23
|
-
width: max(27px, min(9%, 90px));
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
`
|
|
27
|
-
|
|
28
|
-
export const TopControls = styled.div`
|
|
29
|
-
position: absolute;
|
|
30
|
-
top: 0;
|
|
31
|
-
right: 0;
|
|
32
|
-
justify-content: flex-end;
|
|
33
|
-
button {
|
|
34
|
-
height: auto;
|
|
35
|
-
}
|
|
36
|
-
`
|
|
37
|
-
|
|
38
|
-
export interface PosterImageProps {
|
|
39
|
-
asset: VideoAssetDocument
|
|
40
|
-
}
|
|
41
|
-
export interface ThumbnailsMetadataTrackProps {
|
|
42
|
-
asset: VideoAssetDocument
|
|
43
|
-
}
|
|
44
|
-
export function ThumbnailsMetadataTrack({asset}: ThumbnailsMetadataTrackProps) {
|
|
45
|
-
const client = useClient()
|
|
46
|
-
// Why useState instead of useMemo? Because we really really only want to run it exactly once and useMemo doesn't make that guarantee
|
|
47
|
-
const [src] = useState<string>(() => getStoryboardSrc({asset, client}))
|
|
48
|
-
|
|
49
|
-
return (
|
|
50
|
-
/* We use Suspense here because `getStoryboardSrc` uses suspend() under the hood */
|
|
51
|
-
<Suspense fallback={null}>
|
|
52
|
-
<track label="thumbnails" default kind="metadata" src={src} />
|
|
53
|
-
</Suspense>
|
|
54
|
-
)
|
|
55
|
-
}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import {Card, Text} from '@sanity/ui'
|
|
2
|
-
import React, {useEffect, useMemo, useRef} from 'react'
|
|
3
|
-
|
|
4
|
-
import {useCancelUpload} from '../hooks/useCancelUpload'
|
|
5
|
-
import type {MuxInputProps, PluginConfig, VideoAssetDocument} from '../util/types'
|
|
6
|
-
import {TopControls} from './Player.styled'
|
|
7
|
-
import {UploadProgress} from './UploadProgress'
|
|
8
|
-
import VideoPlayer from './VideoPlayer'
|
|
9
|
-
|
|
10
|
-
interface Props extends Pick<MuxInputProps, 'onChange' | 'readOnly'> {
|
|
11
|
-
buttons?: React.ReactNode
|
|
12
|
-
asset: VideoAssetDocument
|
|
13
|
-
config?: PluginConfig
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const Player = ({asset, buttons, readOnly, onChange, config}: Props) => {
|
|
17
|
-
const isLoading = useMemo<boolean | string>(() => {
|
|
18
|
-
if (asset?.status === 'preparing') {
|
|
19
|
-
return 'Preparing the video'
|
|
20
|
-
}
|
|
21
|
-
if (asset?.status === 'waiting_for_upload') {
|
|
22
|
-
return 'Waiting for upload to start'
|
|
23
|
-
}
|
|
24
|
-
if (asset?.status === 'waiting') {
|
|
25
|
-
return 'Processing upload'
|
|
26
|
-
}
|
|
27
|
-
if (asset?.status === 'ready') {
|
|
28
|
-
return false
|
|
29
|
-
}
|
|
30
|
-
if (typeof asset?.status === 'undefined') {
|
|
31
|
-
return false
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return true
|
|
35
|
-
}, [asset])
|
|
36
|
-
const isPreparingStaticRenditions = useMemo<boolean>(() => {
|
|
37
|
-
// Legacy: If static_renditions has a status field, it was created with mp4_support (deprecated)
|
|
38
|
-
// We don't process this old format, just return false
|
|
39
|
-
// Note: 'disabled' status is valid in the new format when no renditions were requested
|
|
40
|
-
if (
|
|
41
|
-
asset?.data?.static_renditions?.status &&
|
|
42
|
-
asset?.data?.static_renditions?.status !== 'disabled'
|
|
43
|
-
) {
|
|
44
|
-
return false
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Check if any file in static_renditions is still preparing
|
|
48
|
-
const files = asset?.data?.static_renditions?.files
|
|
49
|
-
if (!files || files.length === 0) {
|
|
50
|
-
return false
|
|
51
|
-
}
|
|
52
|
-
return files.some((file) => file.status === 'preparing')
|
|
53
|
-
}, [asset?.data?.static_renditions?.status, asset?.data?.static_renditions?.files])
|
|
54
|
-
const playRef = useRef<HTMLDivElement>(null)
|
|
55
|
-
const muteRef = useRef<HTMLDivElement>(null)
|
|
56
|
-
const handleCancelUpload = useCancelUpload(asset, onChange)
|
|
57
|
-
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
const style = document.createElement('style')
|
|
60
|
-
style.innerHTML = 'button svg { vertical-align: middle; }'
|
|
61
|
-
|
|
62
|
-
if (playRef.current?.shadowRoot) {
|
|
63
|
-
playRef.current.shadowRoot.appendChild(style)
|
|
64
|
-
}
|
|
65
|
-
if (muteRef?.current?.shadowRoot) {
|
|
66
|
-
muteRef.current.shadowRoot.appendChild(style.cloneNode(true))
|
|
67
|
-
}
|
|
68
|
-
}, [])
|
|
69
|
-
|
|
70
|
-
useEffect(() => {
|
|
71
|
-
if (asset?.status === 'errored') {
|
|
72
|
-
handleCancelUpload()
|
|
73
|
-
// eslint-disable-next-line no-warning-comments
|
|
74
|
-
// @TODO use better error handling
|
|
75
|
-
throw new Error(asset.data?.errors?.messages?.join(' '))
|
|
76
|
-
}
|
|
77
|
-
}, [asset.data?.errors?.messages, asset?.status, handleCancelUpload])
|
|
78
|
-
|
|
79
|
-
if (!asset || !asset.status) {
|
|
80
|
-
return null
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (isLoading) {
|
|
84
|
-
return (
|
|
85
|
-
<UploadProgress
|
|
86
|
-
progress={100}
|
|
87
|
-
filename={asset?.filename}
|
|
88
|
-
text={(isLoading !== true && isLoading) || 'Waiting for Mux to complete the upload'}
|
|
89
|
-
onCancel={readOnly ? undefined : () => handleCancelUpload()}
|
|
90
|
-
/>
|
|
91
|
-
)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return (
|
|
95
|
-
<VideoPlayer asset={asset} hlsConfig={config?.hlsConfig}>
|
|
96
|
-
{buttons && <TopControls slot="top-chrome">{buttons}</TopControls>}
|
|
97
|
-
{isPreparingStaticRenditions && (
|
|
98
|
-
<Card
|
|
99
|
-
padding={2}
|
|
100
|
-
radius={1}
|
|
101
|
-
style={{
|
|
102
|
-
background: 'var(--card-fg-color)',
|
|
103
|
-
position: 'absolute',
|
|
104
|
-
top: '0.5em',
|
|
105
|
-
left: '0.5em',
|
|
106
|
-
}}
|
|
107
|
-
>
|
|
108
|
-
<Text size={1} style={{color: 'var(--card-bg-color)'}}>
|
|
109
|
-
MUX is preparing static renditions, please stand by
|
|
110
|
-
</Text>
|
|
111
|
-
</Card>
|
|
112
|
-
)}
|
|
113
|
-
</VideoPlayer>
|
|
114
|
-
)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export default Player
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
EllipsisHorizontalIcon,
|
|
3
|
-
ImageIcon,
|
|
4
|
-
LockIcon,
|
|
5
|
-
PlugIcon,
|
|
6
|
-
ResetIcon,
|
|
7
|
-
SearchIcon,
|
|
8
|
-
SyncIcon,
|
|
9
|
-
TranslateIcon,
|
|
10
|
-
UploadIcon,
|
|
11
|
-
} from '@sanity/icons'
|
|
12
|
-
import {
|
|
13
|
-
Box,
|
|
14
|
-
Button,
|
|
15
|
-
Card,
|
|
16
|
-
Inline,
|
|
17
|
-
Label,
|
|
18
|
-
Menu,
|
|
19
|
-
MenuDivider,
|
|
20
|
-
MenuItem,
|
|
21
|
-
Popover,
|
|
22
|
-
Text,
|
|
23
|
-
Tooltip,
|
|
24
|
-
useClickOutsideEvent,
|
|
25
|
-
} from '@sanity/ui'
|
|
26
|
-
import {memo, useCallback, useEffect, useMemo, useState} from 'react'
|
|
27
|
-
import {PatchEvent, unset} from 'sanity'
|
|
28
|
-
import {styled} from 'styled-components'
|
|
29
|
-
|
|
30
|
-
import {useAccessControl} from '../hooks/useAccessControl'
|
|
31
|
-
import {type DialogState, type SetDialogState} from '../hooks/useDialogState'
|
|
32
|
-
import {useResyncAsset} from '../hooks/useResyncAsset'
|
|
33
|
-
import {getPlaybackPolicy} from '../util/getPlaybackPolicy'
|
|
34
|
-
import type {MuxInputProps, PluginConfig, VideoAssetDocument} from '../util/types'
|
|
35
|
-
import {FileInputMenuItem} from './FileInputMenuItem'
|
|
36
|
-
|
|
37
|
-
const LockCard = styled(Card)`
|
|
38
|
-
position: absolute;
|
|
39
|
-
top: 0;
|
|
40
|
-
left: 0;
|
|
41
|
-
opacity: 0.6;
|
|
42
|
-
mix-blend-mode: screen;
|
|
43
|
-
background: transparent;
|
|
44
|
-
`
|
|
45
|
-
|
|
46
|
-
const LockButton = styled(Button)`
|
|
47
|
-
background: transparent;
|
|
48
|
-
color: white;
|
|
49
|
-
`
|
|
50
|
-
|
|
51
|
-
// @TODO: add support for audio type (asset._type) when uploading an audio file so we can hide the thumbnail option.
|
|
52
|
-
const isVideoAsset = (asset: VideoAssetDocument) => {
|
|
53
|
-
return asset._type === 'mux.videoAsset'
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function PlayerActionsMenu(
|
|
57
|
-
props: Pick<MuxInputProps, 'onChange' | 'readOnly'> & {
|
|
58
|
-
asset: VideoAssetDocument
|
|
59
|
-
onSelect: (files: File[]) => void
|
|
60
|
-
dialogState: DialogState
|
|
61
|
-
setDialogState: SetDialogState
|
|
62
|
-
config: PluginConfig
|
|
63
|
-
accept: string
|
|
64
|
-
}
|
|
65
|
-
) {
|
|
66
|
-
const {asset, readOnly, dialogState, setDialogState, onChange, onSelect, accept} = props
|
|
67
|
-
const [open, setOpen] = useState(false)
|
|
68
|
-
const [menuElement, setMenuRef] = useState<HTMLDivElement | null>(null)
|
|
69
|
-
const isSigned = useMemo(() => getPlaybackPolicy(asset)?.policy === 'signed', [asset])
|
|
70
|
-
const {hasConfigAccess} = useAccessControl(props.config)
|
|
71
|
-
const {resyncAsset, isResyncing} = useResyncAsset({showToast: true})
|
|
72
|
-
|
|
73
|
-
const onReset = useCallback(() => onChange(PatchEvent.from(unset([]))), [onChange])
|
|
74
|
-
|
|
75
|
-
const handleResync = useCallback(async () => {
|
|
76
|
-
setOpen(false)
|
|
77
|
-
await resyncAsset(asset)
|
|
78
|
-
}, [resyncAsset, asset])
|
|
79
|
-
|
|
80
|
-
useEffect(() => {
|
|
81
|
-
if (open && dialogState) {
|
|
82
|
-
setOpen(false)
|
|
83
|
-
}
|
|
84
|
-
}, [dialogState, open])
|
|
85
|
-
|
|
86
|
-
useClickOutsideEvent(
|
|
87
|
-
() => setOpen(false),
|
|
88
|
-
() => [menuElement]
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
return (
|
|
92
|
-
<Inline space={1} padding={2}>
|
|
93
|
-
{isSigned && (
|
|
94
|
-
<Tooltip
|
|
95
|
-
animate
|
|
96
|
-
content={
|
|
97
|
-
<Box padding={2}>
|
|
98
|
-
<Text muted size={1}>
|
|
99
|
-
Signed playback policy
|
|
100
|
-
</Text>
|
|
101
|
-
</Box>
|
|
102
|
-
}
|
|
103
|
-
placement="right"
|
|
104
|
-
portal
|
|
105
|
-
>
|
|
106
|
-
<LockCard radius={2} margin={2} scheme="dark" tone="positive">
|
|
107
|
-
<LockButton icon={LockIcon} mode="bleed" tone="positive" />
|
|
108
|
-
</LockCard>
|
|
109
|
-
</Tooltip>
|
|
110
|
-
)}
|
|
111
|
-
<Popover
|
|
112
|
-
animate
|
|
113
|
-
content={
|
|
114
|
-
<Menu ref={setMenuRef}>
|
|
115
|
-
<Box padding={2}>
|
|
116
|
-
<Label muted size={1}>
|
|
117
|
-
Replace
|
|
118
|
-
</Label>
|
|
119
|
-
</Box>
|
|
120
|
-
<FileInputMenuItem
|
|
121
|
-
accept={accept}
|
|
122
|
-
icon={UploadIcon}
|
|
123
|
-
onSelect={onSelect}
|
|
124
|
-
text="Upload"
|
|
125
|
-
disabled={readOnly}
|
|
126
|
-
fontSize={1}
|
|
127
|
-
/>
|
|
128
|
-
<MenuItem
|
|
129
|
-
icon={SearchIcon}
|
|
130
|
-
text="Browse"
|
|
131
|
-
onClick={() => setDialogState('select-video')}
|
|
132
|
-
/>
|
|
133
|
-
{isVideoAsset(asset) && (
|
|
134
|
-
<>
|
|
135
|
-
<MenuItem
|
|
136
|
-
icon={ImageIcon}
|
|
137
|
-
text="Thumbnail"
|
|
138
|
-
onClick={() => setDialogState('edit-thumbnail')}
|
|
139
|
-
/>
|
|
140
|
-
<MenuItem
|
|
141
|
-
icon={TranslateIcon}
|
|
142
|
-
text="Captions"
|
|
143
|
-
onClick={() => setDialogState('edit-captions')}
|
|
144
|
-
/>
|
|
145
|
-
<MenuItem
|
|
146
|
-
icon={SyncIcon}
|
|
147
|
-
text="Resync from Mux"
|
|
148
|
-
onClick={handleResync}
|
|
149
|
-
disabled={readOnly || isResyncing}
|
|
150
|
-
/>
|
|
151
|
-
</>
|
|
152
|
-
)}
|
|
153
|
-
<MenuDivider />
|
|
154
|
-
{hasConfigAccess && (
|
|
155
|
-
<>
|
|
156
|
-
<MenuItem
|
|
157
|
-
icon={PlugIcon}
|
|
158
|
-
text="Configure API"
|
|
159
|
-
onClick={() => setDialogState('secrets')}
|
|
160
|
-
/>
|
|
161
|
-
<MenuDivider />
|
|
162
|
-
</>
|
|
163
|
-
)}
|
|
164
|
-
<MenuItem
|
|
165
|
-
tone="critical"
|
|
166
|
-
icon={ResetIcon}
|
|
167
|
-
text="Clear field"
|
|
168
|
-
onClick={onReset}
|
|
169
|
-
disabled={readOnly}
|
|
170
|
-
/>
|
|
171
|
-
</Menu>
|
|
172
|
-
}
|
|
173
|
-
portal
|
|
174
|
-
open={open}
|
|
175
|
-
>
|
|
176
|
-
<Button
|
|
177
|
-
icon={EllipsisHorizontalIcon}
|
|
178
|
-
mode="ghost"
|
|
179
|
-
fontSize={1}
|
|
180
|
-
onClick={() => {
|
|
181
|
-
setDialogState(false)
|
|
182
|
-
setOpen(true)
|
|
183
|
-
}}
|
|
184
|
-
/>
|
|
185
|
-
</Popover>
|
|
186
|
-
</Inline>
|
|
187
|
-
)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export default memo(PlayerActionsMenu)
|