sanity-plugin-workflow 1.0.0-beta.8 → 1.0.0

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/README.md CHANGED
@@ -1,18 +1,14 @@
1
1
  > This is a **Sanity Studio v3** plugin.
2
2
 
3
- ## Installation
4
-
5
- ```sh
6
- npm install sanity-plugin-workflow
7
- ```
8
-
9
3
  # Sanity Workflow Demo Plugin Example
10
4
 
11
5
  With Sanity Studio you can [customize your content tools to support arbitrary workflows like assignment and content pipelines](https://www.sanity.io/docs/custom-workflows).
12
6
 
13
- This plugin is distributed as a **reference implementation** of these customization APIs and is not considered to be a feature-complete implementation of what workflow management requires in production. It is a starting point intended to be forked and customized to the needs of your organization and content creators.
7
+ This plugin is distributed as an **example implementation** of customization APIs in the Sanity Studio V3 and is not considered to be a feature-complete implementation of what workflow management requires in production. It is meant as a starting point intended to be forked and customized to the needs of your organization and content creators, or simply as an illustration of what is possible in Sanity Studio V3.
8
+
9
+ An intentional design choice of this plugin is that it **does not influence or modify whether a document is in draft or published**. It only tracks the values of a separate "metadata" document. In this implementation, an "Approved" document could be a draft but will still need publishing. "Approving" the document deletes the "metadata" and so removes it from the "Workflow" process. You choose if Publishing the document happens in the Studio like normal, using the [Scheduled Publishing plugin](https://www.sanity.io/plugins/scheduled-publishing) or the [Scheduling API](https://www.sanity.io/docs/scheduling-api#fa3bb95f83ed).
14
10
 
15
- A key intention of this plugin is that it **does not influence or modify whether a document is in draft or published**. It only tracks the values of a separate "metadata" document. In this implementation, an "Approved" document could be a draft but will still need publishing. "Approving" the document simply removes it from the Workflow process. You will decide if Publishing the document happens in the Studio like normal, using the [Scheduled Publishing plugin](https://www.sanity.io/plugins/scheduled-publishing) or the [Scheduling API](https://www.sanity.io/docs/scheduling-api#fa3bb95f83ed).
11
+ This plugin is considered finished in its current form. Your feedback for workflow features you would like to see in Sanity Studio would be appreciated and can be [shared in our Slack community](https://slack.sanity.io/).
16
12
 
17
13
  ![Screenshot 2023-03-21 at 12 11 24](https://user-images.githubusercontent.com/9684022/226602179-5bd3d91a-9c27-431e-be18-3c70f06c6ccb.png)
18
14
 
@@ -28,13 +24,13 @@ This work demonstrates how a single plugin can define:
28
24
  ## Installation
29
25
 
30
26
  ```
31
- npm install --save sanity-plugin-workflow@beta
27
+ npm install --save sanity-plugin-workflow
32
28
  ```
33
29
 
34
30
  or
35
31
 
36
32
  ```
37
- yarn add sanity-plugin-workflow@beta
33
+ yarn add sanity-plugin-workflow
38
34
  ```
39
35
 
40
36
  ## Usage
@@ -61,9 +57,9 @@ Add it as a plugin in sanity.config.ts (or .js):
61
57
 
62
58
  ## Configuring "States"
63
59
 
64
- The plugin comes with a default set of "States". These are tracked by the plugin creating a separate "metadata" document for each document that has begun the Workflow.
60
+ The plugin comes with a default set of "States". These are tracked by the plugin creating a separate "metadata" document for each document that has begun the Workflow.
65
61
 
66
- Documents can be promoted and demoted in the Workflow with the provided Document Actions as well as a drag-and-drop custom Tool. The settings below are not enforced by the API, custom access control rules could be used to do so.
62
+ Documents can be promoted and demoted in the Workflow with the provided Document Actions as well as a drag-and-drop custom Tool. The settings below are not enforced by the API, custom access control rules could be used to do so.
67
63
 
68
64
  ```ts
69
65
  {
@@ -78,7 +74,7 @@ Documents can be promoted and demoted in the Workflow with the provided Document
78
74
  // Requires the user to be "assigned" in order to update to this State
79
75
  requireAssignment: true,
80
76
  // Requires the document to be valid before being promoted out of this State
81
- // Warning: With many documents in the Kanban view this can negatively impact performance
77
+ // Warning: With many documents in the Kanban view this can negatively impact performance
82
78
  requireValidation: true,
83
79
  // Defines which States a document can be moved to from this one
84
80
  transitions: ['changesRequested', 'approved']
@@ -103,12 +99,12 @@ Once the Workflow is complete, the metadata can be removed by using the "Complet
103
99
 
104
100
  This plugin is largely based on the original Workflow Demo built into a Sanity Studio v2 project. The major differences are:
105
101
 
106
- * This plugin is not concerned with nor will modify whether a document is in draft or published.
107
- * This plugin can be more easily installed and configured, not just code examples built into a Studio project.
108
- * Documents must "opt-in" to and be removed from the Workflow. In the previous version, all documents were in the workflow which would fill up the interface and negatively affect performance.
109
- * Document validation status can be used as a way to prevent movement through the workflow.
110
- * User Roles and Assignments can affect the Workflow. Set rules to enforce which States documents can move between and if being assigned to a document is required to move it to a new State.
111
- * This plugin can filter Schema types and assigned Users.
102
+ - This plugin is not concerned with nor will modify whether a document is in draft or published.
103
+ - This plugin can be more easily installed and configured.
104
+ - Documents must "opt-in" to and be removed from the Workflow. In the previous version, all documents were in the workflow which would fill up the interface and negatively affect performance.
105
+ - Document validation status can be used as a way to prevent movement through the workflow.
106
+ - User Roles and Assignments can affect the Workflow. Set rules to enforce which States documents can move between and if being assigned to a document is required to move it to a new State. These are only enforced in the Studio and not the API.
107
+ - This plugin can filter Schema types and assigned Users.
112
108
 
113
109
  ## License
114
110
 
package/lib/index.esm.js CHANGED
@@ -1,9 +1,9 @@
1
1
  var _templateObject, _templateObject2, _templateObject3;
2
2
  function _taggedTemplateLiteral(strings, raw) { if (!raw) { raw = strings.slice(0); } return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); }
3
- import { useClient, useCurrentUser, useValidationStatus, useSchema, Preview, useFormValue, defineType, defineField, UserAvatar, useTimeAgo, TextWithTone, definePlugin } from 'sanity';
3
+ import { useClient, useCurrentUser, useValidationStatus, useSchema, Preview, useFormValue, defineType, defineField, UserAvatar, useTimeAgo, TextWithTone, definePlugin, isObjectInputProps } from 'sanity';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  import { UsersIcon, SplitVerticalIcon, CheckmarkIcon, ArrowRightIcon, ArrowLeftIcon, EditIcon, AddIcon, PublishIcon, ErrorOutlineIcon, WarningOutlineIcon, DragHandleIcon, UserIcon, ResetIcon, InfoOutlineIcon } from '@sanity/icons';
6
- import React, { useState, useCallback, useEffect, useMemo, useRef } from 'react';
6
+ import React, { useMemo, createContext, useContext, useState, useCallback, useEffect, useRef } from 'react';
7
7
  import { UserSelectMenu, useListeningQuery, useProjectUsers, Feedback } from 'sanity-plugin-utils';
8
8
  import { useToast, Button, Spinner, Card, Flex, Box, Text, useClickOutside, Popover, Grid, Tooltip, useTheme, Stack, MenuButton, Menu, Badge, Container } from '@sanity/ui';
9
9
  import { LexoRank } from 'lexorank';
@@ -122,58 +122,99 @@ function UserAssignment(props) {
122
122
  onRemove: removeAssignee
123
123
  });
124
124
  }
125
- function useWorkflowMetadata(id, states) {
125
+ function useWorkflowMetadata(ids) {
126
126
  const {
127
- data: metadata,
127
+ data: rawData,
128
128
  loading,
129
129
  error
130
- } = useListeningQuery("*[_type == \"workflow.metadata\" && documentId == $id][0]", {
130
+ } = useListeningQuery("*[_type == \"workflow.metadata\" && documentId in $ids]{\n _id,\n _type,\n _rev,\n assignees,\n documentId,\n state,\n orderRank\n }", {
131
131
  params: {
132
- id
132
+ ids
133
+ },
134
+ options: {
135
+ apiVersion: API_VERSION
133
136
  }
134
137
  });
135
- if (metadata == null ? void 0 : metadata.state) {
136
- return {
137
- data: {
138
- metadata,
139
- state: states.find(s => s.id === metadata.state)
140
- },
141
- loading,
142
- error
143
- };
144
- }
138
+ const keyedMetadata = useMemo(() => {
139
+ if (!rawData || rawData.length === 0) return {};
140
+ return rawData.reduce((acc, cur) => {
141
+ return {
142
+ ...acc,
143
+ [cur.documentId]: cur
144
+ };
145
+ }, {});
146
+ }, [rawData]);
145
147
  return {
146
- data: {},
148
+ data: keyedMetadata,
147
149
  loading,
148
150
  error
149
151
  };
150
152
  }
151
- function AssignWorkflow(props, states) {
152
- var _a, _b;
153
+ const WorkflowContext = createContext({
154
+ data: {},
155
+ loading: false,
156
+ error: false,
157
+ ids: [],
158
+ addId: () => null,
159
+ removeId: () => null,
160
+ ...DEFAULT_CONFIG
161
+ });
162
+ function useWorkflowContext(id) {
163
+ const current = useContext(WorkflowContext);
164
+ return {
165
+ ...current,
166
+ metadata: id ? current.data[id] : null
167
+ };
168
+ }
169
+ function WorkflowProvider(props) {
170
+ const [ids, setIds] = useState([]);
171
+ const addId = useCallback(id => setIds(current => current.includes(id) ? current : [...current, id]), []);
172
+ const removeId = useCallback(id => setIds(current => current.filter(i => i !== id)), []);
173
+ const {
174
+ data,
175
+ loading,
176
+ error
177
+ } = useWorkflowMetadata(ids);
178
+ return /* @__PURE__ */jsx(WorkflowContext.Provider, {
179
+ value: {
180
+ data,
181
+ loading,
182
+ error,
183
+ ids,
184
+ addId,
185
+ removeId,
186
+ states: props.workflow.states,
187
+ schemaTypes: props.workflow.schemaTypes
188
+ },
189
+ children: props.renderDefault(props)
190
+ });
191
+ }
192
+ function AssignWorkflow(props) {
193
+ var _a;
153
194
  const {
154
195
  id
155
196
  } = props;
197
+ const {
198
+ metadata,
199
+ loading,
200
+ error
201
+ } = useWorkflowContext(id);
156
202
  const [isDialogOpen, setDialogOpen] = useState(false);
157
203
  const userList = useProjectUsers({
158
204
  apiVersion: API_VERSION
159
205
  });
160
- const {
161
- data,
162
- loading,
163
- error
164
- } = useWorkflowMetadata(id, states);
165
206
  if (error) {
166
207
  console.error(error);
167
208
  }
168
- if (!(data == null ? void 0 : data.metadata)) {
209
+ if (!metadata) {
169
210
  return null;
170
211
  }
171
212
  return {
172
213
  icon: UsersIcon,
173
214
  type: "dialog",
174
- disabled: !data || loading || error,
215
+ disabled: !metadata || loading || error,
175
216
  label: "Assign",
176
- title: data ? null : "Document is not in Workflow",
217
+ title: metadata ? null : "Document is not in Workflow",
177
218
  dialog: isDialogOpen && {
178
219
  type: "popover",
179
220
  onClose: () => {
@@ -181,7 +222,7 @@ function AssignWorkflow(props, states) {
181
222
  },
182
223
  content: /* @__PURE__ */jsx(UserAssignment, {
183
224
  userList,
184
- assignees: (_b = (_a = data.metadata) == null ? void 0 : _a.assignees) != null ? _b : [],
225
+ assignees: ((_a = metadata == null ? void 0 : metadata.assignees) == null ? void 0 : _a.length) > 0 ? metadata.assignees : [],
185
226
  documentId: id
186
227
  })
187
228
  },
@@ -190,16 +231,17 @@ function AssignWorkflow(props, states) {
190
231
  }
191
232
  };
192
233
  }
193
- function BeginWorkflow(props, states) {
234
+ function BeginWorkflow(props) {
194
235
  const {
195
236
  id,
196
237
  draft
197
238
  } = props;
198
239
  const {
199
- data,
240
+ metadata,
200
241
  loading,
201
- error
202
- } = useWorkflowMetadata(id, states);
242
+ error,
243
+ states
244
+ } = useWorkflowContext(id);
203
245
  const client = useClient({
204
246
  apiVersion: API_VERSION
205
247
  });
@@ -220,10 +262,6 @@ function BeginWorkflow(props, states) {
220
262
  documentId: id,
221
263
  state: states[0].id,
222
264
  orderRank: lowestOrderFirstState ? LexoRank.parse(lowestOrderFirstState).genNext().toString() : LexoRank.min().toString()
223
- },
224
- // Faster!
225
- {
226
- visibility: "async"
227
265
  }).then(() => {
228
266
  toast.push({
229
267
  status: "success",
@@ -234,29 +272,29 @@ function BeginWorkflow(props, states) {
234
272
  setComplete(true);
235
273
  });
236
274
  }, [id, states, client, toast]);
237
- if (!draft || complete || data.metadata) {
275
+ if (!draft || complete || metadata) {
238
276
  return null;
239
277
  }
240
278
  return {
241
279
  icon: SplitVerticalIcon,
242
280
  type: "dialog",
243
- disabled: (data == null ? void 0 : data.metadata) || loading || error || beginning || complete,
281
+ disabled: metadata || loading || error || beginning || complete,
244
282
  label: beginning ? "Beginning..." : "Begin Workflow",
245
283
  onHandle: () => {
246
284
  handle();
247
285
  }
248
286
  };
249
287
  }
250
- function CompleteWorkflow(props, states) {
251
- var _a;
288
+ function CompleteWorkflow(props) {
252
289
  const {
253
290
  id
254
291
  } = props;
255
292
  const {
256
- data,
293
+ metadata,
257
294
  loading,
258
- error
259
- } = useWorkflowMetadata(id, states);
295
+ error,
296
+ states
297
+ } = useWorkflowContext(id);
260
298
  const client = useClient({
261
299
  apiVersion: API_VERSION
262
300
  });
@@ -266,10 +304,11 @@ function CompleteWorkflow(props, states) {
266
304
  const handle = useCallback(() => {
267
305
  client.delete("workflow-metadata.".concat(id));
268
306
  }, [id, client]);
269
- const isLastState = ((_a = data == null ? void 0 : data.state) == null ? void 0 : _a.id) === states[states.length - 1].id;
270
- if (!data.metadata) {
307
+ if (!metadata) {
271
308
  return null;
272
309
  }
310
+ const state = states.find(s => s.id === metadata.state);
311
+ const isLastState = (state == null ? void 0 : state.id) === states[states.length - 1].id;
273
312
  return {
274
313
  icon: CheckmarkIcon,
275
314
  type: "dialog",
@@ -285,7 +324,7 @@ function CompleteWorkflow(props, states) {
285
324
  function arraysContainMatchingString(one, two) {
286
325
  return one.some(item => two.includes(item));
287
326
  }
288
- function UpdateWorkflow(props, allStates, actionState) {
327
+ function UpdateWorkflow(props, actionState) {
289
328
  var _a, _b, _c, _d;
290
329
  const {
291
330
  id,
@@ -298,16 +337,15 @@ function UpdateWorkflow(props, allStates, actionState) {
298
337
  const toast = useToast();
299
338
  const currentUser = useCurrentUser();
300
339
  const {
301
- data,
340
+ metadata,
302
341
  loading,
303
- error
304
- } = useWorkflowMetadata(id, allStates);
305
- const {
306
- state: currentState
307
- } = data;
342
+ error,
343
+ states
344
+ } = useWorkflowContext(id);
345
+ const currentState = states.find(s => s.id === (metadata == null ? void 0 : metadata.state));
308
346
  const {
309
347
  assignees = []
310
- } = (_a = data == null ? void 0 : data.metadata) != null ? _a : {};
348
+ } = metadata != null ? metadata : {};
311
349
  const {
312
350
  validation,
313
351
  isValidating
@@ -334,21 +372,21 @@ function UpdateWorkflow(props, allStates, actionState) {
334
372
  });
335
373
  });
336
374
  };
337
- if (!data.metadata || currentState && currentState.id === actionState.id) {
375
+ if (!metadata || currentState && currentState.id === actionState.id) {
338
376
  return null;
339
377
  }
340
- const currentStateIndex = allStates.findIndex(s => s.id === (currentState == null ? void 0 : currentState.id));
341
- const actionStateIndex = allStates.findIndex(s => s.id === actionState.id);
378
+ const currentStateIndex = states.findIndex(s => s.id === (currentState == null ? void 0 : currentState.id));
379
+ const actionStateIndex = states.findIndex(s => s.id === actionState.id);
342
380
  const direction = actionStateIndex > currentStateIndex ? "promote" : "demote";
343
381
  const DirectionIcon = direction === "promote" ? ArrowRightIcon : ArrowLeftIcon;
344
382
  const directionLabel = direction === "promote" ? "Promote" : "Demote";
345
- 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) ?
383
+ const userRoleCanUpdateState = ((_a = user == null ? void 0 : user.roles) == null ? void 0 : _a.length) && ((_b = actionState == null ? void 0 : actionState.roles) == null ? void 0 : _b.length) ?
346
384
  // If the Action state is limited to specific roles
347
385
  // check that the current user has one of those roles
348
386
  arraysContainMatchingString(user.roles.map(r => r.name), actionState.roles) :
349
387
  // No roles specified on the next state, so anyone can update
350
- ((_d = actionState == null ? void 0 : actionState.roles) == null ? void 0 : _d.length) !== 0;
351
- const actionStateIsAValidTransition = (currentState == null ? void 0 : currentState.id) && currentState.transitions.length ?
388
+ ((_c = actionState == null ? void 0 : actionState.roles) == null ? void 0 : _c.length) !== 0;
389
+ const actionStateIsAValidTransition = (currentState == null ? void 0 : currentState.id) && ((_d = currentState == null ? void 0 : currentState.transitions) == null ? void 0 : _d.length) ?
352
390
  // If the Current State limits transitions to specific States
353
391
  // Check that the Action State is in Current State's transitions array
354
392
  currentState.transitions.includes(actionState.id) :
@@ -357,7 +395,7 @@ function UpdateWorkflow(props, allStates, actionState) {
357
395
  const userAssignmentCanUpdateState = actionState.requireAssignment ?
358
396
  // If the Action State requires assigned users
359
397
  // Check the current user ID is in the assignees array
360
- currentUser && assignees.length && assignees.includes(currentUser.id) :
398
+ currentUser && (assignees == null ? void 0 : assignees.length) && assignees.includes(currentUser.id) :
361
399
  // Otherwise this isn't a problem
362
400
  true;
363
401
  let title = "".concat(directionLabel, " State to \"").concat(actionState.title, "\"");
@@ -380,16 +418,13 @@ function UpdateWorkflow(props, allStates, actionState) {
380
418
  onHandle: () => onHandle(id, actionState)
381
419
  };
382
420
  }
383
- function AssigneesBadge(states, documentId, currentUser) {
421
+ function AssigneesBadge(documentId, currentUser) {
384
422
  var _a;
385
423
  const {
386
- data,
424
+ metadata,
387
425
  loading,
388
426
  error
389
- } = useWorkflowMetadata(documentId, states);
390
- const {
391
- metadata
392
- } = data;
427
+ } = useWorkflowContext(documentId);
393
428
  const userList = useProjectUsers({
394
429
  apiVersion: API_VERSION
395
430
  });
@@ -425,15 +460,14 @@ function AssigneesBadge(states, documentId, currentUser) {
425
460
  color: "primary"
426
461
  };
427
462
  }
428
- function StateBadge(states, documentId) {
463
+ function StateBadge(documentId) {
429
464
  const {
430
- data,
465
+ metadata,
431
466
  loading,
432
- error
433
- } = useWorkflowMetadata(documentId, states);
434
- const {
435
- state
436
- } = data;
467
+ error,
468
+ states
469
+ } = useWorkflowContext(documentId);
470
+ const state = states.find(s => s.id === (metadata == null ? void 0 : metadata.state));
437
471
  if (loading || error) {
438
472
  if (error) {
439
473
  console.error(error);
@@ -449,6 +483,25 @@ function StateBadge(states, documentId) {
449
483
  color: state == null ? void 0 : state.color
450
484
  };
451
485
  }
486
+ function WorkflowSignal(props) {
487
+ var _a;
488
+ const documentId = ((_a = props == null ? void 0 : props.value) == null ? void 0 : _a._id) ? props.value._id.replace("drafts.", "") : null;
489
+ const {
490
+ addId,
491
+ removeId
492
+ } = useWorkflowContext();
493
+ useEffect(() => {
494
+ if (documentId) {
495
+ addId(documentId);
496
+ }
497
+ return () => {
498
+ if (documentId) {
499
+ removeId(documentId);
500
+ }
501
+ };
502
+ }, [documentId, addId, removeId]);
503
+ return props.renderDefault(props);
504
+ }
452
505
  function EditButton(props) {
453
506
  const {
454
507
  id,
@@ -1059,7 +1112,8 @@ function DocumentCard(props) {
1059
1112
  children: [/* @__PURE__ */jsx(Box, {
1060
1113
  flex: 1,
1061
1114
  children: /* @__PURE__ */jsx(Preview, {
1062
- layout: "block",
1115
+ layout: "default",
1116
+ skipVisibilityCheck: true,
1063
1117
  value: item,
1064
1118
  schemaType: schema.get(item._type)
1065
1119
  })
@@ -1113,6 +1167,21 @@ function DocumentCard(props) {
1113
1167
  })]
1114
1168
  });
1115
1169
  }
1170
+ function getStyle(draggableStyle, virtualItem) {
1171
+ let transform = "translateY(".concat(virtualItem.start, "px)");
1172
+ if (draggableStyle && draggableStyle.transform) {
1173
+ const draggableTransformY = parseInt(draggableStyle.transform.split(",")[1].split("px")[0], 10);
1174
+ transform = "translateY(".concat(virtualItem.start + draggableTransformY, "px)");
1175
+ }
1176
+ return {
1177
+ position: "absolute",
1178
+ top: 0,
1179
+ left: 0,
1180
+ width: "100%",
1181
+ height: "".concat(virtualItem.size, "px"),
1182
+ transform
1183
+ };
1184
+ }
1116
1185
  function DocumentList(props) {
1117
1186
  const {
1118
1187
  data = [],
@@ -1131,17 +1200,20 @@ function DocumentList(props) {
1131
1200
  return data.length ? filterItemsAndSort(data, state.id, selectedUserIds, selectedSchemaTypes) : [];
1132
1201
  }, [data, selectedSchemaTypes, selectedUserIds, state.id]);
1133
1202
  const parentRef = useRef(null);
1134
- const rowVirtualizer = useVirtualizer({
1135
- count: data.length,
1203
+ const virtualizer = useVirtualizer({
1204
+ count: dataFiltered.length,
1136
1205
  getScrollElement: () => parentRef.current,
1137
1206
  getItemKey: index => {
1138
1207
  var _a, _b, _c;
1139
1208
  return (_c = (_b = (_a = dataFiltered[index]) == null ? void 0 : _a._metadata) == null ? void 0 : _b.documentId) != null ? _c : index;
1140
1209
  },
1141
- estimateSize: () => 113,
1142
- overscan: 10
1210
+ estimateSize: () => 115,
1211
+ overscan: 7,
1212
+ measureElement: element => {
1213
+ return element.getBoundingClientRect().height || 115;
1214
+ }
1143
1215
  });
1144
- if (!data.length) {
1216
+ if (!data.length || !dataFiltered.length) {
1145
1217
  return null;
1146
1218
  }
1147
1219
  return /* @__PURE__ */jsx("div", {
@@ -1149,44 +1221,53 @@ function DocumentList(props) {
1149
1221
  style: {
1150
1222
  height: "100%",
1151
1223
  overflow: "auto",
1152
- paddingTop: 1,
1153
1224
  // Smooths scrollbar behaviour
1154
1225
  overflowAnchor: "none",
1155
- scrollBehavior: "auto"
1226
+ scrollBehavior: "auto",
1227
+ paddingTop: 1
1156
1228
  },
1157
- children: rowVirtualizer.getVirtualItems().map(virtualItem => {
1158
- var _a;
1159
- const item = dataFiltered[virtualItem.index];
1160
- const {
1161
- documentId,
1162
- assignees
1163
- } = (_a = item == null ? void 0 : item._metadata) != null ? _a : {};
1164
- if (!documentId) {
1165
- return null;
1166
- }
1167
- const isInvalid = invalidDocumentIds.includes(documentId);
1168
- const meInAssignees = (user == null ? void 0 : user.id) ? assignees == null ? void 0 : assignees.includes(user.id) : false;
1169
- const isDragDisabled = patchingIds.includes(documentId) || !userRoleCanDrop || isInvalid || !(state.requireAssignment ? state.requireAssignment && meInAssignees : true);
1170
- return /* @__PURE__ */jsx(Draggable, {
1171
- draggableId: documentId,
1172
- index: virtualItem.index,
1173
- isDragDisabled,
1174
- children: (draggableProvided, draggableSnapshot) => /* @__PURE__ */jsx("div", {
1175
- ref: draggableProvided.innerRef,
1176
- ...draggableProvided.draggableProps,
1177
- ...draggableProvided.dragHandleProps,
1178
- children: /* @__PURE__ */jsx(DocumentCard, {
1179
- userRoleCanDrop,
1180
- isDragDisabled,
1181
- isPatching: patchingIds.includes(documentId),
1182
- isDragging: draggableSnapshot.isDragging,
1183
- item,
1184
- toggleInvalidDocumentId,
1185
- userList,
1186
- states
1229
+ children: /* @__PURE__ */jsx("div", {
1230
+ style: {
1231
+ height: "".concat(virtualizer.getTotalSize(), "px"),
1232
+ width: "100%",
1233
+ position: "relative"
1234
+ },
1235
+ children: virtualizer.getVirtualItems().map(virtualItem => {
1236
+ var _a;
1237
+ const item = dataFiltered[virtualItem.index];
1238
+ const {
1239
+ documentId,
1240
+ assignees
1241
+ } = (_a = item == null ? void 0 : item._metadata) != null ? _a : {};
1242
+ const isInvalid = invalidDocumentIds.includes(documentId);
1243
+ const meInAssignees = (user == null ? void 0 : user.id) ? assignees == null ? void 0 : assignees.includes(user.id) : false;
1244
+ const isDragDisabled = patchingIds.includes(documentId) || !userRoleCanDrop || isInvalid || !(state.requireAssignment ? state.requireAssignment && meInAssignees : true);
1245
+ return /* @__PURE__ */jsx(Draggable, {
1246
+ draggableId: documentId,
1247
+ index: virtualItem.index,
1248
+ isDragDisabled,
1249
+ children: (draggableProvided, draggableSnapshot) => /* @__PURE__ */jsx("div", {
1250
+ ref: draggableProvided.innerRef,
1251
+ ...draggableProvided.draggableProps,
1252
+ ...draggableProvided.dragHandleProps,
1253
+ style: getStyle(draggableProvided.draggableProps.style, virtualItem),
1254
+ children: /* @__PURE__ */jsx("div", {
1255
+ ref: virtualizer.measureElement,
1256
+ "data-index": virtualItem.index,
1257
+ children: /* @__PURE__ */jsx(DocumentCard, {
1258
+ userRoleCanDrop,
1259
+ isDragDisabled,
1260
+ isPatching: patchingIds.includes(documentId),
1261
+ isDragging: draggableSnapshot.isDragging,
1262
+ item,
1263
+ toggleInvalidDocumentId,
1264
+ userList,
1265
+ states
1266
+ })
1267
+ })
1187
1268
  })
1188
- })
1189
- }, virtualItem.key);
1269
+ }, virtualItem.key);
1270
+ })
1190
1271
  })
1191
1272
  });
1192
1273
  }
@@ -1913,7 +1994,6 @@ function WorkflowTool(props) {
1913
1994
  ref: provided.innerRef,
1914
1995
  tone: snapshot.isDraggingOver ? "primary" : defaultCardTone,
1915
1996
  height: "fill",
1916
- paddingTop: 1,
1917
1997
  children: [loading ? /* @__PURE__ */jsx(Flex, {
1918
1998
  padding: 5,
1919
1999
  align: "center",
@@ -1961,7 +2041,10 @@ const workflow = definePlugin(function () {
1961
2041
  ...config
1962
2042
  };
1963
2043
  if (!(states == null ? void 0 : states.length)) {
1964
- throw new Error("Workflow: Missing states in config");
2044
+ throw new Error("Workflow plugin: Missing \"states\" in config");
2045
+ }
2046
+ if (!(schemaTypes == null ? void 0 : schemaTypes.length)) {
2047
+ throw new Error("Workflow plugin: Missing \"schemaTypes\" in config");
1965
2048
  }
1966
2049
  return {
1967
2050
  name: "sanity-plugin-workflow",
@@ -1970,12 +2053,33 @@ const workflow = definePlugin(function () {
1970
2053
  },
1971
2054
  // TODO: Remove 'workflow.metadata' from list of new document types
1972
2055
  // ...
2056
+ studio: {
2057
+ components: {
2058
+ layout: props => WorkflowProvider({
2059
+ ...props,
2060
+ workflow: {
2061
+ schemaTypes,
2062
+ states
2063
+ }
2064
+ })
2065
+ }
2066
+ },
2067
+ form: {
2068
+ components: {
2069
+ input: props => {
2070
+ if (props.id === "root" && isObjectInputProps(props) && schemaTypes.includes(props.schemaType.name)) {
2071
+ return WorkflowSignal(props);
2072
+ }
2073
+ return props.renderDefault(props);
2074
+ }
2075
+ }
2076
+ },
1973
2077
  document: {
1974
2078
  actions: (prev, context) => {
1975
2079
  if (!schemaTypes.includes(context.schemaType)) {
1976
2080
  return prev;
1977
2081
  }
1978
- return [props => BeginWorkflow(props, states), props => AssignWorkflow(props, states), ...states.map(state => props => UpdateWorkflow(props, states, state)), props => CompleteWorkflow(props, states), ...prev];
2082
+ return [props => BeginWorkflow(props), props => AssignWorkflow(props), ...states.map(state => props => UpdateWorkflow(props, state)), props => CompleteWorkflow(props), ...prev];
1979
2083
  },
1980
2084
  badges: (prev, context) => {
1981
2085
  if (!schemaTypes.includes(context.schemaType)) {
@@ -1988,10 +2092,12 @@ const workflow = definePlugin(function () {
1988
2092
  if (!documentId) {
1989
2093
  return prev;
1990
2094
  }
1991
- return [() => StateBadge(states, documentId), () => AssigneesBadge(states, documentId, currentUser), ...prev];
2095
+ return [() => StateBadge(documentId), () => AssigneesBadge(documentId, currentUser), ...prev];
1992
2096
  }
1993
2097
  },
1994
- tools: [workflowTool({
2098
+ tools: [
2099
+ // TODO: These configs could be read from Context
2100
+ workflowTool({
1995
2101
  schemaTypes,
1996
2102
  states
1997
2103
  })]