sanity-plugin-workflow 1.0.0-beta.5 → 1.0.0-beta.7

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sanity-plugin-workflow",
3
- "version": "1.0.0-beta.5",
3
+ "version": "1.0.0-beta.7",
4
4
  "description": "A demonstration of a custom content publishing workflow using Sanity.",
5
5
  "keywords": [
6
6
  "sanity",
@@ -1,6 +1,6 @@
1
- import React from 'react'
2
- import {Button, useToast} from '@sanity/ui'
3
1
  import {CheckmarkIcon} from '@sanity/icons'
2
+ import {Box, Button, Text, Tooltip, useToast} from '@sanity/ui'
3
+ import React from 'react'
4
4
  import {useClient} from 'sanity'
5
5
 
6
6
  import {API_VERSION} from '../../constants'
@@ -15,39 +15,54 @@ export default function CompleteButton(props: CompleteButtonProps) {
15
15
  const client = useClient({apiVersion: API_VERSION})
16
16
  const toast = useToast()
17
17
 
18
- const handleComplete = React.useCallback(
19
- (id: string) => {
20
- client
21
- .delete(`workflow-metadata.${id}`)
22
- .then(() => {
23
- toast.push({
24
- status: 'success',
25
- title: 'Workflow completed',
26
- description: id,
18
+ const handleComplete: React.MouseEventHandler<HTMLButtonElement> =
19
+ React.useCallback(
20
+ (event) => {
21
+ const id = event.currentTarget.value
22
+
23
+ if (!id) {
24
+ return
25
+ }
26
+
27
+ client
28
+ .delete(`workflow-metadata.${id}`)
29
+ .then(() => {
30
+ toast.push({
31
+ status: 'success',
32
+ title: 'Workflow completed',
33
+ })
27
34
  })
28
- })
29
- .catch(() => {
30
- toast.push({
31
- status: 'error',
32
- title: 'Could not complete Workflow',
33
- description: id,
35
+ .catch(() => {
36
+ toast.push({
37
+ status: 'error',
38
+ title: 'Could not complete Workflow',
39
+ })
34
40
  })
35
- })
36
- },
37
- [client, toast]
38
- )
41
+ },
42
+ [client, toast]
43
+ )
39
44
 
40
45
  return (
41
- <Button
42
- onClick={() => handleComplete(documentId)}
43
- text="Complete"
44
- icon={CheckmarkIcon}
45
- tone="positive"
46
- mode="ghost"
47
- fontSize={1}
48
- padding={2}
49
- tabIndex={-1}
50
- disabled={disabled}
51
- />
46
+ <Tooltip
47
+ portal
48
+ content={
49
+ <Box padding={2}>
50
+ <Text size={1}>Remove this document from Workflow</Text>
51
+ </Box>
52
+ }
53
+ >
54
+ <Button
55
+ value={documentId}
56
+ onClick={handleComplete}
57
+ text="Complete"
58
+ icon={CheckmarkIcon}
59
+ tone="positive"
60
+ mode="ghost"
61
+ fontSize={1}
62
+ padding={2}
63
+ tabIndex={-1}
64
+ disabled={disabled}
65
+ />
66
+ </Tooltip>
52
67
  )
53
68
  }
@@ -20,6 +20,7 @@ import {ValidationStatus} from './ValidationStatus'
20
20
 
21
21
  type DocumentCardProps = {
22
22
  isDragDisabled: boolean
23
+ isPatching: boolean
23
24
  userRoleCanDrop: boolean
24
25
  isDragging: boolean
25
26
  item: SanityDocumentWithMetadata
@@ -34,6 +35,7 @@ type DocumentCardProps = {
34
35
  export function DocumentCard(props: DocumentCardProps) {
35
36
  const {
36
37
  isDragDisabled,
38
+ isPatching,
37
39
  userRoleCanDrop,
38
40
  isDragging,
39
41
  item,
@@ -45,45 +47,6 @@ export function DocumentCard(props: DocumentCardProps) {
45
47
  const schema = useSchema()
46
48
  const state = states.find((s) => s.id === item._metadata?.state)
47
49
 
48
- // Perform document operations after State changes
49
- // If State has changed and the document needs to be un/published
50
- // This functionality was deemed too dangerous / unexpected
51
- // Revisit with improved UX
52
- // const currentState = useMemo(
53
- // () => states.find((state) => state.id === item._metadata?.state),
54
- // [states, item]
55
- // )
56
- // const ops = useDocumentOperation(documentId ?? ``, item._type)
57
- // const toast = useToast()
58
-
59
- // useEffect(() => {
60
- // const isDraft = item._id.startsWith('drafts.')
61
-
62
- // if (isDraft && currentState?.operation === 'publish' && !item?._metadata?.optimistic) {
63
- // if (!ops.publish.disabled) {
64
- // ops.publish.execute()
65
- // toast.push({
66
- // title: 'Published Document',
67
- // description: documentId,
68
- // status: 'success',
69
- // })
70
- // }
71
- // } else if (
72
- // !isDraft &&
73
- // currentState?.operation === 'unpublish' &&
74
- // !item?._metadata?.optimistic
75
- // ) {
76
- // if (!ops.unpublish.disabled) {
77
- // ops.unpublish.execute()
78
- // toast.push({
79
- // title: 'Unpublished Document',
80
- // description: documentId,
81
- // status: 'success',
82
- // })
83
- // }
84
- // }
85
- // }, [currentState, documentId, item, ops, toast])
86
-
87
50
  const isDarkMode = useTheme().sanity.color.dark
88
51
  const defaultCardTone = isDarkMode ? `transparent` : `default`
89
52
 
@@ -107,6 +70,7 @@ export function DocumentCard(props: DocumentCardProps) {
107
70
 
108
71
  if (!userRoleCanDrop) return isDarkMode ? `default` : `transparent`
109
72
  if (!documentId) return tone
73
+ if (isPatching) tone = isDarkMode ? `default` : `transparent`
110
74
  if (isDragging) tone = `positive`
111
75
 
112
76
  if (state?.requireValidation && !isValidating && validation.length > 0) {
@@ -121,6 +85,7 @@ export function DocumentCard(props: DocumentCardProps) {
121
85
  }, [
122
86
  defaultCardTone,
123
87
  userRoleCanDrop,
88
+ isPatching,
124
89
  isDarkMode,
125
90
  documentId,
126
91
  isDragging,
@@ -176,13 +141,18 @@ export function DocumentCard(props: DocumentCardProps) {
176
141
  <Flex align="center" justify="space-between" gap={1}>
177
142
  <Box flex={1}>
178
143
  <Preview
179
- layout="default"
144
+ // Like as in desk lists, except it has an intermittent loading state
145
+ // layout="default"
146
+ // Like in the PTE, with no loading state
147
+ layout="block"
180
148
  value={item}
181
149
  schemaType={schema.get(item._type) as SchemaType}
182
150
  />
183
151
  </Box>
184
152
  <Box style={{flexShrink: 0}}>
185
- {hasError || isDragDisabled ? null : <DragHandleIcon />}
153
+ {hasError || isDragDisabled || isPatching ? null : (
154
+ <DragHandleIcon />
155
+ )}
186
156
  </Box>
187
157
  </Flex>
188
158
  </Card>
@@ -209,13 +179,21 @@ export function DocumentCard(props: DocumentCardProps) {
209
179
  type={item._type}
210
180
  disabled={!userRoleCanDrop}
211
181
  />
212
- {isLastState ? (
182
+ {isLastState && states.length <= 3 ? (
213
183
  <CompleteButton
214
184
  documentId={documentId}
215
185
  disabled={!userRoleCanDrop}
216
186
  />
217
187
  ) : null}
218
188
  </Flex>
189
+ {isLastState && states.length > 3 ? (
190
+ <Stack paddingTop={2}>
191
+ <CompleteButton
192
+ documentId={documentId}
193
+ disabled={!userRoleCanDrop}
194
+ />
195
+ </Stack>
196
+ ) : null}
219
197
  </Card>
220
198
  </Stack>
221
199
  </Card>
@@ -11,6 +11,7 @@ import {DocumentCard} from './DocumentCard'
11
11
  type DocumentListProps = {
12
12
  data: SanityDocumentWithMetadata[]
13
13
  invalidDocumentIds: string[]
14
+ patchingIds: string[]
14
15
  selectedSchemaTypes: string[]
15
16
  selectedUserIds: string[]
16
17
  state: State
@@ -28,6 +29,7 @@ export default function DocumentList(props: DocumentListProps) {
28
29
  const {
29
30
  data = [],
30
31
  invalidDocumentIds,
32
+ patchingIds,
31
33
  selectedSchemaTypes,
32
34
  selectedUserIds,
33
35
  state,
@@ -51,7 +53,7 @@ export default function DocumentList(props: DocumentListProps) {
51
53
  getScrollElement: () => parentRef.current,
52
54
  getItemKey: (index) => dataFiltered[index]?._metadata?.documentId ?? index,
53
55
  estimateSize: () => 113,
54
- overscan: 5,
56
+ overscan: 10,
55
57
  })
56
58
 
57
59
  if (!data.length) {
@@ -70,7 +72,6 @@ export default function DocumentList(props: DocumentListProps) {
70
72
  scrollBehavior: 'auto',
71
73
  }}
72
74
  >
73
- {/* {dataFiltered.map((item, itemIndex) => { */}
74
75
  {rowVirtualizer.getVirtualItems().map((virtualItem) => {
75
76
  const item = dataFiltered[virtualItem.index]
76
77
 
@@ -83,6 +84,7 @@ export default function DocumentList(props: DocumentListProps) {
83
84
  const isInvalid = invalidDocumentIds.includes(documentId)
84
85
  const meInAssignees = user?.id ? assignees?.includes(user.id) : false
85
86
  const isDragDisabled =
87
+ patchingIds.includes(documentId) ||
86
88
  !userRoleCanDrop ||
87
89
  isInvalid ||
88
90
  !(state.requireAssignment
@@ -106,6 +108,7 @@ export default function DocumentList(props: DocumentListProps) {
106
108
  <DocumentCard
107
109
  userRoleCanDrop={userRoleCanDrop}
108
110
  isDragDisabled={isDragDisabled}
111
+ isPatching={patchingIds.includes(documentId)}
109
112
  isDragging={draggableSnapshot.isDragging}
110
113
  item={item}
111
114
  toggleInvalidDocumentId={toggleInvalidDocumentId}
@@ -139,7 +139,7 @@ export default function Filters(props: FiltersProps) {
139
139
  )}
140
140
  </Flex>
141
141
 
142
- {schemaTypes.length > 0 ? (
142
+ {schemaTypes.length > 1 ? (
143
143
  <Flex align="center" gap={1}>
144
144
  {schemaTypes.map((typeName) => {
145
145
  const schemaType = schema.get(typeName)
@@ -1,21 +1,9 @@
1
- import {Flex, Card, Badge, BadgeTone} from '@sanity/ui'
2
1
  import {InfoOutlineIcon, UserIcon} from '@sanity/icons'
2
+ import {Badge, BadgeTone, Box, Card, Flex, Text} from '@sanity/ui'
3
3
  import styled, {css} from 'styled-components'
4
4
 
5
+ import {State} from '../../types'
5
6
  import {Status} from './Status'
6
- import {
7
- // Operation,
8
- State,
9
- } from '../../types'
10
-
11
- type StateTitleProps = {
12
- state: State
13
- requireAssignment: boolean
14
- userRoleCanDrop: boolean
15
- isDropDisabled: boolean
16
- draggingFrom: string
17
- // operation?: Operation
18
- }
19
7
 
20
8
  const StyledStickyCard = styled(Card)(
21
9
  () => css`
@@ -25,8 +13,24 @@ const StyledStickyCard = styled(Card)(
25
13
  `
26
14
  )
27
15
 
16
+ type StateTitleProps = {
17
+ state: State
18
+ requireAssignment: boolean
19
+ userRoleCanDrop: boolean
20
+ isDropDisabled: boolean
21
+ draggingFrom: string
22
+ documentCount: number
23
+ }
24
+
28
25
  export default function StateTitle(props: StateTitleProps) {
29
- const {state, requireAssignment, userRoleCanDrop, isDropDisabled, draggingFrom} = props
26
+ const {
27
+ state,
28
+ requireAssignment,
29
+ userRoleCanDrop,
30
+ isDropDisabled,
31
+ draggingFrom,
32
+ documentCount,
33
+ } = props
30
34
 
31
35
  let tone: BadgeTone = 'default'
32
36
  const isSource = draggingFrom === state.id
@@ -39,7 +43,11 @@ export default function StateTitle(props: StateTitleProps) {
39
43
  <StyledStickyCard paddingY={4} padding={3} tone="inherit">
40
44
  <Flex gap={3} align="center">
41
45
  <Badge
42
- mode={(draggingFrom && !isDropDisabled) || isSource ? 'default' : 'outline'}
46
+ mode={
47
+ (draggingFrom && !isDropDisabled) || isSource
48
+ ? 'default'
49
+ : 'outline'
50
+ }
43
51
  tone={tone}
44
52
  muted={!userRoleCanDrop || isDropDisabled}
45
53
  >
@@ -57,16 +65,13 @@ export default function StateTitle(props: StateTitleProps) {
57
65
  icon={UserIcon}
58
66
  />
59
67
  ) : null}
60
- {/* {operation ? (
61
- <Status
62
- text={
63
- operation === 'publish'
64
- ? `A document moved to this State will also publish the current Draft`
65
- : `A document moved to this State will also unpublish the current Published version`
66
- }
67
- icon={operation === 'publish' ? PublishIcon : UnpublishIcon}
68
- />
69
- ) : null} */}
68
+ <Box flex={1}>
69
+ {documentCount > 0 ? (
70
+ <Text weight="semibold" align="right" size={1}>
71
+ {documentCount}
72
+ </Text>
73
+ ) : null}
74
+ </Box>
70
75
  </Flex>
71
76
  </StyledStickyCard>
72
77
  )
@@ -51,6 +51,18 @@ export default function Verify(props: VerifyProps) {
51
51
  }, [] as string[])
52
52
  : []
53
53
 
54
+ const documentsWithDuplicatedOrderIds = data?.length
55
+ ? data.reduce((acc, cur) => {
56
+ const {documentId, orderRank} = cur._metadata ?? {}
57
+
58
+ return orderRank &&
59
+ data.filter((d) => d._metadata?.orderRank === orderRank).length > 1 &&
60
+ documentId
61
+ ? [...acc, documentId]
62
+ : acc
63
+ }, [] as string[])
64
+ : []
65
+
54
66
  // Updates metadata documents to a valid, existing state
55
67
  const correctDocuments = React.useCallback(
56
68
  async (ids: string[]) => {
@@ -118,17 +130,26 @@ export default function Verify(props: VerifyProps) {
118
130
  status: 'info',
119
131
  })
120
132
 
121
- // Get first order value
122
- const firstOrder = data[0]?._metadata?.orderRank
123
- let newLexo =
133
+ // Get first and second order values
134
+ const [firstOrder, secondOrder] = [...data]
135
+ .slice(0, 2)
136
+ .map((d) => d._metadata?.orderRank)
137
+ const minLexo =
124
138
  firstOrder && data.length !== ids.length
125
139
  ? LexoRank.parse(firstOrder)
126
140
  : LexoRank.min()
141
+ const maxLexo =
142
+ secondOrder && data.length !== ids.length
143
+ ? LexoRank.parse(secondOrder)
144
+ : LexoRank.max()
145
+ let newLexo = minLexo.between(maxLexo)
146
+ const lastLexo = maxLexo
127
147
 
128
148
  const tx = client.transaction()
129
149
 
150
+ // Create a new in-between value for each document
130
151
  for (let index = 0; index < ids.length; index += 1) {
131
- newLexo = newLexo.genNext().genNext()
152
+ newLexo = newLexo.between(lastLexo)
132
153
 
133
154
  tx.patch(`workflow-metadata.${ids[index]}`, {
134
155
  set: {orderRank: newLexo.toString()},
@@ -208,6 +229,17 @@ export default function Verify(props: VerifyProps) {
208
229
  }
209
230
  />
210
231
  ) : null}
232
+ {documentsWithDuplicatedOrderIds.length > 0 ? (
233
+ <Button
234
+ tone="caution"
235
+ onClick={() => addOrderToDocuments(documentsWithDuplicatedOrderIds)}
236
+ text={
237
+ documentsWithDuplicatedOrderIds.length === 1
238
+ ? `Set Unique Order for 1 Document`
239
+ : `Set Unique Order for ${documentsWithDuplicatedOrderIds.length} Documents`
240
+ }
241
+ />
242
+ ) : null}
211
243
  {orphanedMetadataDocumentIds.length > 0 ? (
212
244
  <Button
213
245
  text="Cleanup orphaned metadata"