sanity-plugin-workflow 1.0.5 → 2.0.0

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 -19
  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 +30 -69
  7. package/lib/index.d.ts +0 -20
  8. package/lib/index.esm.js +0 -2128
  9. package/lib/index.esm.js.map +0 -1
  10. package/lib/index.js +0 -2140
  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 -41
  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 -68
  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,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,68 +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 {API_VERSION} from '../../constants'
7
-
8
- type CompleteButtonProps = {
9
- documentId: string
10
- disabled: boolean
11
- }
12
-
13
- export default function CompleteButton(props: CompleteButtonProps) {
14
- const {documentId, disabled = false} = props
15
- const client = useClient({apiVersion: API_VERSION})
16
- const toast = useToast()
17
-
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
- })
34
- })
35
- .catch(() => {
36
- toast.push({
37
- status: 'error',
38
- title: 'Could not complete Workflow',
39
- })
40
- })
41
- },
42
- [client, toast]
43
- )
44
-
45
- return (
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>
67
- )
68
- }
@@ -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
- }