sanity-plugin-workflow 1.0.6 → 2.0.1

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 (52) hide show
  1. package/README.md +2 -17
  2. package/dist/index.d.ts +17 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +1647 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +32 -71
  7. package/lib/index.d.ts +0 -20
  8. package/lib/index.esm.js +0 -2135
  9. package/lib/index.esm.js.map +0 -1
  10. package/lib/index.js +0 -2147
  11. package/lib/index.js.map +0 -1
  12. package/sanity.json +0 -8
  13. package/src/actions/AssignWorkflow.tsx +0 -47
  14. package/src/actions/BeginWorkflow.tsx +0 -63
  15. package/src/actions/CompleteWorkflow.tsx +0 -64
  16. package/src/actions/UpdateWorkflow.tsx +0 -126
  17. package/src/badges/AssigneesBadge.tsx +0 -53
  18. package/src/badges/StateBadge.tsx +0 -28
  19. package/src/components/DocumentCard/AvatarGroup.tsx +0 -43
  20. package/src/components/DocumentCard/CompleteButton.tsx +0 -56
  21. package/src/components/DocumentCard/EditButton.tsx +0 -28
  22. package/src/components/DocumentCard/Field.tsx +0 -38
  23. package/src/components/DocumentCard/Validate.tsx +0 -21
  24. package/src/components/DocumentCard/ValidationStatus.tsx +0 -37
  25. package/src/components/DocumentCard/core/DraftStatus.tsx +0 -32
  26. package/src/components/DocumentCard/core/PublishedStatus.tsx +0 -39
  27. package/src/components/DocumentCard/core/TimeAgo.tsx +0 -11
  28. package/src/components/DocumentCard/index.tsx +0 -200
  29. package/src/components/DocumentList.tsx +0 -169
  30. package/src/components/Filters.tsx +0 -174
  31. package/src/components/FloatingCard.tsx +0 -36
  32. package/src/components/StateTitle/Status.tsx +0 -27
  33. package/src/components/StateTitle/index.tsx +0 -78
  34. package/src/components/UserAssignment.tsx +0 -121
  35. package/src/components/UserAssignmentInput.tsx +0 -27
  36. package/src/components/UserDisplay.tsx +0 -57
  37. package/src/components/Verify.tsx +0 -297
  38. package/src/components/WorkflowContext.tsx +0 -71
  39. package/src/components/WorkflowSignal.tsx +0 -30
  40. package/src/components/WorkflowTool.tsx +0 -437
  41. package/src/constants/index.ts +0 -31
  42. package/src/helpers/arraysContainMatchingString.ts +0 -6
  43. package/src/helpers/filterItemsAndSort.ts +0 -41
  44. package/src/helpers/generateMultipleOrderRanks.ts +0 -80
  45. package/src/helpers/initialRank.ts +0 -13
  46. package/src/hooks/useWorkflowDocuments.tsx +0 -167
  47. package/src/hooks/useWorkflowMetadata.tsx +0 -49
  48. package/src/index.ts +0 -97
  49. package/src/schema/workflow/workflow.metadata.ts +0 -68
  50. package/src/tools/index.ts +0 -15
  51. package/src/types/index.ts +0 -71
  52. package/v2-incompatible.js +0 -11
@@ -1,64 +0,0 @@
1
- import {CheckmarkIcon} from '@sanity/icons'
2
- import {ToastContextValue, useToast} from '@sanity/ui'
3
- import {useCallback} from 'react'
4
- import {DocumentActionProps, SanityClient, useClient} from 'sanity'
5
-
6
- import {useWorkflowContext} from '../components/WorkflowContext'
7
- import {API_VERSION} from '../constants'
8
-
9
- export const handleDeleteMetadata = async (
10
- client: SanityClient,
11
- toast: ToastContextValue,
12
- id: string
13
- ) => {
14
- try {
15
- await client.delete(`workflow-metadata.${id}`)
16
- toast.push({
17
- status: 'success',
18
- title: 'Workflow completed',
19
- })
20
- } catch (error) {
21
- console.error(error)
22
- toast.push({
23
- status: 'error',
24
- title: 'Could not complete Workflow',
25
- })
26
- }
27
- }
28
-
29
- export function CompleteWorkflow(props: DocumentActionProps) {
30
- const {id} = props
31
- const {metadata, loading, error, states} = useWorkflowContext(id)
32
- const client = useClient({apiVersion: API_VERSION})
33
- const toast = useToast()
34
-
35
- if (error) {
36
- console.error(error)
37
- }
38
-
39
- const handle = useCallback(async () => {
40
- await handleDeleteMetadata(client, toast, id)
41
- }, [client, toast, id])
42
-
43
- if (!metadata) {
44
- return null
45
- }
46
-
47
- const state = states.find((s) => s.id === metadata.state)
48
- const isLastState = state?.id === states[states.length - 1].id
49
-
50
- return {
51
- icon: CheckmarkIcon,
52
- type: 'dialog',
53
- disabled: loading || error || !isLastState,
54
- label: `Complete Workflow`,
55
- title: isLastState
56
- ? `Removes the document from the Workflow process`
57
- : `Cannot remove from workflow until in the last state`,
58
- onHandle: async () => {
59
- await handle()
60
- props.onComplete()
61
- },
62
- color: 'positive',
63
- }
64
- }
@@ -1,126 +0,0 @@
1
- import {ArrowLeftIcon, ArrowRightIcon} from '@sanity/icons'
2
- import {useToast} from '@sanity/ui'
3
- import {useCurrentUser, useValidationStatus} from 'sanity'
4
- import {DocumentActionProps, useClient} from 'sanity'
5
-
6
- import {useWorkflowContext} from '../components/WorkflowContext'
7
- import {API_VERSION} from '../constants'
8
- import {arraysContainMatchingString} from '../helpers/arraysContainMatchingString'
9
- import {State} from '../types'
10
-
11
- // eslint-disable-next-line complexity
12
- export function UpdateWorkflow(props: DocumentActionProps, actionState: State) {
13
- const {id, type} = props
14
-
15
- const user = useCurrentUser()
16
- const client = useClient({apiVersion: API_VERSION})
17
- const toast = useToast()
18
- const currentUser = useCurrentUser()
19
-
20
- const {metadata, loading, error, states} = useWorkflowContext(id)
21
- const currentState = states.find((s) => s.id === metadata?.state)
22
- const {assignees = []} = metadata ?? {}
23
-
24
- // TODO: Shouldn't the document action props contain this?
25
- const {validation, isValidating} = useValidationStatus(id, type)
26
- const hasValidationErrors =
27
- currentState?.requireValidation &&
28
- !isValidating &&
29
- validation?.length > 0 &&
30
- validation.find((v) => v.level === 'error')
31
-
32
- if (error) {
33
- console.error(error)
34
- }
35
-
36
- const onHandle = (documentId: string, newState: State) => {
37
- client
38
- .patch(`workflow-metadata.${documentId}`)
39
- .set({state: newState.id})
40
- .commit()
41
- .then(() => {
42
- props.onComplete()
43
- toast.push({
44
- status: 'success',
45
- title: `Document state now "${newState.title}"`,
46
- })
47
- })
48
- .catch((err) => {
49
- props.onComplete()
50
- console.error(err)
51
- toast.push({
52
- status: 'error',
53
- title: `Document state update failed`,
54
- })
55
- })
56
- }
57
-
58
- // Remove button if:
59
- // Document is not in Workflow OR
60
- // The current State is the same as this actions State
61
- if (!metadata || (currentState && currentState.id === actionState.id)) {
62
- return null
63
- }
64
-
65
- const currentStateIndex = states.findIndex((s) => s.id === currentState?.id)
66
- const actionStateIndex = states.findIndex((s) => s.id === actionState.id)
67
- const direction = actionStateIndex > currentStateIndex ? 'promote' : 'demote'
68
- const DirectionIcon = direction === 'promote' ? ArrowRightIcon : ArrowLeftIcon
69
- const directionLabel = direction === 'promote' ? 'Promote' : 'Demote'
70
-
71
- const userRoleCanUpdateState =
72
- user?.roles?.length && actionState?.roles?.length
73
- ? // If the Action state is limited to specific roles
74
- // check that the current user has one of those roles
75
- arraysContainMatchingString(
76
- user.roles.map((r) => r.name),
77
- actionState.roles
78
- )
79
- : // No roles specified on the next state, so anyone can update
80
- actionState?.roles?.length !== 0
81
-
82
- const actionStateIsAValidTransition =
83
- currentState?.id && currentState?.transitions?.length
84
- ? // If the Current State limits transitions to specific States
85
- // Check that the Action State is in Current State's transitions array
86
- currentState.transitions.includes(actionState.id)
87
- : // Otherwise this isn't a problem
88
- true
89
-
90
- const userAssignmentCanUpdateState = actionState.requireAssignment
91
- ? // If the Action State requires assigned users
92
- // Check the current user ID is in the assignees array
93
- currentUser && assignees?.length && assignees.includes(currentUser.id)
94
- : // Otherwise this isn't a problem
95
- true
96
-
97
- let title = `${directionLabel} State to "${actionState.title}"`
98
-
99
- if (!userRoleCanUpdateState) {
100
- title = `Your User role cannot ${directionLabel} State to "${actionState.title}"`
101
- } else if (!actionStateIsAValidTransition) {
102
- title = `You cannot ${directionLabel} State to "${actionState.title}" from "${currentState?.title}"`
103
- } else if (!userAssignmentCanUpdateState) {
104
- title = `You must be assigned to the document to ${directionLabel} State to "${actionState.title}"`
105
- } else if (currentState?.requireValidation && isValidating) {
106
- title = `Document is validating, cannot ${directionLabel} State to "${actionState.title}"`
107
- } else if (hasValidationErrors) {
108
- title = `Document has validation errors, cannot ${directionLabel} State to "${actionState.title}"`
109
- }
110
-
111
- return {
112
- icon: DirectionIcon,
113
- disabled:
114
- loading ||
115
- error ||
116
- (currentState?.requireValidation && isValidating) ||
117
- hasValidationErrors ||
118
- !currentState ||
119
- !userRoleCanUpdateState ||
120
- !actionStateIsAValidTransition ||
121
- !userAssignmentCanUpdateState,
122
- title,
123
- label: actionState.title,
124
- onHandle: () => onHandle(id, actionState),
125
- }
126
- }
@@ -1,53 +0,0 @@
1
- import {CurrentUser, DocumentBadgeDescription} from 'sanity'
2
- import {useProjectUsers} from 'sanity-plugin-utils'
3
-
4
- import {useWorkflowContext} from '../components/WorkflowContext'
5
- import {API_VERSION} from '../constants'
6
-
7
- export function AssigneesBadge(
8
- documentId: string,
9
- currentUser: CurrentUser | null
10
- ): DocumentBadgeDescription | null {
11
- const {metadata, loading, error} = useWorkflowContext(documentId)
12
- const userList = useProjectUsers({apiVersion: API_VERSION})
13
-
14
- if (loading || error || !metadata) {
15
- if (error) {
16
- console.error(error)
17
- }
18
-
19
- return null
20
- }
21
-
22
- if (!metadata?.assignees?.length) {
23
- return {
24
- label: 'Unassigned',
25
- }
26
- }
27
-
28
- const {assignees} = metadata ?? []
29
- const hasMe = currentUser
30
- ? assignees.some((assignee) => assignee === currentUser.id)
31
- : false
32
- const assigneesCount = hasMe ? assignees.length - 1 : assignees.length
33
- const assigneeUsers = userList.filter((user) => assignees.includes(user.id))
34
- const title = assigneeUsers.map((user) => user.displayName).join(', ')
35
-
36
- let label
37
-
38
- if (hasMe && assigneesCount === 0) {
39
- label = 'Assigned to Me'
40
- } else if (hasMe && assigneesCount > 0) {
41
- label = `Me and ${assigneesCount} ${
42
- assigneesCount === 1 ? 'other' : 'others'
43
- }`
44
- } else {
45
- label = `${assigneesCount} assigned`
46
- }
47
-
48
- return {
49
- label,
50
- title,
51
- color: 'primary',
52
- }
53
- }
@@ -1,28 +0,0 @@
1
- import {DocumentBadgeDescription} from 'sanity'
2
-
3
- import {useWorkflowContext} from '../components/WorkflowContext'
4
-
5
- export function StateBadge(
6
- documentId: string
7
- ): DocumentBadgeDescription | null {
8
- const {metadata, loading, error, states} = useWorkflowContext(documentId)
9
- const state = states.find((s) => s.id === metadata?.state)
10
-
11
- if (loading || error) {
12
- if (error) {
13
- console.error(error)
14
- }
15
-
16
- return null
17
- }
18
-
19
- if (!state) {
20
- return null
21
- }
22
-
23
- return {
24
- label: state.title,
25
- // title: state.title,
26
- color: state?.color,
27
- }
28
- }
@@ -1,43 +0,0 @@
1
- import React from 'react'
2
- import {Box, Flex, Text} from '@sanity/ui'
3
- import {useCurrentUser, UserAvatar} from 'sanity'
4
-
5
- import {User} from '../../types'
6
-
7
- type AvatarGroupProps = {
8
- users: User[]
9
- max?: number
10
- }
11
-
12
- export default function AvatarGroup(props: AvatarGroupProps) {
13
- const currentUser = useCurrentUser()
14
- const {users, max = 4} = props
15
-
16
- const len = users?.length
17
- const {me, visibleUsers} = React.useMemo(() => {
18
- return {
19
- me: currentUser?.id ? users.find((u) => u.id === currentUser.id) : undefined,
20
- visibleUsers: users.filter((u) => u.id !== currentUser?.id).slice(0, max - 1),
21
- }
22
- }, [users, max, currentUser])
23
-
24
- if (!users?.length) {
25
- return null
26
- }
27
-
28
- return (
29
- <Flex align="center" gap={1}>
30
- {me ? <UserAvatar user={me} /> : null}
31
- {visibleUsers.map((user) => (
32
- <Box key={user.id} style={{marginRight: -8}}>
33
- <UserAvatar user={user} />
34
- </Box>
35
- ))}
36
- {len > max && (
37
- <Box paddingLeft={2}>
38
- <Text size={1}>+{len - max}</Text>
39
- </Box>
40
- )}
41
- </Flex>
42
- )
43
- }
@@ -1,56 +0,0 @@
1
- import {CheckmarkIcon} from '@sanity/icons'
2
- import {Box, Button, Text, Tooltip, useToast} from '@sanity/ui'
3
- import React from 'react'
4
- import {useClient} from 'sanity'
5
-
6
- import {handleDeleteMetadata} from '../../actions/CompleteWorkflow'
7
- import {API_VERSION} from '../../constants'
8
-
9
- type CompleteButtonProps = {
10
- documentId: string
11
- disabled: boolean
12
- }
13
-
14
- export default function CompleteButton(props: CompleteButtonProps) {
15
- const {documentId, disabled = false} = props
16
- const client = useClient({apiVersion: API_VERSION})
17
- const toast = useToast()
18
-
19
- const handleComplete: React.MouseEventHandler<HTMLButtonElement> =
20
- React.useCallback(
21
- (event) => {
22
- const id = event.currentTarget.value
23
-
24
- if (!id) {
25
- return
26
- }
27
-
28
- handleDeleteMetadata(client, toast, id)
29
- },
30
- [client, toast]
31
- )
32
-
33
- return (
34
- <Tooltip
35
- portal
36
- content={
37
- <Box padding={2}>
38
- <Text size={1}>Remove this document from Workflow</Text>
39
- </Box>
40
- }
41
- >
42
- <Button
43
- value={documentId}
44
- onClick={handleComplete}
45
- text="Complete"
46
- icon={CheckmarkIcon}
47
- tone="positive"
48
- mode="ghost"
49
- fontSize={1}
50
- padding={2}
51
- tabIndex={-1}
52
- disabled={disabled}
53
- />
54
- </Tooltip>
55
- )
56
- }
@@ -1,28 +0,0 @@
1
- import {Button} from '@sanity/ui'
2
- import {EditIcon} from '@sanity/icons'
3
- import {useRouter} from 'sanity/router'
4
-
5
- type EditButtonProps = {
6
- id: string
7
- type: string
8
- disabled?: boolean
9
- }
10
-
11
- export default function EditButton(props: EditButtonProps) {
12
- const {id, type, disabled = false} = props
13
- const {navigateIntent} = useRouter()
14
-
15
- return (
16
- <Button
17
- // eslint-disable-next-line react/jsx-no-bind
18
- onClick={() => navigateIntent('edit', {id, type})}
19
- mode="ghost"
20
- fontSize={1}
21
- padding={2}
22
- tabIndex={-1}
23
- icon={EditIcon}
24
- text="Edit"
25
- disabled={disabled}
26
- />
27
- )
28
- }
@@ -1,38 +0,0 @@
1
- import {Flex, Card, Spinner} from '@sanity/ui'
2
- import {Preview, SanityDocument, StringInputProps, useSchema} from 'sanity'
3
- import {useListeningQuery, Feedback} from 'sanity-plugin-utils'
4
-
5
- import EditButton from './EditButton'
6
-
7
- // TODO: Update this to use the same component as the Tool
8
- export default function Field(props: StringInputProps) {
9
- const schema = useSchema()
10
- const {data, loading, error} = useListeningQuery<SanityDocument>(
11
- `*[_id in [$id, $draftId]]|order(_updatedAt)[0]`,
12
- {
13
- params: {
14
- id: String(props.value),
15
- draftId: `drafts.${String(props.value)}`,
16
- },
17
- }
18
- )
19
-
20
- if (loading) {
21
- return <Spinner />
22
- }
23
-
24
- const schemaType = schema.get(data?._type ?? ``)
25
-
26
- if (error || !data?._type || !schemaType) {
27
- return <Feedback tone="critical" title="Error with query" />
28
- }
29
-
30
- return (
31
- <Card border padding={2}>
32
- <Flex align="center" justify="space-between" gap={2}>
33
- <Preview layout="default" value={data} schemaType={schemaType} />
34
- <EditButton id={data._id} type={data._type} />
35
- </Flex>
36
- </Card>
37
- )
38
- }
@@ -1,21 +0,0 @@
1
- import {useEffect} from 'react'
2
- import {useValidationStatus, ValidationStatus} from 'sanity'
3
-
4
- type ValidateProps = {
5
- documentId: string
6
- type: string
7
- onChange: (validation: ValidationStatus) => void
8
- }
9
-
10
- // Document validation is siloed into its own component
11
- // Because it's not performant to run on a lot of documents
12
- export default function Validate(props: ValidateProps) {
13
- const {documentId, type, onChange} = props
14
- const {isValidating, validation = []} = useValidationStatus(documentId, type)
15
-
16
- useEffect(() => {
17
- onChange({isValidating, validation})
18
- }, [onChange, isValidating, validation])
19
-
20
- return null
21
- }
@@ -1,37 +0,0 @@
1
- import {ErrorOutlineIcon, WarningOutlineIcon} from '@sanity/icons'
2
- import {ValidationMarker} from '@sanity/types'
3
- import {Box, Text, Tooltip} from '@sanity/ui'
4
- import {TextWithTone} from 'sanity'
5
-
6
- type ValidationStatusProps = {
7
- validation: ValidationMarker[]
8
- }
9
-
10
- export function ValidationStatus(props: ValidationStatusProps) {
11
- const {validation = []} = props
12
-
13
- if (!validation.length) {
14
- return null
15
- }
16
-
17
- const hasError = validation.some((item) => item.level === 'error')
18
-
19
- return (
20
- <Tooltip
21
- portal
22
- content={
23
- <Box padding={2}>
24
- <Text size={1}>
25
- {validation.length === 1
26
- ? `1 validation issue`
27
- : `${validation.length} validation issues`}
28
- </Text>
29
- </Box>
30
- }
31
- >
32
- <TextWithTone tone={hasError ? `critical` : `caution`} size={1}>
33
- {hasError ? <ErrorOutlineIcon /> : <WarningOutlineIcon />}
34
- </TextWithTone>
35
- </Tooltip>
36
- )
37
- }
@@ -1,32 +0,0 @@
1
- import {EditIcon} from '@sanity/icons'
2
- import {PreviewValue, SanityDocument} from '@sanity/types'
3
- import {Box, Text, Tooltip} from '@sanity/ui'
4
- import {TextWithTone} from 'sanity'
5
-
6
- import {TimeAgo} from './TimeAgo'
7
-
8
- export function DraftStatus(props: {document?: PreviewValue | Partial<SanityDocument> | null}) {
9
- const {document} = props
10
- const updatedAt = document && '_updatedAt' in document && document._updatedAt
11
-
12
- return (
13
- <Tooltip
14
- portal
15
- content={
16
- <Box padding={2}>
17
- <Text size={1}>
18
- {document ? (
19
- <>Edited {updatedAt && <TimeAgo time={updatedAt} />}</>
20
- ) : (
21
- <>No unpublished edits</>
22
- )}
23
- </Text>
24
- </Box>
25
- }
26
- >
27
- <TextWithTone tone="caution" dimmed={!document} muted={!document} size={1}>
28
- <EditIcon />
29
- </TextWithTone>
30
- </Tooltip>
31
- )
32
- }
@@ -1,39 +0,0 @@
1
- import {PublishIcon} from '@sanity/icons'
2
- import {PreviewValue, SanityDocument} from '@sanity/types'
3
- import {Box, Text, Tooltip} from '@sanity/ui'
4
- import {TextWithTone} from 'sanity'
5
-
6
- import {TimeAgo} from './TimeAgo'
7
-
8
- export function PublishedStatus(props: {
9
- document?: PreviewValue | Partial<SanityDocument> | null
10
- }) {
11
- const {document} = props
12
- const updatedAt = document && '_updatedAt' in document && document._updatedAt
13
-
14
- return (
15
- <Tooltip
16
- portal
17
- content={
18
- <Box padding={2}>
19
- <Text size={1}>
20
- {document ? (
21
- <>Published {updatedAt && <TimeAgo time={updatedAt} />}</>
22
- ) : (
23
- <>Not published</>
24
- )}
25
- </Text>
26
- </Box>
27
- }
28
- >
29
- <TextWithTone
30
- tone="positive"
31
- dimmed={!document}
32
- muted={!document}
33
- size={1}
34
- >
35
- <PublishIcon />
36
- </TextWithTone>
37
- </Tooltip>
38
- )
39
- }
@@ -1,11 +0,0 @@
1
- import {useTimeAgo} from 'sanity'
2
-
3
- export interface TimeAgoProps {
4
- time: string | Date
5
- }
6
-
7
- export function TimeAgo({time}: TimeAgoProps) {
8
- const timeAgo = useTimeAgo(time)
9
-
10
- return <span title={timeAgo}>{timeAgo} ago</span>
11
- }