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

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