sanity-plugin-media 4.3.6 → 5.0.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.
Files changed (162) hide show
  1. package/package.json +8 -17
  2. package/dist/index.cjs +0 -4721
  3. package/dist/index.cjs.map +0 -1
  4. package/dist/index.d.cts +0 -239
  5. package/dist/index.d.cts.map +0 -1
  6. package/sanity.json +0 -8
  7. package/src/__tests__/fixtures/createEpicTestStore.ts +0 -28
  8. package/src/__tests__/fixtures/listenMock.ts +0 -9
  9. package/src/__tests__/fixtures/mockSanityClient.ts +0 -84
  10. package/src/__tests__/fixtures/renderWithProviders.tsx +0 -55
  11. package/src/__tests__/fixtures/rootState.ts +0 -27
  12. package/src/__tests__/fixtures/withinDialog.ts +0 -28
  13. package/src/components/AssetGridVirtualized/index.tsx +0 -94
  14. package/src/components/AssetMetadata/index.tsx +0 -122
  15. package/src/components/AssetTableVirtualized/index.tsx +0 -73
  16. package/src/components/AutoTagInputWrapper/index.tsx +0 -85
  17. package/src/components/Browser/Browser.test.tsx +0 -45
  18. package/src/components/Browser/index.tsx +0 -90
  19. package/src/components/Browser/useBrowserInit.ts +0 -126
  20. package/src/components/ButtonAssetCopy/index.tsx +0 -65
  21. package/src/components/ButtonViewGroup/index.tsx +0 -39
  22. package/src/components/CardAsset/CardAsset.test.tsx +0 -323
  23. package/src/components/CardAsset/index.tsx +0 -290
  24. package/src/components/CardUpload/index.tsx +0 -161
  25. package/src/components/Controls/index.tsx +0 -136
  26. package/src/components/DebugControls/index.tsx +0 -80
  27. package/src/components/Dialog/index.tsx +0 -11
  28. package/src/components/DialogAssetEdit/Details.tsx +0 -181
  29. package/src/components/DialogAssetEdit/DialogAssetEdit.test.tsx +0 -216
  30. package/src/components/DialogAssetEdit/index.tsx +0 -493
  31. package/src/components/DialogConfirm/index.tsx +0 -90
  32. package/src/components/DialogSearchFacets/index.tsx +0 -42
  33. package/src/components/DialogTagCreate/DialogTagCreate.test.tsx +0 -121
  34. package/src/components/DialogTagCreate/index.tsx +0 -111
  35. package/src/components/DialogTagEdit/DialogTagEdit.test.tsx +0 -165
  36. package/src/components/DialogTagEdit/index.tsx +0 -201
  37. package/src/components/DialogTags/index.tsx +0 -45
  38. package/src/components/Dialogs/index.tsx +0 -76
  39. package/src/components/DocumentList/index.tsx +0 -62
  40. package/src/components/FileAssetPreview/index.tsx +0 -37
  41. package/src/components/FileIcon/index.tsx +0 -43
  42. package/src/components/FormBuilderTool/FormBuilderTool.test.tsx +0 -63
  43. package/src/components/FormBuilderTool/index.tsx +0 -69
  44. package/src/components/FormFieldInputLabel/index.tsx +0 -66
  45. package/src/components/FormFieldInputTags/index.tsx +0 -98
  46. package/src/components/FormFieldInputText/index.tsx +0 -41
  47. package/src/components/FormFieldInputTextarea/index.tsx +0 -43
  48. package/src/components/FormSubmitButton/index.tsx +0 -59
  49. package/src/components/Header/index.tsx +0 -80
  50. package/src/components/Image/index.tsx +0 -41
  51. package/src/components/Items/index.tsx +0 -68
  52. package/src/components/Notifications/index.tsx +0 -24
  53. package/src/components/OrderSelect/index.tsx +0 -66
  54. package/src/components/PickedBar/index.tsx +0 -77
  55. package/src/components/Progress/index.tsx +0 -38
  56. package/src/components/ReduxProvider/index.tsx +0 -96
  57. package/src/components/SearchFacet/index.tsx +0 -66
  58. package/src/components/SearchFacetNumber/index.tsx +0 -133
  59. package/src/components/SearchFacetSelect/index.tsx +0 -110
  60. package/src/components/SearchFacetString/index.tsx +0 -88
  61. package/src/components/SearchFacetTags/index.tsx +0 -121
  62. package/src/components/SearchFacets/index.tsx +0 -72
  63. package/src/components/SearchFacetsControl/index.tsx +0 -140
  64. package/src/components/TableHeader/index.tsx +0 -110
  65. package/src/components/TableHeaderItem/index.tsx +0 -61
  66. package/src/components/TableRowAsset/index.tsx +0 -419
  67. package/src/components/TableRowUpload/index.tsx +0 -164
  68. package/src/components/Tag/index.tsx +0 -200
  69. package/src/components/TagIcon/index.tsx +0 -22
  70. package/src/components/TagView/index.tsx +0 -39
  71. package/src/components/TagViewHeader/index.tsx +0 -70
  72. package/src/components/TagsPanel/index.tsx +0 -40
  73. package/src/components/TagsVirtualized/index.tsx +0 -160
  74. package/src/components/TextInputNumber/index.tsx +0 -32
  75. package/src/components/TextInputSearch/index.tsx +0 -60
  76. package/src/components/Tool/index.tsx +0 -13
  77. package/src/components/UploadDropzone/UploadDropzone.test.tsx +0 -40
  78. package/src/components/UploadDropzone/index.tsx +0 -173
  79. package/src/config/orders.ts +0 -28
  80. package/src/config/searchFacets.ts +0 -312
  81. package/src/constants.ts +0 -87
  82. package/src/contexts/AssetSourceDispatchContext.tsx +0 -38
  83. package/src/contexts/DropzoneDispatchContext.tsx +0 -32
  84. package/src/contexts/ToolOptionsContext.tsx +0 -66
  85. package/src/formSchema/index.test.ts +0 -56
  86. package/src/formSchema/index.ts +0 -39
  87. package/src/hooks/useBreakpointIndex.ts +0 -50
  88. package/src/hooks/useKeyPress.ts +0 -39
  89. package/src/hooks/usePortalPopoverProps.ts +0 -13
  90. package/src/hooks/useTypedSelector.ts +0 -7
  91. package/src/hooks/useVersionedClient.ts +0 -6
  92. package/src/index.ts +0 -5
  93. package/src/modules/assets/actions.ts +0 -42
  94. package/src/modules/assets/deleteAndUpdateEpics.test.ts +0 -87
  95. package/src/modules/assets/fetchEpic.test.ts +0 -73
  96. package/src/modules/assets/index.ts +0 -782
  97. package/src/modules/assets/reducer.test.ts +0 -91
  98. package/src/modules/assets/tagsAndListenerEpics.test.ts +0 -206
  99. package/src/modules/debug/index.ts +0 -28
  100. package/src/modules/dialog/actions.ts +0 -10
  101. package/src/modules/dialog/epics.test.ts +0 -168
  102. package/src/modules/dialog/index.ts +0 -238
  103. package/src/modules/dialog/reducer.test.ts +0 -185
  104. package/src/modules/index.ts +0 -117
  105. package/src/modules/notifications/epics.test.ts +0 -374
  106. package/src/modules/notifications/index.ts +0 -199
  107. package/src/modules/notifications/reducer.test.ts +0 -54
  108. package/src/modules/search/index.test.ts +0 -36
  109. package/src/modules/search/index.ts +0 -167
  110. package/src/modules/selected/index.ts +0 -22
  111. package/src/modules/selectors.test.ts +0 -21
  112. package/src/modules/selectors.ts +0 -17
  113. package/src/modules/tags/epics.test.ts +0 -96
  114. package/src/modules/tags/index.test.ts +0 -42
  115. package/src/modules/tags/index.ts +0 -540
  116. package/src/modules/types.ts +0 -3
  117. package/src/modules/uploads/actions.ts +0 -13
  118. package/src/modules/uploads/epics.test.ts +0 -109
  119. package/src/modules/uploads/index.test.ts +0 -59
  120. package/src/modules/uploads/index.ts +0 -272
  121. package/src/operators/checkTagName.test.ts +0 -29
  122. package/src/operators/checkTagName.ts +0 -33
  123. package/src/operators/debugThrottle.ts +0 -25
  124. package/src/plugin.tsx +0 -54
  125. package/src/schemas/tag.ts +0 -28
  126. package/src/styled/GlobalStyles/index.tsx +0 -40
  127. package/src/styled/react-select/creatable.tsx +0 -184
  128. package/src/styled/react-select/single.tsx +0 -184
  129. package/src/types/index.ts +0 -346
  130. package/src/types/sanity-ui.d.ts +0 -5
  131. package/src/utils/applyMediaTags.ts +0 -87
  132. package/src/utils/blocksToText.test.ts +0 -43
  133. package/src/utils/blocksToText.ts +0 -27
  134. package/src/utils/constructFilter.test.ts +0 -120
  135. package/src/utils/constructFilter.ts +0 -98
  136. package/src/utils/generatePreviewBlobUrl.test.ts +0 -68
  137. package/src/utils/generatePreviewBlobUrl.ts +0 -53
  138. package/src/utils/getAssetResolution.test.ts +0 -13
  139. package/src/utils/getAssetResolution.ts +0 -7
  140. package/src/utils/getDocumentAssetIds.test.ts +0 -50
  141. package/src/utils/getDocumentAssetIds.ts +0 -35
  142. package/src/utils/getSchemeColor.test.ts +0 -12
  143. package/src/utils/getSchemeColor.ts +0 -43
  144. package/src/utils/getTagSelectOptions.test.ts +0 -44
  145. package/src/utils/getTagSelectOptions.ts +0 -16
  146. package/src/utils/getUniqueDocuments.test.ts +0 -26
  147. package/src/utils/getUniqueDocuments.ts +0 -15
  148. package/src/utils/imageDprUrl.test.ts +0 -46
  149. package/src/utils/imageDprUrl.ts +0 -27
  150. package/src/utils/isSupportedAssetType.test.ts +0 -16
  151. package/src/utils/isSupportedAssetType.ts +0 -15
  152. package/src/utils/mediaField.ts +0 -73
  153. package/src/utils/sanitizeFormData.test.ts +0 -59
  154. package/src/utils/sanitizeFormData.ts +0 -26
  155. package/src/utils/typeGuards.test.ts +0 -18
  156. package/src/utils/typeGuards.ts +0 -9
  157. package/src/utils/uploadSanityAsset.test.ts +0 -29
  158. package/src/utils/uploadSanityAsset.ts +0 -97
  159. package/src/utils/withMaxConcurrency.test.ts +0 -43
  160. package/src/utils/withMaxConcurrency.ts +0 -55
  161. package/src/utils/zodFormResolver.ts +0 -17
  162. package/v2-incompatible.js +0 -11
@@ -1,122 +0,0 @@
1
- import {DownloadIcon} from '@sanity/icons'
2
- import {Box, Button, Flex, Inline, Stack, Text} from '@sanity/ui'
3
- import {format} from 'date-fns'
4
- import filesize from 'filesize'
5
- import {type ReactNode} from 'react'
6
-
7
- import type {Asset, AssetItem} from '../../types'
8
- import getAssetResolution from '../../utils/getAssetResolution'
9
- import {isImageAsset} from '../../utils/typeGuards'
10
- import ButtonAssetCopy from '../ButtonAssetCopy'
11
-
12
- type Props = {
13
- asset: Asset
14
- item?: AssetItem
15
- }
16
-
17
- const Row = ({label, value}: {label: string; value: ReactNode}) => {
18
- return (
19
- <Flex justify="space-between">
20
- <Text
21
- size={1}
22
- style={{
23
- opacity: 0.8,
24
- width: '40%',
25
- }}
26
- textOverflow="ellipsis"
27
- >
28
- {label}
29
- </Text>
30
- <Text
31
- size={1}
32
- style={{
33
- opacity: 0.4,
34
- textAlign: 'right',
35
- width: '60%',
36
- }}
37
- textOverflow="ellipsis"
38
- >
39
- {value}
40
- </Text>
41
- </Flex>
42
- )
43
- }
44
-
45
- const AssetMetadata = (props: Props) => {
46
- const {asset, item} = props
47
-
48
- const exif = asset?.metadata?.exif
49
-
50
- // Callbacks
51
- const handleDownload = () => {
52
- window.location.href = `${asset.url}?dl=${asset.originalFilename}`
53
- }
54
-
55
- return (
56
- <Box marginTop={3}>
57
- {/* Base */}
58
- <Box>
59
- <Stack space={3}>
60
- <Row label="Size" value={filesize(asset?.size, {base: 10, round: 0})} />
61
- <Row label="MIME type" value={asset?.mimeType} />
62
- <Row label="Extension" value={asset?.extension?.toUpperCase()} />
63
- {isImageAsset(asset) && <Row label="Dimensions" value={getAssetResolution(asset)} />}
64
- </Stack>
65
- </Box>
66
- {/* EXIF */}
67
- {exif &&
68
- (exif.DateTimeOriginal ||
69
- exif.FNumber ||
70
- exif.FocalLength ||
71
- exif.ExposureTime ||
72
- exif.ISO) && (
73
- <>
74
- {/* Divider */}
75
- <Box
76
- marginY={4}
77
- style={{
78
- background: 'var(--card-border-color)',
79
- height: '1px',
80
- width: '100%',
81
- }}
82
- />
83
- <Box>
84
- <Stack space={3}>
85
- {exif.ISO && <Row label="ISO" value={exif.ISO} />}
86
- {exif.FNumber && <Row label="Aperture" value={`ƒ/${exif.FNumber}`} />}
87
- {exif.FocalLength && <Row label="Focal length" value={`${exif.FocalLength}mm`} />}
88
- {exif.ExposureTime && (
89
- <Row label="Exposure time" value={`1/${1 / exif.ExposureTime}`} />
90
- )}
91
- {exif.DateTimeOriginal && (
92
- <Row
93
- label="Original date"
94
- value={format(new Date(exif.DateTimeOriginal), 'PPp')}
95
- />
96
- )}
97
- </Stack>
98
- </Box>
99
- </>
100
- )}
101
-
102
- {/* Asset actions */}
103
- <Box marginTop={5}>
104
- <Inline space={2}>
105
- {/* Download */}
106
- <Button
107
- disabled={!item || item?.updating}
108
- fontSize={1}
109
- icon={DownloadIcon}
110
- mode="ghost"
111
- onClick={handleDownload}
112
- text="Download"
113
- />
114
- {/* Copy to clipboard */}
115
- <ButtonAssetCopy disabled={!item || item?.updating} url={asset.url} />
116
- </Inline>
117
- </Box>
118
- </Box>
119
- )
120
- }
121
-
122
- export default AssetMetadata
@@ -1,73 +0,0 @@
1
- import {Box} from '@sanity/ui'
2
- import {memo} from 'react'
3
- import {GroupedVirtuoso} from 'react-virtuoso'
4
-
5
- import useTypedSelector from '../../hooks/useTypedSelector'
6
- import type {CardAssetData, CardUploadData} from '../../types'
7
- import TableHeader from '../TableHeader'
8
- import TableRowAsset from '../TableRowAsset'
9
- import TableRowUpload from '../TableRowUpload'
10
-
11
- type Props = {
12
- items: (CardAssetData | CardUploadData)[]
13
- onLoadMore?: () => void
14
- }
15
-
16
- const VirtualRow = memo(
17
- ({item, selected}: {item: CardAssetData | CardUploadData; selected: boolean}) => {
18
- if (item?.type === 'asset') {
19
- return (
20
- <Box style={{height: '100px'}}>
21
- <TableRowAsset id={item.id} selected={selected} />
22
- </Box>
23
- )
24
- }
25
-
26
- if (item?.type === 'upload') {
27
- return (
28
- <Box style={{height: '100px'}}>
29
- <TableRowUpload id={item.id} />
30
- </Box>
31
- )
32
- }
33
-
34
- return null
35
- },
36
- )
37
-
38
- const AssetTableVirtualized = (props: Props) => {
39
- const {items, onLoadMore} = props
40
-
41
- // Redux
42
- const selectedAssets = useTypedSelector((state) => state.selected.assets)
43
-
44
- const selectedIds = (selectedAssets && selectedAssets.map((asset) => asset._id)) || []
45
- const totalCount = items?.length
46
-
47
- if (totalCount === 0) {
48
- return null
49
- }
50
-
51
- return (
52
- <GroupedVirtuoso
53
- className="media__custom-scrollbar"
54
- computeItemKey={(index) => {
55
- const item = items[index]
56
- return item?.id || index
57
- }}
58
- endReached={onLoadMore}
59
- groupCounts={Array(1).fill(totalCount)}
60
- groupContent={() => {
61
- return <TableHeader />
62
- }}
63
- itemContent={(index) => {
64
- const item = items[index]
65
- const selected = selectedIds.includes(item?.id || '')
66
- return <VirtualRow item={item!} selected={selected} />
67
- }}
68
- style={{overflowX: 'hidden'}}
69
- />
70
- )
71
- }
72
-
73
- export default AssetTableVirtualized
@@ -1,85 +0,0 @@
1
- import {useToast} from '@sanity/ui'
2
- import {useEffect, useRef} from 'react'
3
- import {type InputProps} from 'sanity'
4
-
5
- import {useToolOptions} from '../../contexts/ToolOptionsContext'
6
- import useVersionedClient from '../../hooks/useVersionedClient'
7
- import {applyMediaTags} from '../../utils/applyMediaTags'
8
-
9
- type AssetValue = {
10
- _type: 'image' | 'file'
11
- asset?: {
12
- _ref: string
13
- _type: 'reference'
14
- }
15
- }
16
-
17
- export type AutoTagInputProps = InputProps & {
18
- mediaTags?: string[]
19
- }
20
-
21
- /**
22
- * Input component that automatically applies media tags when an asset is selected or uploaded.
23
- *
24
- * Apply explicitly to image/file fields that should be auto-tagged:
25
- * ```ts
26
- * import {AutoTagInput} from 'sanity-plugin-media'
27
- *
28
- * defineField({
29
- * type: 'image',
30
- * options: { mediaTags: ['product'] }, // also pre-filters the media browser
31
- * components: { input: AutoTagInput },
32
- * })
33
- * ```
34
- *
35
- * Pass `mediaTags` as a prop to override or use without `options`:
36
- * ```ts
37
- * components: { input: (props) => <AutoTagInput {...props} mediaTags={['product']} /> }
38
- * ```
39
- */
40
- export function AutoTagInput(props: AutoTagInputProps) {
41
- const {renderDefault, schemaType, value, mediaTags: mediaTagsProp} = props
42
- const toast = useToast()
43
-
44
- // Prop takes precedence; fall back to schemaType.options.mediaTags (set for browser pre-filtering)
45
- const mediaTags =
46
- mediaTagsProp ?? (schemaType?.options as {mediaTags?: string[]} | undefined)?.mediaTags
47
-
48
- const client = useVersionedClient()
49
- const {createTagsOnUpload} = useToolOptions()
50
-
51
- const prevAssetRef = useRef<string | undefined>(undefined)
52
- const isInitialMount = useRef(true)
53
-
54
- const currentAssetRef = (value as AssetValue | undefined)?.asset?._ref
55
-
56
- useEffect(() => {
57
- if (isInitialMount.current) {
58
- isInitialMount.current = false
59
- prevAssetRef.current = currentAssetRef
60
- return
61
- }
62
-
63
- const previousRef = prevAssetRef.current
64
- prevAssetRef.current = currentAssetRef
65
-
66
- if (!mediaTags?.length || !currentAssetRef || currentAssetRef === previousRef) return
67
-
68
- applyMediaTags({
69
- client,
70
- assetId: currentAssetRef,
71
- mediaTags,
72
- createTagsOnUpload,
73
- }).catch((err) => {
74
- console.error('[sanity-plugin-media] Failed to apply auto-tags:', err)
75
- const label = mediaTags.length === 1 ? 'tag' : 'tags'
76
- toast.push({
77
- closable: true,
78
- status: 'error',
79
- title: `Failed to apply the media ${label} ${mediaTags.join(', ')}`,
80
- })
81
- })
82
- }, [currentAssetRef, mediaTags, client, createTagsOnUpload, toast])
83
-
84
- return renderDefault(props as InputProps)
85
- }
@@ -1,45 +0,0 @@
1
- import {studioTheme, ThemeProvider, ToastProvider} from '@sanity/ui'
2
- import {render, screen, waitFor} from '@testing-library/react'
3
- import {of} from 'rxjs'
4
- import {ColorSchemeProvider} from 'sanity'
5
- import {describe, expect, it, vi, beforeEach} from 'vitest'
6
-
7
- import {createListenMock} from '../../__tests__/fixtures/listenMock'
8
- import {createMockSanityClient} from '../../__tests__/fixtures/mockSanityClient'
9
- import {ToolOptionsProvider} from '../../contexts/ToolOptionsContext'
10
- import useVersionedClient from '../../hooks/useVersionedClient'
11
- import Browser from './index'
12
-
13
- vi.mock('../../hooks/useVersionedClient', () => ({
14
- default: vi.fn(),
15
- }))
16
-
17
- describe('Browser', () => {
18
- beforeEach(() => {
19
- const fetch = vi.fn().mockReturnValue(of({items: []}))
20
- vi.mocked(useVersionedClient).mockReturnValue(
21
- createMockSanityClient({
22
- listen: createListenMock(),
23
- observable: {fetch},
24
- }),
25
- )
26
- })
27
-
28
- it('renders Browse Assets header in tool mode', async () => {
29
- render(
30
- <ColorSchemeProvider scheme="light">
31
- <ThemeProvider theme={studioTheme}>
32
- <ToastProvider>
33
- <ToolOptionsProvider options={{creditLine: {enabled: false}}}>
34
- <Browser />
35
- </ToolOptionsProvider>
36
- </ToastProvider>
37
- </ThemeProvider>
38
- </ColorSchemeProvider>,
39
- )
40
-
41
- await waitFor(() => {
42
- expect(screen.getByText('Browse Assets')).toBeInTheDocument()
43
- })
44
- })
45
- })
@@ -1,90 +0,0 @@
1
- import {Card, Flex, PortalProvider} from '@sanity/ui'
2
- import {useState} from 'react'
3
- import {type AssetSourceComponentProps, type SanityDocument} from 'sanity'
4
-
5
- import {AssetBrowserDispatchProvider} from '../../contexts/AssetSourceDispatchContext'
6
- import useVersionedClient from '../../hooks/useVersionedClient'
7
- import GlobalStyle from '../../styled/GlobalStyles'
8
- import Controls from '../Controls'
9
- import DebugControls from '../DebugControls'
10
- import Dialogs from '../Dialogs'
11
- import Header from '../Header'
12
- import Items from '../Items'
13
- import Notifications from '../Notifications'
14
- import PickedBar from '../PickedBar'
15
- import ReduxProvider from '../ReduxProvider'
16
- import TagsPanel from '../TagsPanel'
17
- import UploadDropzone from '../UploadDropzone'
18
- import {useBrowserInit} from './useBrowserInit'
19
-
20
- type Props = {
21
- assetType?: AssetSourceComponentProps['assetType']
22
- document?: SanityDocument
23
- onClose?: AssetSourceComponentProps['onClose']
24
- onSelect?: AssetSourceComponentProps['onSelect']
25
- selectedAssets?: AssetSourceComponentProps['selectedAssets']
26
- schemaType?: AssetSourceComponentProps['schemaType']
27
- }
28
-
29
- const BrowserContent = ({
30
- onClose,
31
- schemaType,
32
- }: {
33
- onClose?: AssetSourceComponentProps['onClose']
34
- schemaType?: AssetSourceComponentProps['schemaType']
35
- }) => {
36
- const client = useVersionedClient()
37
- const [portalElement, setPortalElement] = useState<HTMLDivElement | null>(null)
38
-
39
- useBrowserInit(client, schemaType)
40
-
41
- return (
42
- <PortalProvider element={portalElement}>
43
- <UploadDropzone>
44
- <Dialogs />
45
- <Notifications />
46
-
47
- <Card display="flex" height="fill" ref={setPortalElement}>
48
- <Flex direction="column" flex={1}>
49
- {/* Header */}
50
- <Header onClose={onClose} />
51
-
52
- {/* Browser Controls */}
53
- <Controls />
54
-
55
- <Flex flex={1}>
56
- <Flex align="flex-end" direction="column" flex={1} style={{position: 'relative'}}>
57
- <PickedBar />
58
- <Items />
59
- </Flex>
60
- <TagsPanel />
61
- </Flex>
62
-
63
- {/* Debug */}
64
- <DebugControls />
65
- </Flex>
66
- </Card>
67
- </UploadDropzone>
68
- </PortalProvider>
69
- )
70
- }
71
-
72
- const Browser = (props: Props) => {
73
- const client = useVersionedClient()
74
-
75
- return (
76
- <ReduxProvider
77
- assetType={props?.assetType}
78
- client={client}
79
- document={props?.document}
80
- selectedAssets={props?.selectedAssets}
81
- >
82
- <AssetBrowserDispatchProvider onSelect={props?.onSelect}>
83
- <GlobalStyle />
84
- <BrowserContent onClose={props?.onClose} schemaType={props?.schemaType} />
85
- </AssetBrowserDispatchProvider>
86
- </ReduxProvider>
87
- )
88
- }
89
-
90
- export default Browser
@@ -1,126 +0,0 @@
1
- import type {MutationEvent, SanityClient} from '@sanity/client'
2
- import groq from 'groq'
3
- import {useEffect} from 'react'
4
- import {useDispatch, useSelector} from 'react-redux'
5
- import type {Dispatch} from 'redux'
6
- import type {AssetSourceComponentProps} from 'sanity'
7
-
8
- import {inputs} from '../../config/searchFacets'
9
- import {TAG_DOCUMENT_NAME} from '../../constants'
10
- import {assetsActions} from '../../modules/assets'
11
- import {searchActions} from '../../modules/search'
12
- import {tagsActions} from '../../modules/tags'
13
- import type {RootReducerState} from '../../modules/types'
14
- import type {Asset, Tag} from '../../types'
15
-
16
- function getMediaTagNames(schemaType?: AssetSourceComponentProps['schemaType']): string[] {
17
- const mediaTags = (schemaType?.options as {mediaTags?: string[]} | undefined)?.mediaTags
18
- if (!mediaTags?.length) return []
19
- const unique = new Set(
20
- mediaTags.map((t) => t?.trim()).filter((t): t is string => Boolean(t?.length)),
21
- )
22
- return Array.from(unique)
23
- }
24
-
25
- function createAssetHandler(dispatch: Dispatch) {
26
- return (update: MutationEvent) => {
27
- const {documentId, result, transition} = update
28
-
29
- switch (transition) {
30
- case 'appear':
31
- dispatch(assetsActions.listenerCreateQueue({asset: result as Asset}))
32
- break
33
- case 'disappear':
34
- dispatch(assetsActions.listenerDeleteQueue({assetId: documentId}))
35
- break
36
- case 'update':
37
- dispatch(assetsActions.listenerUpdateQueue({asset: result as Asset}))
38
- break
39
- default:
40
- break
41
- }
42
- }
43
- }
44
-
45
- function createTagHandler(dispatch: Dispatch) {
46
- return (update: MutationEvent) => {
47
- const {documentId, result, transition} = update
48
-
49
- switch (transition) {
50
- case 'appear':
51
- dispatch(tagsActions.listenerCreateQueue({tag: result as Tag}))
52
- break
53
- case 'disappear':
54
- dispatch(tagsActions.listenerDeleteQueue({tagId: documentId}))
55
- break
56
- case 'update':
57
- dispatch(tagsActions.listenerUpdateQueue({tag: result as Tag}))
58
- break
59
- default:
60
- break
61
- }
62
- }
63
- }
64
-
65
- export function useBrowserInit(
66
- client: SanityClient,
67
- schemaType?: AssetSourceComponentProps['schemaType'],
68
- ): void {
69
- const dispatch = useDispatch()
70
- const tagsByIds = useSelector((state: RootReducerState) => state.tags.byIds)
71
- const tagsFetchCount = useSelector((state: RootReducerState) => state.tags.fetchCount)
72
-
73
- const tagNames = getMediaTagNames(schemaType)
74
- const hasMediaTags = tagNames.length > 0
75
-
76
- useEffect(() => {
77
- if (!hasMediaTags) {
78
- dispatch(searchActions.facetsClear())
79
- }
80
-
81
- dispatch(tagsActions.fetchRequest())
82
-
83
- const assetSubscription = client
84
- .listen(
85
- groq`*[_type in ["sanity.fileAsset", "sanity.imageAsset"] && !(_id in path("drafts.**"))]`,
86
- )
87
- .subscribe(createAssetHandler(dispatch))
88
-
89
- const tagSubscription = client
90
- .listen(groq`*[_type == "${TAG_DOCUMENT_NAME}" && !(_id in path("drafts.**"))]`)
91
- .subscribe(createTagHandler(dispatch))
92
-
93
- return () => {
94
- assetSubscription.unsubscribe()
95
- tagSubscription.unsubscribe()
96
- }
97
- }, [client, dispatch, hasMediaTags])
98
-
99
- // When mediaTags are configured, wait for the tag fetch to complete then apply facets.
100
- // Dispatching clear + add synchronously keeps all actions within assetsSearchEpic's
101
- // 400ms debounce window, so the browser performs exactly one asset fetch on open.
102
- useEffect(() => {
103
- if (!hasMediaTags || tagsFetchCount < 0) return
104
-
105
- const tagFacetInput = inputs.tag
106
- if (tagFacetInput.type !== 'searchable') return
107
-
108
- const resolvedTags = tagNames
109
- .map((name) => Object.values(tagsByIds).find((item) => item.tag.name.current === name))
110
- .filter((item): item is NonNullable<typeof item> => Boolean(item))
111
-
112
- dispatch(searchActions.facetsClear())
113
-
114
- for (const tagItem of resolvedTags) {
115
- dispatch(
116
- searchActions.facetsAdd({
117
- facet: {
118
- ...tagFacetInput,
119
- operatorType: 'references',
120
- value: {label: tagItem.tag.name.current, value: tagItem.tag._id},
121
- },
122
- }),
123
- )
124
- }
125
- }, [tagsFetchCount, hasMediaTags]) // eslint-disable-line react-hooks/exhaustive-deps
126
- }
@@ -1,65 +0,0 @@
1
- import {ClipboardIcon} from '@sanity/icons'
2
- import {Button, Popover, Text} from '@sanity/ui'
3
- import copy from 'copy-to-clipboard'
4
- import {useEffect, useRef, useState} from 'react'
5
-
6
- import {usePortalPopoverProps} from '../../hooks/usePortalPopoverProps'
7
-
8
- type Props = {
9
- disabled?: boolean
10
- url: string
11
- }
12
-
13
- const ButtonAssetCopy = ({disabled, url}: Props) => {
14
- const popoverProps = usePortalPopoverProps()
15
- const refPopoverTimeout = useRef<ReturnType<typeof window.setTimeout>>(null)
16
- const [popoverVisible, setPopoverVisible] = useState(false)
17
-
18
- const handleClick = () => {
19
- if (refPopoverTimeout.current) {
20
- clearTimeout(refPopoverTimeout.current)
21
- }
22
-
23
- setPopoverVisible(true)
24
- copy(url)
25
-
26
- refPopoverTimeout.current = setTimeout(() => {
27
- setPopoverVisible(false)
28
- }, 1250)
29
- }
30
-
31
- // Effects
32
- useEffect(() => {
33
- return () => {
34
- if (refPopoverTimeout.current) {
35
- clearTimeout(refPopoverTimeout.current)
36
- }
37
- }
38
- }, [])
39
-
40
- return (
41
- <Popover
42
- content={
43
- <Text muted size={1}>
44
- Copied!
45
- </Text>
46
- }
47
- open={popoverVisible}
48
- padding={2}
49
- placement="top"
50
- radius={1}
51
- {...popoverProps}
52
- >
53
- <Button
54
- disabled={disabled}
55
- fontSize={1}
56
- icon={ClipboardIcon}
57
- mode="ghost"
58
- onClick={handleClick}
59
- text="Copy URL"
60
- />
61
- </Popover>
62
- )
63
- }
64
-
65
- export default ButtonAssetCopy
@@ -1,39 +0,0 @@
1
- import {ThLargeIcon, ThListIcon} from '@sanity/icons'
2
- import {Button, Inline} from '@sanity/ui'
3
- import {useDispatch} from 'react-redux'
4
-
5
- import useTypedSelector from '../../hooks/useTypedSelector'
6
- import {assetsActions} from '../../modules/assets'
7
-
8
- const ButtonViewGroup = () => {
9
- // Redux
10
- const dispatch = useDispatch()
11
- const view = useTypedSelector((state) => state.assets.view)
12
-
13
- return (
14
- <Inline space={0} style={{whiteSpace: 'nowrap'}}>
15
- <Button
16
- fontSize={1}
17
- icon={ThLargeIcon}
18
- mode={view === 'grid' ? 'default' : 'ghost'}
19
- onClick={() => dispatch(assetsActions.viewSet({view: 'grid'}))}
20
- style={{
21
- borderBottomRightRadius: 0,
22
- borderTopRightRadius: 0,
23
- }}
24
- />
25
- <Button
26
- fontSize={1}
27
- icon={ThListIcon}
28
- mode={view === 'table' ? 'default' : 'ghost'}
29
- onClick={() => dispatch(assetsActions.viewSet({view: 'table'}))}
30
- style={{
31
- borderBottomLeftRadius: 0,
32
- borderTopLeftRadius: 0,
33
- }}
34
- />
35
- </Inline>
36
- )
37
- }
38
-
39
- export default ButtonViewGroup