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