sanity-plugin-cloudinary 0.2.2 → 1.0.0-v3-studio.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +160 -66
  3. package/lib/index.esm.js +2 -0
  4. package/lib/index.esm.js.map +1 -0
  5. package/lib/index.js +2 -0
  6. package/lib/index.js.map +1 -0
  7. package/lib/src/index.d.ts +35 -0
  8. package/package.json +67 -41
  9. package/sanity.json +3 -15
  10. package/src/arrayFunctions.tsx +71 -0
  11. package/src/components/AssetDiff.tsx +44 -0
  12. package/src/components/AssetPreview.tsx +41 -0
  13. package/src/components/CloudinaryInput.tsx +61 -0
  14. package/src/components/SecretsConfigView.tsx +39 -0
  15. package/src/components/VideoPlayer.tsx +62 -0
  16. package/src/components/WidgetInput.tsx +66 -0
  17. package/src/components/asset-source/CloudinaryAssetSource.tsx +155 -0
  18. package/src/components/asset-source/Icon.tsx +93 -0
  19. package/src/index.ts +30 -0
  20. package/src/schema/cloudinaryAsset.ts +101 -0
  21. package/src/schema/cloudinaryAssetDerived.ts +26 -0
  22. package/src/typings.d.ts +74 -0
  23. package/src/utils.ts +115 -0
  24. package/v2-incompatible.js +11 -0
  25. package/dist/arrayFunctions.js +0 -82
  26. package/dist/arrayFunctions.js.map +0 -1
  27. package/dist/components/AssetDiff.js +0 -62
  28. package/dist/components/AssetDiff.js.map +0 -1
  29. package/dist/components/AssetPreview.js +0 -46
  30. package/dist/components/AssetPreview.js.map +0 -1
  31. package/dist/components/CloudinaryInput.js +0 -90
  32. package/dist/components/CloudinaryInput.js.map +0 -1
  33. package/dist/components/SecretsConfigView.js +0 -37
  34. package/dist/components/SecretsConfigView.js.map +0 -1
  35. package/dist/components/VideoPlayer.js +0 -70
  36. package/dist/components/VideoPlayer.js.map +0 -1
  37. package/dist/components/WidgetInput.js +0 -87
  38. package/dist/components/WidgetInput.js.map +0 -1
  39. package/dist/index.js +0 -24
  40. package/dist/index.js.map +0 -1
  41. package/dist/schema/cloudinary.js +0 -19
  42. package/dist/schema/cloudinary.js.map +0 -1
  43. package/dist/schema/cloudinaryAsset.js +0 -100
  44. package/dist/schema/cloudinaryAsset.js.map +0 -1
  45. package/dist/schema/cloudinaryAssetDerived.js +0 -22
  46. package/dist/schema/cloudinaryAssetDerived.js.map +0 -1
  47. package/dist/typings.d.js +0 -8
  48. package/dist/typings.d.js.map +0 -1
  49. package/dist/utils.js +0 -92
  50. package/dist/utils.js.map +0 -1
package/package.json CHANGED
@@ -1,61 +1,87 @@
1
1
  {
2
- "version": "0.2.2",
2
+ "name": "sanity-plugin-cloudinary",
3
+ "version": "1.0.0-v3-studio.3",
4
+ "description": "> **NOTE**",
5
+ "author": "Sanity.io <hello@sanity.io>",
3
6
  "license": "MIT",
7
+ "source": "./src/index.ts",
8
+ "main": "./lib/index.js",
9
+ "module": "./lib/index.esm.js",
10
+ "types": "./lib/src/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./lib/src/index.d.ts",
14
+ "source": "./src/index.ts",
15
+ "import": "./lib/index.esm.js",
16
+ "require": "./lib/index.js",
17
+ "default": "./lib/index.esm.js"
18
+ }
19
+ },
4
20
  "files": [
5
- "dist",
21
+ "src",
22
+ "lib",
23
+ "v2-incompatible.js",
6
24
  "sanity.json"
7
25
  ],
8
26
  "engines": {
9
- "node": ">=10"
27
+ "node": ">=14.0.0"
10
28
  },
11
29
  "scripts": {
12
- "build": "sanipack build",
13
- "watch": "sanipack build --watch",
14
- "prepublishOnly": "sanipack build && sanipack verify"
30
+ "build": "pkg-utils build",
31
+ "watch": "pkg-utils watch",
32
+ "prepublishOnly": "npm run build",
33
+ "clean": "rimraf lib",
34
+ "lint": "eslint .",
35
+ "prebuild": "npm run clean && plugin-kit verify-package --silent && pkg-utils",
36
+ "link-watch": "plugin-kit link-watch",
37
+ "prepare": "husky install"
15
38
  },
16
39
  "peerDependencies": {
17
- "@sanity/base": "^2.6.2",
18
- "@sanity/field": "^2.6.2",
19
- "@sanity/form-builder": "^2.6.2",
20
- "react": "^17.0",
21
- "react-dom": "^17.0"
22
- },
23
- "husky": {
24
- "hooks": {
25
- "pre-commit": "sanipack verify"
26
- }
27
- },
28
- "prettier": {
29
- "printWidth": 80,
30
- "semi": true,
31
- "singleQuote": true,
32
- "trailingComma": "es5"
40
+ "react": "^18.0.0",
41
+ "react-dom": "^18.0.0",
42
+ "sanity": "dev-preview || 3.0.0-rc.0"
33
43
  },
34
- "name": "sanity-plugin-cloudinary",
35
- "author": "Sanity.io <hello@sanity.io>",
36
44
  "devDependencies": {
37
- "@sanity/base": "^2.6.2",
38
- "@sanity/field": "^2.6.2",
39
- "@sanity/form-builder": "^2.6.2",
40
- "@sanity/types": "^2.6.0",
41
- "@size-limit/preset-small-lib": "^4.9.2",
42
- "@types/react": "^17.0.2",
45
+ "@commitlint/cli": "^17.2.0",
46
+ "@commitlint/config-conventional": "^17.2.0",
47
+ "@sanity/pkg-utils": "^1.16.2",
48
+ "@sanity/plugin-kit": "^2.0.7",
49
+ "@sanity/semantic-release-preset": "^2.0.2",
50
+ "@types/react": "^18.0.0",
51
+ "@types/styled-components": "^5.1.26",
43
52
  "@types/video.js": "^7.3.15",
44
- "autoprefixer": "^9.0.0",
45
- "cssnano": "^4.1.10",
46
- "husky": "^5.1.3",
53
+ "@typescript-eslint/eslint-plugin": "^5.42.0",
54
+ "@typescript-eslint/parser": "^5.42.0",
55
+ "eslint": "^8.26.0",
56
+ "eslint-config-prettier": "^8.5.0",
57
+ "eslint-config-sanity": "^6.0.0",
58
+ "eslint-plugin-prettier": "^4.2.1",
59
+ "eslint-plugin-react": "^7.31.10",
60
+ "eslint-plugin-react-hooks": "^4.6.0",
61
+ "husky": "^8.0.1",
62
+ "lint-staged": "^13.0.3",
47
63
  "postcss": "^8.0.0",
48
- "rollup-plugin-postcss": "^4.0.0",
49
- "sanipack": "2.0.1",
50
- "size-limit": "^4.9.2",
51
- "tsdx": "^0.14.1",
52
- "tslib": "^2.1.0",
53
- "typescript": "^4.2.2"
64
+ "prettier": "^2.7.1",
65
+ "react": "^18.0.0",
66
+ "rimraf": "^3.0.2",
67
+ "sanity": "dev-preview || 3.0.0-rc.0",
68
+ "typescript": "^4.8.4"
54
69
  },
55
70
  "dependencies": {
71
+ "@sanity/icons": "^1.3.6",
72
+ "@sanity/incompatible-plugin": "^1.0.4",
73
+ "@sanity/studio-secrets": "^2.0.0-v3-studio.6",
74
+ "@sanity/ui": "^1.0.0-beta.31",
56
75
  "nanoid": "^3.1.22",
57
- "sanity-secrets": "^0.0.6",
58
76
  "styled-components": "^5.2.1",
59
77
  "video.js": "^7.11.7"
60
- }
78
+ },
79
+ "repository": {
80
+ "type": "git",
81
+ "url": "git@github.com:sanity-io/sanity-plugin-cloudinary.git"
82
+ },
83
+ "bugs": {
84
+ "url": "https://github.com/sanity-io/sanity-plugin-cloudinary/issues"
85
+ },
86
+ "homepage": "https://github.com/sanity-io/sanity-plugin-cloudinary#readme"
61
87
  }
package/sanity.json CHANGED
@@ -1,20 +1,8 @@
1
1
  {
2
- "paths": {
3
- "source": "src",
4
- "compiled": "dist"
5
- },
6
2
  "parts": [
7
3
  {
8
- "implements": "part:@sanity/base/schema-type",
9
- "path": "schema/cloudinaryAssetDerived"
10
- },
11
- {
12
- "implements": "part:@sanity/base/schema-type",
13
- "path": "schema/cloudinaryAsset"
14
- },
15
- {
16
- "implements": "part:@sanity/form-builder/input/array/functions",
17
- "path": "arrayFunctions"
4
+ "implements": "part:@sanity/base/sanity-root",
5
+ "path": "./v2-incompatible.js"
18
6
  }
19
7
  ]
20
- }
8
+ }
@@ -0,0 +1,71 @@
1
+ import React from 'react'
2
+ import {Button} from '@sanity/ui'
3
+ import {PatchEvent, setIfMissing, insert} from 'sanity'
4
+
5
+ import {useSecrets} from '@sanity/studio-secrets'
6
+ import SecretsConfigView, {namespace} from './components/SecretsConfigView'
7
+ import {cloudinaryAssetSchema} from './schema/cloudinaryAsset'
8
+ import {openMediaSelector} from './utils'
9
+ import {InsertHandlerParams} from './typings'
10
+
11
+ interface ApiConfig {
12
+ cloudName: string
13
+ apiKey: string
14
+ }
15
+
16
+ const AssetListFunctions = (props: any) => {
17
+ const {secrets, loading} = useSecrets<ApiConfig>(namespace)
18
+ const [showSettings, setShowSettings] = React.useState(false)
19
+
20
+ const cloudinaryType = props.type.of.find(
21
+ (t: {name: string}) => t.name === cloudinaryAssetSchema.name
22
+ )
23
+
24
+ const handleSelect = (selected: InsertHandlerParams) => {
25
+ const {onCreateValue, onChange} = props
26
+ const items = selected.assets.map((asset) =>
27
+ Object.assign(
28
+ {},
29
+ asset,
30
+ {
31
+ // Schema version. In case we ever change our schema.
32
+ _version: 1,
33
+ },
34
+ onCreateValue(cloudinaryType)
35
+ )
36
+ )
37
+ onChange(PatchEvent.from([setIfMissing([]), insert(items, 'after', [-1])]))
38
+ }
39
+
40
+ const actions = (
41
+ <>
42
+ <Button
43
+ disabled={props.readOnly || loading}
44
+ mode="ghost"
45
+ onClick={() =>
46
+ secrets &&
47
+ openMediaSelector(
48
+ secrets.cloudName,
49
+ secrets.apiKey,
50
+ true, // multi-selection
51
+ handleSelect
52
+ )
53
+ }
54
+ >
55
+ Add multiple
56
+ </Button>
57
+ <Button onClick={() => setShowSettings(true)}>Configure</Button>
58
+ </>
59
+ )
60
+
61
+ return (
62
+ <>
63
+ {showSettings && <SecretsConfigView onClose={() => setShowSettings(false)} />}
64
+ {/* <DefaultArrayFunctions {...props}>*/}
65
+ {cloudinaryType && actions}
66
+ {/* </DefaultArrayFunctions>*/}
67
+ </>
68
+ )
69
+ }
70
+
71
+ export default AssetListFunctions
@@ -0,0 +1,44 @@
1
+ import React from 'react'
2
+ import {DiffFromTo} from 'sanity'
3
+ import VideoPlayer from './VideoPlayer'
4
+ import {assetUrl} from '../utils'
5
+ import {CloudinaryAsset} from '../typings'
6
+
7
+ type Props = {
8
+ value: CloudinaryAsset | undefined
9
+ }
10
+
11
+ const CloudinaryDiffPreview = ({value}: Props) => {
12
+ if (!value) {
13
+ return null
14
+ }
15
+
16
+ const url = assetUrl(value)
17
+
18
+ if (value.resource_type === 'video') {
19
+ return (
20
+ <section
21
+ style={{
22
+ display: 'flex',
23
+ flexWrap: 'wrap',
24
+ justifyContent: 'space-between',
25
+ }}
26
+ >
27
+ <VideoPlayer src={url} kind="diff" />
28
+ </section>
29
+ )
30
+ }
31
+
32
+ return <img alt="preview" src={url} style={{maxWidth: '100%', height: 'auto'}} />
33
+ }
34
+
35
+ type DiffProps = {
36
+ diff: any
37
+ schemaType: any
38
+ }
39
+
40
+ const AssetDiff = ({diff, schemaType}: DiffProps) => {
41
+ return <DiffFromTo diff={diff} schemaType={schemaType} previewComponent={CloudinaryDiffPreview} />
42
+ }
43
+
44
+ export default AssetDiff
@@ -0,0 +1,41 @@
1
+ import React from 'react'
2
+ import VideoPlayer from './VideoPlayer'
3
+ import {assetUrl} from '../utils'
4
+ import {Box} from '@sanity/ui'
5
+ import {CloudinaryAsset} from '../typings'
6
+
7
+ type ComponentProps = {
8
+ layout?: 'default' | 'block'
9
+ value: CloudinaryAsset | undefined
10
+ }
11
+
12
+ const AssetPreview = ({value, layout}: ComponentProps) => {
13
+ const url = value && assetUrl(value)
14
+ if (!value || !url) {
15
+ return null
16
+ }
17
+
18
+ switch (value.resource_type) {
19
+ case 'video':
20
+ return (
21
+ <Box>
22
+ <VideoPlayer src={url} kind="player" />
23
+ </Box>
24
+ )
25
+ default:
26
+ return (
27
+ <Box>
28
+ <img
29
+ alt="preview"
30
+ src={url}
31
+ style={{
32
+ maxWidth: layout === 'default' ? '80px' : '100%',
33
+ height: 'auto',
34
+ }}
35
+ />
36
+ </Box>
37
+ )
38
+ }
39
+ }
40
+
41
+ export default AssetPreview
@@ -0,0 +1,61 @@
1
+ import React, {useCallback, useState} from 'react'
2
+ import WidgetInput from './WidgetInput'
3
+ import {nanoid} from 'nanoid'
4
+ import {ObjectInputProps, PatchEvent, set} from 'sanity'
5
+ import {CloudinaryAsset} from '../typings'
6
+ import {useSecrets} from '@sanity/studio-secrets'
7
+ import {InsertHandlerParams} from '../typings'
8
+ import {openMediaSelector} from '../utils'
9
+ import SecretsConfigView, {namespace, Secrets} from './SecretsConfigView'
10
+
11
+ const CloudinaryInput = (props: ObjectInputProps) => {
12
+ const [showSettings, setShowSettings] = useState(false)
13
+ const {secrets} = useSecrets<Secrets>(namespace)
14
+ const {onChange, schemaType: type} = props
15
+ const value = (props.value as CloudinaryAsset) || undefined
16
+
17
+ const handleSelect = useCallback(
18
+ (payload: InsertHandlerParams) => {
19
+ const [asset] = payload.assets
20
+ if (!asset) {
21
+ return
22
+ }
23
+
24
+ onChange(
25
+ PatchEvent.from([
26
+ set(
27
+ Object.assign(
28
+ {
29
+ _type: type.name,
30
+ _version: 1,
31
+ ...(value?._key ? {_key: value._key} : {_key: nanoid()}),
32
+ },
33
+ asset
34
+ )
35
+ ),
36
+ ])
37
+ )
38
+ },
39
+ [onChange, type, value?._key]
40
+ )
41
+
42
+ const action = secrets
43
+ ? () =>
44
+ openMediaSelector(
45
+ secrets.cloudName,
46
+ secrets.apiKey,
47
+ false, // single selection
48
+ handleSelect,
49
+ value
50
+ )
51
+ : () => setShowSettings(true)
52
+
53
+ return (
54
+ <>
55
+ {showSettings && <SecretsConfigView onClose={() => setShowSettings(false)} />}
56
+ <WidgetInput onSetup={() => setShowSettings(true)} openMediaSelector={action} {...props} />
57
+ </>
58
+ )
59
+ }
60
+
61
+ export default CloudinaryInput
@@ -0,0 +1,39 @@
1
+ import React from 'react'
2
+ import {SettingsView} from '@sanity/studio-secrets'
3
+
4
+ export type Secrets = {
5
+ cloudName: string
6
+ apiKey: string
7
+ }
8
+
9
+ const pluginConfigKeys = [
10
+ {
11
+ key: 'cloudName',
12
+ title: 'Cloud name',
13
+ description: '',
14
+ },
15
+ {
16
+ key: 'apiKey',
17
+ title: 'API key',
18
+ description: '',
19
+ },
20
+ ]
21
+
22
+ export const namespace = 'cloudinary'
23
+
24
+ type Props = {
25
+ onClose: () => void
26
+ }
27
+
28
+ const SecretsConfigView = (props: Props) => {
29
+ return (
30
+ <SettingsView
31
+ title="Cloudinary config"
32
+ namespace={namespace}
33
+ keys={pluginConfigKeys}
34
+ onClose={props.onClose}
35
+ />
36
+ )
37
+ }
38
+
39
+ export default SecretsConfigView
@@ -0,0 +1,62 @@
1
+ import React, { CSSProperties } from 'react';
2
+ import videojs, { VideoJsPlayer } from 'video.js';
3
+
4
+ type PlayerKind = 'player' | 'diff';
5
+
6
+ export type VideoPlayerProps = {
7
+ src: string;
8
+ kind: PlayerKind;
9
+ };
10
+
11
+ export default class VideoPlayer extends React.Component<
12
+ VideoPlayerProps,
13
+ any
14
+ > {
15
+ videoNode?: HTMLVideoElement;
16
+ player?: VideoJsPlayer;
17
+
18
+ componentDidMount() {
19
+ const { src } = this.props;
20
+ this.player = videojs(this.videoNode, {
21
+ sources: [{ src }],
22
+ controls: true,
23
+ });
24
+ }
25
+
26
+ componentWillUnmount() {
27
+ if (this.player) {
28
+ this.player.dispose();
29
+ }
30
+ }
31
+
32
+ render() {
33
+ const { kind } = this.props;
34
+ const className: Record<PlayerKind, string> = {
35
+ player: 'video-js vjs-16-9 vjs-big-play-centered',
36
+ diff: 'video-js vjs-layout-tiny vjs-fluid',
37
+ };
38
+
39
+ const style: CSSProperties = { position: 'relative' };
40
+
41
+ return (
42
+ <div>
43
+ <link
44
+ href="https://vjs.zencdn.net/7.8.4/video-js.css"
45
+ rel="stylesheet"
46
+ />
47
+ <div data-vjs-player>
48
+ <video
49
+ onClick={event => event.stopPropagation()}
50
+ style={kind === 'diff' ? style : {}}
51
+ className={className[kind]}
52
+ ref={node => {
53
+ if (node) {
54
+ this.videoNode = node;
55
+ }
56
+ }}
57
+ ></video>
58
+ </div>
59
+ </div>
60
+ );
61
+ }
62
+ }
@@ -0,0 +1,66 @@
1
+ import React, {useCallback} from 'react'
2
+ import {ObjectInputProps, PatchEvent, unset} from 'sanity'
3
+ import {Button, Flex, Grid, Stack} from '@sanity/ui'
4
+ import {PlugIcon} from '@sanity/icons'
5
+ import styled from 'styled-components'
6
+ import AssetPreview from './AssetPreview'
7
+ import {CloudinaryAsset} from '../typings'
8
+
9
+ const SetupButtonContainer = styled.div`
10
+ position: relative;
11
+ display: block;
12
+ font-size: 0.8em;
13
+ transform: translate(0%, -10%);
14
+ `
15
+
16
+ type WidgetInputProps = ObjectInputProps & {openMediaSelector: () => void; onSetup: () => void}
17
+
18
+ const WidgetInput = (props: WidgetInputProps) => {
19
+ const {onChange, readOnly, value, openMediaSelector} = props
20
+
21
+ const removeValue = useCallback(() => {
22
+ onChange(PatchEvent.from([unset()]))
23
+ }, [onChange])
24
+
25
+ return (
26
+ <Stack>
27
+ <SetupButtonContainer>
28
+ <Flex flex={1} justify="flex-end">
29
+ <Button
30
+ color="primary"
31
+ icon={PlugIcon}
32
+ mode="bleed"
33
+ title="Configure"
34
+ onClick={props.onSetup}
35
+ tabIndex={1}
36
+ />
37
+ </Flex>
38
+ </SetupButtonContainer>
39
+
40
+ <Flex style={{textAlign: 'center'}} marginBottom={2}>
41
+ <AssetPreview value={value as CloudinaryAsset} />
42
+ </Flex>
43
+
44
+ <Grid gap={1} style={{gridTemplateColumns: 'repeat(auto-fit, minmax(100px, 1fr))'}}>
45
+ <Button
46
+ disabled={readOnly}
47
+ mode="ghost"
48
+ title="Select an asset"
49
+ tone="default"
50
+ onClick={openMediaSelector}
51
+ text="Select…"
52
+ />
53
+ <Button
54
+ disabled={readOnly || !value}
55
+ tone="critical"
56
+ mode="ghost"
57
+ title="Remove asset"
58
+ text="Remove"
59
+ onClick={removeValue}
60
+ />
61
+ </Grid>
62
+ </Stack>
63
+ )
64
+ }
65
+
66
+ export default WidgetInput