sanity-plugin-shopify-assets 1.2.2 → 2.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.
@@ -1,207 +0,0 @@
1
- import {BehaviorSubject, Subscription} from 'rxjs'
2
- import {ErrorOutlineIcon} from '@sanity/icons'
3
- import {Card, Dialog, Flex, Inline, Spinner, Stack, Text, TextInput} from '@sanity/ui'
4
- import {PatchEvent, set, useProjectId, ObjectInputProps, useDataset, useClient} from 'sanity'
5
- import React, {useCallback, useEffect, useMemo, useState} from 'react'
6
- import PhotoAlbum from 'react-photo-album'
7
- import InfiniteScroll from 'react-infinite-scroll-component'
8
-
9
- import {search} from '../datastores/shopify'
10
- import type {Asset, PageInfo, ShopifyAPIResponse, ShopifyFile} from '../types'
11
- import DialogHeader from './DialogHeader'
12
- import File from './File'
13
- import {Search} from './ShopifyAssetInput.styled'
14
-
15
- const RESULTS_PER_PAGE = 42
16
- const PHOTO_SPACING = 2
17
- const PHOTO_PADDING = 1
18
-
19
- export interface AssetPickerProps extends ObjectInputProps<Asset> {
20
- shopifyDomain: string
21
- isOpen: boolean
22
- onClose: () => void
23
- }
24
-
25
- export default function ShopifyAssetPicker(props: AssetPickerProps) {
26
- const {isOpen, onClose, shopifyDomain, onChange, schemaType, value} = props
27
- const projectId = useProjectId()
28
- const dataset = useDataset()
29
- const client = useClient({apiVersion: '2021-06-07'})
30
- const token = client.config().token
31
-
32
- const [error, setError] = useState('')
33
- const [query, setQuery] = useState('')
34
- const [searchResults, setSearchResults] = useState<any[]>([])
35
- const [pageInfo, setPageInfo] = useState<PageInfo>()
36
- const [isLoading, setIsLoading] = useState(true)
37
-
38
- const searchSubject$ = useMemo(() => new BehaviorSubject(''), [])
39
- const cursorSubject$ = useMemo(() => new BehaviorSubject(''), [])
40
-
41
- useEffect(() => {
42
- if (!shopifyDomain) setError('Please configure your Shopify domain in the plugin config')
43
- }, [shopifyDomain])
44
-
45
- useEffect(() => {
46
- const searchSubscription: Subscription = search({
47
- projectId,
48
- dataset,
49
- shop: shopifyDomain,
50
- query: searchSubject$,
51
- cursor: cursorSubject$,
52
- resultsPerPage: RESULTS_PER_PAGE,
53
- token,
54
- }).subscribe({
55
- next: (results: ShopifyAPIResponse) => {
56
- setSearchResults((prevResults) => [...prevResults, ...results.assets])
57
- setPageInfo(results.pageInfo)
58
- setIsLoading(false)
59
- },
60
- error: (err) => {
61
- setError(
62
- `${
63
- err.response?.data?.message || err.message || 'An error occurred'
64
- } - check plugin configuration`
65
- )
66
- },
67
- })
68
-
69
- return () => searchSubscription.unsubscribe()
70
- }, [searchSubject$, cursorSubject$, shopifyDomain, projectId, dataset, token])
71
-
72
- const handleSearchTermChanged = useCallback(
73
- (event: React.ChangeEvent<HTMLInputElement>) => {
74
- const newQuery = event.currentTarget.value
75
- setQuery(newQuery)
76
- setSearchResults([])
77
- setPageInfo(undefined)
78
- setIsLoading(true)
79
-
80
- cursorSubject$.next('')
81
- searchSubject$.next(newQuery)
82
- },
83
- [cursorSubject$, searchSubject$]
84
- )
85
-
86
- const handleScollerLoadMore = useCallback(() => {
87
- setIsLoading(true)
88
- if (pageInfo) cursorSubject$.next(pageInfo.cursor)
89
- searchSubject$.next(query)
90
- }, [cursorSubject$, pageInfo, searchSubject$, query])
91
-
92
- const handleSelect = useCallback(
93
- (file: Asset) => {
94
- file._key = value?._key
95
- file._type = schemaType.name
96
- onChange(PatchEvent.from([set(file)]))
97
- onClose()
98
- },
99
- [onChange, onClose, schemaType.name, value?._key]
100
- )
101
-
102
- const renderFile = useCallback(
103
- (fileProps: any) => {
104
- const {photo, layout} = fileProps
105
- return (
106
- <File
107
- onClick={handleSelect}
108
- data={photo.data}
109
- width={layout.width}
110
- height={layout.height}
111
- />
112
- )
113
- },
114
- [handleSelect]
115
- )
116
-
117
- const handleWidth = useCallback((width: number) => {
118
- if (width < 300) return 150
119
- else if (width < 600) return 200
120
- return 300
121
- }, [])
122
-
123
- return (
124
- <Dialog
125
- id="shopify-asset-source"
126
- header={<DialogHeader title="Shopify Assets" shopifyDomain={shopifyDomain} />}
127
- onClose={onClose}
128
- open={isOpen}
129
- width={4}
130
- >
131
- <Stack space={3} padding={4}>
132
- {error ? (
133
- <Card overflow="hidden" padding={4} radius={2} shadow={1} tone="critical">
134
- <Flex align="center" gap={3}>
135
- <Text size={2}>
136
- <ErrorOutlineIcon />
137
- </Text>
138
- <Inline space={2}>
139
- <Text size={1}>{error}</Text>
140
- </Inline>
141
- </Flex>
142
- </Card>
143
- ) : (
144
- <>
145
- <Card>
146
- <Search space={3}>
147
- <Text size={1} weight="semibold">
148
- Search Shopify for assets
149
- </Text>
150
- <TextInput
151
- label="Search Images"
152
- placeholder="filename.jpg"
153
- value={query}
154
- onChange={handleSearchTermChanged}
155
- />
156
- </Search>
157
- </Card>
158
- {!isLoading && searchResults.length === 0 && (
159
- <Text size={1} muted>
160
- No results found
161
- </Text>
162
- )}
163
- <InfiniteScroll
164
- dataLength={searchResults.length} // This is important field to render the next data
165
- next={handleScollerLoadMore}
166
- hasMore={pageInfo ? pageInfo?.hasNextPage : true}
167
- scrollThreshold={0.99}
168
- height="60vh"
169
- loader={
170
- <Flex align="center" justify="center" padding={3}>
171
- <Spinner muted />
172
- </Flex>
173
- }
174
- endMessage={
175
- <Flex align="center" justify="center" padding={3}>
176
- <Text size={1} muted>
177
- No more results
178
- </Text>
179
- </Flex>
180
- }
181
- >
182
- {searchResults && (
183
- <PhotoAlbum
184
- layout="rows"
185
- spacing={PHOTO_SPACING}
186
- padding={PHOTO_PADDING}
187
- targetRowHeight={handleWidth}
188
- photos={searchResults.map((file: ShopifyFile) => ({
189
- src: file?.preview?.url,
190
- width: file?.preview?.width || 2048,
191
- height: file?.preview?.height || 2048,
192
- key: file.id,
193
- data: file,
194
- }))}
195
- renderPhoto={renderFile}
196
- componentsProps={{
197
- containerProps: {style: {marginBottom: `${PHOTO_SPACING}px`}},
198
- }}
199
- />
200
- )}
201
- </InfiniteScroll>
202
- </>
203
- )}
204
- </Stack>
205
- </Dialog>
206
- )
207
- }
@@ -1,22 +0,0 @@
1
- import React from 'react'
2
-
3
- const ShopifyIcon = () => {
4
- return (
5
- <svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
6
- <path
7
- d="M15.3269 3.85113C15.3132 3.75015 15.2258 3.69411 15.1531 3.688C15.081 3.6819 13.6693 3.66026 13.6693 3.66026C13.6693 3.66026 12.4887 2.50392 12.3722 2.38628C12.2555 2.26865 12.0277 2.30417 11.9392 2.3308C11.9381 2.33135 11.7175 2.40016 11.3461 2.51612C11.2839 2.31304 11.1927 2.06335 11.0622 1.81255C10.6419 1.00356 10.0263 0.575752 9.2825 0.574642C9.28142 0.574642 9.28092 0.574642 9.27975 0.574642C9.22808 0.574642 9.17692 0.579636 9.12517 0.584074C9.10317 0.557441 9.08117 0.531362 9.05808 0.505838C8.73408 0.156272 8.31869 -0.0140727 7.82082 0.000908712C6.86027 0.0286521 5.90357 0.72834 5.12787 1.97124C4.58212 2.84572 4.16677 3.94435 4.04904 4.79497C2.94601 5.13953 2.17471 5.38035 2.15766 5.3859C1.60091 5.56235 1.58331 5.57955 1.51069 6.10889C1.45677 6.50895 0 17.8704 0 17.8704L12.2082 20L17.4994 18.6733C17.4994 18.6733 15.3407 3.95212 15.3269 3.85113ZM10.7349 2.707C10.4537 2.79467 10.1342 2.89454 9.78758 3.00274C9.78042 2.51224 9.72267 1.82975 9.496 1.23992C10.2249 1.3792 10.5836 2.21095 10.7349 2.707ZM9.14883 3.20249C8.509 3.40225 7.81091 3.62031 7.11058 3.83892C7.30753 3.0782 7.68107 2.32081 8.13989 1.8242C8.31044 1.63943 8.54917 1.43358 8.832 1.31594C9.09767 1.87525 9.15542 2.66705 9.14883 3.20249ZM7.84007 0.645665C8.06562 0.640671 8.25542 0.690609 8.41775 0.798253C8.15805 0.9342 7.90718 1.12951 7.67172 1.38419C7.06162 2.04448 6.594 3.06932 6.4075 4.0581C5.826 4.23954 5.25715 4.41766 4.73342 4.58078C5.06405 3.02438 6.35743 0.688944 7.84007 0.645665Z"
8
- fill="#95BF47"
9
- />
10
- <path
11
- d="M9.276 6.43238L8.66142 8.75117C8.66142 8.75117 7.97598 8.43658 7.16342 8.48817C5.97181 8.56417 5.95916 9.32217 5.97126 9.51242C6.03618 10.5495 8.74125 10.7759 8.89308 13.2051C9.01242 15.1161 7.88796 16.4233 6.26779 16.5265C4.32303 16.6502 3.25246 15.4933 3.25246 15.4933L3.66452 13.7256C3.66452 13.7256 4.74224 14.5457 5.60487 14.4907C6.16821 14.4547 6.36957 13.9924 6.34921 13.6657C6.26448 12.3128 4.06172 12.3927 3.92253 10.17C3.80536 8.29951 5.02337 6.40408 7.71081 6.23318C8.74617 6.16604 9.276 6.43238 9.276 6.43238Z"
12
- fill="white"
13
- />
14
- <path
15
- d="M15.1536 3.68853C15.0815 3.68243 13.6698 3.66078 13.6698 3.66078C13.6698 3.66078 12.4893 2.50444 12.3726 2.38681C12.3292 2.34298 12.2703 2.32023 12.2087 2.31079L12.2093 19.9994L17.4999 18.6733C17.4999 18.6733 15.3412 3.95264 15.3274 3.85166C15.3137 3.75068 15.2257 3.69463 15.1536 3.68853Z"
16
- fill="#5E8E3E"
17
- />
18
- </svg>
19
- )
20
- }
21
-
22
- export default ShopifyIcon
@@ -1,50 +0,0 @@
1
- import React, {type CSSProperties, type MouseEvent, useCallback, useEffect} from 'react'
2
- import videojs, {type VideoJsPlayer} from 'video.js'
3
-
4
- type PlayerKind = 'player' | 'diff'
5
-
6
- interface VideoProps {
7
- src: string
8
- kind: PlayerKind
9
- }
10
-
11
- const VideoPlayer = ({src, kind}: VideoProps) => {
12
- const videoNode = React.useRef<HTMLVideoElement>(null)
13
- const player = React.useRef<VideoJsPlayer>()
14
-
15
- useEffect(() => {
16
- player.current = videojs(videoNode.current ?? '', {
17
- sources: [{src}],
18
- controls: true,
19
- })
20
-
21
- player.current.src({src})
22
- }, [src])
23
-
24
- const stopPropagation = useCallback((event: MouseEvent) => {
25
- event.stopPropagation()
26
- }, [])
27
-
28
- const className: Record<PlayerKind, string> = {
29
- player: 'video-js vjs-16-9 vjs-big-play-centered',
30
- diff: 'video-js vjs-layout-tiny vjs-fluid',
31
- }
32
-
33
- const style: CSSProperties = {position: 'relative'}
34
-
35
- return (
36
- <div>
37
- <link href="https://vjs.zencdn.net/7.8.4/video-js.css" rel="stylesheet" />
38
- <div data-vjs-player>
39
- <video
40
- onClick={stopPropagation}
41
- style={kind === 'diff' ? style : {}}
42
- className={className[kind]}
43
- ref={videoNode}
44
- />
45
- </div>
46
- </div>
47
- )
48
- }
49
-
50
- export default VideoPlayer
@@ -1,93 +0,0 @@
1
- import {BehaviorSubject, Observable, concat, defer} from 'rxjs'
2
- import {debounceTime, distinctUntilChanged, map, switchMap, withLatestFrom} from 'rxjs/operators'
3
-
4
- import axios from 'axios'
5
-
6
- type SearchSubject = BehaviorSubject<string>
7
- type CursorSubject = BehaviorSubject<any>
8
-
9
- interface fetchProps {
10
- projectId: string
11
- dataset: string
12
- shop: string
13
- query: SearchSubject
14
- cursor: CursorSubject
15
- resultsPerPage: number
16
- token?: string
17
- }
18
-
19
- interface searchProps extends Omit<fetchProps, 'query' | 'cursor'> {
20
- query: string
21
- cursor: string
22
- }
23
- interface listProps extends Omit<fetchProps, 'query' | 'cursor'> {
24
- cursor: string
25
- }
26
-
27
- const fetchSearch = (props: searchProps): Observable<any> => {
28
- const {projectId, dataset, shop, query, cursor, resultsPerPage, token} = props
29
-
30
- return defer(() => {
31
- return axios.get(
32
- `https://${projectId}.api.sanity.io/v1/shopify/assets/${dataset}?shop=${shop}&query=${encodeURIComponent(
33
- query
34
- )}${cursor && `&cursor=${cursor}`}&limit=${resultsPerPage}`,
35
- {
36
- withCredentials: true,
37
- method: 'GET',
38
- headers: token
39
- ? {
40
- Authorization: `Bearer ${token}`,
41
- }
42
- : {},
43
- }
44
- )
45
- }).pipe(map((result) => result.data))
46
- }
47
-
48
- const fetchList = (props: listProps): Observable<any> => {
49
- const {projectId, dataset, shop, cursor, resultsPerPage, token} = props
50
-
51
- return defer(() =>
52
- axios.get(
53
- `https://${projectId}.api.sanity.io/v1/shopify/assets/${dataset}?shop=${shop}${
54
- cursor && `&cursor=${cursor}`
55
- }&limit=${resultsPerPage}`,
56
- {
57
- withCredentials: true,
58
- method: 'GET',
59
- headers: token
60
- ? {
61
- Authorization: `Bearer ${token}`,
62
- }
63
- : {},
64
- }
65
- )
66
- ).pipe(map((result) => result.data))
67
- }
68
-
69
- export const search = (props: fetchProps): Observable<any> => {
70
- const {projectId, dataset, shop, query, cursor, resultsPerPage, token} = props
71
-
72
- return concat(
73
- query.pipe(
74
- withLatestFrom(cursor),
75
- debounceTime(500),
76
- distinctUntilChanged(),
77
- switchMap(([q, c]) => {
78
- if (q) {
79
- return fetchSearch({
80
- projectId,
81
- dataset,
82
- shop,
83
- query: q,
84
- cursor: c,
85
- resultsPerPage,
86
- token,
87
- }).pipe(distinctUntilChanged())
88
- }
89
- return fetchList({projectId, dataset, shop, cursor: c, resultsPerPage, token})
90
- })
91
- )
92
- )
93
- }
package/src/index.ts DELETED
@@ -1,33 +0,0 @@
1
- import {definePlugin, type ObjectDefinition} from 'sanity'
2
- import {shopifyAssetSchema} from './schema/shopifyAssetSchema'
3
- import {shopifyAssetPreviewSchema} from './schema/shopifyAssetPreviewSchema'
4
- import {shopifyAssetMetadataSchema} from './schema/shopifyAssetMetadataSchema'
5
- import type {PluginConfig} from './types'
6
-
7
- export * from './types'
8
-
9
- // enables autocompletion and validation of document options
10
- declare module 'sanity' {
11
- export namespace Schema {
12
- // here we type up our custom schema definition
13
- export type ShopifyAssetTypeDef = Omit<ObjectDefinition, 'type' | 'fields'> & {
14
- type: 'shopify.asset'
15
- options: {
16
- shopifyDomain: string
17
- }
18
- }
19
- // Adds 'extension-type' as an intrinsic type
20
- export interface IntrinsicTypeDefinition {
21
- 'shopify.asset': ShopifyAssetTypeDef
22
- }
23
- }
24
- }
25
-
26
- export const shopifyAssets = definePlugin<PluginConfig>((config) => {
27
- return {
28
- name: 'shopify-asset-schema',
29
- schema: {
30
- types: [shopifyAssetPreviewSchema, shopifyAssetMetadataSchema, shopifyAssetSchema(config)],
31
- },
32
- }
33
- })
@@ -1,6 +0,0 @@
1
- import {type Theme} from '@sanity/ui'
2
-
3
- declare module 'styled-components' {
4
- // eslint-disable-next-line
5
- interface DefaultTheme extends Theme {}
6
- }
@@ -1,34 +0,0 @@
1
- import {defineField, defineType} from 'sanity'
2
-
3
- export const shopifyAssetMetadataSchema = defineType({
4
- type: 'object',
5
- name: 'shopify.assetMetadata',
6
- title: 'Asset metadata',
7
- fields: [
8
- defineField({
9
- type: 'string',
10
- name: 'alt',
11
- title: 'Alternative text',
12
- }),
13
- defineField({
14
- type: 'number',
15
- name: 'duration',
16
- title: 'Duration',
17
- }),
18
- defineField({
19
- type: 'number',
20
- name: 'fileSize',
21
- title: 'File size',
22
- }),
23
- defineField({
24
- type: 'number',
25
- name: 'height',
26
- title: 'Height',
27
- }),
28
- defineField({
29
- type: 'number',
30
- name: 'width',
31
- title: 'Width',
32
- }),
33
- ],
34
- })
@@ -1,24 +0,0 @@
1
- import {defineField, defineType} from 'sanity'
2
-
3
- export const shopifyAssetPreviewSchema = defineType({
4
- type: 'object',
5
- name: 'shopify.assetPreview',
6
- title: 'Asset preview',
7
- fields: [
8
- defineField({
9
- type: 'number',
10
- name: 'height',
11
- title: 'Height',
12
- }),
13
- defineField({
14
- type: 'number',
15
- name: 'width',
16
- title: 'Width',
17
- }),
18
- defineField({
19
- type: 'url',
20
- name: 'url',
21
- title: 'URL',
22
- }),
23
- ],
24
- })
@@ -1,88 +0,0 @@
1
- /* eslint-disable */
2
- import ShopifyAssetInput from '../components/ShopifyAssetInput'
3
- import AssetDiff from '../components/AssetDiff'
4
- import AssetPreview from '../components/AssetPreview'
5
- import {defineField, defineType} from 'sanity'
6
-
7
- interface ObjectConfig {
8
- shopifyDomain: string
9
- }
10
-
11
- declare module 'sanity' {
12
- interface ObjectOptions {
13
- shopifyDomain?: string
14
- }
15
- }
16
-
17
- export const shopifyAssetSchema = (config: ObjectConfig) => {
18
- const {shopifyDomain} = config
19
-
20
- return defineType({
21
- type: 'object',
22
- name: 'shopify.asset',
23
- title: 'Shopify Asset',
24
- options: {
25
- shopifyDomain,
26
- },
27
- fields: [
28
- defineField({
29
- type: 'string',
30
- name: 'filename',
31
- title: 'Filename',
32
- }),
33
- defineField({
34
- type: 'string',
35
- name: 'id',
36
- title: 'ID',
37
- }),
38
- defineField({
39
- type: 'shopify.assetMetadata',
40
- name: 'meta',
41
- title: 'Metadata',
42
- }),
43
- defineField({
44
- type: 'shopify.assetPreview',
45
- name: 'preview',
46
- title: 'Preview',
47
- }),
48
- defineField({
49
- type: 'string',
50
- name: 'type',
51
- title: 'Type',
52
- }),
53
- defineField({
54
- type: 'url',
55
- name: 'url',
56
- title: 'URL',
57
- }),
58
- ],
59
- ...({
60
- components: {
61
- input: ShopifyAssetInput,
62
- diff: AssetDiff,
63
- preview: AssetPreview,
64
- },
65
- } as {}),
66
- preview: {
67
- select: {
68
- meta: 'meta',
69
- preview: 'preview',
70
- url: 'url',
71
- filename: 'filename',
72
- type: 'type',
73
- },
74
- prepare({url, meta, preview, filename, type}) {
75
- return {
76
- title: filename,
77
- subtitle: type,
78
- value: {
79
- url,
80
- meta,
81
- preview,
82
- filename,
83
- },
84
- }
85
- },
86
- },
87
- })
88
- }
package/src/types.ts DELETED
@@ -1,54 +0,0 @@
1
- import type {ObjectSchemaType} from 'sanity'
2
-
3
- export interface PluginConfig {
4
- /**
5
- * Your *.myshopify.com domain. Do not include https:// or any path.
6
- */
7
- shopifyDomain: string
8
- }
9
-
10
- export interface ObjectSchemaWithOptions extends ObjectSchemaType {
11
- options: {
12
- shopifyDomain: string
13
- }
14
- }
15
-
16
- type PossibleFileTypes = 'file' | 'image' | 'video'
17
-
18
- export interface AssetPreviewImage {
19
- height: number
20
- url: string
21
- width: number
22
- }
23
-
24
- export interface AssetMeta {
25
- alt?: string
26
- duration?: number
27
- fileSize?: number
28
- height?: number
29
- width?: number
30
- }
31
-
32
- export interface ShopifyFile {
33
- meta: AssetMeta
34
- preview: AssetPreviewImage
35
- id: string
36
- type: PossibleFileTypes
37
- url: string
38
- }
39
-
40
- export interface Asset extends ShopifyFile {
41
- _type?: string
42
- _key?: string
43
- filename?: string
44
- }
45
-
46
- export interface PageInfo {
47
- hasNextPage: boolean
48
- cursor: string
49
- }
50
-
51
- export interface ShopifyAPIResponse {
52
- pageInfo: PageInfo
53
- assets: ShopifyFile[]
54
- }
@@ -1 +0,0 @@
1
- export const extractName = (name: string): string => name?.split('/')?.pop()?.split('?')[0] ?? ''
@@ -1,11 +0,0 @@
1
- const {showIncompatiblePluginDialog} = require('@sanity/incompatible-plugin')
2
- const {name, version, sanityExchangeUrl} = require('./package.json')
3
-
4
- export default showIncompatiblePluginDialog({
5
- name: name,
6
- versions: {
7
- v3: version,
8
- v2: undefined,
9
- },
10
- sanityExchangeUrl,
11
- })