sanity-plugin-workflow 1.0.0-beta.1 → 1.0.0-beta.10

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.
Files changed (56) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +81 -13
  3. package/lib/{src/index.d.ts → index.d.ts} +4 -3
  4. package/lib/index.esm.js +2106 -1
  5. package/lib/index.esm.js.map +1 -1
  6. package/lib/index.js +2119 -1
  7. package/lib/index.js.map +1 -1
  8. package/package.json +51 -40
  9. package/src/actions/AssignWorkflow.tsx +49 -0
  10. package/src/actions/BeginWorkflow.tsx +63 -0
  11. package/src/actions/CompleteWorkflow.tsx +41 -0
  12. package/src/actions/UpdateWorkflow.tsx +126 -0
  13. package/src/badges/AssigneesBadge.tsx +53 -0
  14. package/src/badges/StateBadge.tsx +28 -0
  15. package/src/components/DocumentCard/AvatarGroup.tsx +12 -8
  16. package/src/components/DocumentCard/CompleteButton.tsx +68 -0
  17. package/src/components/DocumentCard/EditButton.tsx +3 -2
  18. package/src/components/DocumentCard/Field.tsx +38 -0
  19. package/src/components/DocumentCard/Validate.tsx +21 -0
  20. package/src/components/DocumentCard/ValidationStatus.tsx +37 -0
  21. package/src/components/DocumentCard/core/DraftStatus.tsx +32 -0
  22. package/src/components/DocumentCard/core/PublishedStatus.tsx +39 -0
  23. package/src/components/DocumentCard/core/TimeAgo.tsx +11 -0
  24. package/src/components/DocumentCard/index.tsx +177 -68
  25. package/src/components/DocumentList.tsx +169 -0
  26. package/src/components/Filters.tsx +168 -0
  27. package/src/components/FloatingCard.tsx +29 -0
  28. package/src/components/StateTitle/Status.tsx +27 -0
  29. package/src/components/StateTitle/index.tsx +78 -0
  30. package/src/components/UserAssignment.tsx +57 -75
  31. package/src/components/UserAssignmentInput.tsx +27 -0
  32. package/src/components/UserDisplay.tsx +57 -0
  33. package/src/components/Verify.tsx +297 -0
  34. package/src/components/WorkflowContext.tsx +71 -0
  35. package/src/components/WorkflowSignal.tsx +30 -0
  36. package/src/components/WorkflowTool.tsx +373 -162
  37. package/src/constants/index.ts +31 -0
  38. package/src/helpers/arraysContainMatchingString.ts +6 -0
  39. package/src/helpers/filterItemsAndSort.ts +41 -0
  40. package/src/helpers/generateMultipleOrderRanks.ts +80 -0
  41. package/src/helpers/initialRank.ts +13 -0
  42. package/src/hooks/useWorkflowDocuments.tsx +76 -78
  43. package/src/hooks/useWorkflowMetadata.tsx +31 -26
  44. package/src/index.ts +60 -57
  45. package/src/schema/workflow/workflow.metadata.ts +68 -0
  46. package/src/tools/index.ts +15 -0
  47. package/src/types/index.ts +27 -6
  48. package/src/actions/DemoteAction.tsx +0 -62
  49. package/src/actions/PromoteAction.tsx +0 -62
  50. package/src/actions/RequestReviewAction.js +0 -61
  51. package/src/actions/index.js +0 -21
  52. package/src/badges/index.tsx +0 -31
  53. package/src/components/Mutate.tsx +0 -54
  54. package/src/components/StateTimeline.tsx +0 -98
  55. package/src/components/UserSelectInput.tsx +0 -43
  56. package/src/schema/workflow/metadata.ts +0 -38
@@ -1,62 +0,0 @@
1
- import {ArrowLeftIcon} from '@sanity/icons'
2
- import {useToast} from '@sanity/ui'
3
- import {DocumentActionProps, useClient} from 'sanity'
4
- import {useWorkflowMetadata} from '../hooks/useWorkflowMetadata'
5
-
6
- import {State} from '../types'
7
-
8
- export function DemoteAction(props: DocumentActionProps, states: State[]) {
9
- const {id} = props
10
- const {data, loading, error} = useWorkflowMetadata(id, states)
11
- const {state} = data
12
- const client = useClient()
13
- const toast = useToast()
14
-
15
- if (loading || error) {
16
- if (error) {
17
- console.error(error)
18
- }
19
-
20
- return null
21
- }
22
-
23
- if (!state) {
24
- return null
25
- }
26
-
27
- const onHandle = (documentId: string, newState: State) => {
28
- client
29
- .patch(`workflow-metadata.${documentId}`)
30
- .set({state: newState.id})
31
- .commit()
32
- .then(() => {
33
- props.onComplete()
34
- toast.push({
35
- status: 'success',
36
- title: `Document demoted to ${newState.title}`,
37
- })
38
- })
39
- .catch((err) => {
40
- props.onComplete()
41
- console.error(err)
42
- toast.push({
43
- status: 'error',
44
- title: `Document demotion failed`,
45
- })
46
- })
47
- }
48
-
49
- const currentStateIndex = states.findIndex((s) => s.id === state.id)
50
- const prevState = states[currentStateIndex - 1]
51
-
52
- if (!prevState) {
53
- return null
54
- }
55
-
56
- return {
57
- icon: ArrowLeftIcon,
58
- label: `Demote`,
59
- title: `Demote State to "${prevState.title}"`,
60
- onHandle: () => onHandle(id, prevState),
61
- }
62
- }
@@ -1,62 +0,0 @@
1
- import {ArrowRightIcon} from '@sanity/icons'
2
- import {useToast} from '@sanity/ui'
3
- import {DocumentActionProps, useClient} from 'sanity'
4
- import {useWorkflowMetadata} from '../hooks/useWorkflowMetadata'
5
-
6
- import {State} from '../types'
7
-
8
- export function PromoteAction(props: DocumentActionProps, states: State[]) {
9
- const {id} = props
10
- const {data, loading, error} = useWorkflowMetadata(id, states)
11
- const {state} = data
12
- const client = useClient()
13
- const toast = useToast()
14
-
15
- if (loading || error) {
16
- if (error) {
17
- console.error(error)
18
- }
19
-
20
- return null
21
- }
22
-
23
- if (!state) {
24
- return null
25
- }
26
-
27
- const onHandle = (documentId: string, newState: State) => {
28
- client
29
- .patch(`workflow-metadata.${documentId}`)
30
- .set({state: newState.id})
31
- .commit()
32
- .then(() => {
33
- props.onComplete()
34
- toast.push({
35
- status: 'success',
36
- title: `Document promoted to ${newState.title}`,
37
- })
38
- })
39
- .catch((err) => {
40
- props.onComplete()
41
- console.error(err)
42
- toast.push({
43
- status: 'error',
44
- title: `Document promotion failed`,
45
- })
46
- })
47
- }
48
-
49
- const currentStateIndex = states.findIndex((s) => s.id === state.id)
50
- const nextState = states[currentStateIndex + 1]
51
-
52
- if (!nextState) {
53
- return null
54
- }
55
-
56
- return {
57
- icon: ArrowRightIcon,
58
- label: `Promote`,
59
- title: `Promote State to "${nextState.title}"`,
60
- onHandle: () => onHandle(id, nextState),
61
- }
62
- }
@@ -1,61 +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: (
42
- <RequestReviewWizard
43
- metadata={metadata.data}
44
- onClose={onClose}
45
- onSend={onSend}
46
- />
47
- ),
48
- onClose: props.onComplete,
49
- },
50
- disabled: showWizardDialog,
51
- icon: EyeOpenIcon,
52
- label: 'Request review',
53
- onHandle,
54
- }
55
- }
56
-
57
- RequestReviewAction.propTypes = {
58
- draft: PropTypes.object,
59
- id: PropTypes.string,
60
- onComplete: PropTypes.func,
61
- }
@@ -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
- }
@@ -1,31 +0,0 @@
1
- import {DocumentBadgeDescription, DocumentBadgeProps} from 'sanity'
2
- import {useWorkflowMetadata} from '../hooks/useWorkflowMetadata'
3
-
4
- import {State} from '../types'
5
-
6
- export function StateBadge(
7
- props: DocumentBadgeProps,
8
- states: State[]
9
- ): DocumentBadgeDescription | null {
10
- const {id} = props
11
- const {data, loading, error} = useWorkflowMetadata(id, states)
12
- const {state} = data
13
-
14
- if (loading || error) {
15
- if (error) {
16
- console.error(error)
17
- }
18
-
19
- return null
20
- }
21
-
22
- if (!state) {
23
- return null
24
- }
25
-
26
- return {
27
- label: state.title,
28
- title: state.title,
29
- color: state?.color,
30
- }
31
- }
@@ -1,54 +0,0 @@
1
- import React from 'react'
2
- import {Card, useToast} from '@sanity/ui'
3
- import {useDocumentOperation} from 'sanity'
4
-
5
- import {State} from '../types'
6
-
7
- type MutateProps = {
8
- _id: string
9
- _type: string
10
- state: State
11
- documentId: string
12
- onComplete: (id: string) => void
13
- }
14
-
15
- export default function Mutate(props: MutateProps) {
16
- const {_id, _type, documentId, state, onComplete} = props
17
- const ops = useDocumentOperation(documentId, _type)
18
- const isDraft = _id.startsWith('drafts.')
19
-
20
- const toast = useToast()
21
-
22
- if (isDraft && state.operation === 'publish') {
23
- if (!ops.publish.disabled) {
24
- ops.publish.execute()
25
- onComplete(_id)
26
- toast.push({
27
- title: 'Published Document',
28
- description: documentId,
29
- status: 'success',
30
- })
31
- }
32
- } else if (!isDraft && state.operation === 'unpublish') {
33
- if (!ops.unpublish.disabled) {
34
- ops.unpublish.execute()
35
- onComplete(_id)
36
- toast.push({
37
- title: 'Unpublished Document',
38
- description: documentId,
39
- status: 'success',
40
- })
41
- }
42
- } else {
43
- // Clean up if it's not going to un/publish
44
- onComplete(_id)
45
- }
46
-
47
- // return null
48
-
49
- return (
50
- <Card padding={3} shadow={2} tone="primary">
51
- Mutating: {_id} to {state.title}
52
- </Card>
53
- )
54
- }
@@ -1,98 +0,0 @@
1
- import {Button, Card, Text, Inline, Stack, useToast} from '@sanity/ui'
2
- import React, {useEffect} from 'react'
3
- import {ObjectInputProps, useClient} from 'sanity'
4
-
5
- import {useWorkflowMetadata} from '../hooks/useWorkflowMetadata'
6
- import {State} from '../types'
7
-
8
- type StateTimelineProps = ObjectInputProps & {
9
- states: State[]
10
- children: React.ReactNode
11
- }
12
-
13
- export default function StateTimeline(props: StateTimelineProps) {
14
- // return (
15
- // <Stack space={3}>
16
- // <StateTimeline {...props} states={states}>
17
- // {props.renderDefault(props)}
18
- // </StateTimeline>
19
- // </Stack>
20
- // )
21
- console.log(props)
22
- const {value, states, children} = props
23
-
24
- const documentId = String(value?._id)
25
-
26
- const {data, loading, error} = useWorkflowMetadata(documentId, states)
27
- const {state} = data
28
- const [mutatingToState, setMutatingToState] = React.useState<string | null>(
29
- null
30
- )
31
-
32
- const client = useClient()
33
- const toast = useToast()
34
-
35
- // Just because the document is patched ...
36
- // doesn't mean the latest data has been returned from the listener
37
- useEffect(() => {
38
- if (data) {
39
- setMutatingToState(null)
40
- }
41
- }, [data])
42
-
43
- const changeState = React.useCallback(
44
- (publishedId: string, newState: State) => {
45
- setMutatingToState(newState.id)
46
-
47
- client
48
- .patch(`workflow-metadata.${publishedId}`)
49
- .set({state: newState.id})
50
- .commit()
51
- .then(() => {
52
- toast.push({
53
- status: 'success',
54
- title: `Document moved to ${newState.title}`,
55
- })
56
- })
57
- .catch((err) => {
58
- console.error(err)
59
- toast.push({
60
- status: 'error',
61
- title: `Document moved failed`,
62
- })
63
- })
64
- },
65
- [client, toast]
66
- )
67
-
68
- return (
69
- <Stack space={3}>
70
- <Text weight="medium" size={1}>
71
- Workflow State
72
- </Text>
73
- <Card padding={1} radius={3} border tone="primary">
74
- <Inline space={1}>
75
- {states.map((s) => (
76
- <Button
77
- disabled={loading || error || Boolean(mutatingToState)}
78
- fontSize={1}
79
- tone="primary"
80
- mode={
81
- (!mutatingToState && s.id === state?.id) ||
82
- s.id === mutatingToState
83
- ? `default`
84
- : `ghost`
85
- }
86
- key={s.id}
87
- text={s.title}
88
- radius={2}
89
- onClick={() => changeState(documentId, s)}
90
- />
91
- ))}
92
- </Inline>
93
- </Card>
94
-
95
- {children}
96
- </Stack>
97
- )
98
- }
@@ -1,43 +0,0 @@
1
- import {Card} from '@sanity/ui'
2
- import React, {useCallback} from 'react'
3
- import type {ArrayOfPrimitivesInputProps} from 'sanity'
4
- import {setIfMissing, insert, unset} from 'sanity'
5
- import {UserSelectMenu, useProjectUsers} from 'sanity-plugin-utils'
6
-
7
- export default function UserSelectInput(props: ArrayOfPrimitivesInputProps) {
8
- const {value = [], onChange} = props
9
- const userList = useProjectUsers()
10
-
11
- const onAssigneeAdd = useCallback(
12
- (userId: string) => {
13
- onChange([setIfMissing([]), insert([userId], `after`, [-1])])
14
- },
15
- [onChange]
16
- )
17
-
18
- const onAssigneeRemove = useCallback(
19
- (userId: string) => {
20
- const userIdIndex = value.findIndex((v) => v === userId)
21
-
22
- onChange(unset([userIdIndex]))
23
- },
24
- [onChange, value]
25
- )
26
-
27
- const onAssigneesClear = useCallback(() => {
28
- onChange(unset())
29
- }, [onChange])
30
-
31
- return (
32
- <Card border radius={3} padding={1}>
33
- <UserSelectMenu
34
- open
35
- value={value as string[]}
36
- userList={userList}
37
- onAdd={onAssigneeAdd}
38
- onClear={onAssigneesClear}
39
- onRemove={onAssigneeRemove}
40
- />
41
- </Card>
42
- )
43
- }
@@ -1,38 +0,0 @@
1
- import {defineType, defineField, defineArrayMember} from 'sanity'
2
- // import UserSelectInput from '../../components/UserSelectInput'
3
- import {State} from '../../types'
4
-
5
- // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
6
- export default (states: State[]) =>
7
- defineType({
8
- type: 'document',
9
- name: 'workflow.metadata',
10
- title: 'Workflow metadata',
11
- liveEdit: true,
12
- fields: [
13
- defineField({
14
- name: 'state',
15
- type: 'string',
16
- options: {
17
- list: states.map((state) => ({
18
- value: state.id,
19
- title: state.title,
20
- })),
21
- },
22
- }),
23
- defineField({
24
- name: 'documentId',
25
- title: 'Document ID',
26
- type: 'string',
27
- readOnly: true,
28
- }),
29
- defineField({
30
- type: 'array',
31
- name: 'assignees',
32
- description:
33
- 'The people who are assigned to move this further in the workflow.',
34
- of: [defineArrayMember({type: 'string'})],
35
- // components: {input: UserSelectInput},
36
- }),
37
- ],
38
- })