sanity-plugin-shopify-assets 1.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/package.json ADDED
@@ -0,0 +1,101 @@
1
+ {
2
+ "name": "sanity-plugin-shopify-assets",
3
+ "version": "1.0.0",
4
+ "description": "Choose assets from your Shopify store in your Sanity Studio",
5
+ "keywords": [
6
+ "sanity",
7
+ "sanity-plugin",
8
+ "images",
9
+ "shopify",
10
+ "assets",
11
+ "source"
12
+ ],
13
+ "homepage": "https://github.com/sanity-io/sanity-plugin-shopify-assets#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/sanity-io/sanity-plugin-shopify-assets/issues"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git@github.com:sanity-io/sanity-plugin-shopify-assets.git"
20
+ },
21
+ "license": "MIT",
22
+ "author": "Sanity.io <hello@sanity.io>",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "source": "./src/index.ts",
27
+ "require": "./dist/index.js",
28
+ "import": "./dist/index.esm.js",
29
+ "default": "./dist/index.esm.js"
30
+ },
31
+ "./package.json": "./package.json"
32
+ },
33
+ "main": "./dist/index.js",
34
+ "module": "./dist/index.esm.js",
35
+ "source": "./src/index.ts",
36
+ "types": "./dist/index.d.ts",
37
+ "files": [
38
+ "dist",
39
+ "sanity.json",
40
+ "src",
41
+ "v2-incompatible.js"
42
+ ],
43
+ "scripts": {
44
+ "build": "run-s clean && plugin-kit verify-package --silent && pkg-utils build --strict && pkg-utils --strict",
45
+ "clean": "rimraf dist",
46
+ "format": "prettier --write --cache --ignore-unknown .",
47
+ "link-watch": "plugin-kit link-watch",
48
+ "lint": "eslint .",
49
+ "prepublishOnly": "run-s build",
50
+ "watch": "pkg-utils watch --strict"
51
+ },
52
+ "dependencies": {
53
+ "@sanity/icons": "^2.2.2",
54
+ "@sanity/incompatible-plugin": "^1.0.4",
55
+ "@sanity/ui": "^1.0.14",
56
+ "axios": "^1.2.2",
57
+ "pretty-bytes": "^6.0.0",
58
+ "pretty-ms": "^8.0.0",
59
+ "react-infinite-scroll-component": "^6.1.0",
60
+ "react-photo-album": "^2.0.0",
61
+ "rxjs": "^7.8.0",
62
+ "video.js": "^7.20.3"
63
+ },
64
+ "devDependencies": {
65
+ "@commitlint/cli": "^17.4.2",
66
+ "@commitlint/config-conventional": "^17.4.2",
67
+ "@sanity/pkg-utils": "^2.2.1",
68
+ "@sanity/plugin-kit": "^3.1.3",
69
+ "@sanity/semantic-release-preset": "^3.0.2",
70
+ "@types/react": "^18.0.26",
71
+ "@types/styled-components": "^5.1.26",
72
+ "@types/video.js": "^7.3.50",
73
+ "@typescript-eslint/eslint-plugin": "^5.48.1",
74
+ "@typescript-eslint/parser": "^5.48.1",
75
+ "eslint": "^8.31.0",
76
+ "eslint-config-prettier": "^8.6.0",
77
+ "eslint-config-sanity": "^6.0.0",
78
+ "eslint-plugin-prettier": "^4.2.1",
79
+ "eslint-plugin-react": "^7.32.0",
80
+ "eslint-plugin-react-hooks": "^4.6.0",
81
+ "husky": "^8.0.3",
82
+ "lint-staged": "^13.1.0",
83
+ "npm-run-all": "^4.1.5",
84
+ "prettier": "^2.8.2",
85
+ "prettier-plugin-packagejson": "^2.3.0",
86
+ "react": "^18.2.0",
87
+ "react-dom": "^18.2.0",
88
+ "react-is": "^18.2.0",
89
+ "rimraf": "^4.0.4",
90
+ "sanity": "^3.2.3",
91
+ "styled-components": "^5.3.6",
92
+ "typescript": "^4.9.4"
93
+ },
94
+ "peerDependencies": {
95
+ "react": "^18",
96
+ "sanity": "^3"
97
+ },
98
+ "engines": {
99
+ "node": ">=14"
100
+ }
101
+ }
package/sanity.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "parts": [
3
+ {
4
+ "implements": "part:@sanity/base/sanity-root",
5
+ "path": "./v2-incompatible.js"
6
+ }
7
+ ]
8
+ }
@@ -0,0 +1,58 @@
1
+ import React from 'react'
2
+ import {DiffFromTo} from 'sanity'
3
+ import {Asset} from '../types'
4
+ import {Flex, Text, Stack} from '@sanity/ui'
5
+
6
+ type Props = {
7
+ value: Asset | undefined
8
+ }
9
+
10
+ const CloudinaryDiffPreview = ({value}: Props) => {
11
+ if (!value) {
12
+ return null
13
+ }
14
+
15
+ if (value?.preview?.url) {
16
+ return (
17
+ <Flex justify="center" align="center" height="fill" width="fill">
18
+ <Stack space={2}>
19
+ <img
20
+ alt="preview"
21
+ src={value?.preview?.url}
22
+ style={{
23
+ objectFit: 'contain',
24
+ margin: 'auto',
25
+ maxWidth: '100%',
26
+ maxHeight: '100%',
27
+ }}
28
+ />
29
+ <Text size={1}>{value.type.charAt(0).toUpperCase() + value.type.slice(1)}</Text>
30
+ </Stack>
31
+ </Flex>
32
+ )
33
+ }
34
+
35
+ return (
36
+ <Flex justify="center" align="center" height="fill" width="fill">
37
+ <div>(no image)</div>
38
+ </Flex>
39
+ )
40
+ }
41
+
42
+ type DiffProps = {
43
+ diff: any
44
+ schemaType: any
45
+ }
46
+
47
+ const AssetDiff = ({diff, schemaType}: DiffProps) => {
48
+ return (
49
+ <DiffFromTo
50
+ diff={diff}
51
+ schemaType={schemaType}
52
+ previewComponent={CloudinaryDiffPreview}
53
+ layout={'grid'}
54
+ />
55
+ )
56
+ }
57
+
58
+ export default AssetDiff
@@ -0,0 +1,82 @@
1
+ import {Box, Flex, Text, Theme, useTheme} from '@sanity/ui'
2
+ import {DurationLine, InfoLine} from './File.styled'
3
+
4
+ import {Asset} from '../types'
5
+ import React from 'react'
6
+ import VideoPlayer from './VideoPlayer'
7
+ import prettyBytes from 'pretty-bytes'
8
+ import prettyMilliseconds from 'pretty-ms'
9
+ import styled from 'styled-components'
10
+
11
+ type SanityTheme = Theme['sanity']
12
+
13
+ interface Style {
14
+ studioTheme: SanityTheme
15
+ }
16
+
17
+ interface ComponentProps {
18
+ value: Asset
19
+ }
20
+
21
+ export const StyledBox = styled(Box)`
22
+ background-color: ${({studioTheme}: Style) => studioTheme?.color?.card?.enabled?.bg2};
23
+ border: ${({studioTheme}: Style) => `1px solid ${studioTheme?.color?.card?.enabled?.border}`};
24
+ display: flex;
25
+ justify-content: center;
26
+ margin-bottom: ${({studioTheme}: Style) => studioTheme.space[4]};
27
+ position: relative;
28
+ `
29
+
30
+ const RenderAsset = ({value, url}: {value: Asset; url: string}) => {
31
+ switch (value.type) {
32
+ case 'video':
33
+ return <VideoPlayer src={url} kind="player" />
34
+ default:
35
+ return (
36
+ <Flex justify="center">
37
+ <img
38
+ alt="preview"
39
+ src={value?.preview?.url}
40
+ style={{
41
+ maxWidth: '100%',
42
+ height: 'auto',
43
+ display: 'block',
44
+ maxHeight: '30vh',
45
+ }}
46
+ />
47
+ </Flex>
48
+ )
49
+ }
50
+ }
51
+
52
+ const AssetPreview = ({value}: ComponentProps) => {
53
+ const url = value && value.url
54
+ const theme = useTheme().sanity
55
+
56
+ if (!value || !url) {
57
+ return null
58
+ }
59
+
60
+ const {filename, meta} = value
61
+ const {fileSize, duration} = meta
62
+
63
+ return (
64
+ <StyledBox studioTheme={theme} marginBottom={2}>
65
+ <RenderAsset value={value} url={url} />
66
+ <InfoLine padding={2} radius={2} margin={2} studioTheme={theme}>
67
+ <Text size={1} title={`Select ${filename}`}>
68
+ {filename} {fileSize && `(${prettyBytes(fileSize)})`}
69
+ </Text>
70
+ </InfoLine>
71
+ {duration && (
72
+ <DurationLine padding={2} radius={2} margin={2} studioTheme={theme}>
73
+ <Text size={1} title={`Video duration: ${filename}`}>
74
+ {prettyMilliseconds(duration, {colonNotation: true, secondsDecimalDigits: 0})}
75
+ </Text>
76
+ </DurationLine>
77
+ )}
78
+ </StyledBox>
79
+ )
80
+ }
81
+
82
+ export default AssetPreview
@@ -0,0 +1,45 @@
1
+ import {useCallback} from 'react'
2
+ import {Box, Flex, Button} from '@sanity/ui'
3
+ import {LaunchIcon} from '@sanity/icons'
4
+ import React from 'react'
5
+
6
+ interface Props {
7
+ title: string
8
+ shopifyDomain: string
9
+ }
10
+
11
+ const DialogHeader = (props: Props) => {
12
+ const {title, shopifyDomain} = props
13
+
14
+ const handleOpenInNewTab = useCallback(() => {
15
+ window.open(`https://${shopifyDomain}/admin/settings/files`, '_blank')
16
+ }, [shopifyDomain])
17
+
18
+ return (
19
+ <Flex align="center">
20
+ {title}
21
+ {/*
22
+ HACK: Sanity UI will attempt to focus the first 'focusable' descendant of any dialog.
23
+ Typically this is fine, but since our first focusable element is a button with a tooltip, this
24
+ default behaviour causes the tooltip to appear whenever the dialog is opened, which we don't want!
25
+
26
+ To get around this, we include a pseudo-hidden input to ensure our tooltip-enabled button remains
27
+ unfocused on initial mount.
28
+ */}
29
+ <input style={{opacity: 0}} tabIndex={-1} type="button" />
30
+ <Box style={{position: 'absolute', right: '-1.5em'}}>
31
+ <Box className="button-large">
32
+ <Button
33
+ fontSize={1}
34
+ icon={LaunchIcon}
35
+ mode="bleed"
36
+ onClick={handleOpenInNewTab}
37
+ text="Add New"
38
+ />
39
+ </Box>
40
+ </Box>
41
+ </Flex>
42
+ )
43
+ }
44
+
45
+ export default DialogHeader
@@ -0,0 +1,66 @@
1
+ import {Card, Theme} from '@sanity/ui'
2
+ import styled from 'styled-components'
3
+
4
+ type SanityTheme = Theme['sanity']
5
+
6
+ interface Style {
7
+ studioTheme: SanityTheme
8
+ }
9
+
10
+ export const Root = styled.div`
11
+ overflow: hidden;
12
+ background-origin: content-box;
13
+ background-repeat: no-repeat;
14
+ background-clip: border-box;
15
+ background-size: cover;
16
+ background-color: ${({studioTheme}: Style) => studioTheme.color.card.enabled.bg2};
17
+ position: relative;
18
+ outline: none !important;
19
+ border: ${({studioTheme}: Style) => `1px solid ${studioTheme.color.card.enabled.border}`};
20
+ box-sizing: content-box;
21
+ user-drag: none;
22
+
23
+ &:hover {
24
+ opacity: 0.85;
25
+ }
26
+
27
+ &:focus,
28
+ &:active {
29
+ border: 1px solid var(--input-border-color-focus);
30
+ box-shadow: inset 0 0 0 3px var(--input-border-color-focus);
31
+ }
32
+ `
33
+
34
+ export const InfoLine = styled(Card)`
35
+ ${({studioTheme}: Style) => `
36
+ --infoline-fg: ${studioTheme.color.card.enabled.fg};
37
+ --infoline-bg: ${studioTheme.color.card.enabled.bg};
38
+ `};
39
+ user-drag: none;
40
+ position: absolute;
41
+ background-color: var(--infoline-bg);
42
+ top: 0;
43
+ left: 0;
44
+ max-width: 65%;
45
+ overflow-wrap: break-word;
46
+
47
+ [data-ui='Text'] {
48
+ color: var(--infoline-fg);
49
+ }
50
+ `
51
+
52
+ export const DurationLine = styled(Card)`
53
+ ${({studioTheme}: Style) => `
54
+ --durationline-fg: ${studioTheme.color.card.enabled.bg};
55
+ --durationline-bg: ${studioTheme.color.card.enabled.fg};
56
+ `};
57
+ user-drag: none;
58
+ position: absolute;
59
+ background-color: var(--durationline-bg);
60
+ top: 0;
61
+ right: 0;
62
+
63
+ [data-ui='Text'] {
64
+ color: var(--durationline-fg);
65
+ }
66
+ `
@@ -0,0 +1,56 @@
1
+ import {Asset, ShopifyFile} from '../types'
2
+ import {DurationLine, InfoLine, Root} from './File.styled'
3
+ import React, {useCallback, useRef} from 'react'
4
+ import {Text, useTheme} from '@sanity/ui'
5
+
6
+ import {extractName} from '../utils/helpers'
7
+ import prettyBytes from 'pretty-bytes'
8
+ import prettyMilliseconds from 'pretty-ms'
9
+
10
+ type Props = {
11
+ data: ShopifyFile
12
+ width: number
13
+ height: number
14
+ onClick: (file: Asset) => void
15
+ }
16
+
17
+ export default function File(props: Props) {
18
+ const {onClick, data, width, height} = props
19
+ const rootElm = useRef<HTMLDivElement>(null)
20
+
21
+ const {preview, meta} = data
22
+ const filename = extractName(data.url)
23
+
24
+ const handleClick = useCallback(() => {
25
+ onClick({...data, filename})
26
+ }, [onClick, data, filename])
27
+
28
+ const theme = useTheme().sanity
29
+ return (
30
+ <Root
31
+ ref={rootElm}
32
+ studioTheme={theme}
33
+ title={`${filename}`}
34
+ tabIndex={0}
35
+ style={{
36
+ width: `${width}px`,
37
+ height: `${height}px`,
38
+ backgroundImage: `url("${preview?.url}")`,
39
+ }}
40
+ onClick={handleClick}
41
+ >
42
+ <InfoLine padding={2} radius={2} margin={2} studioTheme={theme}>
43
+ <Text size={1} title={`Select ${filename}`}>
44
+ {filename} {meta.fileSize && `(${prettyBytes(meta.fileSize)})`}
45
+ </Text>
46
+ </InfoLine>
47
+ {meta.duration && (
48
+ <DurationLine padding={2} radius={2} margin={2} studioTheme={theme}>
49
+ <Text size={1} title={`Video duration: ${filename}`}>
50
+ {prettyMilliseconds(meta.duration, {colonNotation: true, secondsDecimalDigits: 0})}
51
+ </Text>
52
+ </DurationLine>
53
+ )}
54
+ </Root>
55
+ )
56
+ }
@@ -0,0 +1,13 @@
1
+ import {Stack} from '@sanity/ui'
2
+ import styled from 'styled-components'
3
+
4
+ export const Search = styled(Stack)`
5
+ position: sticky;
6
+ top: 0;
7
+ z-index: 1;
8
+ `
9
+
10
+ export const Scroller = styled.div`
11
+ overflow-y: auto;
12
+ max-height: 80vh;
13
+ `
@@ -0,0 +1,84 @@
1
+ import {Button, Card, Flex, Grid, Inline, Stack, Text} from '@sanity/ui'
2
+ import {ObjectInputProps, PatchEvent, unset} from 'sanity'
3
+ import {useCallback, useState} from 'react'
4
+
5
+ import {Asset} from '../types'
6
+ import AssetPreview from './AssetPreview'
7
+ import {ErrorOutlineIcon} from '@sanity/icons'
8
+ import React from 'react'
9
+ import ShopifyAssetPicker from './ShopifyAssetPicker'
10
+ import ShopifyIcon from './ShopifyIcon'
11
+
12
+ export default function ShopifyAssetInput(props: ObjectInputProps) {
13
+ const {onChange, readOnly, value, schemaType} = props
14
+ const {options} = schemaType
15
+ const {shopifyDomain} = options
16
+
17
+ const [dialogOpen, setDialogOpen] = useState(false)
18
+
19
+ const removeValue = useCallback(() => {
20
+ onChange(PatchEvent.from([unset()]))
21
+ }, [onChange])
22
+
23
+ const onOpen = useCallback(() => {
24
+ setDialogOpen(true)
25
+ }, [setDialogOpen])
26
+
27
+ const onClose = useCallback(() => {
28
+ setDialogOpen(false)
29
+ }, [setDialogOpen])
30
+
31
+ if (!shopifyDomain) {
32
+ return (
33
+ <Card overflow="hidden" padding={4} radius={2} shadow={1} tone="critical">
34
+ <Flex align="center" gap={3}>
35
+ <Text size={2}>
36
+ <ErrorOutlineIcon />
37
+ </Text>
38
+ <Inline space={2}>
39
+ <Text size={1}>
40
+ You need to configure your *.myshopify.com domain in the plugin / field options to
41
+ enable Shopify Assets.
42
+ </Text>
43
+ </Inline>
44
+ </Flex>
45
+ </Card>
46
+ )
47
+ }
48
+
49
+ return (
50
+ <>
51
+ {dialogOpen && (
52
+ <ShopifyAssetPicker
53
+ {...props}
54
+ shopifyDomain={shopifyDomain}
55
+ isOpen={dialogOpen}
56
+ onClose={onClose}
57
+ value={value as Asset}
58
+ />
59
+ )}
60
+ <Stack>
61
+ <AssetPreview value={value as Asset} />
62
+
63
+ <Grid gap={1} style={{gridTemplateColumns: 'repeat(auto-fit, minmax(100px, 1fr))'}}>
64
+ <Button
65
+ disabled={readOnly}
66
+ mode="ghost"
67
+ icon={ShopifyIcon}
68
+ title="Select an asset"
69
+ onClick={onOpen}
70
+ text="Select…"
71
+ />
72
+ <Button
73
+ disabled={readOnly || !value}
74
+ tone="critical"
75
+ mode="ghost"
76
+ title="Remove asset"
77
+ text="Remove"
78
+ onClick={removeValue}
79
+ />
80
+ </Grid>
81
+ </Stack>
82
+ </>
83
+ )
84
+ }