sanity-plugin-media 4.3.5 → 5.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.
Files changed (166) hide show
  1. package/dist/index.d.ts +190 -413
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +2532 -3564
  4. package/dist/index.js.map +1 -1
  5. package/package.json +29 -35
  6. package/dist/index.cjs +0 -5753
  7. package/dist/index.cjs.map +0 -1
  8. package/dist/index.d.cts +0 -462
  9. package/sanity.json +0 -8
  10. package/src/__tests__/fixtures/createEpicTestStore.ts +0 -28
  11. package/src/__tests__/fixtures/listenMock.ts +0 -9
  12. package/src/__tests__/fixtures/mockSanityClient.ts +0 -84
  13. package/src/__tests__/fixtures/renderWithProviders.tsx +0 -55
  14. package/src/__tests__/fixtures/rootState.ts +0 -27
  15. package/src/__tests__/fixtures/withinDialog.ts +0 -28
  16. package/src/components/AssetGridVirtualized/index.tsx +0 -94
  17. package/src/components/AssetMetadata/index.tsx +0 -122
  18. package/src/components/AssetTableVirtualized/index.tsx +0 -73
  19. package/src/components/AutoTagInputWrapper/index.tsx +0 -87
  20. package/src/components/Browser/Browser.test.tsx +0 -45
  21. package/src/components/Browser/index.tsx +0 -90
  22. package/src/components/Browser/useBrowserInit.ts +0 -126
  23. package/src/components/ButtonAssetCopy/index.tsx +0 -65
  24. package/src/components/ButtonViewGroup/index.tsx +0 -39
  25. package/src/components/CardAsset/CardAsset.test.tsx +0 -323
  26. package/src/components/CardAsset/index.tsx +0 -290
  27. package/src/components/CardUpload/index.tsx +0 -161
  28. package/src/components/Controls/index.tsx +0 -136
  29. package/src/components/DebugControls/index.tsx +0 -80
  30. package/src/components/Dialog/index.tsx +0 -11
  31. package/src/components/DialogAssetEdit/Details.tsx +0 -181
  32. package/src/components/DialogAssetEdit/DialogAssetEdit.test.tsx +0 -216
  33. package/src/components/DialogAssetEdit/index.tsx +0 -492
  34. package/src/components/DialogConfirm/index.tsx +0 -88
  35. package/src/components/DialogSearchFacets/index.tsx +0 -42
  36. package/src/components/DialogTagCreate/DialogTagCreate.test.tsx +0 -121
  37. package/src/components/DialogTagCreate/index.tsx +0 -103
  38. package/src/components/DialogTagEdit/DialogTagEdit.test.tsx +0 -165
  39. package/src/components/DialogTagEdit/index.tsx +0 -190
  40. package/src/components/DialogTags/index.tsx +0 -45
  41. package/src/components/Dialogs/index.tsx +0 -76
  42. package/src/components/DocumentList/index.tsx +0 -62
  43. package/src/components/FileAssetPreview/index.tsx +0 -37
  44. package/src/components/FileIcon/index.tsx +0 -43
  45. package/src/components/FormBuilderTool/FormBuilderTool.test.tsx +0 -63
  46. package/src/components/FormBuilderTool/index.tsx +0 -69
  47. package/src/components/FormFieldInputLabel/index.tsx +0 -66
  48. package/src/components/FormFieldInputTags/index.tsx +0 -98
  49. package/src/components/FormFieldInputText/index.tsx +0 -41
  50. package/src/components/FormFieldInputTextarea/index.tsx +0 -43
  51. package/src/components/FormSubmitButton/index.tsx +0 -59
  52. package/src/components/Header/index.tsx +0 -80
  53. package/src/components/Image/index.tsx +0 -41
  54. package/src/components/Items/index.tsx +0 -68
  55. package/src/components/Notifications/index.tsx +0 -24
  56. package/src/components/OrderSelect/index.tsx +0 -66
  57. package/src/components/PickedBar/index.tsx +0 -77
  58. package/src/components/Progress/index.tsx +0 -38
  59. package/src/components/ReduxProvider/index.tsx +0 -96
  60. package/src/components/SearchFacet/index.tsx +0 -66
  61. package/src/components/SearchFacetNumber/index.tsx +0 -133
  62. package/src/components/SearchFacetSelect/index.tsx +0 -110
  63. package/src/components/SearchFacetString/index.tsx +0 -88
  64. package/src/components/SearchFacetTags/index.tsx +0 -121
  65. package/src/components/SearchFacets/index.tsx +0 -72
  66. package/src/components/SearchFacetsControl/index.tsx +0 -140
  67. package/src/components/TableHeader/index.tsx +0 -110
  68. package/src/components/TableHeaderItem/index.tsx +0 -61
  69. package/src/components/TableRowAsset/index.tsx +0 -420
  70. package/src/components/TableRowUpload/index.tsx +0 -164
  71. package/src/components/Tag/index.tsx +0 -200
  72. package/src/components/TagIcon/index.tsx +0 -22
  73. package/src/components/TagView/index.tsx +0 -39
  74. package/src/components/TagViewHeader/index.tsx +0 -70
  75. package/src/components/TagsPanel/index.tsx +0 -40
  76. package/src/components/TagsVirtualized/index.tsx +0 -160
  77. package/src/components/TextInputNumber/index.tsx +0 -32
  78. package/src/components/TextInputSearch/index.tsx +0 -60
  79. package/src/components/Tool/index.tsx +0 -13
  80. package/src/components/UploadDropzone/UploadDropzone.test.tsx +0 -40
  81. package/src/components/UploadDropzone/index.tsx +0 -173
  82. package/src/config/orders.ts +0 -28
  83. package/src/config/searchFacets.ts +0 -312
  84. package/src/constants.ts +0 -87
  85. package/src/contexts/AssetSourceDispatchContext.tsx +0 -37
  86. package/src/contexts/DropzoneDispatchContext.tsx +0 -34
  87. package/src/contexts/ToolOptionsContext.tsx +0 -65
  88. package/src/formSchema/index.test.ts +0 -56
  89. package/src/formSchema/index.ts +0 -39
  90. package/src/hooks/useBreakpointIndex.ts +0 -49
  91. package/src/hooks/useKeyPress.ts +0 -39
  92. package/src/hooks/useOnScreen.ts +0 -34
  93. package/src/hooks/usePortalPopoverProps.ts +0 -13
  94. package/src/hooks/useTypedSelector.ts +0 -7
  95. package/src/hooks/useVersionedClient.ts +0 -6
  96. package/src/index.ts +0 -5
  97. package/src/modules/assets/actions.ts +0 -42
  98. package/src/modules/assets/deleteAndUpdateEpics.test.ts +0 -87
  99. package/src/modules/assets/fetchEpic.test.ts +0 -73
  100. package/src/modules/assets/index.ts +0 -825
  101. package/src/modules/assets/reducer.test.ts +0 -91
  102. package/src/modules/assets/tagsAndListenerEpics.test.ts +0 -206
  103. package/src/modules/debug/index.ts +0 -28
  104. package/src/modules/dialog/actions.ts +0 -10
  105. package/src/modules/dialog/epics.test.ts +0 -168
  106. package/src/modules/dialog/index.ts +0 -238
  107. package/src/modules/dialog/reducer.test.ts +0 -185
  108. package/src/modules/index.ts +0 -124
  109. package/src/modules/notifications/epics.test.ts +0 -374
  110. package/src/modules/notifications/index.ts +0 -199
  111. package/src/modules/notifications/reducer.test.ts +0 -54
  112. package/src/modules/search/index.test.ts +0 -36
  113. package/src/modules/search/index.ts +0 -167
  114. package/src/modules/selected/index.ts +0 -22
  115. package/src/modules/selectors.test.ts +0 -21
  116. package/src/modules/selectors.ts +0 -17
  117. package/src/modules/tags/epics.test.ts +0 -96
  118. package/src/modules/tags/index.test.ts +0 -42
  119. package/src/modules/tags/index.ts +0 -540
  120. package/src/modules/types.ts +0 -3
  121. package/src/modules/uploads/actions.ts +0 -13
  122. package/src/modules/uploads/epics.test.ts +0 -109
  123. package/src/modules/uploads/index.test.ts +0 -59
  124. package/src/modules/uploads/index.ts +0 -282
  125. package/src/operators/checkTagName.test.ts +0 -29
  126. package/src/operators/checkTagName.ts +0 -33
  127. package/src/operators/debugThrottle.ts +0 -25
  128. package/src/plugin.tsx +0 -54
  129. package/src/schemas/tag.ts +0 -28
  130. package/src/styled/GlobalStyles/index.tsx +0 -40
  131. package/src/styled/react-select/creatable.tsx +0 -184
  132. package/src/styled/react-select/single.tsx +0 -184
  133. package/src/types/index.ts +0 -379
  134. package/src/types/sanity-ui.d.ts +0 -6
  135. package/src/utils/applyMediaTags.ts +0 -87
  136. package/src/utils/blocksToText.test.ts +0 -43
  137. package/src/utils/blocksToText.ts +0 -27
  138. package/src/utils/constructFilter.test.ts +0 -120
  139. package/src/utils/constructFilter.ts +0 -98
  140. package/src/utils/generatePreviewBlobUrl.test.ts +0 -70
  141. package/src/utils/generatePreviewBlobUrl.ts +0 -53
  142. package/src/utils/getAssetResolution.test.ts +0 -13
  143. package/src/utils/getAssetResolution.ts +0 -7
  144. package/src/utils/getDocumentAssetIds.test.ts +0 -50
  145. package/src/utils/getDocumentAssetIds.ts +0 -35
  146. package/src/utils/getSchemeColor.test.ts +0 -12
  147. package/src/utils/getSchemeColor.ts +0 -43
  148. package/src/utils/getTagSelectOptions.test.ts +0 -44
  149. package/src/utils/getTagSelectOptions.ts +0 -16
  150. package/src/utils/getUniqueDocuments.test.ts +0 -26
  151. package/src/utils/getUniqueDocuments.ts +0 -15
  152. package/src/utils/imageDprUrl.test.ts +0 -46
  153. package/src/utils/imageDprUrl.ts +0 -27
  154. package/src/utils/isSupportedAssetType.test.ts +0 -16
  155. package/src/utils/isSupportedAssetType.ts +0 -15
  156. package/src/utils/mediaField.ts +0 -73
  157. package/src/utils/sanitizeFormData.test.ts +0 -59
  158. package/src/utils/sanitizeFormData.ts +0 -26
  159. package/src/utils/typeGuards.test.ts +0 -18
  160. package/src/utils/typeGuards.ts +0 -9
  161. package/src/utils/uploadSanityAsset.test.ts +0 -29
  162. package/src/utils/uploadSanityAsset.ts +0 -97
  163. package/src/utils/withMaxConcurrency.test.ts +0 -43
  164. package/src/utils/withMaxConcurrency.ts +0 -55
  165. package/src/utils/zodFormResolver.ts +0 -17
  166. package/v2-incompatible.js +0 -11
@@ -1,94 +0,0 @@
1
- import {memo, forwardRef} from 'react'
2
- import {VirtuosoGrid} from 'react-virtuoso'
3
- import {styled} from 'styled-components'
4
-
5
- import useTypedSelector from '../../hooks/useTypedSelector'
6
- import type {CardAssetData, CardUploadData} from '../../types'
7
- import CardAsset from '../CardAsset'
8
- import CardUpload from '../CardUpload'
9
-
10
- type Props = {
11
- items: (CardAssetData | CardUploadData)[]
12
- onLoadMore?: () => void
13
- }
14
-
15
- const CARD_HEIGHT = 220
16
- const CARD_WIDTH = 240
17
-
18
- const VirtualCell = memo(
19
- ({item, selected}: {item: CardAssetData | CardUploadData; selected: boolean}) => {
20
- if (item?.type === 'asset') {
21
- return <CardAsset id={item.id} selected={selected} />
22
- }
23
-
24
- if (item?.type === 'upload') {
25
- return <CardUpload id={item.id} />
26
- }
27
-
28
- return null
29
- },
30
- )
31
-
32
- const StyledItemContainer = styled.div`
33
- height: ${CARD_HEIGHT}px;
34
- width: ${CARD_WIDTH}px;
35
- `
36
-
37
- const ItemContainer = forwardRef<HTMLDivElement, any>((props, ref) => {
38
- // eslint-disable-next-line @typescript-eslint/no-unused-vars -- we're doing this to avoid sc warnings about `context` passed as an attribute
39
- const {context, ...rest} = props
40
- return <StyledItemContainer ref={ref} {...rest} />
41
- })
42
-
43
- const StyledListContainer = styled.div`
44
- display: grid;
45
- grid-template-columns: repeat(auto-fill, ${CARD_WIDTH}px);
46
- grid-template-rows: repeat(auto-fill, ${CARD_HEIGHT}px);
47
- justify-content: center;
48
- margin: 0 auto;
49
- `
50
-
51
- const ListContainer = forwardRef<HTMLDivElement, any>((props, ref) => {
52
- // eslint-disable-next-line @typescript-eslint/no-unused-vars -- we're doing this to avoid sc warnings about `context` passed as an attribute
53
- const {context, ...rest} = props
54
- return <StyledListContainer ref={ref} {...rest} />
55
- })
56
-
57
- const AssetGridVirtualized = (props: Props) => {
58
- const {items, onLoadMore} = props
59
-
60
- // Redux
61
- const selectedAssets = useTypedSelector((state) => state.selected.assets)
62
-
63
- const selectedIds = (selectedAssets && selectedAssets.map((asset) => asset._id)) || []
64
- const totalCount = items?.length
65
-
66
- if (totalCount === 0) {
67
- return null
68
- }
69
-
70
- return (
71
- <VirtuosoGrid
72
- className="media__custom-scrollbar"
73
- computeItemKey={(index) => {
74
- const item = items[index]
75
- return item?.id
76
- }}
77
- components={{
78
- Item: ItemContainer,
79
- List: ListContainer,
80
- }}
81
- endReached={onLoadMore}
82
- itemContent={(index) => {
83
- const item = items[index]
84
- const selected = selectedIds.includes(item?.id)
85
- return <VirtualCell item={item} selected={selected} />
86
- }}
87
- overscan={48}
88
- style={{overflowX: 'hidden', overflowY: 'scroll'}}
89
- totalCount={totalCount}
90
- />
91
- )
92
- }
93
-
94
- export default AssetGridVirtualized
@@ -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,87 +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])
83
-
84
- return renderDefault(props as InputProps)
85
- }
86
-
87
- export default AutoTagInput
@@ -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
- }