sanity-plugin-workflow 1.0.0-beta.6 → 1.0.0-beta.8
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/lib/index.esm.js +215 -73
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +215 -73
- package/lib/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/DocumentCard/CompleteButton.tsx +47 -32
- package/src/components/DocumentCard/index.tsx +20 -3
- package/src/components/DocumentList.tsx +6 -2
- package/src/components/Filters.tsx +1 -1
- package/src/components/Verify.tsx +80 -39
- package/src/components/WorkflowTool.tsx +113 -49
- package/src/helpers/generateMultipleOrderRanks.ts +80 -0
- package/src/hooks/useWorkflowDocuments.tsx +11 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sanity-plugin-workflow",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.8",
|
|
4
4
|
"description": "A demonstration of a custom content publishing workflow using Sanity.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"rimraf": "^4.1.2",
|
|
88
88
|
"sanity": "^3.3.1",
|
|
89
89
|
"semantic-release": "^20.1.0",
|
|
90
|
-
"typescript": "^
|
|
90
|
+
"typescript": "^5.0.0"
|
|
91
91
|
},
|
|
92
92
|
"peerDependencies": {
|
|
93
93
|
"@sanity/ui": "^1.2.2",
|
|
@@ -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,
|
|
@@ -68,6 +70,7 @@ export function DocumentCard(props: DocumentCardProps) {
|
|
|
68
70
|
|
|
69
71
|
if (!userRoleCanDrop) return isDarkMode ? `default` : `transparent`
|
|
70
72
|
if (!documentId) return tone
|
|
73
|
+
if (isPatching) tone = isDarkMode ? `default` : `transparent`
|
|
71
74
|
if (isDragging) tone = `positive`
|
|
72
75
|
|
|
73
76
|
if (state?.requireValidation && !isValidating && validation.length > 0) {
|
|
@@ -82,6 +85,7 @@ export function DocumentCard(props: DocumentCardProps) {
|
|
|
82
85
|
}, [
|
|
83
86
|
defaultCardTone,
|
|
84
87
|
userRoleCanDrop,
|
|
88
|
+
isPatching,
|
|
85
89
|
isDarkMode,
|
|
86
90
|
documentId,
|
|
87
91
|
isDragging,
|
|
@@ -137,13 +141,18 @@ export function DocumentCard(props: DocumentCardProps) {
|
|
|
137
141
|
<Flex align="center" justify="space-between" gap={1}>
|
|
138
142
|
<Box flex={1}>
|
|
139
143
|
<Preview
|
|
140
|
-
|
|
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"
|
|
141
148
|
value={item}
|
|
142
149
|
schemaType={schema.get(item._type) as SchemaType}
|
|
143
150
|
/>
|
|
144
151
|
</Box>
|
|
145
152
|
<Box style={{flexShrink: 0}}>
|
|
146
|
-
{hasError || isDragDisabled ? null :
|
|
153
|
+
{hasError || isDragDisabled || isPatching ? null : (
|
|
154
|
+
<DragHandleIcon />
|
|
155
|
+
)}
|
|
147
156
|
</Box>
|
|
148
157
|
</Flex>
|
|
149
158
|
</Card>
|
|
@@ -170,13 +179,21 @@ export function DocumentCard(props: DocumentCardProps) {
|
|
|
170
179
|
type={item._type}
|
|
171
180
|
disabled={!userRoleCanDrop}
|
|
172
181
|
/>
|
|
173
|
-
{isLastState ? (
|
|
182
|
+
{isLastState && states.length <= 3 ? (
|
|
174
183
|
<CompleteButton
|
|
175
184
|
documentId={documentId}
|
|
176
185
|
disabled={!userRoleCanDrop}
|
|
177
186
|
/>
|
|
178
187
|
) : null}
|
|
179
188
|
</Flex>
|
|
189
|
+
{isLastState && states.length > 3 ? (
|
|
190
|
+
<Stack paddingTop={2}>
|
|
191
|
+
<CompleteButton
|
|
192
|
+
documentId={documentId}
|
|
193
|
+
disabled={!userRoleCanDrop}
|
|
194
|
+
/>
|
|
195
|
+
</Stack>
|
|
196
|
+
) : null}
|
|
180
197
|
</Card>
|
|
181
198
|
</Stack>
|
|
182
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,
|
|
@@ -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
|
|
@@ -92,7 +94,8 @@ export default function DocumentList(props: DocumentListProps) {
|
|
|
92
94
|
return (
|
|
93
95
|
<Draggable
|
|
94
96
|
// The metadata's documentId is always the published one to avoid rerendering
|
|
95
|
-
key={documentId}
|
|
97
|
+
// key={documentId}
|
|
98
|
+
key={virtualItem.key}
|
|
96
99
|
draggableId={documentId}
|
|
97
100
|
index={virtualItem.index}
|
|
98
101
|
isDragDisabled={isDragDisabled}
|
|
@@ -106,6 +109,7 @@ export default function DocumentList(props: DocumentListProps) {
|
|
|
106
109
|
<DocumentCard
|
|
107
110
|
userRoleCanDrop={userRoleCanDrop}
|
|
108
111
|
isDragDisabled={isDragDisabled}
|
|
112
|
+
isPatching={patchingIds.includes(documentId)}
|
|
109
113
|
isDragging={draggableSnapshot.isDragging}
|
|
110
114
|
item={item}
|
|
111
115
|
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)
|
|
@@ -5,6 +5,7 @@ import {useClient} from 'sanity'
|
|
|
5
5
|
import {UserExtended} from 'sanity-plugin-utils'
|
|
6
6
|
|
|
7
7
|
import {API_VERSION} from '../constants'
|
|
8
|
+
import {generateMultipleOrderRanks} from '../helpers/generateMultipleOrderRanks'
|
|
8
9
|
import {SanityDocumentWithMetadata, State} from '../types'
|
|
9
10
|
import FloatingCard from './FloatingCard'
|
|
10
11
|
|
|
@@ -32,16 +33,17 @@ export default function Verify(props: VerifyProps) {
|
|
|
32
33
|
}, [] as string[])
|
|
33
34
|
: []
|
|
34
35
|
|
|
35
|
-
const documentsWithInvalidUserIds =
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
const documentsWithInvalidUserIds =
|
|
37
|
+
data?.length && userList?.length
|
|
38
|
+
? data.reduce((acc, cur) => {
|
|
39
|
+
const {documentId, assignees} = cur._metadata ?? {}
|
|
40
|
+
const allAssigneesExist = assignees?.length
|
|
41
|
+
? assignees?.every((a) => userList.find((u) => u.id === a))
|
|
42
|
+
: true
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
return !allAssigneesExist && documentId ? [...acc, documentId] : acc
|
|
45
|
+
}, [] as string[])
|
|
46
|
+
: []
|
|
45
47
|
|
|
46
48
|
const documentsWithoutOrderIds = data?.length
|
|
47
49
|
? data.reduce((acc, cur) => {
|
|
@@ -130,20 +132,51 @@ export default function Verify(props: VerifyProps) {
|
|
|
130
132
|
status: 'info',
|
|
131
133
|
})
|
|
132
134
|
|
|
133
|
-
// Get first order
|
|
134
|
-
const firstOrder = data
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
// Get first and second order values, if they exist
|
|
136
|
+
const [firstOrder, secondOrder] = [...data]
|
|
137
|
+
.slice(0, 2)
|
|
138
|
+
.map((d) => d._metadata?.orderRank)
|
|
139
|
+
const minLexo = firstOrder ? LexoRank.parse(firstOrder) : undefined
|
|
140
|
+
const maxLexo = secondOrder ? LexoRank.parse(secondOrder) : undefined
|
|
141
|
+
const ranks = generateMultipleOrderRanks(ids.length, minLexo, maxLexo)
|
|
139
142
|
|
|
140
143
|
const tx = client.transaction()
|
|
141
144
|
|
|
145
|
+
// Create a new in-between value for each document
|
|
142
146
|
for (let index = 0; index < ids.length; index += 1) {
|
|
143
|
-
|
|
147
|
+
tx.patch(`workflow-metadata.${ids[index]}`, {
|
|
148
|
+
set: {orderRank: ranks[index].toString()},
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
await tx.commit()
|
|
153
|
+
|
|
154
|
+
toast.push({
|
|
155
|
+
title: `Added order to ${
|
|
156
|
+
ids.length === 1 ? `1 Document` : `${ids.length} Documents`
|
|
157
|
+
}`,
|
|
158
|
+
status: 'success',
|
|
159
|
+
})
|
|
160
|
+
},
|
|
161
|
+
[data, client, toast]
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
// Reset order value on all metadata documents
|
|
165
|
+
const resetOrderOfAllDocuments = React.useCallback(
|
|
166
|
+
async (ids: string[]) => {
|
|
167
|
+
toast.push({
|
|
168
|
+
title: 'Adding ordering...',
|
|
169
|
+
status: 'info',
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
const ranks = generateMultipleOrderRanks(ids.length)
|
|
144
173
|
|
|
174
|
+
const tx = client.transaction()
|
|
175
|
+
|
|
176
|
+
// Create a new in-between value for each document
|
|
177
|
+
for (let index = 0; index < ids.length; index += 1) {
|
|
145
178
|
tx.patch(`workflow-metadata.${ids[index]}`, {
|
|
146
|
-
set: {orderRank:
|
|
179
|
+
set: {orderRank: ranks[index].toString()},
|
|
147
180
|
})
|
|
148
181
|
}
|
|
149
182
|
|
|
@@ -190,6 +223,7 @@ export default function Verify(props: VerifyProps) {
|
|
|
190
223
|
{documentsWithoutValidMetadataIds.length > 0 ? (
|
|
191
224
|
<Button
|
|
192
225
|
tone="caution"
|
|
226
|
+
mode="ghost"
|
|
193
227
|
onClick={() => correctDocuments(documentsWithoutValidMetadataIds)}
|
|
194
228
|
text={
|
|
195
229
|
documentsWithoutValidMetadataIds.length === 1
|
|
@@ -201,6 +235,7 @@ export default function Verify(props: VerifyProps) {
|
|
|
201
235
|
{documentsWithInvalidUserIds.length > 0 ? (
|
|
202
236
|
<Button
|
|
203
237
|
tone="caution"
|
|
238
|
+
mode="ghost"
|
|
204
239
|
onClick={() => removeUsersFromDocuments(documentsWithInvalidUserIds)}
|
|
205
240
|
text={
|
|
206
241
|
documentsWithInvalidUserIds.length === 1
|
|
@@ -212,6 +247,7 @@ export default function Verify(props: VerifyProps) {
|
|
|
212
247
|
{documentsWithoutOrderIds.length > 0 ? (
|
|
213
248
|
<Button
|
|
214
249
|
tone="caution"
|
|
250
|
+
mode="ghost"
|
|
215
251
|
onClick={() => addOrderToDocuments(documentsWithoutOrderIds)}
|
|
216
252
|
text={
|
|
217
253
|
documentsWithoutOrderIds.length === 1
|
|
@@ -221,36 +257,41 @@ export default function Verify(props: VerifyProps) {
|
|
|
221
257
|
/>
|
|
222
258
|
) : null}
|
|
223
259
|
{documentsWithDuplicatedOrderIds.length > 0 ? (
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
260
|
+
<>
|
|
261
|
+
<Button
|
|
262
|
+
tone="caution"
|
|
263
|
+
mode="ghost"
|
|
264
|
+
onClick={() => addOrderToDocuments(documentsWithDuplicatedOrderIds)}
|
|
265
|
+
text={
|
|
266
|
+
documentsWithDuplicatedOrderIds.length === 1
|
|
267
|
+
? `Set Unique Order for 1 Document`
|
|
268
|
+
: `Set Unique Order for ${documentsWithDuplicatedOrderIds.length} Documents`
|
|
269
|
+
}
|
|
270
|
+
/>
|
|
271
|
+
<Button
|
|
272
|
+
tone="caution"
|
|
273
|
+
mode="ghost"
|
|
274
|
+
onClick={() =>
|
|
275
|
+
resetOrderOfAllDocuments(
|
|
276
|
+
data.map((doc) => String(doc._metadata?.documentId))
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
text={
|
|
280
|
+
data.length === 1
|
|
281
|
+
? `Reset Order for 1 Document`
|
|
282
|
+
: `Reset Order for all ${data.length} Documents`
|
|
283
|
+
}
|
|
284
|
+
/>
|
|
285
|
+
</>
|
|
233
286
|
) : null}
|
|
234
287
|
{orphanedMetadataDocumentIds.length > 0 ? (
|
|
235
288
|
<Button
|
|
236
289
|
text="Cleanup orphaned metadata"
|
|
237
290
|
onClick={handleOrphans}
|
|
238
291
|
tone="caution"
|
|
292
|
+
mode="ghost"
|
|
239
293
|
/>
|
|
240
294
|
) : null}
|
|
241
|
-
{/* <Button
|
|
242
|
-
tone="caution"
|
|
243
|
-
onClick={() =>
|
|
244
|
-
addOrderToDocuments(
|
|
245
|
-
data.map((doc) => String(doc._metadata?.documentId))
|
|
246
|
-
)
|
|
247
|
-
}
|
|
248
|
-
text={
|
|
249
|
-
data.length === 1
|
|
250
|
-
? `Reset Order for 1 Document`
|
|
251
|
-
: `Reset Order for all ${data.length} Documents`
|
|
252
|
-
}
|
|
253
|
-
/> */}
|
|
254
295
|
</FloatingCard>
|
|
255
296
|
)
|
|
256
297
|
}
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DragDropContext,
|
|
3
|
+
DraggableChildrenFn,
|
|
3
4
|
DragStart,
|
|
4
5
|
Droppable,
|
|
5
6
|
DropResult,
|
|
6
7
|
} from '@hello-pangea/dnd'
|
|
7
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
Box,
|
|
10
|
+
Card,
|
|
11
|
+
Container,
|
|
12
|
+
Flex,
|
|
13
|
+
Grid,
|
|
14
|
+
Spinner,
|
|
15
|
+
useTheme,
|
|
16
|
+
useToast,
|
|
17
|
+
} from '@sanity/ui'
|
|
8
18
|
import {LexoRank} from 'lexorank'
|
|
9
19
|
import React from 'react'
|
|
10
20
|
import {Tool, useCurrentUser} from 'sanity'
|
|
@@ -30,6 +40,7 @@ export default function WorkflowTool(props: WorkflowToolProps) {
|
|
|
30
40
|
|
|
31
41
|
const isDarkMode = useTheme().sanity.color.dark
|
|
32
42
|
const defaultCardTone = isDarkMode ? 'default' : 'transparent'
|
|
43
|
+
const toast = useToast()
|
|
33
44
|
|
|
34
45
|
const userList = useProjectUsers({apiVersion: API_VERSION})
|
|
35
46
|
|
|
@@ -39,6 +50,7 @@ export default function WorkflowTool(props: WorkflowToolProps) {
|
|
|
39
50
|
: []
|
|
40
51
|
|
|
41
52
|
const {workflowData, operations} = useWorkflowDocuments(schemaTypes)
|
|
53
|
+
const [patchingIds, setPatchingIds] = React.useState<string[]>([])
|
|
42
54
|
|
|
43
55
|
// Data to display in cards
|
|
44
56
|
const {data, loading, error} = workflowData
|
|
@@ -128,48 +140,94 @@ export default function WorkflowTool(props: WorkflowToolProps) {
|
|
|
128
140
|
const destinationStateItems = [
|
|
129
141
|
...filterItemsAndSort(data, destination.droppableId, [], null),
|
|
130
142
|
]
|
|
143
|
+
const destinationStateIndex = states.findIndex(
|
|
144
|
+
(s) => s.id === destination.droppableId
|
|
145
|
+
)
|
|
146
|
+
const globalStateMinimumRank = data[0]._metadata.orderRank
|
|
147
|
+
const globalStateMaximumRank = data[data.length - 1]._metadata.orderRank
|
|
131
148
|
|
|
132
149
|
let newOrder
|
|
133
150
|
|
|
134
151
|
if (!destinationStateItems.length) {
|
|
135
152
|
// Only item in state
|
|
136
153
|
// New minimum rank
|
|
137
|
-
|
|
154
|
+
if (destinationStateIndex === 0) {
|
|
155
|
+
// Only the first state should generate an absolute minimum rank
|
|
156
|
+
newOrder = LexoRank.min().toString()
|
|
157
|
+
} else {
|
|
158
|
+
// Otherwise create the next rank between min and the globally minimum rank
|
|
159
|
+
newOrder = LexoRank.parse(globalStateMinimumRank)
|
|
160
|
+
.between(LexoRank.min())
|
|
161
|
+
.toString()
|
|
162
|
+
}
|
|
138
163
|
} else if (destination.index === 0) {
|
|
139
164
|
// Now first item in order
|
|
140
165
|
const firstItemOrderRank = [...destinationStateItems].shift()?._metadata
|
|
141
166
|
?.orderRank
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
167
|
+
|
|
168
|
+
if (firstItemOrderRank && typeof firstItemOrderRank === 'string') {
|
|
169
|
+
newOrder = LexoRank.parse(firstItemOrderRank).genPrev().toString()
|
|
170
|
+
} else if (destinationStateIndex === 0) {
|
|
171
|
+
// Only the first state should generate an absolute minimum rank
|
|
172
|
+
newOrder = LexoRank.min().toString()
|
|
173
|
+
} else {
|
|
174
|
+
// Otherwise create the next rank between min and the globally minimum rank
|
|
175
|
+
newOrder = LexoRank.parse(globalStateMinimumRank)
|
|
176
|
+
.between(LexoRank.min())
|
|
177
|
+
.toString()
|
|
178
|
+
}
|
|
146
179
|
} else if (destination.index + 1 === destinationStateItems.length) {
|
|
147
180
|
// Now last item in order
|
|
148
181
|
const lastItemOrderRank = [...destinationStateItems].pop()?._metadata
|
|
149
182
|
?.orderRank
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
183
|
+
|
|
184
|
+
if (lastItemOrderRank && typeof lastItemOrderRank === 'string') {
|
|
185
|
+
newOrder = LexoRank.parse(lastItemOrderRank).genNext().toString()
|
|
186
|
+
} else if (destinationStateIndex === states.length - 1) {
|
|
187
|
+
// Only the last state should generate an absolute maximum rank
|
|
188
|
+
newOrder = LexoRank.max().toString()
|
|
189
|
+
} else {
|
|
190
|
+
// Otherwise create the next rank between max and the globally maximum rank
|
|
191
|
+
newOrder = LexoRank.parse(globalStateMaximumRank)
|
|
192
|
+
.between(LexoRank.min())
|
|
193
|
+
.toString()
|
|
194
|
+
}
|
|
154
195
|
} else {
|
|
155
196
|
// Must be between two items
|
|
156
197
|
const itemBefore = destinationStateItems[destination.index - 1]
|
|
157
198
|
const itemBeforeRank = itemBefore?._metadata?.orderRank
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
199
|
+
let itemBeforeRankParsed
|
|
200
|
+
if (itemBeforeRank) {
|
|
201
|
+
itemBeforeRankParsed = LexoRank.parse(itemBeforeRank)
|
|
202
|
+
} else if (destinationStateIndex === 0) {
|
|
203
|
+
itemBeforeRankParsed = LexoRank.min()
|
|
204
|
+
} else {
|
|
205
|
+
itemBeforeRankParsed = LexoRank.parse(globalStateMinimumRank)
|
|
206
|
+
}
|
|
207
|
+
|
|
161
208
|
const itemAfter = destinationStateItems[destination.index]
|
|
162
209
|
const itemAfterRank = itemAfter?._metadata?.orderRank
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
210
|
+
let itemAfterRankParsed
|
|
211
|
+
if (itemAfterRank) {
|
|
212
|
+
itemAfterRankParsed = LexoRank.parse(itemAfterRank)
|
|
213
|
+
} else if (destinationStateIndex === states.length - 1) {
|
|
214
|
+
itemAfterRankParsed = LexoRank.max()
|
|
215
|
+
} else {
|
|
216
|
+
itemAfterRankParsed = LexoRank.parse(globalStateMaximumRank)
|
|
217
|
+
}
|
|
166
218
|
|
|
167
219
|
newOrder = itemBeforeRankParsed.between(itemAfterRankParsed).toString()
|
|
168
220
|
}
|
|
169
221
|
|
|
170
|
-
|
|
222
|
+
setPatchingIds([...patchingIds, draggableId])
|
|
223
|
+
toast.push({
|
|
224
|
+
status: 'info',
|
|
225
|
+
title: 'Updating document state...',
|
|
226
|
+
})
|
|
227
|
+
await move(draggableId, destination, states, newOrder)
|
|
228
|
+
setPatchingIds((ids: string[]) => ids.filter((id) => id !== draggableId))
|
|
171
229
|
},
|
|
172
|
-
[data, move, states]
|
|
230
|
+
[data, patchingIds, toast, move, states]
|
|
173
231
|
)
|
|
174
232
|
|
|
175
233
|
// Used for the user filter UI
|
|
@@ -224,6 +282,41 @@ export default function WorkflowTool(props: WorkflowToolProps) {
|
|
|
224
282
|
[]
|
|
225
283
|
)
|
|
226
284
|
|
|
285
|
+
const Clone: DraggableChildrenFn = React.useCallback(
|
|
286
|
+
(provided, snapshot, rubric) => {
|
|
287
|
+
const item = data.find(
|
|
288
|
+
(doc) => doc?._metadata?.documentId === rubric.draggableId
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
return (
|
|
292
|
+
<div
|
|
293
|
+
{...provided.draggableProps}
|
|
294
|
+
{...provided.dragHandleProps}
|
|
295
|
+
ref={provided.innerRef}
|
|
296
|
+
>
|
|
297
|
+
{item ? (
|
|
298
|
+
<DocumentCard
|
|
299
|
+
// Assumed false, if it's dragging it's not disabled
|
|
300
|
+
isDragDisabled={false}
|
|
301
|
+
// Assumed false, if it's dragging it's not patching
|
|
302
|
+
isPatching={false}
|
|
303
|
+
// Assumed true, if you can drag it you can drop it
|
|
304
|
+
userRoleCanDrop
|
|
305
|
+
isDragging={snapshot.isDragging}
|
|
306
|
+
item={item}
|
|
307
|
+
states={states}
|
|
308
|
+
toggleInvalidDocumentId={toggleInvalidDocumentId}
|
|
309
|
+
userList={userList}
|
|
310
|
+
/>
|
|
311
|
+
) : (
|
|
312
|
+
<Feedback title="Item not found" tone="caution" />
|
|
313
|
+
)}
|
|
314
|
+
</div>
|
|
315
|
+
)
|
|
316
|
+
},
|
|
317
|
+
[data, states, toggleInvalidDocumentId, userList]
|
|
318
|
+
)
|
|
319
|
+
|
|
227
320
|
if (!states?.length) {
|
|
228
321
|
return (
|
|
229
322
|
<Container width={1} padding={5}>
|
|
@@ -297,37 +390,7 @@ export default function WorkflowTool(props: WorkflowToolProps) {
|
|
|
297
390
|
isDropDisabled={isDropDisabled}
|
|
298
391
|
// props required for virtualization
|
|
299
392
|
mode="virtual"
|
|
300
|
-
|
|
301
|
-
renderClone={(provided, snapshot, rubric) => {
|
|
302
|
-
const item = data.find(
|
|
303
|
-
(doc) =>
|
|
304
|
-
doc?._metadata?.documentId === rubric.draggableId
|
|
305
|
-
)
|
|
306
|
-
|
|
307
|
-
return (
|
|
308
|
-
<div
|
|
309
|
-
{...provided.draggableProps}
|
|
310
|
-
{...provided.dragHandleProps}
|
|
311
|
-
ref={provided.innerRef}
|
|
312
|
-
>
|
|
313
|
-
{item ? (
|
|
314
|
-
<DocumentCard
|
|
315
|
-
isDragDisabled={false}
|
|
316
|
-
userRoleCanDrop={userRoleCanDrop}
|
|
317
|
-
isDragging={snapshot.isDragging}
|
|
318
|
-
item={item}
|
|
319
|
-
states={states}
|
|
320
|
-
toggleInvalidDocumentId={
|
|
321
|
-
toggleInvalidDocumentId
|
|
322
|
-
}
|
|
323
|
-
userList={userList}
|
|
324
|
-
/>
|
|
325
|
-
) : (
|
|
326
|
-
<Feedback title="Item not found" tone="caution" />
|
|
327
|
-
)}
|
|
328
|
-
</div>
|
|
329
|
-
)
|
|
330
|
-
}}
|
|
393
|
+
renderClone={Clone}
|
|
331
394
|
>
|
|
332
395
|
{(provided, snapshot) => (
|
|
333
396
|
<Card
|
|
@@ -349,6 +412,7 @@ export default function WorkflowTool(props: WorkflowToolProps) {
|
|
|
349
412
|
<DocumentList
|
|
350
413
|
data={data}
|
|
351
414
|
invalidDocumentIds={invalidDocumentIds}
|
|
415
|
+
patchingIds={patchingIds}
|
|
352
416
|
selectedSchemaTypes={selectedSchemaTypes}
|
|
353
417
|
selectedUserIds={selectedUserIds}
|
|
354
418
|
state={state}
|