sanity-plugin-mux-input 2.7.0 → 2.8.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/dist/index.d.mts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +40 -73
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +40 -74
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/_exports/index.ts +1 -0
- package/src/components/Input.tsx +4 -2
- package/src/components/Onboard.tsx +15 -2
- package/src/components/PlayerActionsMenu.tsx +14 -7
- package/src/components/UploadPlaceholder.tsx +15 -9
- package/src/components/Uploader.tsx +2 -0
- package/src/components/VideoDetails/DeleteDialog.tsx +2 -8
- package/src/components/VideoDetails/VideoDetails.tsx +1 -6
- package/src/components/VideoDetails/VideoReferences.tsx +1 -7
- package/src/components/VideoDetails/useVideoDetails.ts +1 -2
- package/src/components/VideosBrowser.tsx +1 -5
- package/src/components/documentPreview/DocumentPreview.tsx +3 -27
- package/src/hooks/useAccessControl.ts +12 -0
- package/src/schema.ts +1 -1
- package/src/util/types.ts +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sanity-plugin-mux-input",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.1",
|
|
4
4
|
"description": "An input component that integrates Sanity Studio with Mux video encoding/hosting service.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
"peerDependencies": {
|
|
105
105
|
"react": "^18.3 || ^19",
|
|
106
106
|
"react-is": "^18.3 || ^19",
|
|
107
|
-
"sanity": "^3.42.0",
|
|
107
|
+
"sanity": "^3.42.0 || ^4.0.0-0",
|
|
108
108
|
"styled-components": "^5 || ^6"
|
|
109
109
|
},
|
|
110
110
|
"engines": {
|
package/src/_exports/index.ts
CHANGED
package/src/components/Input.tsx
CHANGED
|
@@ -12,6 +12,7 @@ import ErrorBoundaryCard from './ErrorBoundaryCard'
|
|
|
12
12
|
import {InputFallback} from './Input.styled'
|
|
13
13
|
import Onboard from './Onboard'
|
|
14
14
|
import Uploader from './Uploader'
|
|
15
|
+
import {useAccessControl} from '../hooks/useAccessControl'
|
|
15
16
|
|
|
16
17
|
export interface InputProps extends MuxInputProps {
|
|
17
18
|
config: PluginConfig
|
|
@@ -22,6 +23,7 @@ const Input = (props: InputProps) => {
|
|
|
22
23
|
const assetDocumentValues = useAssetDocumentValues(props.value?.asset)
|
|
23
24
|
const poll = useMuxPolling(props.readOnly ? undefined : assetDocumentValues?.value || undefined)
|
|
24
25
|
const [dialogState, setDialogState] = useDialogState()
|
|
26
|
+
const {hasConfigAccess} = useAccessControl(props.config)
|
|
25
27
|
|
|
26
28
|
const error = secretDocumentValues.error || assetDocumentValues.error || poll.error /*||
|
|
27
29
|
// @TODO move errored logic to Uploader, where handleRemoveVideo can be called
|
|
@@ -44,7 +46,7 @@ const Input = (props: InputProps) => {
|
|
|
44
46
|
) : (
|
|
45
47
|
<>
|
|
46
48
|
{secretDocumentValues.value.needsSetup && !assetDocumentValues.value ? (
|
|
47
|
-
<Onboard setDialogState={setDialogState} />
|
|
49
|
+
<Onboard setDialogState={setDialogState} config={props.config} />
|
|
48
50
|
) : (
|
|
49
51
|
<Uploader
|
|
50
52
|
{...props}
|
|
@@ -59,7 +61,7 @@ const Input = (props: InputProps) => {
|
|
|
59
61
|
/>
|
|
60
62
|
)}
|
|
61
63
|
|
|
62
|
-
{dialogState === 'secrets' && (
|
|
64
|
+
{dialogState === 'secrets' && hasConfigAccess && (
|
|
63
65
|
<ConfigureApi
|
|
64
66
|
setDialogState={setDialogState}
|
|
65
67
|
secrets={secretDocumentValues.value.secrets}
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import {PlugIcon} from '@sanity/icons'
|
|
2
|
-
import {Button, Card, Flex, Grid, Heading, Inline} from '@sanity/ui'
|
|
2
|
+
import {Button, Card, Flex, Grid, Heading, Inline, Text} from '@sanity/ui'
|
|
3
3
|
import {useCallback} from 'react'
|
|
4
4
|
|
|
5
5
|
import type {SetDialogState} from '../hooks/useDialogState'
|
|
6
6
|
import MuxLogo from './MuxLogo'
|
|
7
|
+
import {PluginConfig} from '../util/types'
|
|
8
|
+
import {useAccessControl} from '../hooks/useAccessControl'
|
|
7
9
|
|
|
8
10
|
interface OnboardProps {
|
|
9
11
|
setDialogState: SetDialogState
|
|
12
|
+
config: PluginConfig
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
export default function Onboard(props: OnboardProps) {
|
|
13
16
|
const {setDialogState} = props
|
|
14
17
|
const handleOpen = useCallback(() => setDialogState('secrets'), [setDialogState])
|
|
18
|
+
const {hasConfigAccess} = useAccessControl(props.config)
|
|
15
19
|
|
|
16
20
|
return (
|
|
17
21
|
<>
|
|
@@ -41,7 +45,16 @@ export default function Onboard(props: OnboardProps) {
|
|
|
41
45
|
</Heading>
|
|
42
46
|
</Inline>
|
|
43
47
|
<Inline paddingY={1}>
|
|
44
|
-
|
|
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
|
+
)}
|
|
45
58
|
</Inline>
|
|
46
59
|
</Grid>
|
|
47
60
|
</Flex>
|
|
@@ -27,8 +27,9 @@ import {styled} from 'styled-components'
|
|
|
27
27
|
|
|
28
28
|
import {type DialogState, type SetDialogState} from '../hooks/useDialogState'
|
|
29
29
|
import {getPlaybackPolicy} from '../util/getPlaybackPolicy'
|
|
30
|
-
import type {MuxInputProps, VideoAssetDocument} from '../util/types'
|
|
30
|
+
import type {MuxInputProps, PluginConfig, VideoAssetDocument} from '../util/types'
|
|
31
31
|
import {FileInputMenuItem} from './FileInputMenuItem'
|
|
32
|
+
import {useAccessControl} from '../hooks/useAccessControl'
|
|
32
33
|
|
|
33
34
|
const LockCard = styled(Card)`
|
|
34
35
|
position: absolute;
|
|
@@ -55,12 +56,14 @@ function PlayerActionsMenu(
|
|
|
55
56
|
onSelect: (files: File[]) => void
|
|
56
57
|
dialogState: DialogState
|
|
57
58
|
setDialogState: SetDialogState
|
|
59
|
+
config: PluginConfig
|
|
58
60
|
}
|
|
59
61
|
) {
|
|
60
62
|
const {asset, readOnly, dialogState, setDialogState, onChange, onSelect} = props
|
|
61
63
|
const [open, setOpen] = useState(false)
|
|
62
64
|
const [menuElement, setMenuRef] = useState<HTMLDivElement | null>(null)
|
|
63
65
|
const isSigned = useMemo(() => getPlaybackPolicy(asset) === 'signed', [asset])
|
|
66
|
+
const {hasConfigAccess} = useAccessControl(props.config)
|
|
64
67
|
|
|
65
68
|
const onReset = useCallback(() => onChange(PatchEvent.from(unset([]))), [onChange])
|
|
66
69
|
|
|
@@ -125,12 +128,16 @@ function PlayerActionsMenu(
|
|
|
125
128
|
/>
|
|
126
129
|
)}
|
|
127
130
|
<MenuDivider />
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
{hasConfigAccess && (
|
|
132
|
+
<>
|
|
133
|
+
<MenuItem
|
|
134
|
+
icon={PlugIcon}
|
|
135
|
+
text="Configure API"
|
|
136
|
+
onClick={() => setDialogState('secrets')}
|
|
137
|
+
/>
|
|
138
|
+
<MenuDivider />
|
|
139
|
+
</>
|
|
140
|
+
)}
|
|
134
141
|
<MenuItem
|
|
135
142
|
tone="critical"
|
|
136
143
|
icon={ResetIcon}
|
|
@@ -5,6 +5,8 @@ import {useCallback} from 'react'
|
|
|
5
5
|
|
|
6
6
|
import type {SetDialogState} from '../hooks/useDialogState'
|
|
7
7
|
import {FileInputButton, type FileInputButtonProps} from './FileInputButton'
|
|
8
|
+
import {useAccessControl} from '../hooks/useAccessControl'
|
|
9
|
+
import {PluginConfig} from '../util/types'
|
|
8
10
|
|
|
9
11
|
interface UploadPlaceholderProps {
|
|
10
12
|
setDialogState: SetDialogState
|
|
@@ -12,11 +14,13 @@ interface UploadPlaceholderProps {
|
|
|
12
14
|
hovering: boolean
|
|
13
15
|
needsSetup: boolean
|
|
14
16
|
onSelect: FileInputButtonProps['onSelect']
|
|
17
|
+
config: PluginConfig
|
|
15
18
|
}
|
|
16
19
|
export default function UploadPlaceholder(props: UploadPlaceholderProps) {
|
|
17
20
|
const {setDialogState, readOnly, onSelect, hovering, needsSetup} = props
|
|
18
21
|
const handleBrowse = useCallback(() => setDialogState('select-video'), [setDialogState])
|
|
19
22
|
const handleConfigureApi = useCallback(() => setDialogState('secrets'), [setDialogState])
|
|
23
|
+
const {hasConfigAccess} = useAccessControl(props.config)
|
|
20
24
|
|
|
21
25
|
return (
|
|
22
26
|
<Card
|
|
@@ -58,15 +62,17 @@ export default function UploadPlaceholder(props: UploadPlaceholderProps) {
|
|
|
58
62
|
/>
|
|
59
63
|
<Button mode="bleed" icon={SearchIcon} text="Select" onClick={handleBrowse} />
|
|
60
64
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
{hasConfigAccess && (
|
|
66
|
+
<Button
|
|
67
|
+
padding={3}
|
|
68
|
+
radius={3}
|
|
69
|
+
tone={needsSetup ? 'critical' : undefined}
|
|
70
|
+
onClick={handleConfigureApi}
|
|
71
|
+
icon={PlugIcon}
|
|
72
|
+
mode="bleed"
|
|
73
|
+
title="Configure plugin credentials"
|
|
74
|
+
/>
|
|
75
|
+
)}
|
|
70
76
|
</Inline>
|
|
71
77
|
</Flex>
|
|
72
78
|
</Card>
|
|
@@ -364,6 +364,7 @@ export default function Uploader(props: Props) {
|
|
|
364
364
|
onChange={props.onChange}
|
|
365
365
|
onSelect={handleUpload}
|
|
366
366
|
readOnly={props.readOnly}
|
|
367
|
+
config={props.config}
|
|
367
368
|
/>
|
|
368
369
|
}
|
|
369
370
|
/>
|
|
@@ -375,6 +376,7 @@ export default function Uploader(props: Props) {
|
|
|
375
376
|
readOnly={!!props.readOnly}
|
|
376
377
|
setDialogState={props.setDialogState}
|
|
377
378
|
needsSetup={props.needsSetup}
|
|
379
|
+
config={props.config}
|
|
378
380
|
/>
|
|
379
381
|
)}
|
|
380
382
|
</UploadCard>
|
|
@@ -6,7 +6,7 @@ import type {SanityDocument} from 'sanity'
|
|
|
6
6
|
import {deleteAsset} from '../../actions/assets'
|
|
7
7
|
import {useClient} from '../../hooks/useClient'
|
|
8
8
|
import {DIALOGS_Z_INDEX} from '../../util/constants'
|
|
9
|
-
import type {
|
|
9
|
+
import type {VideoAssetDocument} from '../../util/types'
|
|
10
10
|
import SpinnerBox from '../SpinnerBox'
|
|
11
11
|
import VideoReferences from './VideoReferences'
|
|
12
12
|
|
|
@@ -15,11 +15,9 @@ export default function DeleteDialog({
|
|
|
15
15
|
references,
|
|
16
16
|
referencesLoading,
|
|
17
17
|
cancelDelete,
|
|
18
|
-
placement,
|
|
19
18
|
succeededDeleting,
|
|
20
19
|
}: {
|
|
21
20
|
asset: VideoAssetDocument
|
|
22
|
-
placement: PluginPlacement
|
|
23
21
|
references?: SanityDocument[]
|
|
24
22
|
referencesLoading: boolean
|
|
25
23
|
cancelDelete: () => void
|
|
@@ -95,11 +93,7 @@ export default function DeleteDialog({
|
|
|
95
93
|
pointing to this video. Remove their references to this file or delete them before
|
|
96
94
|
proceeding.
|
|
97
95
|
</Text>
|
|
98
|
-
<VideoReferences
|
|
99
|
-
references={references}
|
|
100
|
-
isLoaded={!referencesLoading}
|
|
101
|
-
placement={placement}
|
|
102
|
-
/>
|
|
96
|
+
<VideoReferences references={references} isLoaded={!referencesLoading} />
|
|
103
97
|
</>
|
|
104
98
|
)}
|
|
105
99
|
{state === 'confirm' && (
|
|
@@ -127,7 +127,6 @@ const VideoDetails: React.FC<VideoDetailsProps> = (props) => {
|
|
|
127
127
|
<DeleteDialog
|
|
128
128
|
asset={props.asset}
|
|
129
129
|
cancelDelete={() => setState('idle')}
|
|
130
|
-
placement={props.placement}
|
|
131
130
|
referencesLoading={referencesLoading}
|
|
132
131
|
references={references}
|
|
133
132
|
succeededDeleting={() => {
|
|
@@ -295,11 +294,7 @@ const VideoDetails: React.FC<VideoDetailsProps> = (props) => {
|
|
|
295
294
|
id="references-panel"
|
|
296
295
|
hidden={tab !== 'references'}
|
|
297
296
|
>
|
|
298
|
-
<VideoReferences
|
|
299
|
-
references={references}
|
|
300
|
-
isLoaded={!referencesLoading}
|
|
301
|
-
placement={props.placement}
|
|
302
|
-
/>
|
|
297
|
+
<VideoReferences references={references} isLoaded={!referencesLoading} />
|
|
303
298
|
</TabPanel>
|
|
304
299
|
</Stack>
|
|
305
300
|
</Flex>
|
|
@@ -3,7 +3,6 @@ import {Box, Card, Text} from '@sanity/ui'
|
|
|
3
3
|
import {collate, useSchema} from 'sanity'
|
|
4
4
|
import {styled} from 'styled-components'
|
|
5
5
|
|
|
6
|
-
import type {PluginPlacement} from '../../util/types'
|
|
7
6
|
import {DocumentPreview} from '../documentPreview/DocumentPreview'
|
|
8
7
|
import SpinnerBox from '../SpinnerBox'
|
|
9
8
|
|
|
@@ -22,7 +21,6 @@ const Container = styled(Box)`
|
|
|
22
21
|
const VideoReferences: React.FC<{
|
|
23
22
|
references?: SanityDocument[]
|
|
24
23
|
isLoaded: boolean
|
|
25
|
-
placement: PluginPlacement
|
|
26
24
|
}> = (props) => {
|
|
27
25
|
const schema = useSchema()
|
|
28
26
|
if (!props.isLoaded) {
|
|
@@ -53,11 +51,7 @@ const VideoReferences: React.FC<{
|
|
|
53
51
|
style={{overflow: 'hidden'}}
|
|
54
52
|
>
|
|
55
53
|
<Box>
|
|
56
|
-
<DocumentPreview
|
|
57
|
-
documentPair={documentPair}
|
|
58
|
-
schemaType={schemaType}
|
|
59
|
-
placement={props.placement}
|
|
60
|
-
/>
|
|
54
|
+
<DocumentPreview documentPair={documentPair} schemaType={schemaType} />
|
|
61
55
|
</Box>
|
|
62
56
|
</Card>
|
|
63
57
|
)
|
|
@@ -5,10 +5,9 @@ import {useDocumentStore} from 'sanity'
|
|
|
5
5
|
import {useClient} from '../../hooks/useClient'
|
|
6
6
|
import useDocReferences from '../../hooks/useDocReferences'
|
|
7
7
|
import getVideoMetadata from '../../util/getVideoMetadata'
|
|
8
|
-
import {
|
|
8
|
+
import {VideoAssetDocument} from '../../util/types'
|
|
9
9
|
|
|
10
10
|
export interface VideoDetailsProps {
|
|
11
|
-
placement: PluginPlacement
|
|
12
11
|
closeDialog: () => void
|
|
13
12
|
asset: VideoAssetDocument & {autoPlay?: boolean}
|
|
14
13
|
}
|
|
@@ -75,11 +75,7 @@ export default function VideosBrowser({onSelect}: VideosBrowserProps) {
|
|
|
75
75
|
)}
|
|
76
76
|
</Stack>
|
|
77
77
|
{freshEditedAsset && (
|
|
78
|
-
<VideoDetails
|
|
79
|
-
closeDialog={() => setEditedAsset(null)}
|
|
80
|
-
asset={freshEditedAsset}
|
|
81
|
-
placement={placement}
|
|
82
|
-
/>
|
|
78
|
+
<VideoDetails closeDialog={() => setEditedAsset(null)} asset={freshEditedAsset} />
|
|
83
79
|
)}
|
|
84
80
|
</>
|
|
85
81
|
)
|
|
@@ -3,20 +3,16 @@
|
|
|
3
3
|
import {DocumentIcon} from '@sanity/icons'
|
|
4
4
|
import type {PropsWithChildren} from 'react'
|
|
5
5
|
import React, {useMemo} from 'react'
|
|
6
|
-
import type {SanityDocument} from 'sanity'
|
|
7
|
-
import type {CollatedHit, FIXME, SchemaType} from 'sanity'
|
|
6
|
+
import type {CollatedHit, FIXME, SanityDocument, SchemaType} from 'sanity'
|
|
8
7
|
import {PreviewCard, useDocumentPresence, useDocumentPreviewStore, useSchema} from 'sanity'
|
|
9
|
-
import {usePaneRouter} from 'sanity/desk'
|
|
10
8
|
import {IntentLink} from 'sanity/router'
|
|
11
9
|
|
|
12
|
-
import {PluginPlacement} from '../../util/types'
|
|
13
10
|
import {MissingSchemaType} from './MissingSchemaType'
|
|
14
11
|
import {PaneItemPreview} from './PaneItemPreview'
|
|
15
12
|
|
|
16
13
|
interface DocumentPreviewProps {
|
|
17
14
|
schemaType?: SchemaType
|
|
18
15
|
documentPair: CollatedHit<SanityDocument>
|
|
19
|
-
placement?: PluginPlacement
|
|
20
16
|
}
|
|
21
17
|
|
|
22
18
|
/**
|
|
@@ -35,23 +31,7 @@ export function getIconWithFallback(
|
|
|
35
31
|
return icon || ((schemaType && schemaType.icon) as any) || defaultIcon || false
|
|
36
32
|
}
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
function DocumentPreviewInInput(props: PropsWithChildren<DocumentPreviewProps>) {
|
|
40
|
-
const {ChildLink} = usePaneRouter()
|
|
41
|
-
|
|
42
|
-
return (linkProps: PropsWithChildren) => (
|
|
43
|
-
<ChildLink
|
|
44
|
-
childId={props.documentPair.id}
|
|
45
|
-
// Pass the schemaType of the document so `paneChild` in `buildPagesStructure` can access it
|
|
46
|
-
childParameters={{type: props.documentPair.type}}
|
|
47
|
-
>
|
|
48
|
-
{linkProps.children}
|
|
49
|
-
</ChildLink>
|
|
50
|
-
)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** When inside the tool, we must use a regular intent link to take users to the desk tool */
|
|
54
|
-
function DocumentPreviewInRool(props: DocumentPreviewProps) {
|
|
34
|
+
function DocumentPreviewLink(props: DocumentPreviewProps) {
|
|
55
35
|
return (linkProps: PropsWithChildren) => (
|
|
56
36
|
<IntentLink intent="edit" params={{id: props.documentPair.id}}>
|
|
57
37
|
{linkProps.children}
|
|
@@ -90,11 +70,7 @@ export function DocumentPreview(props: DocumentPreviewProps) {
|
|
|
90
70
|
return (
|
|
91
71
|
<PreviewCard
|
|
92
72
|
__unstable_focusRing
|
|
93
|
-
as={
|
|
94
|
-
(props.placement === 'input'
|
|
95
|
-
? DocumentPreviewInInput(props)
|
|
96
|
-
: DocumentPreviewInRool(props)) as FIXME
|
|
97
|
-
}
|
|
73
|
+
as={DocumentPreviewLink(props) as FIXME}
|
|
98
74
|
data-as="a"
|
|
99
75
|
data-ui="PaneItem"
|
|
100
76
|
padding={2}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {useCurrentUser} from 'sanity'
|
|
2
|
+
import {PluginConfig} from '../util/types'
|
|
3
|
+
|
|
4
|
+
export const useAccessControl = (config: PluginConfig) => {
|
|
5
|
+
const user = useCurrentUser()
|
|
6
|
+
|
|
7
|
+
const hasConfigAccess =
|
|
8
|
+
!config?.allowedRolesForConfiguration ||
|
|
9
|
+
user?.roles?.some((role) => config.allowedRolesForConfiguration.includes(role.name))
|
|
10
|
+
|
|
11
|
+
return {hasConfigAccess}
|
|
12
|
+
}
|
package/src/schema.ts
CHANGED
package/src/util/types.ts
CHANGED
|
@@ -88,6 +88,14 @@ export interface PluginConfig extends MuxInputConfig {
|
|
|
88
88
|
title?: string
|
|
89
89
|
icon?: React.ComponentType
|
|
90
90
|
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The roles that are allowed to configure the plugin.
|
|
94
|
+
*
|
|
95
|
+
* If not set, all roles will be allowed to configure the plugin.
|
|
96
|
+
* @defaultValue []
|
|
97
|
+
*/
|
|
98
|
+
allowedRolesForConfiguration: string[]
|
|
91
99
|
}
|
|
92
100
|
|
|
93
101
|
export const SUPPORTED_MUX_LANGUAGES = [
|