sanity-plugin-media 2.0.5 → 2.1.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,6 +1,6 @@
1
1
  import {hues} from '@sanity/color'
2
2
  import {CloseIcon} from '@sanity/icons'
3
- import {Box, Button, Flex, Grid, Stack, Text, Tooltip, useMediaIndex} from '@sanity/ui'
3
+ import {Box, Button, Flex, Grid, Stack, Text, useMediaIndex} from '@sanity/ui'
4
4
  import filesize from 'filesize'
5
5
  import React from 'react'
6
6
  import {useDispatch} from 'react-redux'
@@ -100,10 +100,10 @@ const TableRowUpload = (props: Props) => {
100
100
  </div>
101
101
  )}
102
102
 
103
- {/*
103
+ {/*
104
104
  Cancel upload button.
105
105
  Assets will only have a `complete` status _after_ it has been created on your dataset.
106
- As such, we also hide the cancel button when `percentLoaded === 100`, as cancelling when the asset
106
+ As such, we also hide the cancel button when `percentLoaded === 100`, as cancelling when the asset
107
107
  has been fully uploaded (even with a status of `progress`) won't stop the asset from being created.
108
108
  */}
109
109
  {!isComplete && percentLoaded !== 100 && (
@@ -118,27 +118,15 @@ const TableRowUpload = (props: Props) => {
118
118
  width: '100%'
119
119
  }}
120
120
  >
121
- <Tooltip
122
- content={
123
- <Box padding={2}>
124
- <Text muted size={1}>
125
- Cancel
126
- </Text>
127
- </Box>
128
- }
129
- disabled={'ontouchstart' in window}
130
- placement="top"
131
- >
132
- <Button
133
- fontSize={3}
134
- icon={CloseIcon}
135
- mode="bleed"
136
- onClick={handleCancelUpload}
137
- padding={2}
138
- style={{background: 'none', boxShadow: 'none'}}
139
- tone="critical"
140
- />
141
- </Tooltip>
121
+ <Button
122
+ fontSize={3}
123
+ icon={CloseIcon}
124
+ mode="bleed"
125
+ onClick={handleCancelUpload}
126
+ padding={2}
127
+ style={{background: 'none', boxShadow: 'none'}}
128
+ tone="critical"
129
+ />
142
130
  </Flex>
143
131
  )}
144
132
  </Box>
@@ -11,7 +11,7 @@ import useTypedSelector from '../../hooks/useTypedSelector'
11
11
  import {selectAssetsPicked} from '../../modules/assets'
12
12
  import {dialogActions} from '../../modules/dialog'
13
13
  import {DIALOG_ACTIONS} from '../../modules/dialog/actions'
14
- import {searchActions, selectHasSearchFacetTag, selectIsSearchFacetTag} from '../../modules/search'
14
+ import {searchActions, selectIsSearchFacetTag} from '../../modules/search'
15
15
 
16
16
  type Props = {
17
17
  actions?: TagActions[]
@@ -44,6 +44,7 @@ type TagButtonProps = {
44
44
 
45
45
  const TagButton = (props: TagButtonProps) => {
46
46
  const {disabled, icon, onClick, tone, tooltip} = props
47
+
47
48
  return (
48
49
  <Tooltip
49
50
  content={
@@ -55,6 +56,7 @@ const TagButton = (props: TagButtonProps) => {
55
56
  }
56
57
  disabled={'ontouchstart' in window}
57
58
  placement="top"
59
+ portal
58
60
  >
59
61
  <Button
60
62
  disabled={disabled}
@@ -75,12 +77,11 @@ const Tag = (props: Props) => {
75
77
  // Redux
76
78
  const dispatch = useDispatch()
77
79
  const assetsPicked = useTypedSelector(selectAssetsPicked)
78
- const hasSearchFacetTag = useTypedSelector(selectHasSearchFacetTag)
79
80
  const isSearchFacetTag = useTypedSelector(state => selectIsSearchFacetTag(state, tag?.tag?._id))
80
81
 
81
82
  // Callbacks
82
83
  const handleSearchFacetTagRemove = () => {
83
- dispatch(searchActions.facetsRemove({facetName: 'tag'}))
84
+ dispatch(searchActions.facetsRemoveByTag({tagId: tag.tag._id}))
84
85
  }
85
86
 
86
87
  const handleShowAddTagToAssetsDialog = () => {
@@ -108,7 +109,7 @@ const Tag = (props: Props) => {
108
109
  }
109
110
  } as SearchFacetInputSearchableProps
110
111
 
111
- if (hasSearchFacetTag) {
112
+ if (isSearchFacetTag) {
112
113
  dispatch(
113
114
  searchActions.facetsUpdate({
114
115
  name: 'tag',
@@ -13,15 +13,11 @@ type Props = {
13
13
  title: string
14
14
  }
15
15
 
16
- const TagViewHeader = (props: Props) => {
17
- const {allowCreate, light, title} = props
18
-
19
- // Redux
16
+ const TagViewHeader = ({allowCreate, light, title}: Props) => {
20
17
  const dispatch = useDispatch()
21
18
  const tagsCreating = useTypedSelector(state => state.tags.creating)
22
19
  const tagsFetching = useTypedSelector(state => state.tags.fetching)
23
20
 
24
- // Callbacks
25
21
  const handleTagCreate = () => {
26
22
  dispatch(DIALOG_ACTIONS.showTagCreate())
27
23
  }
@@ -35,6 +31,7 @@ const TagViewHeader = (props: Props) => {
35
31
  style={{
36
32
  background: light ? hues.gray?.[900].hex : black.hex,
37
33
  borderBottom: `1px solid ${hues.gray?.[900].hex}`,
34
+ flexShrink: 0,
38
35
  height: `${PANEL_HEIGHT}px`
39
36
  }}
40
37
  >
@@ -1,24 +1,26 @@
1
1
  import isHotkey from 'is-hotkey'
2
- import {RefObject, useEffect, useRef} from 'react'
2
+ import {RefObject, useCallback, useEffect, useRef} from 'react'
3
3
 
4
4
  const useKeyPress = (hotkey: string, onPress?: () => void): RefObject<boolean> => {
5
5
  const keyPressed = useRef(false)
6
6
 
7
7
  // If pressed key is our target key then set to true
8
- function downHandler(e: KeyboardEvent) {
9
- if (isHotkey(hotkey, e)) {
10
- keyPressed.current = true
11
-
12
- if (onPress) {
13
- onPress()
8
+ const downHandler = useCallback(
9
+ (e: KeyboardEvent) => {
10
+ if (isHotkey(hotkey, e)) {
11
+ keyPressed.current = true
12
+ if (onPress) {
13
+ onPress()
14
+ }
14
15
  }
15
- }
16
- }
16
+ },
17
+ [hotkey, onPress]
18
+ )
17
19
 
18
20
  // If released key is our target key then set to false
19
- const upHandler = () => {
21
+ const upHandler = useCallback(() => {
20
22
  keyPressed.current = false
21
- }
23
+ }, [])
22
24
 
23
25
  // Add event listeners
24
26
  useEffect(() => {
@@ -29,7 +31,7 @@ const useKeyPress = (hotkey: string, onPress?: () => void): RefObject<boolean> =
29
31
  window.removeEventListener('keydown', downHandler)
30
32
  window.removeEventListener('keyup', upHandler)
31
33
  }
32
- }, []) // Empty array ensures that effect is only run on mount and unmount
34
+ }, [downHandler, upHandler])
33
35
 
34
36
  return keyPressed
35
37
  }
@@ -0,0 +1,12 @@
1
+ import {PopoverProps, usePortal} from '@sanity/ui'
2
+
3
+ export function usePortalPopoverProps(): PopoverProps {
4
+ const portal = usePortal()
5
+
6
+ return {
7
+ constrainSize: true,
8
+ floatingBoundary: portal.element,
9
+ portal: true,
10
+ referenceBoundary: portal.element
11
+ }
12
+ }
@@ -575,8 +575,11 @@ export const assetsSearchEpic: MyEpic = action$ =>
575
575
  ofType(
576
576
  searchActions.facetsAdd.type,
577
577
  searchActions.facetsClear.type,
578
- searchActions.facetsRemove.type,
578
+ searchActions.facetsRemoveById.type,
579
+ searchActions.facetsRemoveByName.type,
580
+ searchActions.facetsRemoveByTag.type,
579
581
  searchActions.facetsUpdate.type,
582
+ searchActions.facetsUpdateById.type,
580
583
  searchActions.querySet.type
581
584
  ),
582
585
  debounceTime(400),
@@ -723,8 +726,11 @@ export const assetsUnpickEpic: MyEpic = action$ =>
723
726
  assetsActions.viewSet.type,
724
727
  searchActions.facetsAdd.type,
725
728
  searchActions.facetsClear.type,
726
- searchActions.facetsRemove.type,
729
+ searchActions.facetsRemoveById.type,
730
+ searchActions.facetsRemoveByName.type,
731
+ searchActions.facetsRemoveByTag.type,
727
732
  searchActions.facetsUpdate.type,
733
+ searchActions.facetsUpdateById.type,
728
734
  searchActions.querySet.type
729
735
  ),
730
736
  mergeMap(() => {
@@ -1,4 +1,9 @@
1
- import {combineReducers} from '@reduxjs/toolkit'
1
+ import {
2
+ ActionFromReducersMapObject,
3
+ Reducer,
4
+ StateFromReducersMapObject,
5
+ combineReducers
6
+ } from '@reduxjs/toolkit'
2
7
  import {combineEpics} from 'redux-observable'
3
8
 
4
9
  import assetsReducer, {
@@ -97,7 +102,7 @@ export const rootEpic = combineEpics(
97
102
  uploadsCompleteQueueEpic
98
103
  )
99
104
 
100
- const reducers = combineReducers({
105
+ const reducers = {
101
106
  assets: assetsReducer,
102
107
  debug: debugReducer,
103
108
  dialog: dialogReducer,
@@ -106,5 +111,14 @@ const reducers = combineReducers({
106
111
  selected: selectedReducer,
107
112
  tags: tagsReducer,
108
113
  uploads: uploadsReducer
109
- })
110
- export const rootReducer = reducers
114
+ }
115
+
116
+ type ReducersMapObject = typeof reducers
117
+
118
+ // Workaround to avoid `$CombinedState` ts errors
119
+ // source: https://github.com/reduxjs/redux-toolkit/issues/2068#issuecomment-1130796500
120
+ // TODO: remove once we use `redux-toolkit` v2
121
+ export const rootReducer: Reducer<
122
+ StateFromReducersMapObject<ReducersMapObject>,
123
+ ActionFromReducersMapObject<ReducersMapObject>
124
+ > = combineReducers(reducers)
@@ -1,8 +1,9 @@
1
- import {createSelector, createSlice, PayloadAction} from '@reduxjs/toolkit'
2
- import type {MyEpic, SearchFacetInputProps, SearchFacetOperatorType} from '@types'
3
- import {Selector} from 'react-redux'
1
+ import {PayloadAction, createSelector, createSlice} from '@reduxjs/toolkit'
2
+ import type {MyEpic, SearchFacetInputProps, SearchFacetOperatorType, WithId} from '@types'
4
3
  import {empty, of} from 'rxjs'
5
4
  import {filter, mergeMap, withLatestFrom} from 'rxjs/operators'
5
+ import {uuid} from '@sanity/uuid'
6
+
6
7
  import {tagsActions} from '../tags'
7
8
  import type {RootReducerState} from '../types'
8
9
 
@@ -10,7 +11,7 @@ import type {RootReducerState} from '../types'
10
11
  // (The main offender is `fieldModifier` which is currently a function)
11
12
 
12
13
  type SearchState = {
13
- facets: SearchFacetInputProps[]
14
+ facets: WithId<SearchFacetInputProps>[]
14
15
  query: string
15
16
  }
16
17
 
@@ -25,16 +26,32 @@ const searchSlice = createSlice({
25
26
  reducers: {
26
27
  // Add search facet
27
28
  facetsAdd(state, action: PayloadAction<{facet: SearchFacetInputProps}>) {
28
- state.facets.push(action.payload.facet)
29
+ state.facets.push({...action.payload.facet, id: uuid()})
29
30
  },
30
31
  // Clear all search facets
31
32
  facetsClear(state) {
32
33
  state.facets = []
33
34
  },
34
35
  // Remove search facet by name
35
- facetsRemove(state, action: PayloadAction<{facetName: string}>) {
36
+ facetsRemoveByName(state, action: PayloadAction<{facetName: string}>) {
36
37
  state.facets = state.facets.filter(facet => facet.name !== action.payload.facetName)
37
38
  },
39
+ // Remove search facet by name
40
+ facetsRemoveByTag(state, action: PayloadAction<{tagId: string}>) {
41
+ state.facets = state.facets.filter(
42
+ facet =>
43
+ !(
44
+ facet.name === 'tag' &&
45
+ facet.type === 'searchable' &&
46
+ (facet.operatorType === 'references' || facet.operatorType === 'doesNotReference') &&
47
+ facet.value?.value === action.payload.tagId
48
+ )
49
+ )
50
+ },
51
+ // Remove search facet by name
52
+ facetsRemoveById(state, action: PayloadAction<{facetId: string}>) {
53
+ state.facets = state.facets.filter(facet => facet.id !== action.payload.facetId)
54
+ },
38
55
  // Update an existing search facet
39
56
  facetsUpdate(
40
57
  state,
@@ -47,8 +64,38 @@ const searchSlice = createSlice({
47
64
  ) {
48
65
  const {modifier, name, operatorType, value} = action.payload
49
66
 
67
+ const facet = state.facets.find(f => f.name === name)
68
+
69
+ if (!facet) {
70
+ return
71
+ }
72
+
73
+ if (facet.type === 'number' && modifier) {
74
+ facet.modifier = modifier
75
+ }
76
+ if (operatorType) {
77
+ facet.operatorType = operatorType
78
+ }
79
+ if (typeof value !== 'undefined') {
80
+ facet.value = value
81
+ }
82
+
83
+ state.facets = state.facets.filter(f => f.name !== facet.name || f.id === facet.id)
84
+ },
85
+ // Update an existing search facet
86
+ facetsUpdateById(
87
+ state,
88
+ action: PayloadAction<{
89
+ modifier?: string
90
+ id: string
91
+ operatorType?: SearchFacetOperatorType
92
+ value?: any // TODO: type correctly
93
+ }>
94
+ ) {
95
+ const {modifier, id, operatorType, value} = action.payload
96
+
50
97
  state.facets.forEach((facet, index) => {
51
- if (facet.name === name) {
98
+ if (facet.id === id) {
52
99
  if (facet.type === 'number' && modifier) {
53
100
  facet.modifier = modifier
54
101
  }
@@ -100,33 +147,19 @@ export const searchFacetTagUpdateEpic: MyEpic = (action$, state$) =>
100
147
  )
101
148
 
102
149
  // Selectors
103
-
104
- export const selectHasSearchFacetTag: Selector<RootReducerState, boolean> = createSelector(
105
- (state: RootReducerState) => state.search.facets,
106
- searchFacets => !!searchFacets?.find(facet => facet.name === 'tag')
107
- )
108
-
109
150
  export const selectIsSearchFacetTag = createSelector(
110
151
  [
111
- (state: RootReducerState) => state.tags.byIds,
112
152
  (state: RootReducerState) => state.search.facets,
113
153
  (_state: RootReducerState, tagId: string) => tagId
114
154
  ],
115
- (tagsByIds, searchFacets, tagId) => {
116
- const searchFacet = searchFacets?.find(facet => facet.name === 'tag')
117
-
118
- if (searchFacet?.type === 'searchable') {
119
- const searchFacetTagId = searchFacet.value?.value
120
- if (searchFacetTagId) {
121
- return (
122
- tagsByIds[searchFacetTagId]?.tag?._id === tagId &&
123
- searchFacet?.operatorType === 'references'
124
- )
125
- }
126
- }
127
-
128
- return false
129
- }
155
+ (searchFacets, tagId) =>
156
+ searchFacets.some(
157
+ facet =>
158
+ facet.name === 'tag' &&
159
+ facet.type === 'searchable' &&
160
+ (facet.operatorType === 'references' || facet.operatorType === 'doesNotReference') &&
161
+ facet.value?.value === tagId
162
+ )
130
163
  )
131
164
 
132
165
  export const searchActions = searchSlice.actions
@@ -330,3 +330,7 @@ export type UploadItem = {
330
330
  size: number
331
331
  status: 'complete' | 'queued' | 'uploading'
332
332
  }
333
+
334
+ export type WithId<T extends SearchFacetInputProps> = T & {
335
+ id: string
336
+ }