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

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