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

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,26 +1,25 @@
1
- import React from 'react'
2
- import {Flex, Card, Grid, Spinner, Container, useTheme} from '@sanity/ui'
3
- import {Feedback, useProjectUsers} from 'sanity-plugin-utils'
4
- import {Tool, useCurrentUser} from 'sanity'
5
1
  import {
6
2
  DragDropContext,
3
+ DragStart,
7
4
  Droppable,
8
- Draggable,
9
5
  DropResult,
10
- DragStart,
11
- } from 'react-beautiful-dnd'
6
+ } from '@hello-pangea/dnd'
7
+ import {Box, Card, Container, Flex, Grid, Spinner, useTheme} from '@sanity/ui'
8
+ import {LexoRank} from 'lexorank'
9
+ import React from 'react'
10
+ import {Tool, useCurrentUser} from 'sanity'
11
+ import {Feedback, useProjectUsers} from 'sanity-plugin-utils'
12
12
 
13
+ import {API_VERSION} from '../constants'
14
+ import {arraysContainMatchingString} from '../helpers/arraysContainMatchingString'
15
+ import {filterItemsAndSort} from '../helpers/filterItemsAndSort'
16
+ import {useWorkflowDocuments} from '../hooks/useWorkflowDocuments'
13
17
  import {State, WorkflowConfig} from '../types'
14
18
  import {DocumentCard} from './DocumentCard'
15
- import {useWorkflowDocuments} from '../hooks/useWorkflowDocuments'
16
- import {API_VERSION} from '../constants'
17
-
18
- import Validators from './Validators'
19
+ import DocumentList from './DocumentList'
19
20
  import Filters from './Filters'
20
- import {filterItemsAndSort} from '../helpers/filterItemsAndSort'
21
- import {arraysContainMatchingString} from '../helpers/arraysContainMatchingString'
22
21
  import StateTitle from './StateTitle'
23
- import {LexoRank} from 'lexorank'
22
+ import Verify from './Verify'
24
23
 
25
24
  type WorkflowToolProps = {
26
25
  tool: Tool<WorkflowConfig>
@@ -154,14 +153,14 @@ export default function WorkflowTool(props: WorkflowToolProps) {
154
153
  : LexoRank.min().toString()
155
154
  } else {
156
155
  // Must be between two items
157
- const itemBefore = destinationStateItems[destination.index]
156
+ const itemBefore = destinationStateItems[destination.index - 1]
158
157
  const itemBeforeRank = itemBefore?._metadata?.orderRank
159
- const itemBeforeRankParsed = itemBefore._metadata.orderRank
158
+ const itemBeforeRankParsed = itemBeforeRank
160
159
  ? LexoRank.parse(itemBeforeRank)
161
160
  : LexoRank.min()
162
- const itemAfter = destinationStateItems[destination.index + 1]
161
+ const itemAfter = destinationStateItems[destination.index]
163
162
  const itemAfterRank = itemAfter?._metadata?.orderRank
164
- const itemAfterRankParsed = itemAfter._metadata.orderRank
163
+ const itemAfterRankParsed = itemAfterRank
165
164
  ? LexoRank.parse(itemAfterRank)
166
165
  : LexoRank.max()
167
166
 
@@ -173,6 +172,7 @@ export default function WorkflowTool(props: WorkflowToolProps) {
173
172
  [data, move, states]
174
173
  )
175
174
 
175
+ // Used for the user filter UI
176
176
  const uniqueAssignedUsers = React.useMemo(() => {
177
177
  const uniqueUserIds = data.reduce((acc, item) => {
178
178
  const {assignees = []} = item._metadata ?? {}
@@ -185,6 +185,7 @@ export default function WorkflowTool(props: WorkflowToolProps) {
185
185
  return userList.filter((u) => uniqueUserIds.includes(u.id))
186
186
  }, [data, userList])
187
187
 
188
+ // Selected user IDs filter the visible workflow documents
188
189
  const [selectedUserIds, setSelectedUserIds] = React.useState<string[]>(
189
190
  uniqueAssignedUsers.map((u) => u.id)
190
191
  )
@@ -199,6 +200,7 @@ export default function WorkflowTool(props: WorkflowToolProps) {
199
200
  setSelectedUserIds([])
200
201
  }, [])
201
202
 
203
+ // Selected schema types filter the visible workflow documents
202
204
  const [selectedSchemaTypes, setSelectedSchemaTypes] =
203
205
  React.useState<string[]>(schemaTypes)
204
206
  const toggleSelectedSchemaType = React.useCallback((schemaType: string) => {
@@ -209,6 +211,7 @@ export default function WorkflowTool(props: WorkflowToolProps) {
209
211
  )
210
212
  }, [])
211
213
 
214
+ // Document IDs that have validation errors
212
215
  const [invalidDocumentIds, setInvalidDocumentIds] = React.useState<string[]>(
213
216
  []
214
217
  )
@@ -245,8 +248,9 @@ export default function WorkflowTool(props: WorkflowToolProps) {
245
248
  }
246
249
 
247
250
  return (
248
- <Card height="fill" overflow="hidden">
249
- <Validators data={data} userList={userList} states={states} />
251
+ <Flex direction="column" height="fill" overflow="hidden">
252
+ <Verify data={data} userList={userList} states={states} />
253
+
250
254
  <Filters
251
255
  uniqueAssignedUsers={uniqueAssignedUsers}
252
256
  selectedUserIds={selectedUserIds}
@@ -270,100 +274,96 @@ export default function WorkflowTool(props: WorkflowToolProps) {
270
274
  key={state.id}
271
275
  borderLeft={stateIndex > 0}
272
276
  tone={defaultCardTone}
273
- height="fill"
274
- overflow="auto"
275
277
  >
276
- <StateTitle
277
- state={state}
278
- requireAssignment={state.requireAssignment ?? false}
279
- userRoleCanDrop={userRoleCanDrop}
280
- // operation={state.operation}
281
- isDropDisabled={isDropDisabled}
282
- draggingFrom={draggingFrom}
283
- />
284
- <Droppable
285
- droppableId={state.id}
286
- isDropDisabled={isDropDisabled}
287
- >
288
- {(provided, snapshot) => (
289
- <Card
290
- ref={provided.innerRef}
291
- tone={
292
- snapshot.isDraggingOver ? `primary` : defaultCardTone
293
- }
294
- height="fill"
295
- paddingTop={1}
278
+ <Flex direction="column" height="fill">
279
+ <StateTitle
280
+ state={state}
281
+ requireAssignment={state.requireAssignment ?? false}
282
+ userRoleCanDrop={userRoleCanDrop}
283
+ // operation={state.operation}
284
+ isDropDisabled={isDropDisabled}
285
+ draggingFrom={draggingFrom}
286
+ />
287
+ <Box flex={1}>
288
+ <Droppable
289
+ droppableId={state.id}
290
+ isDropDisabled={isDropDisabled}
291
+ // props required for virtualization
292
+ mode="virtual"
293
+ // TODO: Render this as a memo/callback
294
+ renderClone={(provided, snapshot, rubric) => {
295
+ const item = data.find(
296
+ (doc) =>
297
+ doc?._metadata?.documentId === rubric.draggableId
298
+ )
299
+
300
+ return (
301
+ <div
302
+ {...provided.draggableProps}
303
+ {...provided.dragHandleProps}
304
+ ref={provided.innerRef}
305
+ >
306
+ {item ? (
307
+ <DocumentCard
308
+ isDragDisabled={false}
309
+ userRoleCanDrop={userRoleCanDrop}
310
+ isDragging={snapshot.isDragging}
311
+ item={item}
312
+ states={states}
313
+ toggleInvalidDocumentId={
314
+ toggleInvalidDocumentId
315
+ }
316
+ userList={userList}
317
+ />
318
+ ) : (
319
+ <Feedback title="Item not found" tone="caution" />
320
+ )}
321
+ </div>
322
+ )
323
+ }}
296
324
  >
297
- {loading ? (
298
- <Flex padding={5} align="center" justify="center">
299
- <Spinner muted />
300
- </Flex>
301
- ) : null}
302
-
303
- {data.length > 0 &&
304
- filterItemsAndSort(
305
- data,
306
- state.id,
307
- selectedUserIds,
308
- selectedSchemaTypes
309
- ).map((item, itemIndex) => {
310
- const isInvalid = invalidDocumentIds.includes(
311
- String(item?._metadata?.documentId)
312
- )
313
- const meInAssignees = user?.id
314
- ? item?._metadata?.assignees?.includes(user.id)
315
- : false
316
- const isDragDisabled =
317
- !userRoleCanDrop ||
318
- isInvalid ||
319
- !(state.requireAssignment
320
- ? state.requireAssignment && meInAssignees
321
- : true)
322
- const {documentId} = item._metadata ?? {}
323
-
324
- if (!documentId) {
325
- return null
325
+ {(provided, snapshot) => (
326
+ <Card
327
+ ref={provided.innerRef}
328
+ tone={
329
+ snapshot.isDraggingOver
330
+ ? `primary`
331
+ : defaultCardTone
326
332
  }
327
-
328
- return (
329
- <Draggable
330
- // The metadata's documentId is always the published one to avoid rerendering
331
- key={documentId}
332
- draggableId={documentId}
333
- index={itemIndex}
334
- isDragDisabled={isDragDisabled}
335
- >
336
- {(draggableProvided, draggableSnapshot) => (
337
- <div
338
- ref={draggableProvided.innerRef}
339
- {...draggableProvided.draggableProps}
340
- {...draggableProvided.dragHandleProps}
341
- >
342
- <DocumentCard
343
- userRoleCanDrop={userRoleCanDrop}
344
- isDragDisabled={isDragDisabled}
345
- isDragging={draggableSnapshot.isDragging}
346
- item={item}
347
- toggleInvalidDocumentId={
348
- toggleInvalidDocumentId
349
- }
350
- userList={userList}
351
- states={states}
352
- />
353
- </div>
354
- )}
355
- </Draggable>
356
- )
357
- })}
358
- {provided.placeholder}
359
- </Card>
360
- )}
361
- </Droppable>
333
+ height="fill"
334
+ paddingTop={1}
335
+ >
336
+ {loading ? (
337
+ <Flex padding={5} align="center" justify="center">
338
+ <Spinner muted />
339
+ </Flex>
340
+ ) : null}
341
+
342
+ <DocumentList
343
+ data={data}
344
+ invalidDocumentIds={invalidDocumentIds}
345
+ selectedSchemaTypes={selectedSchemaTypes}
346
+ selectedUserIds={selectedUserIds}
347
+ state={state}
348
+ states={states}
349
+ toggleInvalidDocumentId={toggleInvalidDocumentId}
350
+ user={user}
351
+ userList={userList}
352
+ userRoleCanDrop={userRoleCanDrop}
353
+ />
354
+
355
+ {/* Not required for virtualized lists */}
356
+ {/* {provided.placeholder} */}
357
+ </Card>
358
+ )}
359
+ </Droppable>
360
+ </Box>
361
+ </Flex>
362
362
  </Card>
363
363
  )
364
364
  })}
365
365
  </Grid>
366
366
  </DragDropContext>
367
- </Card>
367
+ </Flex>
368
368
  )
369
369
  }
@@ -1,4 +1,4 @@
1
- import {WorkflowConfig, defineStates} from '../types'
1
+ import {defineStates, WorkflowConfig} from '../types'
2
2
 
3
3
  export const API_VERSION = `2023-01-01`
4
4
 
@@ -24,8 +24,8 @@ export const DEFAULT_CONFIG: WorkflowConfig = {
24
24
  title: 'Approved',
25
25
  color: 'success',
26
26
  roles: ['administrator'],
27
- requireAssignment: true,
28
27
  transitions: ['changesRequested'],
28
+ requireAssignment: true,
29
29
  },
30
30
  ]),
31
31
  }
@@ -8,6 +8,8 @@ export function filterItemsAndSort(
8
8
  ): SanityDocumentWithMetadata[] {
9
9
  return (
10
10
  items
11
+ // Only items that have existing documents
12
+ .filter((item) => item?._id)
11
13
  // Only items of this state
12
14
  .filter((item) => item?._metadata?.state === stateId)
13
15
  // Only items with selected users, if the document has any assigned users
@@ -1,12 +1,12 @@
1
- import React from 'react'
2
- import {useListeningQuery} from 'sanity-plugin-utils'
1
+ import {DraggableLocation} from '@hello-pangea/dnd'
3
2
  import {useToast} from '@sanity/ui'
4
- import {useClient} from 'sanity'
5
- import {DraggableLocation} from 'react-beautiful-dnd'
6
3
  import groq from 'groq'
4
+ import React from 'react'
5
+ import {useClient} from 'sanity'
6
+ import {useListeningQuery} from 'sanity-plugin-utils'
7
7
 
8
- import {SanityDocumentWithMetadata, State} from '../types'
9
8
  import {API_VERSION} from '../constants'
9
+ import {SanityDocumentWithMetadata, State} from '../types'
10
10
 
11
11
  const QUERY = groq`*[_type == "workflow.metadata"]|order(orderRank){
12
12
  "_metadata": {
@@ -24,7 +24,7 @@ const QUERY = groq`*[_type == "workflow.metadata"]|order(orderRank){
24
24
  _updatedAt
25
25
  }
26
26
  )
27
- }[defined(_id)]`
27
+ }`
28
28
 
29
29
  type WorkflowDocuments = {
30
30
  workflowData: {
package/src/index.ts CHANGED
@@ -1,15 +1,15 @@
1
1
  import {definePlugin, DocumentActionProps} from 'sanity'
2
2
 
3
- import {DEFAULT_CONFIG} from './constants'
4
- import {WorkflowConfig} from './types'
5
- import {workflowTool} from './tools'
6
- import metadata from './schema/workflow/workflow.metadata'
7
3
  import {AssignWorkflow} from './actions/AssignWorkflow'
8
4
  import {BeginWorkflow} from './actions/BeginWorkflow'
9
5
  import {CompleteWorkflow} from './actions/CompleteWorkflow'
6
+ import {UpdateWorkflow} from './actions/UpdateWorkflow'
10
7
  import {AssigneesBadge} from './badges/AssigneesBadge'
11
8
  import {StateBadge} from './badges/StateBadge'
12
- import {UpdateWorkflow} from './actions/UpdateWorkflow'
9
+ import {DEFAULT_CONFIG} from './constants'
10
+ import metadata from './schema/workflow/workflow.metadata'
11
+ import {workflowTool} from './tools'
12
+ import {WorkflowConfig} from './types'
13
13
 
14
14
  export const workflow = definePlugin<WorkflowConfig>(
15
15
  (config = DEFAULT_CONFIG) => {
@@ -1,4 +1,4 @@
1
- import {defineType, defineField} from 'sanity'
1
+ import {defineField, defineType} from 'sanity'
2
2
 
3
3
  import Field from '../../components/DocumentCard/Field'
4
4
  import UserAssignmentInput from '../../components/UserAssignmentInput'
@@ -1,5 +1,5 @@
1
- import {Tool} from 'sanity'
2
1
  import {SplitVerticalIcon} from '@sanity/icons'
2
+ import {Tool} from 'sanity'
3
3
 
4
4
  import WorkflowTool from '../components/WorkflowTool'
5
5
  import {WorkflowConfig} from '../types'
@@ -9,6 +9,7 @@ export type State = {
9
9
  // operation?: Operation
10
10
  roles?: string[]
11
11
  requireAssignment?: boolean
12
+ requireValidation?: boolean
12
13
  // From document badges
13
14
  color?: 'primary' | 'success' | 'warning' | 'danger'
14
15
  }
@@ -1,55 +0,0 @@
1
- import PropTypes from 'prop-types'
2
- import React from 'react'
3
- import {EyeOpenIcon} from '@sanity/icons'
4
-
5
- import {inferMetadataState, useWorkflowMetadata} from '../../lib/workflow'
6
- import RequestReviewWizard from '../../components/RequestReviewWizard'
7
-
8
- export function RequestReviewAction(props) {
9
- const [showWizardDialog, setShowWizardDialog] = React.useState(false)
10
- const metadata = useWorkflowMetadata(props.id, inferMetadataState(props))
11
- const {state} = metadata.data
12
-
13
- if (!props.draft || state === 'inReview' || state === 'approved') {
14
- return null
15
- }
16
-
17
- const onHandle = () => {
18
- if (!showWizardDialog) {
19
- setShowWizardDialog(true)
20
- }
21
- }
22
-
23
- const onSend = (assignees) => {
24
- setShowWizardDialog(false)
25
-
26
- if (assignees.length === 0) {
27
- metadata.clearAssignees()
28
- } else {
29
- metadata.setAssignees(assignees)
30
- }
31
-
32
- metadata.setState('inReview')
33
- props.onComplete()
34
- }
35
-
36
- const onClose = () => setShowWizardDialog(false)
37
-
38
- return {
39
- dialog: showWizardDialog && {
40
- type: 'popover',
41
- content: <RequestReviewWizard metadata={metadata.data} onClose={onClose} onSend={onSend} />,
42
- onClose: props.onComplete,
43
- },
44
- disabled: showWizardDialog,
45
- icon: EyeOpenIcon,
46
- label: 'Request review',
47
- onHandle,
48
- }
49
- }
50
-
51
- RequestReviewAction.propTypes = {
52
- draft: PropTypes.object,
53
- id: PropTypes.string,
54
- onComplete: PropTypes.func,
55
- }
@@ -1,21 +0,0 @@
1
- import {ApproveAction} from './ApproveAction'
2
- import {DeleteAction} from './DeleteAction'
3
- import {DiscardChangesAction} from './DiscardChangesAction'
4
- import {PublishAction} from './PublishAction'
5
- import {RequestChangesAction} from './RequestChangesAction'
6
- import {RequestReviewAction} from './RequestReviewAction'
7
- import {SyncAction} from './SyncAction'
8
- import {UnpublishAction} from './Unpublish'
9
-
10
- export function resolveWorkflowActions(/* docInfo */) {
11
- return [
12
- SyncAction,
13
- RequestReviewAction,
14
- ApproveAction,
15
- RequestChangesAction,
16
- PublishAction,
17
- UnpublishAction,
18
- DiscardChangesAction,
19
- DeleteAction,
20
- ]
21
- }