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/README.md +9 -5
- package/lib/index.esm.js +171 -71
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +171 -71
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/DocumentCard/CompleteButton.tsx +47 -32
- package/src/components/DocumentCard/index.tsx +20 -42
- package/src/components/DocumentList.tsx +5 -2
- package/src/components/Filters.tsx +1 -1
- package/src/components/StateTitle/index.tsx +31 -26
- package/src/components/Verify.tsx +36 -4
- package/src/components/WorkflowTool.tsx +122 -51
- package/src/hooks/useWorkflowDocuments.tsx +11 -8
- package/src/types/index.ts +0 -3
package/package.json
CHANGED
|
@@ -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
|
|
19
|
-
(
|
|
20
|
-
|
|
21
|
-
.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
description: id,
|
|
35
|
+
.catch(() => {
|
|
36
|
+
toast.push({
|
|
37
|
+
status: 'error',
|
|
38
|
+
title: 'Could not complete Workflow',
|
|
39
|
+
})
|
|
34
40
|
})
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
)
|
|
41
|
+
},
|
|
42
|
+
[client, toast]
|
|
43
|
+
)
|
|
39
44
|
|
|
40
45
|
return (
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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 :
|
|
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:
|
|
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 >
|
|
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 {
|
|
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={
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
122
|
-
const firstOrder = data
|
|
123
|
-
|
|
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.
|
|
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"
|