sanity-plugin-mux-input 2.2.4 → 2.3.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 +148 -16
- package/lib/index.cjs +3996 -3677
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +210 -0
- package/lib/index.d.ts +109 -25
- package/lib/index.esm.js +4390 -0
- package/lib/index.esm.js.map +1 -0
- package/lib/index.js +3964 -3626
- package/lib/index.js.map +1 -1
- package/package.json +48 -52
- package/src/_exports/index.ts +32 -0
- package/src/actions/upload.ts +35 -40
- package/src/clients/upChunkObservable.ts +5 -1
- package/src/components/ConfigureApi.tsx +0 -1
- package/src/components/FileInputArea.tsx +92 -0
- package/src/components/FileInputButton.tsx +3 -2
- package/src/components/FileInputMenuItem.styled.tsx +2 -2
- package/src/components/FileInputMenuItem.tsx +2 -10
- package/src/components/ImportVideosFromMux.tsx +317 -0
- package/src/components/Input.tsx +3 -3
- package/src/components/PlayerActionsMenu.tsx +14 -12
- package/src/components/SelectAsset.tsx +1 -1
- package/src/components/StudioTool.tsx +11 -6
- package/src/components/TextTracksEditor.tsx +214 -0
- package/src/components/UploadConfiguration.tsx +390 -0
- package/src/components/UploadPlaceholder.tsx +41 -55
- package/src/components/Uploader.styled.tsx +0 -1
- package/src/components/Uploader.tsx +384 -0
- package/src/components/VideoDetails/DeleteDialog.tsx +20 -24
- package/src/components/VideoPlayer.tsx +33 -5
- package/src/components/VideoThumbnail.tsx +21 -7
- package/src/components/VideosBrowser.tsx +6 -3
- package/src/components/withFocusRing/withFocusRing.ts +20 -22
- package/src/hooks/useClient.ts +1 -1
- package/src/hooks/useImportMuxAssets.ts +127 -0
- package/src/hooks/useMuxAssets.ts +168 -0
- package/src/plugin.tsx +5 -5
- package/src/util/asserters.ts +9 -0
- package/src/util/createSearchFilter.ts +1 -1
- package/src/util/formatBytes.ts +32 -0
- package/src/util/generateJwt.ts +1 -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/parsers.ts +5 -0
- package/src/util/types.ts +195 -12
- package/lib/index.cjs.js +0 -5
- package/src/components/__legacy__Uploader.tsx +0 -280
- package/src/index.ts +0 -29
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sanity-plugin-mux-input",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "An input component that integrates Sanity Studio with Mux video encoding/hosting service.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -25,21 +25,16 @@
|
|
|
25
25
|
"type": "module",
|
|
26
26
|
"exports": {
|
|
27
27
|
".": {
|
|
28
|
-
"
|
|
29
|
-
"source": "./src/index.ts",
|
|
30
|
-
"require": "./lib/index.cjs",
|
|
31
|
-
"node": {
|
|
32
|
-
"module": "./lib/index.js",
|
|
33
|
-
"import": "./lib/index.cjs.js"
|
|
34
|
-
},
|
|
28
|
+
"source": "./src/_exports/index.ts",
|
|
35
29
|
"import": "./lib/index.js",
|
|
30
|
+
"require": "./lib/index.cjs",
|
|
36
31
|
"default": "./lib/index.js"
|
|
37
32
|
},
|
|
38
33
|
"./package.json": "./package.json"
|
|
39
34
|
},
|
|
40
35
|
"main": "./lib/index.cjs",
|
|
41
|
-
"module": "./lib/index.js",
|
|
42
|
-
"source": "./src/index.ts",
|
|
36
|
+
"module": "./lib/index.esm.js",
|
|
37
|
+
"source": "./src/_exports/index.ts",
|
|
43
38
|
"types": "./lib/index.d.ts",
|
|
44
39
|
"files": [
|
|
45
40
|
"src",
|
|
@@ -48,9 +43,9 @@
|
|
|
48
43
|
"v2-incompatible.js"
|
|
49
44
|
],
|
|
50
45
|
"scripts": {
|
|
51
|
-
"build": "run-s clean &&
|
|
46
|
+
"build": "run-s clean && pkg-utils build --strict && pkg-utils --strict",
|
|
52
47
|
"clean": "rimraf lib",
|
|
53
|
-
"dev": "
|
|
48
|
+
"dev": "plugin-kit link-watch",
|
|
54
49
|
"format": "prettier --write --cache --ignore-unknown .",
|
|
55
50
|
"link-watch": "plugin-kit link-watch",
|
|
56
51
|
"lint": "eslint .",
|
|
@@ -61,63 +56,64 @@
|
|
|
61
56
|
"watch": "pkg-utils watch --strict"
|
|
62
57
|
},
|
|
63
58
|
"dependencies": {
|
|
64
|
-
"@mux/mux-player-react": "
|
|
65
|
-
"@mux/upchunk": "^3.2
|
|
66
|
-
"@sanity/icons": "^2",
|
|
67
|
-
"@sanity/incompatible-plugin": "^1",
|
|
68
|
-
"@sanity/ui": "^1",
|
|
69
|
-
"@sanity/uuid": "^3",
|
|
59
|
+
"@mux/mux-player-react": "^2.4.1",
|
|
60
|
+
"@mux/upchunk": "^3.3.2",
|
|
61
|
+
"@sanity/icons": "^2.11.7",
|
|
62
|
+
"@sanity/incompatible-plugin": "^1.0.4",
|
|
63
|
+
"@sanity/ui": "^2.1.0",
|
|
64
|
+
"@sanity/uuid": "^3.0.2",
|
|
65
|
+
"iso-639-1": "^3.1.2",
|
|
70
66
|
"jsonwebtoken-esm": "^1.0.5",
|
|
71
|
-
"lodash": "^4",
|
|
67
|
+
"lodash": "^4.17.21",
|
|
72
68
|
"react-rx": "^2.1.3",
|
|
73
|
-
"rxjs": "^7",
|
|
74
|
-
"scroll-into-view-if-needed": "^3",
|
|
69
|
+
"rxjs": "^7.8.1",
|
|
70
|
+
"scroll-into-view-if-needed": "^3.1.0",
|
|
75
71
|
"suspend-react": "^0.1.3",
|
|
76
|
-
"swr": "^2.2.
|
|
77
|
-
"type-fest": "^4.2
|
|
72
|
+
"swr": "^2.2.5",
|
|
73
|
+
"type-fest": "^4.10.2",
|
|
78
74
|
"use-error-boundary": "^2.0.6"
|
|
79
75
|
},
|
|
80
76
|
"devDependencies": {
|
|
81
|
-
"@commitlint/cli": "^
|
|
82
|
-
"@commitlint/config-conventional": "^
|
|
83
|
-
"@sanity/
|
|
77
|
+
"@commitlint/cli": "^19.2.1",
|
|
78
|
+
"@commitlint/config-conventional": "^19.1.0",
|
|
79
|
+
"@sanity/client": "^6.15.11",
|
|
80
|
+
"@sanity/pkg-utils": "^6.0.1",
|
|
84
81
|
"@sanity/plugin-kit": "^3.1.10",
|
|
85
|
-
"@sanity/semantic-release-preset": "^4.1.
|
|
86
|
-
"@sanity/vision": "^3.
|
|
87
|
-
"@types/
|
|
88
|
-
"@types/
|
|
89
|
-
"@
|
|
90
|
-
"@typescript-eslint/
|
|
82
|
+
"@sanity/semantic-release-preset": "^4.1.7",
|
|
83
|
+
"@sanity/vision": "^3.36.4",
|
|
84
|
+
"@types/lodash": "^4.17.0",
|
|
85
|
+
"@types/react": "^18.2.74",
|
|
86
|
+
"@types/react-is": "^18.2.4",
|
|
87
|
+
"@typescript-eslint/eslint-plugin": "^7.6.0",
|
|
88
|
+
"@typescript-eslint/parser": "^7.6.0",
|
|
91
89
|
"cz-conventional-changelog": "^3.3.0",
|
|
92
|
-
"eslint": "^8.
|
|
93
|
-
"eslint-config-prettier": "^9.
|
|
94
|
-
"eslint-config-
|
|
95
|
-
"eslint-
|
|
96
|
-
"eslint-plugin-
|
|
97
|
-
"eslint-plugin-prettier": "^5.0.1",
|
|
98
|
-
"eslint-plugin-react": "^7.33.2",
|
|
90
|
+
"eslint": "^8.57.0",
|
|
91
|
+
"eslint-config-prettier": "^9.1.0",
|
|
92
|
+
"eslint-config-sanity": "^7.1.2",
|
|
93
|
+
"eslint-plugin-import": "^2.29.1",
|
|
94
|
+
"eslint-plugin-prettier": "^5.1.3",
|
|
99
95
|
"eslint-plugin-react-hooks": "^4.6.0",
|
|
100
|
-
"eslint-plugin-simple-import-sort": "^
|
|
101
|
-
"husky": "^
|
|
102
|
-
"lint-staged": "^
|
|
103
|
-
"
|
|
104
|
-
"
|
|
105
|
-
"
|
|
106
|
-
"prettier": "^3.1.0",
|
|
107
|
-
"prettier-plugin-packagejson": "^2.4.6",
|
|
96
|
+
"eslint-plugin-simple-import-sort": "^12.0.0",
|
|
97
|
+
"husky": "^9.0.11",
|
|
98
|
+
"lint-staged": "^15.2.2",
|
|
99
|
+
"npm-run-all2": "^5.0.0",
|
|
100
|
+
"prettier": "^3.2.5",
|
|
101
|
+
"prettier-plugin-packagejson": "^2.4.14",
|
|
108
102
|
"react": "^18.2.0",
|
|
109
103
|
"react-dom": "^18.2.0",
|
|
110
104
|
"react-is": "^18.2.0",
|
|
111
|
-
"rimraf": "^5.0.
|
|
112
|
-
"sanity": "^3.
|
|
113
|
-
"
|
|
114
|
-
"
|
|
105
|
+
"rimraf": "^5.0.5",
|
|
106
|
+
"sanity": "^3.36.4",
|
|
107
|
+
"semantic-release": "^23.0.7",
|
|
108
|
+
"styled-components": "^6.1.8",
|
|
109
|
+
"typescript": "5.4.2",
|
|
110
|
+
"yalc": "1.0.0-pre.53"
|
|
115
111
|
},
|
|
116
112
|
"peerDependencies": {
|
|
117
113
|
"react": "^18",
|
|
118
114
|
"react-is": "^18",
|
|
119
115
|
"sanity": "^3",
|
|
120
|
-
"styled-components": "^
|
|
116
|
+
"styled-components": "^6"
|
|
121
117
|
},
|
|
122
118
|
"engines": {
|
|
123
119
|
"node": ">=14"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {definePlugin} from 'sanity'
|
|
2
|
+
import createStudioTool, {DEFAULT_TOOL_CONFIG} from '../components/StudioTool'
|
|
3
|
+
import {muxVideoCustomRendering} from '../plugin'
|
|
4
|
+
import {muxVideo, muxVideoAsset} from '../schema'
|
|
5
|
+
import type {PluginConfig} from '../util/types'
|
|
6
|
+
export type {VideoAssetDocument} from '../util/types'
|
|
7
|
+
|
|
8
|
+
export const defaultConfig: PluginConfig = {
|
|
9
|
+
mp4_support: 'none',
|
|
10
|
+
encoding_tier: 'smart',
|
|
11
|
+
max_resolution_tier: '1080p',
|
|
12
|
+
normalize_audio: false,
|
|
13
|
+
defaultSigned: false,
|
|
14
|
+
tool: DEFAULT_TOOL_CONFIG,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const muxInput = definePlugin<Partial<PluginConfig> | void>((userConfig) => {
|
|
18
|
+
const config: PluginConfig = {...defaultConfig, ...(userConfig || {})}
|
|
19
|
+
return {
|
|
20
|
+
name: 'mux-input',
|
|
21
|
+
schema: {
|
|
22
|
+
types: [
|
|
23
|
+
muxVideoAsset,
|
|
24
|
+
{
|
|
25
|
+
...muxVideo,
|
|
26
|
+
...muxVideoCustomRendering(config),
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
tools: config.tool === false ? undefined : [createStudioTool(config)],
|
|
31
|
+
}
|
|
32
|
+
})
|
package/src/actions/upload.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
/* eslint-disable camelcase */
|
|
2
1
|
import {uuid as generateUuid} from '@sanity/uuid'
|
|
3
|
-
import {concat, defer, from,
|
|
2
|
+
import {concat, defer, from, of, throwError, type Observable} from 'rxjs'
|
|
4
3
|
import {catchError, mergeMap, mergeMapTo, switchMap} from 'rxjs/operators'
|
|
5
4
|
import type {SanityClient} from 'sanity'
|
|
6
5
|
|
|
7
6
|
import {createUpChunkObservable} from '../clients/upChunkObservable'
|
|
8
|
-
import type {
|
|
7
|
+
import type {MuxAsset, MuxNewAssetSettings, PluginConfig, UploadConfig} from '../util/types'
|
|
9
8
|
import {getAsset} from './assets'
|
|
10
9
|
import {testSecretsObservable} from './secrets'
|
|
11
10
|
|
|
@@ -17,28 +16,29 @@ export function cancelUpload(client: SanityClient, uuid: string) {
|
|
|
17
16
|
})
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
export function uploadUrl(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
export function uploadUrl({
|
|
20
|
+
url,
|
|
21
|
+
settings,
|
|
22
|
+
client,
|
|
23
|
+
}: {
|
|
24
|
+
url: string
|
|
25
|
+
settings: MuxNewAssetSettings
|
|
26
|
+
client: SanityClient
|
|
27
|
+
}) {
|
|
26
28
|
return testUrl(url).pipe(
|
|
27
29
|
switchMap((validUrl) => {
|
|
28
30
|
return concat(
|
|
29
|
-
of({type: 'url', url: validUrl}),
|
|
31
|
+
of({type: 'url' as const, url: validUrl}),
|
|
30
32
|
testSecretsObservable(client).pipe(
|
|
31
33
|
switchMap((json) => {
|
|
32
34
|
if (!json || !json.status) {
|
|
33
35
|
return throwError(new Error('Invalid credentials'))
|
|
34
36
|
}
|
|
35
37
|
const uuid = generateUuid()
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
mp4_support: config.mp4_support,
|
|
41
|
-
}
|
|
38
|
+
const muxBody = settings
|
|
39
|
+
if (!muxBody.input) muxBody.input = [{type: 'video'}]
|
|
40
|
+
muxBody.input[0].url = validUrl
|
|
41
|
+
|
|
42
42
|
const query = {
|
|
43
43
|
muxBody: JSON.stringify(muxBody),
|
|
44
44
|
filename: validUrl.split('/').slice(-1)[0],
|
|
@@ -65,7 +65,7 @@ export function uploadUrl(
|
|
|
65
65
|
if (!asset) {
|
|
66
66
|
return throwError(new Error('No asset document returned'))
|
|
67
67
|
}
|
|
68
|
-
return of({type: 'success', id: uuid, asset})
|
|
68
|
+
return of({type: 'success' as const, id: uuid, asset})
|
|
69
69
|
})
|
|
70
70
|
)
|
|
71
71
|
})
|
|
@@ -75,41 +75,36 @@ export function uploadUrl(
|
|
|
75
75
|
)
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
export function uploadFile(
|
|
79
|
-
|
|
80
|
-
client
|
|
81
|
-
file
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
export function uploadFile({
|
|
79
|
+
settings,
|
|
80
|
+
client,
|
|
81
|
+
file,
|
|
82
|
+
}: {
|
|
83
|
+
settings: MuxNewAssetSettings
|
|
84
|
+
client: SanityClient
|
|
85
|
+
file: File
|
|
86
|
+
}) {
|
|
84
87
|
return testFile(file).pipe(
|
|
85
88
|
switchMap((fileOptions) => {
|
|
86
89
|
return concat(
|
|
87
|
-
of({type: 'file', file: fileOptions}),
|
|
90
|
+
of({type: 'file' as const, file: fileOptions}),
|
|
88
91
|
testSecretsObservable(client).pipe(
|
|
89
92
|
switchMap((json) => {
|
|
90
93
|
if (!json || !json.status) {
|
|
91
|
-
return throwError(new Error('Invalid credentials'))
|
|
94
|
+
return throwError(() => new Error('Invalid credentials'))
|
|
92
95
|
}
|
|
93
96
|
const uuid = generateUuid()
|
|
94
|
-
const
|
|
95
|
-
const body = {
|
|
96
|
-
mp4_support: config.mp4_support,
|
|
97
|
-
playback_policy: [enableSignedUrls ? 'signed' : 'public'],
|
|
98
|
-
}
|
|
97
|
+
const body = settings
|
|
99
98
|
|
|
100
99
|
return concat(
|
|
101
|
-
of({type: 'uuid', uuid}),
|
|
100
|
+
of({type: 'uuid' as const, uuid}),
|
|
102
101
|
defer(() =>
|
|
103
102
|
client.observable.request<{
|
|
104
103
|
sanityAssetId: string
|
|
105
104
|
upload: {
|
|
106
105
|
cors_origin: string
|
|
107
106
|
id: string
|
|
108
|
-
new_asset_settings:
|
|
109
|
-
mp4_support: 'standard' | 'none'
|
|
110
|
-
passthrough: string
|
|
111
|
-
playback_policies: ['public' | 'signed']
|
|
112
|
-
}
|
|
107
|
+
new_asset_settings: MuxNewAssetSettings
|
|
113
108
|
status: 'waiting'
|
|
114
109
|
timeout: number
|
|
115
110
|
url: string
|
|
@@ -130,7 +125,7 @@ export function uploadFile(
|
|
|
130
125
|
// eslint-disable-next-line no-warning-comments
|
|
131
126
|
// @TODO type the observable events
|
|
132
127
|
// eslint-disable-next-line max-nested-callbacks
|
|
133
|
-
mergeMap((event
|
|
128
|
+
mergeMap((event) => {
|
|
134
129
|
if (event.type !== 'success') {
|
|
135
130
|
return of(event)
|
|
136
131
|
}
|
|
@@ -234,7 +229,7 @@ async function updateAssetDocumentFromUpload(client: SanityClient, uuid: string)
|
|
|
234
229
|
})
|
|
235
230
|
}
|
|
236
231
|
|
|
237
|
-
function testFile(file: File) {
|
|
232
|
+
export function testFile(file: File) {
|
|
238
233
|
if (typeof window !== 'undefined' && file instanceof window.File) {
|
|
239
234
|
const fileOptions = optionsFromFile({}, file)
|
|
240
235
|
return of(fileOptions)
|
|
@@ -242,7 +237,7 @@ function testFile(file: File) {
|
|
|
242
237
|
return throwError(new Error('Invalid file'))
|
|
243
238
|
}
|
|
244
239
|
|
|
245
|
-
function testUrl(url: string): Observable<string> {
|
|
240
|
+
export function testUrl(url: string): Observable<string> {
|
|
246
241
|
const error = new Error('Invalid URL')
|
|
247
242
|
if (typeof url !== 'string') {
|
|
248
243
|
return throwError(error)
|
|
@@ -261,7 +256,7 @@ function testUrl(url: string): Observable<string> {
|
|
|
261
256
|
|
|
262
257
|
function optionsFromFile(opts: {preserveFilename?: boolean}, file: File) {
|
|
263
258
|
if (typeof window === 'undefined' || !(file instanceof window.File)) {
|
|
264
|
-
return
|
|
259
|
+
return undefined
|
|
265
260
|
}
|
|
266
261
|
return {
|
|
267
262
|
name: opts.preserveFilename === false ? undefined : file.name,
|
|
@@ -2,7 +2,11 @@ import {UpChunk} from '@mux/upchunk'
|
|
|
2
2
|
import {Observable} from 'rxjs'
|
|
3
3
|
|
|
4
4
|
export function createUpChunkObservable(uuid: string, uploadUrl: string, source: File) {
|
|
5
|
-
return new Observable
|
|
5
|
+
return new Observable<
|
|
6
|
+
| {type: 'pause' | 'resume'; id: string}
|
|
7
|
+
| {type: 'success'; id: string}
|
|
8
|
+
| {type: 'progress'; percent: number}
|
|
9
|
+
>((subscriber) => {
|
|
6
10
|
const upchunk = UpChunk.createUpload({
|
|
7
11
|
endpoint: uploadUrl,
|
|
8
12
|
file: source,
|
|
@@ -178,7 +178,6 @@ function ConfigureApi({secrets, setDialogState}: Props) {
|
|
|
178
178
|
</Text>
|
|
179
179
|
</Box>
|
|
180
180
|
</Flex>
|
|
181
|
-
{/* TODO: use a popover instead to avoid jumping around */}
|
|
182
181
|
{secrets.signingKeyId && state.enableSignedUrls ? (
|
|
183
182
|
<Card padding={[3, 3, 3]} radius={2} shadow={1} tone="caution">
|
|
184
183
|
<Stack space={3}>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {PropsWithChildren, useRef, useState} from 'react'
|
|
2
|
+
import {Box, Button, Card, CardTone, Flex, Inline, Text} from '@sanity/ui'
|
|
3
|
+
import {FileInputButton} from './FileInputButton'
|
|
4
|
+
import {UploadIcon} from '@sanity/icons'
|
|
5
|
+
import {extractDroppedFiles} from '../util/extractFiles'
|
|
6
|
+
|
|
7
|
+
interface FileInputAreaProps extends PropsWithChildren {
|
|
8
|
+
accept?: string
|
|
9
|
+
acceptMIMETypes?: string[]
|
|
10
|
+
label: React.ReactNode
|
|
11
|
+
onSelect: (files: FileList | File[]) => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function FileInputArea({
|
|
15
|
+
label,
|
|
16
|
+
accept,
|
|
17
|
+
acceptMIMETypes,
|
|
18
|
+
onSelect,
|
|
19
|
+
}: FileInputAreaProps) {
|
|
20
|
+
const dragEnteredEls = useRef<EventTarget[]>([])
|
|
21
|
+
const [dragState, setDragState] = useState<'valid' | 'invalid' | null>(null)
|
|
22
|
+
|
|
23
|
+
// Stages and validates an upload from dragging+dropping files or folders
|
|
24
|
+
const handleDrop: React.DragEventHandler<HTMLDivElement> = (event) => {
|
|
25
|
+
setDragState(null)
|
|
26
|
+
event.preventDefault()
|
|
27
|
+
event.stopPropagation()
|
|
28
|
+
extractDroppedFiles(event.nativeEvent.dataTransfer!).then(onSelect)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* ------------------------------- Drag State ------------------------------- */
|
|
32
|
+
|
|
33
|
+
const handleDragOver: React.DragEventHandler<HTMLDivElement> = (event) => {
|
|
34
|
+
event.preventDefault()
|
|
35
|
+
event.stopPropagation()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const handleDragEnter: React.DragEventHandler<HTMLDivElement> = (event) => {
|
|
39
|
+
event.stopPropagation()
|
|
40
|
+
dragEnteredEls.current.push(event.target)
|
|
41
|
+
const type = event.dataTransfer.items?.[0]?.type
|
|
42
|
+
setDragState(
|
|
43
|
+
!acceptMIMETypes || acceptMIMETypes.some((mimeType) => type?.match(mimeType))
|
|
44
|
+
? 'valid'
|
|
45
|
+
: 'invalid'
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const handleDragLeave: React.DragEventHandler<HTMLDivElement> = (event) => {
|
|
50
|
+
event.stopPropagation()
|
|
51
|
+
const idx = dragEnteredEls.current.indexOf(event.target)
|
|
52
|
+
if (idx > -1) {
|
|
53
|
+
dragEnteredEls.current.splice(idx, 1)
|
|
54
|
+
}
|
|
55
|
+
if (dragEnteredEls.current.length === 0) {
|
|
56
|
+
setDragState(null)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let tone: CardTone = 'inherit'
|
|
61
|
+
if (dragState) tone = dragState === 'valid' ? 'positive' : 'critical'
|
|
62
|
+
return (
|
|
63
|
+
<Card border sizing="border" tone={tone} style={{borderStyle: 'dashed'}} padding={3}>
|
|
64
|
+
<Flex
|
|
65
|
+
align="center"
|
|
66
|
+
justify="space-between"
|
|
67
|
+
gap={4}
|
|
68
|
+
direction={['column', 'column', 'row']}
|
|
69
|
+
paddingY={[2, 2, 0]}
|
|
70
|
+
sizing="border"
|
|
71
|
+
onDrop={handleDrop}
|
|
72
|
+
onDragOver={handleDragOver}
|
|
73
|
+
onDragLeave={handleDragLeave}
|
|
74
|
+
onDragEnter={handleDragEnter}
|
|
75
|
+
>
|
|
76
|
+
<Flex align="center" justify="center" gap={2} flex={1}>
|
|
77
|
+
{label}
|
|
78
|
+
</Flex>
|
|
79
|
+
<Inline space={2}>
|
|
80
|
+
<FileInputButton
|
|
81
|
+
mode="ghost"
|
|
82
|
+
tone="default"
|
|
83
|
+
icon={UploadIcon}
|
|
84
|
+
text="Upload"
|
|
85
|
+
onSelect={onSelect}
|
|
86
|
+
accept={accept}
|
|
87
|
+
/>
|
|
88
|
+
</Inline>
|
|
89
|
+
</Flex>
|
|
90
|
+
</Card>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
@@ -17,8 +17,9 @@ const Label = styled.label`
|
|
|
17
17
|
|
|
18
18
|
export interface FileInputButtonProps extends ButtonProps {
|
|
19
19
|
onSelect: (files: FileList) => void
|
|
20
|
+
accept?: string
|
|
20
21
|
}
|
|
21
|
-
export const FileInputButton = ({onSelect, ...props}: FileInputButtonProps) => {
|
|
22
|
+
export const FileInputButton = ({onSelect, accept, ...props}: FileInputButtonProps) => {
|
|
22
23
|
const inputId = `FileSelect${useId()}`
|
|
23
24
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
24
25
|
const handleSelect = useCallback<React.ChangeEventHandler<HTMLInputElement>>(
|
|
@@ -33,7 +34,7 @@ export const FileInputButton = ({onSelect, ...props}: FileInputButtonProps) => {
|
|
|
33
34
|
return (
|
|
34
35
|
<Label htmlFor={inputId}>
|
|
35
36
|
<HiddenInput
|
|
36
|
-
accept=
|
|
37
|
+
accept={accept || 'video/*'}
|
|
37
38
|
ref={inputRef}
|
|
38
39
|
tabIndex={0}
|
|
39
40
|
type="file"
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {MenuItem
|
|
1
|
+
import {MenuItem} from '@sanity/ui'
|
|
2
2
|
import styled, {css} from 'styled-components'
|
|
3
3
|
|
|
4
4
|
import {focusRingStyle} from './withFocusRing/helpers'
|
|
5
5
|
|
|
6
|
-
export const FileButton = styled(MenuItem)(({theme}
|
|
6
|
+
export const FileButton = styled(MenuItem)(({theme}) => {
|
|
7
7
|
const {focusRing} = theme.sanity
|
|
8
8
|
const base = theme.sanity.color.base
|
|
9
9
|
const border = {width: 1, color: 'var(--card-border-color)'}
|
|
@@ -25,7 +25,6 @@ export const FileInputMenuItem = React.forwardRef(function FileInputMenuItem(
|
|
|
25
25
|
fontSize,
|
|
26
26
|
multiple,
|
|
27
27
|
onSelect,
|
|
28
|
-
padding = 3,
|
|
29
28
|
space = 3,
|
|
30
29
|
textAlign,
|
|
31
30
|
text,
|
|
@@ -45,7 +44,7 @@ export const FileInputMenuItem = React.forwardRef(function FileInputMenuItem(
|
|
|
45
44
|
)
|
|
46
45
|
|
|
47
46
|
const content = (
|
|
48
|
-
<Flex align="center" justify="flex-start"
|
|
47
|
+
<Flex align="center" justify="flex-start">
|
|
49
48
|
{/* Icon */}
|
|
50
49
|
{icon && (
|
|
51
50
|
<Box marginRight={text ? space : undefined}>
|
|
@@ -66,14 +65,7 @@ export const FileInputMenuItem = React.forwardRef(function FileInputMenuItem(
|
|
|
66
65
|
)
|
|
67
66
|
|
|
68
67
|
return (
|
|
69
|
-
<FileButton
|
|
70
|
-
{...rest}
|
|
71
|
-
htmlFor={id}
|
|
72
|
-
padding={0}
|
|
73
|
-
fontSize={2}
|
|
74
|
-
disabled={disabled}
|
|
75
|
-
ref={forwardedRef}
|
|
76
|
-
>
|
|
68
|
+
<FileButton {...rest} htmlFor={id} disabled={disabled} ref={forwardedRef}>
|
|
77
69
|
{content}
|
|
78
70
|
|
|
79
71
|
{/* Visibly hidden input */}
|