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

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 (49) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +71 -12
  3. package/lib/{src/index.d.ts → index.d.ts} +3 -3
  4. package/lib/index.esm.js +1691 -1
  5. package/lib/index.esm.js.map +1 -1
  6. package/lib/index.js +1704 -1
  7. package/lib/index.js.map +1 -1
  8. package/package.json +48 -38
  9. package/src/actions/AssignWorkflow.tsx +48 -0
  10. package/src/actions/BeginWorkflow.tsx +68 -0
  11. package/src/actions/CompleteWorkflow.tsx +41 -0
  12. package/src/actions/RequestReviewAction.js +1 -7
  13. package/src/actions/UpdateWorkflow.tsx +142 -0
  14. package/src/badges/AssigneesBadge.tsx +52 -0
  15. package/src/badges/{index.tsx → StateBadge.tsx} +4 -8
  16. package/src/components/DocumentCard/AvatarGroup.tsx +12 -8
  17. package/src/components/DocumentCard/CompleteButton.tsx +53 -0
  18. package/src/components/DocumentCard/EditButton.tsx +3 -2
  19. package/src/components/DocumentCard/Field.tsx +38 -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 +32 -0
  23. package/src/components/DocumentCard/core/TimeAgo.tsx +11 -0
  24. package/src/components/DocumentCard/index.tsx +156 -50
  25. package/src/components/Filters.tsx +168 -0
  26. package/src/components/FloatingCard.tsx +29 -0
  27. package/src/components/StateTitle/Status.tsx +27 -0
  28. package/src/components/StateTitle/index.tsx +73 -0
  29. package/src/components/UserAssignment.tsx +57 -75
  30. package/src/components/UserAssignmentInput.tsx +27 -0
  31. package/src/components/UserDisplay.tsx +57 -0
  32. package/src/components/Validators.tsx +196 -0
  33. package/src/components/WorkflowTool.tsx +301 -160
  34. package/src/constants/index.ts +31 -0
  35. package/src/helpers/arraysContainMatchingString.ts +6 -0
  36. package/src/helpers/filterItemsAndSort.ts +39 -0
  37. package/src/helpers/initialRank.ts +13 -0
  38. package/src/hooks/useWorkflowDocuments.tsx +62 -70
  39. package/src/hooks/useWorkflowMetadata.tsx +0 -1
  40. package/src/index.ts +38 -58
  41. package/src/schema/workflow/workflow.metadata.ts +68 -0
  42. package/src/tools/index.ts +15 -0
  43. package/src/types/index.ts +27 -6
  44. package/src/actions/DemoteAction.tsx +0 -62
  45. package/src/actions/PromoteAction.tsx +0 -62
  46. package/src/components/Mutate.tsx +0 -54
  47. package/src/components/StateTimeline.tsx +0 -98
  48. package/src/components/UserSelectInput.tsx +0 -43
  49. package/src/schema/workflow/metadata.ts +0 -38
package/lib/index.esm.js CHANGED
@@ -1 +1,1691 @@
1
- function t(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(t);e&&(o=o.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,o)}return n}function e(e){for(var o=1;o<arguments.length;o++){var i=null!=arguments[o]?arguments[o]:{};o%2?t(Object(i),!0).forEach((function(t){n(e,t,i[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(i)):t(Object(i)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(i,t))}))}return e}function n(t,e,n){return(e=function(t){var e=function(t,e){if("object"!=typeof t||null===t)return t;var n=t[Symbol.toPrimitive];if(void 0!==n){var o=n.call(t,e||"default");if("object"!=typeof o)return o;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===e?String:Number)(t)}(t,"string");return"symbol"==typeof e?e:String(e)}(e))in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}import{UserAvatar as o,useClient as i,useSchema as r,Preview as a,useDocumentOperation as s,defineType as l,defineField as d,defineArrayMember as c,definePlugin as u}from"sanity";import{EditIcon as p,AddIcon as m,DragHandleIcon as h,ArrowRightIcon as f,ArrowLeftIcon as g,SplitVerticalIcon as v,CheckmarkIcon as y}from"@sanity/icons";import{jsx as b,jsxs as w,Fragment as _}from"react/jsx-runtime";import k from"react";import{Button as I,Flex as D,Box as C,Text as O,useToast as j,Popover as P,useTheme as x,Card as S,Stack as E,Container as T,Grid as L,Label as R,Spinner as M}from"@sanity/ui";import{UserSelectMenu as W,useListeningQuery as A,useProjectUsers as F,Feedback as H}from"sanity-plugin-utils";import{DragDropContext as q,Droppable as z,Draggable as N}from"react-beautiful-dnd";import{useRouter as B}from"sanity/router";function Y(t){const{id:e,type:n}=t,{navigateIntent:o}=B();return b(I,{onClick:()=>o("edit",{id:e,type:n}),mode:"ghost",fontSize:1,padding:2,tabIndex:-1,icon:p,text:"Edit"})}function $(t){const{users:e,max:n=3}=t,i=null==e?void 0:e.length,r=k.useMemo((()=>e.slice(0,n)),[e]);return(null==e?void 0:e.length)?w(D,{align:"center",children:[r.map((t=>b(C,{style:{marginRight:-5},children:b(o,{user:t})},t.id))),i>n&&b(C,{paddingLeft:2,children:w(O,{size:1,children:["+",i-n]})})]}):null}function U(t){const{assignees:e,userList:n,documentId:o}=t,r=i(),a=j(),[s,l]=k.useState(""),d=k.useCallback((t=>{if(!t)return a.push({status:"error",title:"No user selected"});r.patch("workflow-metadata.".concat(o)).setIfMissing({assignees:[]}).insert("after","assignees[-1]",[t]).commit().then((()=>a.push({title:"Assigned user to document",description:t,status:"success"}))).catch((e=>(console.error(e),a.push({title:"Failed to add assignee",description:t,status:"error"}))))}),[o,r,a]),c=k.useCallback(((t,e)=>{r.patch("workflow-metadata.".concat(t)).unset(['assignees[@ == "'.concat(e,'"]')]).commit().then((t=>t)).catch((e=>(console.error(e),a.push({title:"Failed to remove assignee",description:t,status:"error"}))))}),[r,a]),u=k.useCallback((t=>{r.patch("workflow-metadata.".concat(t)).unset(["assignees"]).commit().then((t=>t)).catch((e=>(console.error(e),a.push({title:"Failed to clear assignees",description:t,status:"error"}))))}),[r,a]);return b(P,{content:b(W,{style:{maxHeight:300},value:e||[],userList:n,onAdd:d,onClear:u,onRemove:c,open:s===o}),portal:!0,open:s===o,children:e&&0!==e.length?b(I,{onClick:()=>l(o),padding:0,mode:"bleed",style:{width:"100%"},children:b($,{users:n.filter((t=>e.includes(t.id)))})}):b(I,{onClick:()=>l(o),fontSize:1,padding:2,tabIndex:-1,icon:m,text:"Assign",tone:"positive"})})}function V(t){var e;const{userList:n,isDragging:o,item:i}=t,{assignees:s=[],documentId:l}=null!=(e=i._metadata)?e:{},d=r(),c=x().sanity.color.dark;return b(C,{paddingY:2,paddingX:3,children:b(S,{radius:2,shadow:o?3:1,tone:o?"positive":c?"transparent":"default",children:w(E,{children:[b(S,{borderBottom:!0,radius:2,padding:3,paddingLeft:2,tone:"inherit",style:{pointerEvents:"none"},children:w(D,{align:"center",justify:"space-between",gap:1,children:[b(a,{layout:"default",value:i,schemaType:d.get(i._type)}),b(h,{style:{flexShrink:0}})]})}),b(S,{padding:2,radius:2,tone:"inherit",children:w(D,{align:"center",justify:"space-between",gap:1,children:[l&&b(U,{userList:n,assignees:s,documentId:l}),b(Y,{id:i._id,type:i._type})]})})]})})})}function X(t){const{_id:e,_type:n,documentId:o,state:i,onComplete:r}=t,a=s(o,n),l=e.startsWith("drafts."),d=j();return l&&"publish"===i.operation?a.publish.disabled||(a.publish.execute(),r(e),d.push({title:"Published Document",description:o,status:"success"})):l||"unpublish"!==i.operation?r(e):a.unpublish.disabled||(a.unpublish.execute(),r(e),d.push({title:"Unpublished Document",description:o,status:"success"})),w(S,{padding:3,shadow:2,tone:"primary",children:["Mutating: ",e," to ",i.title]})}const G='{\n "documents": '.concat("*[_type in $schemaTypes]{ _id, _type, _rev }",',\n "metadata": ').concat('*[_type == "workflow.metadata"]{\n _rev,\n assignees,\n documentId,\n state\n}',"\n}"),J={documents:[],metadata:[]};function K(t){var n,o;const{schemaTypes:r=[],states:a=[]}=null!=(o=null==(n=null==t?void 0:t.tool)?void 0:n.options)?o:{},[s,l]=k.useState([]),d=k.useCallback((t=>{l((e=>e.filter((e=>e._id!==t))))}),[]),c=i(),u=j(),p=x().sanity.color.dark?"default":"transparent",m=F()||[],{workflowData:h,operations:f}=function(t){const n=j(),o=i(),[r,a]=k.useState([]),{data:s,loading:l,error:d}=A(G,{params:{schemaTypes:t},initialValue:J});k.useEffect((()=>{if(s){const t=s.documents.reduce(((t,n)=>{const o=s.metadata.find((t=>t.documentId===n._id.replace("drafts.","")));if(!o)return[...t,e({_metadata:null},n)];const i=e({_metadata:o},n);return n._id.startsWith("drafts.")?[...t,i]:Boolean(s.documents.find((t=>t._id==="drafts.".concat(n._id))))?t:[...t,i]}),[]);a(t)}}),[s]);const c=k.useCallback(((t,i,s)=>{const l=r,d=r.map((n=>{var o;return(null==(o=null==n?void 0:n._metadata)?void 0:o.documentId)===t?e(e({},n),{},{_metadata:e(e({},n._metadata),{},{state:i.droppableId})}):n}));a(d);const c=i.droppableId,u=s.find((t=>t.id===c)),p=r.find((e=>{var n;return(null==(n=null==e?void 0:e._metadata)?void 0:n.documentId)===t}));if(!(null==u?void 0:u.id))return n.push({title:"Could not find target state ".concat(c),status:"error"}),null;if(!p)return n.push({title:"Could not find dragged document in data",status:"error"}),null;const{_id:m,_type:h}=p,{_rev:f,documentId:g}=p._metadata||{};return o.patch("workflow-metadata.".concat(g)).ifRevisionId(f).set({state:c}).commit().then((()=>{var t;return n.push({title:'Moved to "'.concat(null!=(t=null==u?void 0:u.title)?t:c,'"'),description:g,status:"success"})})).catch((()=>{var t;return a(l),n.push({title:'Failed to move to "'.concat(null!=(t=null==u?void 0:u.title)?t:c,'"'),description:g,status:"error"})})),{_id:m,_type:h,documentId:g,state:u}}),[o,n,r]);return{workflowData:{data:r,loading:l,error:d},operations:{move:c}}}(r),{data:g,loading:v,error:y}=h,{move:O}=f,P=g.filter((t=>!t._metadata)).map((t=>t._id.replace("drafts.",""))),E=k.useCallback((async t=>{u.push({title:"Importing documents",status:"info"});const e=t.reduce(((t,e)=>t.createOrReplace({_id:"workflow-metadata.".concat(e),_type:"workflow.metadata",state:a[0].id,documentId:e})),c.transaction());await e.commit(),u.push({title:"Imported documents",status:"success"})}),[]),W=k.useCallback((t=>{const{draggableId:e,source:n,destination:o}=t;if(console.log("sending ".concat(e," from ").concat(n.droppableId," to ").concat(null==o?void 0:o.droppableId)),!o||o.droppableId===n.droppableId)return;const i=O(e,o,a);i&&l((t=>[...t,i]))}),[O,a]);return(null==a?void 0:a.length)?y?b(T,{width:1,padding:5,children:b(H,{tone:"critical",title:"Error with query"})}):w(_,{children:[s.length?b("div",{style:{position:"absolute",bottom:0,background:"red"},children:s.map((t=>b(X,e(e({},t),{},{onComplete:d}),t._id)))}):null,P.length>0&&b(C,{padding:5,children:b(S,{border:!0,padding:3,tone:"caution",children:b(D,{align:"center",justify:"center",children:w(I,{onClick:()=>E(P),children:["Import ",P.length," Missing"," ",1===P.length?"Document":"Documents"," ","into Workflow"]})})})}),b(q,{onDragEnd:W,children:b(L,{columns:a.length,height:"fill",children:a.map(((t,n)=>w(S,{borderLeft:n>0,children:[b(S,{paddingY:4,padding:3,style:{pointerEvents:"none"},children:b(R,{children:t.title})}),b(z,{droppableId:t.id,children:(n,o)=>{return w(S,{ref:n.innerRef,tone:o.isDraggingOver?"primary":p,height:"fill",children:[v?b(D,{padding:5,align:"center",justify:"center",children:b(M,{muted:!0})}):null,g.length>0&&(i=g,r=t.id,i.filter((t=>{var e;return(null==(e=null==t?void 0:t._metadata)?void 0:e.state)===r}))).map(((t,n)=>{var o,i;return b(N,{draggableId:null==(i=null==t?void 0:t._metadata)?void 0:i.documentId,index:n,children:(n,o)=>b("div",e(e(e({ref:n.innerRef},n.draggableProps),n.dragHandleProps),{},{children:b(V,{isDragging:o.isDragging,item:t,userList:m})}))},null==(o=null==t?void 0:t._metadata)?void 0:o.documentId)}))]});var i,r}})]},t.id)))})})]}):b(T,{width:1,padding:5,children:b(H,{tone:"caution",title:"Plugin options error",description:"No States defined in plugin config"})})}var Q=t=>l({type:"document",name:"workflow.metadata",title:"Workflow metadata",liveEdit:!0,fields:[d({name:"state",type:"string",options:{list:t.map((t=>({value:t.id,title:t.title})))}}),d({name:"documentId",title:"Document ID",type:"string",readOnly:!0}),d({type:"array",name:"assignees",description:"The people who are assigned to move this further in the workflow.",of:[c({type:"string"})]})]});function Z(t,e){const{data:n,loading:o,error:i}=A('*[_type == "workflow.metadata" && documentId == $id][0]',{params:{id:t}});return(null==n?void 0:n.state)?{data:{metadata:n,state:e.find((t=>t.id===n.state))},loading:o,error:i}:{data:{},loading:o,error:i}}function tt(t,e){const{id:n}=t,{data:o,loading:i,error:r}=Z(n,e),{state:a}=o;return i||r?(r&&console.error(r),null):a?{label:a.title,title:a.title,color:null==a?void 0:a.color}:null}function et(t,e){const{id:n}=t,{data:o,loading:r,error:a}=Z(n,e),{state:s}=o,l=i(),d=j();if(r||a)return a&&console.error(a),null;if(!s)return null;const c=e.findIndex((t=>t.id===s.id)),u=e[c+1];return u?{icon:f,label:"Promote",title:'Promote State to "'.concat(u.title,'"'),onHandle:()=>{return e=n,o=u,void l.patch("workflow-metadata.".concat(e)).set({state:o.id}).commit().then((()=>{t.onComplete(),d.push({status:"success",title:"Document promoted to ".concat(o.title)})})).catch((e=>{t.onComplete(),console.error(e),d.push({status:"error",title:"Document promotion failed"})}));var e,o}}:null}function nt(t,e){const{id:n}=t,{data:o,loading:r,error:a}=Z(n,e),{state:s}=o,l=i(),d=j();if(r||a)return a&&console.error(a),null;if(!s)return null;const c=e.findIndex((t=>t.id===s.id)),u=e[c-1];return u?{icon:g,label:"Demote",title:'Demote State to "'.concat(u.title,'"'),onHandle:()=>{return e=n,o=u,void l.patch("workflow-metadata.".concat(e)).set({state:o.id}).commit().then((()=>{t.onComplete(),d.push({status:"success",title:"Document demoted to ".concat(o.title)})})).catch((e=>{t.onComplete(),console.error(e),d.push({status:"error",title:"Document demotion failed"})}));var e,o}}:null}const ot={schemaTypes:[],states:[{id:"draft",title:"Draft",operation:"unpublish"},{id:"inReview",title:"In review",operation:null,color:"primary"},{id:"approved",title:"Approved",operation:null,color:"success",icon:y},{id:"changesRequested",title:"Changes requested",operation:null,color:"warning"},{id:"published",title:"Published",operation:"publish",color:"success"}]},it=u((function(){let t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:ot;const{schemaTypes:n,states:o}=e(e({},ot),t);if(!(null==o?void 0:o.length))throw new Error("Workflow: Missing states in config");return{name:"sanity-plugin-workflow",schema:{types:[Q(o)]},document:{actions:(t,e)=>n.includes(e.schemaType)?[t=>et(t,o),t=>nt(t,o),...t]:t,badges:(t,e)=>n.includes(e.schemaType)?[t=>tt(t,o),...t]:t},tools:[{name:"workflow",title:"Workflow",component:K,icon:v,options:{schemaTypes:n,states:o}}]}}));export{it as workflow};//# sourceMappingURL=index.esm.js.map
1
+ var _templateObject, _templateObject2, _templateObject3;
2
+ function _taggedTemplateLiteral(strings, raw) { if (!raw) { raw = strings.slice(0); } return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); }
3
+ import { useCurrentUser, UserAvatar, useClient, useTimeAgo, TextWithTone, useSchema, useValidationStatus, Preview, useFormValue, defineType, defineField, definePlugin } from 'sanity';
4
+ import { EditIcon, AddIcon, PublishIcon, ErrorOutlineIcon, WarningOutlineIcon, CheckmarkIcon, DragHandleIcon, UserIcon, ResetIcon, InfoOutlineIcon, SplitVerticalIcon, UsersIcon, ArrowRightIcon, ArrowLeftIcon } from '@sanity/icons';
5
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
+ import React, { useMemo, useEffect, useCallback, useState } from 'react';
7
+ import { Button, Flex, Box, Text, useToast, useClickOutside, Popover, Grid, Tooltip, useTheme, Card, Stack, MenuButton, Menu, Badge, Container, Spinner } from '@sanity/ui';
8
+ import { UserSelectMenu, useListeningQuery, useProjectUsers, Feedback } from 'sanity-plugin-utils';
9
+ import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
10
+ import { useRouter } from 'sanity/router';
11
+ import groq from 'groq';
12
+ import { LexoRank } from 'lexorank';
13
+ import styled, { css } from 'styled-components';
14
+ import { AnimatePresence, motion } from 'framer-motion';
15
+ function defineStates(states) {
16
+ return states;
17
+ }
18
+ const API_VERSION = "2023-01-01";
19
+ const DEFAULT_CONFIG = {
20
+ schemaTypes: [],
21
+ states: defineStates([{
22
+ id: "inReview",
23
+ title: "In review",
24
+ color: "primary",
25
+ roles: ["editor", "administrator"],
26
+ transitions: ["changesRequested", "approved"]
27
+ }, {
28
+ id: "changesRequested",
29
+ title: "Changes requested",
30
+ color: "warning",
31
+ roles: ["editor", "administrator"],
32
+ transitions: ["approved"]
33
+ }, {
34
+ id: "approved",
35
+ title: "Approved",
36
+ color: "success",
37
+ roles: ["administrator"],
38
+ requireAssignment: true,
39
+ transitions: ["changesRequested"]
40
+ }])
41
+ };
42
+ function EditButton(props) {
43
+ const {
44
+ id,
45
+ type,
46
+ disabled = false
47
+ } = props;
48
+ const {
49
+ navigateIntent
50
+ } = useRouter();
51
+ return /* @__PURE__ */jsx(Button, {
52
+ onClick: () => navigateIntent("edit", {
53
+ id,
54
+ type
55
+ }),
56
+ mode: "ghost",
57
+ fontSize: 1,
58
+ padding: 2,
59
+ tabIndex: -1,
60
+ icon: EditIcon,
61
+ text: "Edit",
62
+ disabled
63
+ });
64
+ }
65
+ function AvatarGroup(props) {
66
+ const currentUser = useCurrentUser();
67
+ const {
68
+ users,
69
+ max = 4
70
+ } = props;
71
+ const len = users == null ? void 0 : users.length;
72
+ const {
73
+ me,
74
+ visibleUsers
75
+ } = React.useMemo(() => {
76
+ return {
77
+ me: (currentUser == null ? void 0 : currentUser.id) ? users.find(u => u.id === currentUser.id) : void 0,
78
+ visibleUsers: users.filter(u => u.id !== (currentUser == null ? void 0 : currentUser.id)).slice(0, max - 1)
79
+ };
80
+ }, [users, max, currentUser]);
81
+ if (!(users == null ? void 0 : users.length)) {
82
+ return null;
83
+ }
84
+ return /* @__PURE__ */jsxs(Flex, {
85
+ align: "center",
86
+ gap: 1,
87
+ children: [me ? /* @__PURE__ */jsx(UserAvatar, {
88
+ user: me
89
+ }) : null, visibleUsers.map(user => /* @__PURE__ */jsx(Box, {
90
+ style: {
91
+ marginRight: -8
92
+ },
93
+ children: /* @__PURE__ */jsx(UserAvatar, {
94
+ user
95
+ })
96
+ }, user.id)), len > max && /* @__PURE__ */jsx(Box, {
97
+ paddingLeft: 2,
98
+ children: /* @__PURE__ */jsxs(Text, {
99
+ size: 1,
100
+ children: ["+", len - max]
101
+ })
102
+ })]
103
+ });
104
+ }
105
+ function UserAssignment(props) {
106
+ const {
107
+ assignees,
108
+ userList,
109
+ documentId
110
+ } = props;
111
+ const client = useClient({
112
+ apiVersion: API_VERSION
113
+ });
114
+ const toast = useToast();
115
+ const addAssignee = React.useCallback(userId => {
116
+ const user = userList.find(u => u.id === userId);
117
+ if (!userId || !user) {
118
+ return toast.push({
119
+ status: "error",
120
+ title: "Could not find User"
121
+ });
122
+ }
123
+ return client.patch("workflow-metadata.".concat(documentId)).setIfMissing({
124
+ assignees: []
125
+ }).insert("after", "assignees[-1]", [userId]).commit().then(() => {
126
+ return toast.push({
127
+ title: "Added ".concat(user.displayName, " to assignees"),
128
+ status: "success"
129
+ });
130
+ }).catch(err => {
131
+ console.error(err);
132
+ return toast.push({
133
+ title: "Failed to add assignee",
134
+ description: userId,
135
+ status: "error"
136
+ });
137
+ });
138
+ }, [documentId, client, toast, userList]);
139
+ const removeAssignee = React.useCallback(userId => {
140
+ const user = userList.find(u => u.id === userId);
141
+ if (!userId || !user) {
142
+ return toast.push({
143
+ status: "error",
144
+ title: "Could not find User"
145
+ });
146
+ }
147
+ return client.patch("workflow-metadata.".concat(documentId)).unset(["assignees[@ == \"".concat(userId, "\"]")]).commit().then(() => {
148
+ return toast.push({
149
+ title: "Removed ".concat(user.displayName, " from assignees"),
150
+ status: "success"
151
+ });
152
+ }).catch(err => {
153
+ console.error(err);
154
+ return toast.push({
155
+ title: "Failed to remove assignee",
156
+ description: documentId,
157
+ status: "error"
158
+ });
159
+ });
160
+ }, [client, toast, documentId, userList]);
161
+ const clearAssignees = React.useCallback(() => {
162
+ return client.patch("workflow-metadata.".concat(documentId)).unset(["assignees"]).commit().then(() => {
163
+ return toast.push({
164
+ title: "Cleared assignees",
165
+ status: "success"
166
+ });
167
+ }).catch(err => {
168
+ console.error(err);
169
+ return toast.push({
170
+ title: "Failed to clear assignees",
171
+ description: documentId,
172
+ status: "error"
173
+ });
174
+ });
175
+ }, [client, toast, documentId]);
176
+ return /* @__PURE__ */jsx(UserSelectMenu, {
177
+ style: {
178
+ maxHeight: 300
179
+ },
180
+ value: assignees || [],
181
+ userList,
182
+ onAdd: addAssignee,
183
+ onClear: clearAssignees,
184
+ onRemove: removeAssignee
185
+ });
186
+ }
187
+ function UserDisplay(props) {
188
+ const {
189
+ assignees,
190
+ userList,
191
+ documentId,
192
+ disabled = false
193
+ } = props;
194
+ const [button] = React.useState(null);
195
+ const [popover, setPopover] = React.useState(null);
196
+ const [isOpen, setIsOpen] = React.useState(false);
197
+ const close = React.useCallback(() => setIsOpen(false), []);
198
+ const open = React.useCallback(() => setIsOpen(true), []);
199
+ useClickOutside(close, [button, popover]);
200
+ return /* @__PURE__ */jsx(Popover, {
201
+ ref: setPopover,
202
+ content: /* @__PURE__ */jsx(UserAssignment, {
203
+ userList,
204
+ assignees,
205
+ documentId
206
+ }),
207
+ portal: true,
208
+ open: isOpen,
209
+ children: !assignees || assignees.length === 0 ? /* @__PURE__ */jsx(Button, {
210
+ onClick: open,
211
+ fontSize: 1,
212
+ padding: 2,
213
+ tabIndex: -1,
214
+ icon: AddIcon,
215
+ text: "Assign",
216
+ tone: "positive",
217
+ mode: "ghost",
218
+ disabled
219
+ }) : /* @__PURE__ */jsx(Grid, {
220
+ children: /* @__PURE__ */jsx(Button, {
221
+ onClick: open,
222
+ padding: 0,
223
+ mode: "bleed",
224
+ disabled,
225
+ children: /* @__PURE__ */jsx(AvatarGroup, {
226
+ users: userList.filter(u => assignees.includes(u.id))
227
+ })
228
+ })
229
+ })
230
+ });
231
+ }
232
+ function TimeAgo(_ref) {
233
+ let {
234
+ time
235
+ } = _ref;
236
+ const timeAgo = useTimeAgo(time);
237
+ return /* @__PURE__ */jsxs("span", {
238
+ title: timeAgo,
239
+ children: [timeAgo, " ago"]
240
+ });
241
+ }
242
+ function DraftStatus(props) {
243
+ const {
244
+ document
245
+ } = props;
246
+ const updatedAt = document && "_updatedAt" in document && document._updatedAt;
247
+ return /* @__PURE__ */jsx(Tooltip, {
248
+ portal: true,
249
+ content: /* @__PURE__ */jsx(Box, {
250
+ padding: 2,
251
+ children: /* @__PURE__ */jsx(Text, {
252
+ size: 1,
253
+ children: document ? /* @__PURE__ */jsxs(Fragment, {
254
+ children: ["Edited ", updatedAt && /* @__PURE__ */jsx(TimeAgo, {
255
+ time: updatedAt
256
+ })]
257
+ }) : /* @__PURE__ */jsx(Fragment, {
258
+ children: "No unpublished edits"
259
+ })
260
+ })
261
+ }),
262
+ children: /* @__PURE__ */jsx(TextWithTone, {
263
+ tone: "caution",
264
+ dimmed: !document,
265
+ muted: !document,
266
+ size: 1,
267
+ children: /* @__PURE__ */jsx(EditIcon, {})
268
+ })
269
+ });
270
+ }
271
+ function PublishedStatus(props) {
272
+ const {
273
+ document
274
+ } = props;
275
+ const updatedAt = document && "_updatedAt" in document && document._updatedAt;
276
+ return /* @__PURE__ */jsx(Tooltip, {
277
+ portal: true,
278
+ content: /* @__PURE__ */jsx(Box, {
279
+ padding: 2,
280
+ children: /* @__PURE__ */jsx(Text, {
281
+ size: 1,
282
+ children: document ? /* @__PURE__ */jsxs(Fragment, {
283
+ children: ["Published ", updatedAt && /* @__PURE__ */jsx(TimeAgo, {
284
+ time: updatedAt
285
+ })]
286
+ }) : /* @__PURE__ */jsx(Fragment, {
287
+ children: "Not published"
288
+ })
289
+ })
290
+ }),
291
+ children: /* @__PURE__ */jsx(TextWithTone, {
292
+ tone: "positive",
293
+ dimmed: !document,
294
+ muted: !document,
295
+ size: 1,
296
+ children: /* @__PURE__ */jsx(PublishIcon, {})
297
+ })
298
+ });
299
+ }
300
+ function ValidationStatus(props) {
301
+ const {
302
+ validation = []
303
+ } = props;
304
+ if (!validation.length) {
305
+ return null;
306
+ }
307
+ const hasError = validation.some(item => item.level === "error");
308
+ return /* @__PURE__ */jsx(Tooltip, {
309
+ portal: true,
310
+ content: /* @__PURE__ */jsx(Box, {
311
+ padding: 2,
312
+ children: /* @__PURE__ */jsx(Text, {
313
+ size: 1,
314
+ children: validation.length === 1 ? "1 validation issue" : "".concat(validation.length, " validation issues")
315
+ })
316
+ }),
317
+ children: /* @__PURE__ */jsx(TextWithTone, {
318
+ tone: hasError ? "critical" : "caution",
319
+ size: 1,
320
+ children: hasError ? /* @__PURE__ */jsx(ErrorOutlineIcon, {}) : /* @__PURE__ */jsx(WarningOutlineIcon, {})
321
+ })
322
+ });
323
+ }
324
+ function CompleteButton(props) {
325
+ const {
326
+ documentId,
327
+ disabled = false
328
+ } = props;
329
+ const client = useClient({
330
+ apiVersion: API_VERSION
331
+ });
332
+ const toast = useToast();
333
+ const handleComplete = React.useCallback(id => {
334
+ client.delete("workflow-metadata.".concat(id)).then(() => {
335
+ toast.push({
336
+ status: "success",
337
+ title: "Workflow completed",
338
+ description: id
339
+ });
340
+ }).catch(() => {
341
+ toast.push({
342
+ status: "error",
343
+ title: "Could not complete Workflow",
344
+ description: id
345
+ });
346
+ });
347
+ }, [client, toast]);
348
+ return /* @__PURE__ */jsx(Button, {
349
+ onClick: () => handleComplete(documentId),
350
+ text: "Complete",
351
+ icon: CheckmarkIcon,
352
+ tone: "positive",
353
+ mode: "ghost",
354
+ fontSize: 1,
355
+ padding: 2,
356
+ tabIndex: -1,
357
+ disabled
358
+ });
359
+ }
360
+ function DocumentCard(props) {
361
+ var _a;
362
+ const {
363
+ isDragDisabled,
364
+ userRoleCanDrop,
365
+ isDragging,
366
+ item,
367
+ states,
368
+ toggleInvalidDocumentId,
369
+ userList
370
+ } = props;
371
+ const {
372
+ assignees = [],
373
+ documentId
374
+ } = (_a = item._metadata) != null ? _a : {};
375
+ const schema = useSchema();
376
+ const isDarkMode = useTheme().sanity.color.dark;
377
+ const defaultCardTone = isDarkMode ? "transparent" : "default";
378
+ const {
379
+ validation = [],
380
+ isValidating
381
+ } = useValidationStatus(documentId != null ? documentId : "", item._type);
382
+ const cardTone = useMemo(() => {
383
+ let tone = defaultCardTone;
384
+ if (!userRoleCanDrop) return isDarkMode ? "default" : "transparent";
385
+ if (!documentId) return tone;
386
+ if (isDragging) tone = "positive";
387
+ if (!isValidating && validation.length > 0) {
388
+ if (validation.some(v => v.level === "error")) {
389
+ tone = "critical";
390
+ } else {
391
+ tone = "caution";
392
+ }
393
+ }
394
+ return tone;
395
+ }, [isDarkMode, userRoleCanDrop, defaultCardTone, documentId, isDragging, validation, isValidating]);
396
+ useEffect(() => {
397
+ if (!isValidating && validation.length > 0) {
398
+ if (validation.some(v => v.level === "error")) {
399
+ toggleInvalidDocumentId(documentId, "ADD");
400
+ } else {
401
+ toggleInvalidDocumentId(documentId, "REMOVE");
402
+ }
403
+ } else {
404
+ toggleInvalidDocumentId(documentId, "REMOVE");
405
+ }
406
+ }, [documentId, isValidating, toggleInvalidDocumentId, validation]);
407
+ const hasError = useMemo(() => isValidating ? false : validation.some(v => v.level === "error"), [isValidating, validation]);
408
+ const isLastState = useMemo(() => {
409
+ var _a2;
410
+ return states[states.length - 1].id === ((_a2 = item._metadata) == null ? void 0 : _a2.state);
411
+ }, [states, item._metadata.state]);
412
+ return /* @__PURE__ */jsx(Box, {
413
+ paddingBottom: 3,
414
+ paddingX: 3,
415
+ children: /* @__PURE__ */jsx(Card, {
416
+ radius: 2,
417
+ shadow: isDragging ? 3 : 1,
418
+ tone: cardTone,
419
+ children: /* @__PURE__ */jsxs(Stack, {
420
+ children: [/* @__PURE__ */jsx(Card, {
421
+ borderBottom: true,
422
+ radius: 2,
423
+ padding: 3,
424
+ paddingLeft: 2,
425
+ tone: cardTone,
426
+ style: {
427
+ pointerEvents: "none"
428
+ },
429
+ children: /* @__PURE__ */jsxs(Flex, {
430
+ align: "center",
431
+ justify: "space-between",
432
+ gap: 1,
433
+ children: [/* @__PURE__ */jsx(Box, {
434
+ flex: 1,
435
+ children: /* @__PURE__ */jsx(Preview, {
436
+ layout: "default",
437
+ value: item,
438
+ schemaType: schema.get(item._type)
439
+ })
440
+ }), /* @__PURE__ */jsx(Box, {
441
+ style: {
442
+ flexShrink: 0
443
+ },
444
+ children: hasError || isDragDisabled ? null : /* @__PURE__ */jsx(DragHandleIcon, {})
445
+ })]
446
+ })
447
+ }), /* @__PURE__ */jsx(Card, {
448
+ padding: 2,
449
+ radius: 2,
450
+ tone: "inherit",
451
+ children: /* @__PURE__ */jsxs(Flex, {
452
+ align: "center",
453
+ justify: "space-between",
454
+ gap: 3,
455
+ children: [/* @__PURE__ */jsx(Box, {
456
+ flex: 1,
457
+ children: documentId && /* @__PURE__ */jsx(UserDisplay, {
458
+ userList,
459
+ assignees,
460
+ documentId,
461
+ disabled: !userRoleCanDrop
462
+ })
463
+ }), validation.length > 0 ? /* @__PURE__ */jsx(ValidationStatus, {
464
+ validation
465
+ }) : null, /* @__PURE__ */jsx(DraftStatus, {
466
+ document: item
467
+ }), /* @__PURE__ */jsx(PublishedStatus, {
468
+ document: item
469
+ }), /* @__PURE__ */jsx(EditButton, {
470
+ id: item._id,
471
+ type: item._type,
472
+ disabled: !userRoleCanDrop
473
+ }), isLastState ? /* @__PURE__ */jsx(CompleteButton, {
474
+ documentId,
475
+ disabled: !userRoleCanDrop
476
+ }) : null]
477
+ })
478
+ })]
479
+ })
480
+ })
481
+ });
482
+ }
483
+ const QUERY = groq(_templateObject || (_templateObject = _taggedTemplateLiteral(["*[_type == \"workflow.metadata\"]|order(orderRank){\n \"_metadata\": {\n _rev,\n assignees,\n documentId,\n state,\n orderRank\n },\n ...(\n *[_id in [^.documentId, \"drafts.\" + ^.documentId]]|order(_updatedAt)[0]{ \n _id, \n _type, \n _rev, \n _updatedAt \n }\n )\n}[defined(_id)]"])));
484
+ function useWorkflowDocuments(schemaTypes) {
485
+ const toast = useToast();
486
+ const client = useClient({
487
+ apiVersion: API_VERSION
488
+ });
489
+ const {
490
+ data,
491
+ loading,
492
+ error
493
+ } = useListeningQuery(QUERY, {
494
+ params: {
495
+ schemaTypes
496
+ },
497
+ initialValue: []
498
+ });
499
+ const [localDocuments, setLocalDocuments] = React.useState([]);
500
+ React.useEffect(() => {
501
+ if (data) {
502
+ setLocalDocuments(data);
503
+ }
504
+ }, [data]);
505
+ const move = React.useCallback((draggedId, destination, states, newOrder) => {
506
+ const currentLocalData = localDocuments;
507
+ const newLocalDocuments = localDocuments.map(item => {
508
+ var _a;
509
+ if (((_a = item == null ? void 0 : item._metadata) == null ? void 0 : _a.documentId) === draggedId) {
510
+ return {
511
+ ...item,
512
+ _metadata: {
513
+ ...item._metadata,
514
+ state: destination.droppableId,
515
+ orderRank: newOrder,
516
+ // This value won't be written to the document
517
+ // It's done so that un/publish operations don't happen twice
518
+ // Because a moved document's card will update once optimistically
519
+ // and then again when the document is updated
520
+ optimistic: true
521
+ }
522
+ };
523
+ }
524
+ return item;
525
+ });
526
+ setLocalDocuments(newLocalDocuments);
527
+ const newStateId = destination.droppableId;
528
+ const newState = states.find(s => s.id === newStateId);
529
+ const document = localDocuments.find(d => {
530
+ var _a;
531
+ return ((_a = d == null ? void 0 : d._metadata) == null ? void 0 : _a.documentId) === draggedId;
532
+ });
533
+ if (!(newState == null ? void 0 : newState.id)) {
534
+ toast.push({
535
+ title: "Could not find target state ".concat(newStateId),
536
+ status: "error"
537
+ });
538
+ return null;
539
+ }
540
+ if (!document) {
541
+ toast.push({
542
+ title: "Could not find dragged document in data",
543
+ status: "error"
544
+ });
545
+ return null;
546
+ }
547
+ const {
548
+ _id,
549
+ _type
550
+ } = document;
551
+ const {
552
+ _rev,
553
+ documentId
554
+ } = document._metadata || {};
555
+ client.patch("workflow-metadata.".concat(documentId)).ifRevisionId(_rev).set({
556
+ state: newStateId,
557
+ orderRank: newOrder
558
+ }).commit().then(() => {
559
+ var _a;
560
+ return toast.push({
561
+ title: "Moved to \"".concat((_a = newState == null ? void 0 : newState.title) != null ? _a : newStateId, "\""),
562
+ status: "success"
563
+ });
564
+ }).catch(() => {
565
+ var _a;
566
+ setLocalDocuments(currentLocalData);
567
+ return toast.push({
568
+ title: "Failed to move to \"".concat((_a = newState == null ? void 0 : newState.title) != null ? _a : newStateId, "\""),
569
+ status: "error"
570
+ });
571
+ });
572
+ return {
573
+ _id,
574
+ _type,
575
+ documentId,
576
+ state: newState
577
+ };
578
+ }, [client, toast, localDocuments]);
579
+ return {
580
+ workflowData: {
581
+ data: localDocuments,
582
+ loading,
583
+ error
584
+ },
585
+ operations: {
586
+ move
587
+ }
588
+ };
589
+ }
590
+ const StyledFloatingCard = styled(Card)(() => css(_templateObject2 || (_templateObject2 = _taggedTemplateLiteral(["\n position: fixed;\n bottom: 0;\n left: 0;\n z-index: 1000;\n "]))));
591
+ function FloatingCard(_ref2) {
592
+ let {
593
+ children
594
+ } = _ref2;
595
+ const childrenHaveValues = Array.isArray(children) ? children.some(Boolean) : Boolean(children);
596
+ return /* @__PURE__ */jsx(AnimatePresence, {
597
+ children: childrenHaveValues ? /* @__PURE__ */jsx(motion.div, {
598
+ initial: {
599
+ opacity: 0
600
+ },
601
+ animate: {
602
+ opacity: 1
603
+ },
604
+ exit: {
605
+ opacity: 0
606
+ },
607
+ children: /* @__PURE__ */jsx(StyledFloatingCard, {
608
+ shadow: 3,
609
+ padding: 3,
610
+ margin: 3,
611
+ radius: 3,
612
+ children: /* @__PURE__ */jsx(Grid, {
613
+ gap: 2,
614
+ children
615
+ })
616
+ })
617
+ }, "floater") : null
618
+ });
619
+ }
620
+ function Validators(_ref3) {
621
+ let {
622
+ data,
623
+ userList,
624
+ states
625
+ } = _ref3;
626
+ const client = useClient({
627
+ apiVersion: API_VERSION
628
+ });
629
+ const toast = useToast();
630
+ const documentsWithoutValidMetadataIds = (data == null ? void 0 : data.length) ? data.reduce((acc, cur) => {
631
+ var _a;
632
+ const {
633
+ documentId,
634
+ state
635
+ } = (_a = cur._metadata) != null ? _a : {};
636
+ const stateExists = states.find(s => s.id === state);
637
+ return !stateExists && documentId ? [...acc, documentId] : acc;
638
+ }, []) : [];
639
+ const documentsWithInvalidUserIds = (data == null ? void 0 : data.length) ? data.reduce((acc, cur) => {
640
+ var _a;
641
+ const {
642
+ documentId,
643
+ assignees
644
+ } = (_a = cur._metadata) != null ? _a : {};
645
+ const allAssigneesExist = (assignees == null ? void 0 : assignees.length) ? assignees == null ? void 0 : assignees.every(a => userList.find(u => u.id === a)) : true;
646
+ return !allAssigneesExist && documentId ? [...acc, documentId] : acc;
647
+ }, []) : [];
648
+ const documentsWithoutOrderIds = (data == null ? void 0 : data.length) ? data.reduce((acc, cur) => {
649
+ var _a;
650
+ const {
651
+ documentId,
652
+ orderRank
653
+ } = (_a = cur._metadata) != null ? _a : {};
654
+ return !orderRank && documentId ? [...acc, documentId] : acc;
655
+ }, []) : [];
656
+ const correctDocuments = React.useCallback(async ids => {
657
+ toast.push({
658
+ title: "Correcting...",
659
+ status: "info"
660
+ });
661
+ const tx = ids.reduce((item, documentId) => {
662
+ return item.patch("workflow-metadata.".concat(documentId), {
663
+ set: {
664
+ state: states[0].id
665
+ }
666
+ });
667
+ }, client.transaction());
668
+ await tx.commit();
669
+ toast.push({
670
+ title: "Corrected ".concat(ids.length === 1 ? "1 Document" : "".concat(ids.length, " Documents")),
671
+ status: "success"
672
+ });
673
+ }, [client, states, toast]);
674
+ const removeUsersFromDocuments = React.useCallback(async ids => {
675
+ toast.push({
676
+ title: "Removing users...",
677
+ status: "info"
678
+ });
679
+ const tx = ids.reduce((item, documentId) => {
680
+ var _a, _b;
681
+ const {
682
+ assignees
683
+ } = (_b = (_a = data.find(d => d._id === documentId)) == null ? void 0 : _a._metadata) != null ? _b : {};
684
+ const validAssignees = (assignees == null ? void 0 : assignees.length) ?
685
+ // eslint-disable-next-line max-nested-callbacks
686
+ assignees.filter(a => {
687
+ var _a2;
688
+ return (_a2 = userList.find(u => u.id === a)) == null ? void 0 : _a2.id;
689
+ }) : [];
690
+ return item.patch("workflow-metadata.".concat(documentId), {
691
+ set: {
692
+ assignees: validAssignees
693
+ }
694
+ });
695
+ }, client.transaction());
696
+ await tx.commit();
697
+ toast.push({
698
+ title: "Corrected ".concat(ids.length === 1 ? "1 Document" : "".concat(ids.length, " Documents")),
699
+ status: "success"
700
+ });
701
+ }, [client, data, toast, userList]);
702
+ const addOrderToDocuments = React.useCallback(async ids => {
703
+ var _a, _b;
704
+ toast.push({
705
+ title: "Adding ordering...",
706
+ status: "info"
707
+ });
708
+ const firstOrder = (_b = (_a = data[0]) == null ? void 0 : _a._metadata) == null ? void 0 : _b.orderRank;
709
+ let newLexo = firstOrder && data.length !== ids.length ? LexoRank.parse(firstOrder) : LexoRank.min();
710
+ const tx = client.transaction();
711
+ for (let index = 0; index < ids.length; index += 1) {
712
+ newLexo = newLexo.genNext().genNext();
713
+ tx.patch("workflow-metadata.".concat(ids[index]), {
714
+ set: {
715
+ orderRank: newLexo.toString()
716
+ }
717
+ });
718
+ }
719
+ await tx.commit();
720
+ toast.push({
721
+ title: "Added order to ".concat(ids.length === 1 ? "1 Document" : "".concat(ids.length, " Documents")),
722
+ status: "success"
723
+ });
724
+ }, [data, client, toast]);
725
+ return /* @__PURE__ */jsxs(FloatingCard, {
726
+ children: [documentsWithoutValidMetadataIds.length > 0 ? /* @__PURE__ */jsx(Button, {
727
+ tone: "caution",
728
+ onClick: () => correctDocuments(documentsWithoutValidMetadataIds),
729
+ text: documentsWithoutValidMetadataIds.length === 1 ? "Correct 1 Document State" : "Correct ".concat(documentsWithoutValidMetadataIds.length, " Document States")
730
+ }) : null, documentsWithInvalidUserIds.length > 0 ? /* @__PURE__ */jsx(Button, {
731
+ tone: "caution",
732
+ onClick: () => removeUsersFromDocuments(documentsWithInvalidUserIds),
733
+ text: documentsWithInvalidUserIds.length === 1 ? "Remove Invalid Users from 1 Document" : "Remove Invalid Users from ".concat(documentsWithInvalidUserIds.length, " Documents")
734
+ }) : null, documentsWithoutOrderIds.length > 0 ? /* @__PURE__ */jsx(Button, {
735
+ tone: "caution",
736
+ onClick: () => addOrderToDocuments(documentsWithoutOrderIds),
737
+ text: documentsWithoutOrderIds.length === 1 ? "Set Order for 1 Document" : "Set Order for ".concat(documentsWithoutOrderIds.length, " Documents")
738
+ }) : null]
739
+ });
740
+ }
741
+ function Filters(props) {
742
+ const {
743
+ uniqueAssignedUsers = [],
744
+ selectedUserIds,
745
+ schemaTypes,
746
+ selectedSchemaTypes,
747
+ toggleSelectedUser,
748
+ resetSelectedUsers,
749
+ toggleSelectedSchemaType
750
+ } = props;
751
+ const currentUser = useCurrentUser();
752
+ const schema = useSchema();
753
+ const onAdd = useCallback(id => {
754
+ if (!selectedUserIds.includes(id)) {
755
+ toggleSelectedUser(id);
756
+ }
757
+ }, [selectedUserIds, toggleSelectedUser]);
758
+ const onRemove = useCallback(id => {
759
+ if (selectedUserIds.includes(id)) {
760
+ toggleSelectedUser(id);
761
+ }
762
+ }, [selectedUserIds, toggleSelectedUser]);
763
+ const onClear = useCallback(() => {
764
+ resetSelectedUsers();
765
+ }, [resetSelectedUsers]);
766
+ if (uniqueAssignedUsers.length === 0 && schemaTypes.length < 2) {
767
+ return null;
768
+ }
769
+ const meInUniqueAssignees = (currentUser == null ? void 0 : currentUser.id) && uniqueAssignedUsers.find(u => u.id === currentUser.id);
770
+ const uniqueAssigneesNotMe = uniqueAssignedUsers.filter(u => u.id !== (currentUser == null ? void 0 : currentUser.id));
771
+ return /* @__PURE__ */jsx(Card, {
772
+ tone: "primary",
773
+ padding: 2,
774
+ borderBottom: true,
775
+ style: {
776
+ overflowX: "hidden"
777
+ },
778
+ children: /* @__PURE__ */jsxs(Flex, {
779
+ align: "center",
780
+ children: [/* @__PURE__ */jsx(Flex, {
781
+ align: "center",
782
+ gap: 1,
783
+ flex: 1,
784
+ children: uniqueAssignedUsers.length > 5 ? /* @__PURE__ */jsx(Card, {
785
+ tone: "default",
786
+ children: /* @__PURE__ */jsx(MenuButton, {
787
+ button: /* @__PURE__ */jsx(Button, {
788
+ text: "Filter Assignees",
789
+ tone: "primary",
790
+ icon: UserIcon
791
+ }),
792
+ id: "user-filters",
793
+ menu: /* @__PURE__ */jsx(Menu, {
794
+ children: /* @__PURE__ */jsx(UserSelectMenu, {
795
+ value: selectedUserIds,
796
+ userList: uniqueAssignedUsers,
797
+ onAdd,
798
+ onRemove,
799
+ onClear,
800
+ labels: {
801
+ addMe: "Filter mine",
802
+ removeMe: "Clear mine",
803
+ clear: "Clear filters"
804
+ }
805
+ })
806
+ }),
807
+ popover: {
808
+ portal: true
809
+ }
810
+ })
811
+ }) : /* @__PURE__ */jsxs(Fragment, {
812
+ children: [meInUniqueAssignees ? /* @__PURE__ */jsxs(Fragment, {
813
+ children: [/* @__PURE__ */jsx(Button, {
814
+ padding: 0,
815
+ mode: selectedUserIds.includes(currentUser.id) ? "default" : "bleed",
816
+ onClick: () => toggleSelectedUser(currentUser.id),
817
+ children: /* @__PURE__ */jsx(Flex, {
818
+ padding: 1,
819
+ align: "center",
820
+ justify: "center",
821
+ children: /* @__PURE__ */jsx(UserAvatar, {
822
+ user: currentUser.id,
823
+ size: 1,
824
+ withTooltip: true
825
+ })
826
+ })
827
+ }), /* @__PURE__ */jsx(Card, {
828
+ borderRight: true,
829
+ style: {
830
+ height: 30
831
+ },
832
+ tone: "inherit"
833
+ })]
834
+ }) : null, uniqueAssigneesNotMe.map(user => /* @__PURE__ */jsx(Button, {
835
+ padding: 0,
836
+ mode: selectedUserIds.includes(user.id) ? "default" : "bleed",
837
+ onClick: () => toggleSelectedUser(user.id),
838
+ children: /* @__PURE__ */jsx(Flex, {
839
+ padding: 1,
840
+ align: "center",
841
+ justify: "center",
842
+ children: /* @__PURE__ */jsx(UserAvatar, {
843
+ user,
844
+ size: 1,
845
+ withTooltip: true
846
+ })
847
+ })
848
+ }, user.id)), selectedUserIds.length > 0 ? /* @__PURE__ */jsx(Button, {
849
+ text: "Clear",
850
+ onClick: resetSelectedUsers,
851
+ mode: "ghost",
852
+ icon: ResetIcon
853
+ }) : null]
854
+ })
855
+ }), schemaTypes.length > 0 ? /* @__PURE__ */jsx(Flex, {
856
+ align: "center",
857
+ gap: 1,
858
+ children: schemaTypes.map(typeName => {
859
+ var _a, _b;
860
+ const schemaType = schema.get(typeName);
861
+ if (!schemaType) {
862
+ return null;
863
+ }
864
+ return /* @__PURE__ */jsx(Button, {
865
+ text: (_a = schemaType == null ? void 0 : schemaType.title) != null ? _a : typeName,
866
+ icon: (_b = schemaType == null ? void 0 : schemaType.icon) != null ? _b : void 0,
867
+ mode: selectedSchemaTypes.includes(typeName) ? "default" : "ghost",
868
+ onClick: () => toggleSelectedSchemaType(typeName)
869
+ }, typeName);
870
+ })
871
+ }) : null]
872
+ })
873
+ });
874
+ }
875
+ function filterItemsAndSort(items, stateId) {
876
+ let selectedUsers = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
877
+ let selectedSchemaTypes = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
878
+ return items.filter(item => {
879
+ var _a;
880
+ return ((_a = item == null ? void 0 : item._metadata) == null ? void 0 : _a.state) === stateId;
881
+ }).filter(item => {
882
+ var _a, _b, _c;
883
+ return selectedUsers.length && ((_b = (_a = item._metadata) == null ? void 0 : _a.assignees) == null ? void 0 : _b.length) ? (_c = item._metadata) == null ? void 0 : _c.assignees.some(assignee => selectedUsers.includes(assignee)) : !selectedUsers.length;
884
+ }).filter(item => {
885
+ if (!selectedSchemaTypes) {
886
+ return true;
887
+ }
888
+ return selectedSchemaTypes.length ? selectedSchemaTypes.includes(item._type) : false;
889
+ }).sort((a, b) => {
890
+ var _a, _b;
891
+ const aOrderRank = ((_a = a._metadata) == null ? void 0 : _a.orderRank) || "0";
892
+ const bOrderRank = ((_b = b._metadata) == null ? void 0 : _b.orderRank) || "0";
893
+ return aOrderRank.localeCompare(bOrderRank);
894
+ });
895
+ }
896
+ function arraysContainMatchingString(one, two) {
897
+ return one.some(item => two.includes(item));
898
+ }
899
+ function Status(props) {
900
+ const {
901
+ text,
902
+ icon
903
+ } = props;
904
+ const Icon = icon;
905
+ return /* @__PURE__ */jsx(Tooltip, {
906
+ portal: true,
907
+ content: /* @__PURE__ */jsx(Box, {
908
+ padding: 2,
909
+ children: /* @__PURE__ */jsx(Text, {
910
+ size: 1,
911
+ children: text
912
+ })
913
+ }),
914
+ children: /* @__PURE__ */jsx(Text, {
915
+ size: 1,
916
+ children: /* @__PURE__ */jsx(Icon, {})
917
+ })
918
+ });
919
+ }
920
+ const StyledStickyCard = styled(Card)(() => css(_templateObject3 || (_templateObject3 = _taggedTemplateLiteral(["\n position: sticky;\n top: 0;\n z-index: 1;\n "]))));
921
+ function StateTitle(props) {
922
+ const {
923
+ state,
924
+ requireAssignment,
925
+ userRoleCanDrop,
926
+ isDropDisabled,
927
+ draggingFrom
928
+ } = props;
929
+ let tone = "default";
930
+ const isSource = draggingFrom === state.id;
931
+ if (draggingFrom) {
932
+ tone = isDropDisabled || isSource ? "default" : "positive";
933
+ }
934
+ return /* @__PURE__ */jsx(StyledStickyCard, {
935
+ paddingY: 4,
936
+ padding: 3,
937
+ tone: "inherit",
938
+ children: /* @__PURE__ */jsxs(Flex, {
939
+ gap: 3,
940
+ align: "center",
941
+ children: [/* @__PURE__ */jsx(Badge, {
942
+ mode: draggingFrom && !isDropDisabled || isSource ? "default" : "outline",
943
+ tone,
944
+ muted: !userRoleCanDrop || isDropDisabled,
945
+ children: state.title
946
+ }), userRoleCanDrop ? null : /* @__PURE__ */jsx(Status, {
947
+ text: "You do not have permissions to move documents to this State",
948
+ icon: InfoOutlineIcon
949
+ }), requireAssignment ? /* @__PURE__ */jsx(Status, {
950
+ text: "You must be assigned to the document to move documents to this State",
951
+ icon: UserIcon
952
+ }) : null]
953
+ })
954
+ });
955
+ }
956
+ function WorkflowTool(props) {
957
+ var _a, _b, _c;
958
+ const {
959
+ schemaTypes = [],
960
+ states = []
961
+ } = (_b = (_a = props == null ? void 0 : props.tool) == null ? void 0 : _a.options) != null ? _b : {};
962
+ const isDarkMode = useTheme().sanity.color.dark;
963
+ const defaultCardTone = isDarkMode ? "default" : "transparent";
964
+ const userList = useProjectUsers({
965
+ apiVersion: API_VERSION
966
+ });
967
+ const user = useCurrentUser();
968
+ const userRoleNames = ((_c = user == null ? void 0 : user.roles) == null ? void 0 : _c.length) ? user == null ? void 0 : user.roles.map(r => r.name) : [];
969
+ const {
970
+ workflowData,
971
+ operations
972
+ } = useWorkflowDocuments(schemaTypes);
973
+ const {
974
+ data,
975
+ loading,
976
+ error
977
+ } = workflowData;
978
+ const {
979
+ move
980
+ } = operations;
981
+ const [undroppableStates, setUndroppableStates] = React.useState([]);
982
+ const [draggingFrom, setDraggingFrom] = React.useState("");
983
+ const handleDragStart = React.useCallback(start => {
984
+ var _a2, _b2;
985
+ const {
986
+ draggableId,
987
+ source
988
+ } = start;
989
+ const {
990
+ droppableId: currentStateId
991
+ } = source;
992
+ setDraggingFrom(currentStateId);
993
+ const document = data.find(item => {
994
+ var _a3;
995
+ return ((_a3 = item._metadata) == null ? void 0 : _a3.documentId) === draggableId;
996
+ });
997
+ const state = states.find(s => s.id === currentStateId);
998
+ if (!document || !state) return;
999
+ const undroppableStateIds = [];
1000
+ const statesThatRequireAssignmentIds = states.filter(s => s.requireAssignment).map(s => s.id);
1001
+ if (statesThatRequireAssignmentIds.length) {
1002
+ const documentAssignees = (_b2 = (_a2 = document._metadata) == null ? void 0 : _a2.assignees) != null ? _b2 : [];
1003
+ const userIsAssignedToDocument = (user == null ? void 0 : user.id) ? documentAssignees.includes(user.id) : false;
1004
+ if (!userIsAssignedToDocument) {
1005
+ undroppableStateIds.push(...statesThatRequireAssignmentIds);
1006
+ }
1007
+ }
1008
+ const statesThatCannotBeTransitionedToIds = state.transitions && state.transitions.length ? states.filter(s => {
1009
+ var _a3;
1010
+ return !((_a3 = state.transitions) == null ? void 0 : _a3.includes(s.id));
1011
+ }).map(s => s.id) : [];
1012
+ if (statesThatCannotBeTransitionedToIds.length) {
1013
+ undroppableStateIds.push(...statesThatCannotBeTransitionedToIds);
1014
+ }
1015
+ const undroppableExceptSelf = undroppableStateIds.filter(id => id !== currentStateId);
1016
+ if (undroppableExceptSelf.length) {
1017
+ setUndroppableStates(undroppableExceptSelf);
1018
+ }
1019
+ }, [data, states, user]);
1020
+ const handleDragEnd = React.useCallback(result => {
1021
+ var _a2, _b2, _c2, _d, _e, _f;
1022
+ setUndroppableStates([]);
1023
+ setDraggingFrom("");
1024
+ const {
1025
+ draggableId,
1026
+ source,
1027
+ destination
1028
+ } = result;
1029
+ if (
1030
+ // No destination?
1031
+ !destination ||
1032
+ // No change in position?
1033
+ destination.droppableId === source.droppableId && destination.index === source.index) {
1034
+ return;
1035
+ }
1036
+ const destinationStateItems = [...filterItemsAndSort(data, destination.droppableId, [], null)];
1037
+ let newOrder;
1038
+ if (!destinationStateItems.length) {
1039
+ newOrder = LexoRank.min().toString();
1040
+ } else if (destination.index === 0) {
1041
+ const firstItemOrderRank = (_b2 = (_a2 = [...destinationStateItems].shift()) == null ? void 0 : _a2._metadata) == null ? void 0 : _b2.orderRank;
1042
+ newOrder = firstItemOrderRank && typeof firstItemOrderRank === "string" ? LexoRank.parse(firstItemOrderRank).genPrev().toString() : LexoRank.min().toString();
1043
+ } else if (destination.index + 1 === destinationStateItems.length) {
1044
+ const lastItemOrderRank = (_d = (_c2 = [...destinationStateItems].pop()) == null ? void 0 : _c2._metadata) == null ? void 0 : _d.orderRank;
1045
+ newOrder = lastItemOrderRank && typeof lastItemOrderRank === "string" ? LexoRank.parse(lastItemOrderRank).genNext().toString() : LexoRank.min().toString();
1046
+ } else {
1047
+ const itemBefore = destinationStateItems[destination.index];
1048
+ const itemBeforeRank = (_e = itemBefore == null ? void 0 : itemBefore._metadata) == null ? void 0 : _e.orderRank;
1049
+ const itemBeforeRankParsed = itemBefore._metadata.orderRank ? LexoRank.parse(itemBeforeRank) : LexoRank.min();
1050
+ const itemAfter = destinationStateItems[destination.index + 1];
1051
+ const itemAfterRank = (_f = itemAfter == null ? void 0 : itemAfter._metadata) == null ? void 0 : _f.orderRank;
1052
+ const itemAfterRankParsed = itemAfter._metadata.orderRank ? LexoRank.parse(itemAfterRank) : LexoRank.max();
1053
+ newOrder = itemBeforeRankParsed.between(itemAfterRankParsed).toString();
1054
+ }
1055
+ move(draggableId, destination, states, newOrder);
1056
+ }, [data, move, states]);
1057
+ const uniqueAssignedUsers = React.useMemo(() => {
1058
+ const uniqueUserIds = data.reduce((acc, item) => {
1059
+ var _a2;
1060
+ const {
1061
+ assignees = []
1062
+ } = (_a2 = item._metadata) != null ? _a2 : {};
1063
+ const newAssignees = (assignees == null ? void 0 : assignees.length) ? assignees.filter(a => !acc.includes(a)) : [];
1064
+ return newAssignees.length ? [...acc, ...newAssignees] : acc;
1065
+ }, []);
1066
+ return userList.filter(u => uniqueUserIds.includes(u.id));
1067
+ }, [data, userList]);
1068
+ const [selectedUserIds, setSelectedUserIds] = React.useState(uniqueAssignedUsers.map(u => u.id));
1069
+ const toggleSelectedUser = React.useCallback(userId => {
1070
+ setSelectedUserIds(prev => prev.includes(userId) ? prev.filter(u => u !== userId) : [...prev, userId]);
1071
+ }, []);
1072
+ const resetSelectedUsers = React.useCallback(() => {
1073
+ setSelectedUserIds([]);
1074
+ }, []);
1075
+ const [selectedSchemaTypes, setSelectedSchemaTypes] = React.useState(schemaTypes);
1076
+ const toggleSelectedSchemaType = React.useCallback(schemaType => {
1077
+ setSelectedSchemaTypes(prev => prev.includes(schemaType) ? prev.filter(u => u !== schemaType) : [...prev, schemaType]);
1078
+ }, []);
1079
+ const [invalidDocumentIds, setInvalidDocumentIds] = React.useState([]);
1080
+ const toggleInvalidDocumentId = React.useCallback((docId, action) => {
1081
+ setInvalidDocumentIds(prev => action === "ADD" ? [...prev, docId] : prev.filter(id => id !== docId));
1082
+ }, []);
1083
+ if (!(states == null ? void 0 : states.length)) {
1084
+ return /* @__PURE__ */jsx(Container, {
1085
+ width: 1,
1086
+ padding: 5,
1087
+ children: /* @__PURE__ */jsx(Feedback, {
1088
+ tone: "caution",
1089
+ title: "Plugin options error",
1090
+ description: "No States defined in plugin config"
1091
+ })
1092
+ });
1093
+ }
1094
+ if (error && !data.length) {
1095
+ return /* @__PURE__ */jsx(Container, {
1096
+ width: 1,
1097
+ padding: 5,
1098
+ children: /* @__PURE__ */jsx(Feedback, {
1099
+ tone: "critical",
1100
+ title: "Error querying for Workflow documents"
1101
+ })
1102
+ });
1103
+ }
1104
+ return /* @__PURE__ */jsxs(Card, {
1105
+ height: "fill",
1106
+ overflow: "hidden",
1107
+ children: [/* @__PURE__ */jsx(Validators, {
1108
+ data,
1109
+ userList,
1110
+ states
1111
+ }), /* @__PURE__ */jsx(Filters, {
1112
+ uniqueAssignedUsers,
1113
+ selectedUserIds,
1114
+ toggleSelectedUser,
1115
+ resetSelectedUsers,
1116
+ schemaTypes,
1117
+ selectedSchemaTypes,
1118
+ toggleSelectedSchemaType
1119
+ }), /* @__PURE__ */jsx(DragDropContext, {
1120
+ onDragStart: handleDragStart,
1121
+ onDragEnd: handleDragEnd,
1122
+ children: /* @__PURE__ */jsx(Grid, {
1123
+ columns: states.length,
1124
+ height: "fill",
1125
+ children: states.map((state, stateIndex) => {
1126
+ var _a2, _b2;
1127
+ const userRoleCanDrop = ((_a2 = state == null ? void 0 : state.roles) == null ? void 0 : _a2.length) ? arraysContainMatchingString(state.roles, userRoleNames) : true;
1128
+ const isDropDisabled = !userRoleCanDrop || undroppableStates.includes(state.id);
1129
+ return /* @__PURE__ */jsxs(Card, {
1130
+ borderLeft: stateIndex > 0,
1131
+ tone: defaultCardTone,
1132
+ height: "fill",
1133
+ overflow: "auto",
1134
+ children: [/* @__PURE__ */jsx(StateTitle, {
1135
+ state,
1136
+ requireAssignment: (_b2 = state.requireAssignment) != null ? _b2 : false,
1137
+ userRoleCanDrop,
1138
+ isDropDisabled,
1139
+ draggingFrom
1140
+ }), /* @__PURE__ */jsx(Droppable, {
1141
+ droppableId: state.id,
1142
+ isDropDisabled,
1143
+ children: (provided, snapshot) => /* @__PURE__ */jsxs(Card, {
1144
+ ref: provided.innerRef,
1145
+ tone: snapshot.isDraggingOver ? "primary" : defaultCardTone,
1146
+ height: "fill",
1147
+ paddingTop: 1,
1148
+ children: [loading ? /* @__PURE__ */jsx(Flex, {
1149
+ padding: 5,
1150
+ align: "center",
1151
+ justify: "center",
1152
+ children: /* @__PURE__ */jsx(Spinner, {
1153
+ muted: true
1154
+ })
1155
+ }) : null, data.length > 0 && filterItemsAndSort(data, state.id, selectedUserIds, selectedSchemaTypes).map((item, itemIndex) => {
1156
+ var _a3, _b3, _c2, _d;
1157
+ const isInvalid = invalidDocumentIds.includes(String((_a3 = item == null ? void 0 : item._metadata) == null ? void 0 : _a3.documentId));
1158
+ const meInAssignees = (user == null ? void 0 : user.id) ? (_c2 = (_b3 = item == null ? void 0 : item._metadata) == null ? void 0 : _b3.assignees) == null ? void 0 : _c2.includes(user.id) : false;
1159
+ const isDragDisabled = !userRoleCanDrop || isInvalid || !(state.requireAssignment ? state.requireAssignment && meInAssignees : true);
1160
+ const {
1161
+ documentId
1162
+ } = (_d = item._metadata) != null ? _d : {};
1163
+ if (!documentId) {
1164
+ return null;
1165
+ }
1166
+ return /* @__PURE__ */jsx(Draggable, {
1167
+ draggableId: documentId,
1168
+ index: itemIndex,
1169
+ isDragDisabled,
1170
+ children: (draggableProvided, draggableSnapshot) => /* @__PURE__ */jsx("div", {
1171
+ ref: draggableProvided.innerRef,
1172
+ ...draggableProvided.draggableProps,
1173
+ ...draggableProvided.dragHandleProps,
1174
+ children: /* @__PURE__ */jsx(DocumentCard, {
1175
+ userRoleCanDrop,
1176
+ isDragDisabled,
1177
+ isDragging: draggableSnapshot.isDragging,
1178
+ item,
1179
+ toggleInvalidDocumentId,
1180
+ userList,
1181
+ states
1182
+ })
1183
+ })
1184
+ }, documentId);
1185
+ }), provided.placeholder]
1186
+ })
1187
+ })]
1188
+ }, state.id);
1189
+ })
1190
+ })
1191
+ })]
1192
+ });
1193
+ }
1194
+ const workflowTool = options => ({
1195
+ name: "workflow",
1196
+ title: "Workflow",
1197
+ component: WorkflowTool,
1198
+ icon: SplitVerticalIcon,
1199
+ options
1200
+ });
1201
+ function Field(props) {
1202
+ var _a;
1203
+ const schema = useSchema();
1204
+ const {
1205
+ data,
1206
+ loading,
1207
+ error
1208
+ } = useListeningQuery("*[_id in [$id, $draftId]]|order(_updatedAt)[0]", {
1209
+ params: {
1210
+ id: String(props.value),
1211
+ draftId: "drafts.".concat(String(props.value))
1212
+ }
1213
+ });
1214
+ if (loading) {
1215
+ return /* @__PURE__ */jsx(Spinner, {});
1216
+ }
1217
+ const schemaType = schema.get((_a = data == null ? void 0 : data._type) != null ? _a : "");
1218
+ if (error || !(data == null ? void 0 : data._type) || !schemaType) {
1219
+ return /* @__PURE__ */jsx(Feedback, {
1220
+ tone: "critical",
1221
+ title: "Error with query"
1222
+ });
1223
+ }
1224
+ return /* @__PURE__ */jsx(Card, {
1225
+ border: true,
1226
+ padding: 2,
1227
+ children: /* @__PURE__ */jsxs(Flex, {
1228
+ align: "center",
1229
+ justify: "space-between",
1230
+ gap: 2,
1231
+ children: [/* @__PURE__ */jsx(Preview, {
1232
+ layout: "default",
1233
+ value: data,
1234
+ schemaType
1235
+ }), /* @__PURE__ */jsx(EditButton, {
1236
+ id: data._id,
1237
+ type: data._type
1238
+ })]
1239
+ })
1240
+ });
1241
+ }
1242
+ const UserAssignmentInput = props => {
1243
+ var _a;
1244
+ const documentId = useFormValue(["documentId"]);
1245
+ const userList = useProjectUsers({
1246
+ apiVersion: API_VERSION
1247
+ });
1248
+ const stringValue = Array.isArray(props == null ? void 0 : props.value) && ((_a = props == null ? void 0 : props.value) == null ? void 0 : _a.length) ? props.value.map(item => String(item)) : [];
1249
+ return /* @__PURE__ */jsx(Card, {
1250
+ border: true,
1251
+ padding: 1,
1252
+ children: /* @__PURE__ */jsx(UserAssignment, {
1253
+ userList,
1254
+ assignees: stringValue,
1255
+ documentId: String(documentId)
1256
+ })
1257
+ });
1258
+ };
1259
+ function initialRank() {
1260
+ let lastRankValue = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "";
1261
+ const lastRank = lastRankValue && typeof lastRankValue === "string" ? LexoRank.parse(lastRankValue) : LexoRank.min();
1262
+ const nextRank = lastRank.genNext().genNext();
1263
+ return nextRank.value;
1264
+ }
1265
+ var metadata = states => defineType({
1266
+ type: "document",
1267
+ name: "workflow.metadata",
1268
+ title: "Workflow metadata",
1269
+ liveEdit: true,
1270
+ fields: [defineField({
1271
+ name: "state",
1272
+ description: "The current \"State\" of the document. Field is read only as changing it would not fire the state's \"operation\" setting. These are fired in the Document Actions and in the custom Tool.",
1273
+ readOnly: true,
1274
+ type: "string",
1275
+ options: {
1276
+ list: states.length ? states.map(state => ({
1277
+ value: state.id,
1278
+ title: state.title
1279
+ })) : [],
1280
+ layout: "radio"
1281
+ }
1282
+ }), defineField({
1283
+ name: "documentId",
1284
+ title: "Document ID",
1285
+ description: "Used to help identify the target document that this metadata is tracking state for.",
1286
+ type: "string",
1287
+ readOnly: true,
1288
+ components: {
1289
+ input: Field
1290
+ }
1291
+ }), defineField({
1292
+ name: "orderRank",
1293
+ description: "Used to maintain order position of cards in the Tool.",
1294
+ type: "string",
1295
+ readOnly: true,
1296
+ initialValue: async (p, _ref4) => {
1297
+ let {
1298
+ getClient
1299
+ } = _ref4;
1300
+ const lastDocOrderRank = await getClient({
1301
+ apiVersion: API_VERSION
1302
+ }).fetch("*[_type == $type]|order(@[$order] desc)[0][$order]", {
1303
+ order: "orderRank",
1304
+ type: "workflow.metadata"
1305
+ });
1306
+ return initialRank(lastDocOrderRank);
1307
+ }
1308
+ }), defineField({
1309
+ type: "array",
1310
+ name: "assignees",
1311
+ of: [{
1312
+ type: "string"
1313
+ }],
1314
+ components: {
1315
+ input: UserAssignmentInput
1316
+ }
1317
+ })]
1318
+ });
1319
+ function useWorkflowMetadata(id, states) {
1320
+ const {
1321
+ data: metadata,
1322
+ loading,
1323
+ error
1324
+ } = useListeningQuery("*[_type == \"workflow.metadata\" && documentId == $id][0]", {
1325
+ params: {
1326
+ id
1327
+ }
1328
+ });
1329
+ if (metadata == null ? void 0 : metadata.state) {
1330
+ return {
1331
+ data: {
1332
+ metadata,
1333
+ state: states.find(s => s.id === metadata.state)
1334
+ },
1335
+ loading,
1336
+ error
1337
+ };
1338
+ }
1339
+ return {
1340
+ data: {},
1341
+ loading,
1342
+ error
1343
+ };
1344
+ }
1345
+ function AssignWorkflow(props, states) {
1346
+ var _a, _b;
1347
+ const {
1348
+ id
1349
+ } = props;
1350
+ const [isDialogOpen, setDialogOpen] = useState(false);
1351
+ const userList = useProjectUsers({
1352
+ apiVersion: API_VERSION
1353
+ });
1354
+ const {
1355
+ data,
1356
+ loading,
1357
+ error
1358
+ } = useWorkflowMetadata(id, states);
1359
+ if (error) {
1360
+ console.error(error);
1361
+ }
1362
+ if (!(data == null ? void 0 : data.metadata)) {
1363
+ return null;
1364
+ }
1365
+ return {
1366
+ icon: UsersIcon,
1367
+ type: "dialog",
1368
+ disabled: !data || loading || error,
1369
+ label: "Assign",
1370
+ title: data ? null : "Document is not in Workflow",
1371
+ dialog: isDialogOpen && {
1372
+ type: "popover",
1373
+ onClose: () => {
1374
+ setDialogOpen(false);
1375
+ },
1376
+ content: /* @__PURE__ */jsx(UserAssignment, {
1377
+ userList,
1378
+ assignees: (_b = (_a = data.metadata) == null ? void 0 : _a.assignees) != null ? _b : [],
1379
+ documentId: id
1380
+ })
1381
+ },
1382
+ onHandle: () => {
1383
+ setDialogOpen(true);
1384
+ }
1385
+ };
1386
+ }
1387
+ function BeginWorkflow(props, states) {
1388
+ const {
1389
+ id,
1390
+ draft
1391
+ } = props;
1392
+ const {
1393
+ data,
1394
+ loading,
1395
+ error
1396
+ } = useWorkflowMetadata(id, states);
1397
+ const client = useClient({
1398
+ apiVersion: API_VERSION
1399
+ });
1400
+ const toast = useToast();
1401
+ const [beginning, setBeginning] = useState(false);
1402
+ const [complete, setComplete] = useState(false);
1403
+ if (error) {
1404
+ console.error(error);
1405
+ }
1406
+ const handle = useCallback(async () => {
1407
+ setBeginning(true);
1408
+ const lowestOrderFirstState = await client.fetch("*[_type == \"workflow.metadata\" && state == $state]|order(orderRank)[0].orderRank", {
1409
+ state: states[0].id
1410
+ });
1411
+ client.createIfNotExists({
1412
+ _id: "workflow-metadata.".concat(id),
1413
+ _type: "workflow.metadata",
1414
+ documentId: id,
1415
+ state: states[0].id,
1416
+ orderRank: lowestOrderFirstState ? LexoRank.parse(lowestOrderFirstState).genNext().toString() : LexoRank.min().toString()
1417
+ },
1418
+ // Faster!
1419
+ {
1420
+ visibility: "async"
1421
+ }).then(() => {
1422
+ toast.push({
1423
+ status: "success",
1424
+ title: "Workflow started",
1425
+ description: "Document is now \"".concat(states[0].title, "\"")
1426
+ });
1427
+ setBeginning(false);
1428
+ setComplete(true);
1429
+ });
1430
+ }, [id, states, client, toast]);
1431
+ if (!draft || complete || data.metadata) {
1432
+ return null;
1433
+ }
1434
+ return {
1435
+ icon: SplitVerticalIcon,
1436
+ type: "dialog",
1437
+ disabled: (data == null ? void 0 : data.metadata) || loading || error || beginning || complete,
1438
+ label: beginning ? "Beginning..." : "Begin Workflow",
1439
+ onHandle: () => {
1440
+ handle();
1441
+ }
1442
+ };
1443
+ }
1444
+ function CompleteWorkflow(props, states) {
1445
+ var _a;
1446
+ const {
1447
+ id
1448
+ } = props;
1449
+ const {
1450
+ data,
1451
+ loading,
1452
+ error
1453
+ } = useWorkflowMetadata(id, states);
1454
+ const client = useClient({
1455
+ apiVersion: API_VERSION
1456
+ });
1457
+ if (error) {
1458
+ console.error(error);
1459
+ }
1460
+ const handle = useCallback(() => {
1461
+ client.delete("workflow-metadata.".concat(id));
1462
+ }, [id, client]);
1463
+ const isLastState = ((_a = data == null ? void 0 : data.state) == null ? void 0 : _a.id) === states[states.length - 1].id;
1464
+ if (!data.metadata) {
1465
+ return null;
1466
+ }
1467
+ return {
1468
+ icon: CheckmarkIcon,
1469
+ type: "dialog",
1470
+ disabled: loading || error || !isLastState,
1471
+ label: "Complete Workflow",
1472
+ title: isLastState ? "Removes the document from the Workflow process" : "Cannot remove from workflow until in the last state",
1473
+ onHandle: () => {
1474
+ handle();
1475
+ },
1476
+ color: "positive"
1477
+ };
1478
+ }
1479
+ function AssigneesBadge(states, documentId, currentUser) {
1480
+ var _a;
1481
+ const {
1482
+ data,
1483
+ loading,
1484
+ error
1485
+ } = useWorkflowMetadata(documentId, states);
1486
+ const {
1487
+ metadata
1488
+ } = data;
1489
+ const userList = useProjectUsers({
1490
+ apiVersion: API_VERSION
1491
+ });
1492
+ if (loading || error || !metadata) {
1493
+ if (error) {
1494
+ console.error(error);
1495
+ }
1496
+ return null;
1497
+ }
1498
+ if (!((_a = metadata == null ? void 0 : metadata.assignees) == null ? void 0 : _a.length)) {
1499
+ return {
1500
+ label: "Unassigned"
1501
+ };
1502
+ }
1503
+ const {
1504
+ assignees
1505
+ } = metadata != null ? metadata : [];
1506
+ const hasMe = currentUser ? assignees.some(assignee => assignee === currentUser.id) : false;
1507
+ const assigneesCount = hasMe ? assignees.length - 1 : assignees.length;
1508
+ const assigneeUsers = userList.filter(user => assignees.includes(user.id));
1509
+ const title = assigneeUsers.map(user => user.displayName).join(", ");
1510
+ let label;
1511
+ if (hasMe && assigneesCount === 0) {
1512
+ label = "Assigned to Me";
1513
+ } else if (hasMe && assigneesCount > 0) {
1514
+ label = "Me and ".concat(assigneesCount, " ").concat(assigneesCount === 1 ? "other" : "others");
1515
+ } else {
1516
+ label = "".concat(assigneesCount, " assigned");
1517
+ }
1518
+ return {
1519
+ label,
1520
+ title,
1521
+ color: "primary"
1522
+ };
1523
+ }
1524
+ function StateBadge(states, documentId) {
1525
+ const {
1526
+ data,
1527
+ loading,
1528
+ error
1529
+ } = useWorkflowMetadata(documentId, states);
1530
+ const {
1531
+ state
1532
+ } = data;
1533
+ if (loading || error) {
1534
+ if (error) {
1535
+ console.error(error);
1536
+ }
1537
+ return null;
1538
+ }
1539
+ if (!state) {
1540
+ return null;
1541
+ }
1542
+ return {
1543
+ label: state.title,
1544
+ // title: state.title,
1545
+ color: state == null ? void 0 : state.color
1546
+ };
1547
+ }
1548
+ function UpdateWorkflow(props, allStates, actionState) {
1549
+ var _a, _b, _c, _d;
1550
+ const {
1551
+ id,
1552
+ type
1553
+ } = props;
1554
+ const {
1555
+ validation,
1556
+ isValidating
1557
+ } = useValidationStatus(id, type);
1558
+ const hasValidationErrors = !isValidating && (validation == null ? void 0 : validation.length) > 0 && validation.find(v => v.level === "error");
1559
+ const user = useCurrentUser();
1560
+ const client = useClient({
1561
+ apiVersion: API_VERSION
1562
+ });
1563
+ const toast = useToast();
1564
+ const currentUser = useCurrentUser();
1565
+ const {
1566
+ data,
1567
+ loading,
1568
+ error
1569
+ } = useWorkflowMetadata(id, allStates);
1570
+ const {
1571
+ state: currentState
1572
+ } = data;
1573
+ const {
1574
+ assignees = []
1575
+ } = (_a = data == null ? void 0 : data.metadata) != null ? _a : {};
1576
+ if (error) {
1577
+ console.error(error);
1578
+ }
1579
+ const onHandle = (documentId, newState) => {
1580
+ client.patch("workflow-metadata.".concat(documentId)).set({
1581
+ state: newState.id
1582
+ }).commit().then(() => {
1583
+ props.onComplete();
1584
+ toast.push({
1585
+ status: "success",
1586
+ title: "Document state now \"".concat(newState.title, "\"")
1587
+ });
1588
+ }).catch(err => {
1589
+ props.onComplete();
1590
+ console.error(err);
1591
+ toast.push({
1592
+ status: "error",
1593
+ title: "Document state update failed"
1594
+ });
1595
+ });
1596
+ };
1597
+ if (!data.metadata || currentState && currentState.id === actionState.id) {
1598
+ return null;
1599
+ }
1600
+ const currentStateIndex = allStates.findIndex(s => s.id === (currentState == null ? void 0 : currentState.id));
1601
+ const actionStateIndex = allStates.findIndex(s => s.id === actionState.id);
1602
+ const direction = actionStateIndex > currentStateIndex ? "promote" : "demote";
1603
+ const DirectionIcon = direction === "promote" ? ArrowRightIcon : ArrowLeftIcon;
1604
+ const directionLabel = direction === "promote" ? "Promote" : "Demote";
1605
+ let title = "".concat(directionLabel, " State to \"").concat(actionState.title, "\"");
1606
+ const userRoleCanUpdateState = ((_b = user == null ? void 0 : user.roles) == null ? void 0 : _b.length) && ((_c = actionState == null ? void 0 : actionState.roles) == null ? void 0 : _c.length) ?
1607
+ // If the Action state is limited to specific roles
1608
+ // check that the current user has one of those roles
1609
+ arraysContainMatchingString(user.roles.map(r => r.name), actionState.roles) :
1610
+ // No roles specified on the next state, so anyone can update
1611
+ ((_d = actionState == null ? void 0 : actionState.roles) == null ? void 0 : _d.length) !== 0;
1612
+ if (!userRoleCanUpdateState) {
1613
+ title = "Your User role cannot ".concat(directionLabel, " State to \"").concat(actionState.title, "\"");
1614
+ }
1615
+ const actionStateIsAValidTransition = (currentState == null ? void 0 : currentState.id) && currentState.transitions.length ?
1616
+ // If the Current State limits transitions to specific States
1617
+ // Check that the Action State is in Current State's transitions array
1618
+ currentState.transitions.includes(actionState.id) :
1619
+ // Otherwise this isn't a problem
1620
+ true;
1621
+ if (!actionStateIsAValidTransition) {
1622
+ title = "You cannot ".concat(directionLabel, " State to \"").concat(actionState.title, "\" from \"").concat(currentState == null ? void 0 : currentState.title, "\"");
1623
+ }
1624
+ const userAssignmentCanUpdateState = actionState.requireAssignment ?
1625
+ // If the Action State requires assigned users
1626
+ // Check the current user ID is in the assignees array
1627
+ currentUser && assignees.length && assignees.includes(currentUser.id) :
1628
+ // Otherwise this isn't a problem
1629
+ true;
1630
+ if (!userAssignmentCanUpdateState) {
1631
+ title = "You must be assigned to the document to ".concat(directionLabel, " State to \"").concat(actionState.title, "\"");
1632
+ }
1633
+ if (hasValidationErrors) {
1634
+ title = "Document has validation errors, cannot ".concat(directionLabel, " State to \"").concat(actionState.title, "\"");
1635
+ }
1636
+ return {
1637
+ icon: DirectionIcon,
1638
+ disabled: loading || error || isValidating || hasValidationErrors || !currentState || !userRoleCanUpdateState || !actionStateIsAValidTransition || !userAssignmentCanUpdateState,
1639
+ title,
1640
+ label: actionState.title,
1641
+ onHandle: () => onHandle(id, actionState)
1642
+ };
1643
+ }
1644
+ const workflow = definePlugin(function () {
1645
+ let config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : DEFAULT_CONFIG;
1646
+ const {
1647
+ schemaTypes,
1648
+ states
1649
+ } = {
1650
+ ...DEFAULT_CONFIG,
1651
+ ...config
1652
+ };
1653
+ if (!(states == null ? void 0 : states.length)) {
1654
+ throw new Error("Workflow: Missing states in config");
1655
+ }
1656
+ return {
1657
+ name: "sanity-plugin-workflow",
1658
+ schema: {
1659
+ types: [metadata(states)]
1660
+ },
1661
+ // TODO: Remove 'workflow.metadata' from list of new document types
1662
+ // ...
1663
+ document: {
1664
+ actions: (prev, context) => {
1665
+ if (!schemaTypes.includes(context.schemaType)) {
1666
+ return prev;
1667
+ }
1668
+ return [props => BeginWorkflow(props, states), props => AssignWorkflow(props, states), ...states.map(state => props => UpdateWorkflow(props, states, state)), props => CompleteWorkflow(props, states), ...prev];
1669
+ },
1670
+ badges: (prev, context) => {
1671
+ if (!schemaTypes.includes(context.schemaType)) {
1672
+ return prev;
1673
+ }
1674
+ const {
1675
+ documentId,
1676
+ currentUser
1677
+ } = context;
1678
+ if (!documentId) {
1679
+ return prev;
1680
+ }
1681
+ return [() => StateBadge(states, documentId), () => AssigneesBadge(states, documentId, currentUser), ...prev];
1682
+ }
1683
+ },
1684
+ tools: [workflowTool({
1685
+ schemaTypes,
1686
+ states
1687
+ })]
1688
+ };
1689
+ });
1690
+ export { workflow };
1691
+ //# sourceMappingURL=index.esm.js.map