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