sanity-plugin-workflow 1.0.0-beta.9 → 1.0.1

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
@@ -135,58 +135,99 @@ function UserAssignment(props) {
135
135
  onRemove: removeAssignee
136
136
  });
137
137
  }
138
- function useWorkflowMetadata(id, states) {
138
+ function useWorkflowMetadata(ids) {
139
139
  const {
140
- data: metadata,
140
+ data: rawData,
141
141
  loading,
142
142
  error
143
- } = sanityPluginUtils.useListeningQuery("*[_type == \"workflow.metadata\" && documentId == $id][0]", {
143
+ } = sanityPluginUtils.useListeningQuery("*[_type == \"workflow.metadata\" && documentId in $ids]{\n _id,\n _type,\n _rev,\n assignees,\n documentId,\n state,\n orderRank\n }", {
144
144
  params: {
145
- id
145
+ ids
146
+ },
147
+ options: {
148
+ apiVersion: API_VERSION
146
149
  }
147
150
  });
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
- }
151
+ const keyedMetadata = React.useMemo(() => {
152
+ if (!rawData || rawData.length === 0) return {};
153
+ return rawData.reduce((acc, cur) => {
154
+ return {
155
+ ...acc,
156
+ [cur.documentId]: cur
157
+ };
158
+ }, {});
159
+ }, [rawData]);
158
160
  return {
159
- data: {},
161
+ data: keyedMetadata,
160
162
  loading,
161
163
  error
162
164
  };
163
165
  }
164
- function AssignWorkflow(props, states) {
165
- var _a, _b;
166
+ const WorkflowContext = React.createContext({
167
+ data: {},
168
+ loading: false,
169
+ error: false,
170
+ ids: [],
171
+ addId: () => null,
172
+ removeId: () => null,
173
+ ...DEFAULT_CONFIG
174
+ });
175
+ function useWorkflowContext(id) {
176
+ const current = React.useContext(WorkflowContext);
177
+ return {
178
+ ...current,
179
+ metadata: id ? current.data[id] : null
180
+ };
181
+ }
182
+ function WorkflowProvider(props) {
183
+ const [ids, setIds] = React.useState([]);
184
+ const addId = React.useCallback(id => setIds(current => current.includes(id) ? current : [...current, id]), []);
185
+ const removeId = React.useCallback(id => setIds(current => current.filter(i => i !== id)), []);
186
+ const {
187
+ data,
188
+ loading,
189
+ error
190
+ } = useWorkflowMetadata(ids);
191
+ return /* @__PURE__ */jsxRuntime.jsx(WorkflowContext.Provider, {
192
+ value: {
193
+ data,
194
+ loading,
195
+ error,
196
+ ids,
197
+ addId,
198
+ removeId,
199
+ states: props.workflow.states,
200
+ schemaTypes: props.workflow.schemaTypes
201
+ },
202
+ children: props.renderDefault(props)
203
+ });
204
+ }
205
+ function AssignWorkflow(props) {
206
+ var _a;
166
207
  const {
167
208
  id
168
209
  } = props;
210
+ const {
211
+ metadata,
212
+ loading,
213
+ error
214
+ } = useWorkflowContext(id);
169
215
  const [isDialogOpen, setDialogOpen] = React.useState(false);
170
216
  const userList = sanityPluginUtils.useProjectUsers({
171
217
  apiVersion: API_VERSION
172
218
  });
173
- const {
174
- data,
175
- loading,
176
- error
177
- } = useWorkflowMetadata(id, states);
178
219
  if (error) {
179
220
  console.error(error);
180
221
  }
181
- if (!(data == null ? void 0 : data.metadata)) {
222
+ if (!metadata) {
182
223
  return null;
183
224
  }
184
225
  return {
185
226
  icon: icons.UsersIcon,
186
227
  type: "dialog",
187
- disabled: !data || loading || error,
228
+ disabled: !metadata || loading || error,
188
229
  label: "Assign",
189
- title: data ? null : "Document is not in Workflow",
230
+ title: metadata ? null : "Document is not in Workflow",
190
231
  dialog: isDialogOpen && {
191
232
  type: "popover",
192
233
  onClose: () => {
@@ -194,7 +235,7 @@ function AssignWorkflow(props, states) {
194
235
  },
195
236
  content: /* @__PURE__ */jsxRuntime.jsx(UserAssignment, {
196
237
  userList,
197
- assignees: (_b = (_a = data.metadata) == null ? void 0 : _a.assignees) != null ? _b : [],
238
+ assignees: ((_a = metadata == null ? void 0 : metadata.assignees) == null ? void 0 : _a.length) > 0 ? metadata.assignees : [],
198
239
  documentId: id
199
240
  })
200
241
  },
@@ -203,16 +244,17 @@ function AssignWorkflow(props, states) {
203
244
  }
204
245
  };
205
246
  }
206
- function BeginWorkflow(props, states) {
247
+ function BeginWorkflow(props) {
207
248
  const {
208
249
  id,
209
250
  draft
210
251
  } = props;
211
252
  const {
212
- data,
253
+ metadata,
213
254
  loading,
214
- error
215
- } = useWorkflowMetadata(id, states);
255
+ error,
256
+ states
257
+ } = useWorkflowContext(id);
216
258
  const client = sanity.useClient({
217
259
  apiVersion: API_VERSION
218
260
  });
@@ -233,10 +275,6 @@ function BeginWorkflow(props, states) {
233
275
  documentId: id,
234
276
  state: states[0].id,
235
277
  orderRank: lowestOrderFirstState ? lexorank.LexoRank.parse(lowestOrderFirstState).genNext().toString() : lexorank.LexoRank.min().toString()
236
- },
237
- // Faster!
238
- {
239
- visibility: "async"
240
278
  }).then(() => {
241
279
  toast.push({
242
280
  status: "success",
@@ -247,29 +285,29 @@ function BeginWorkflow(props, states) {
247
285
  setComplete(true);
248
286
  });
249
287
  }, [id, states, client, toast]);
250
- if (!draft || complete || data.metadata) {
288
+ if (!draft || complete || metadata) {
251
289
  return null;
252
290
  }
253
291
  return {
254
292
  icon: icons.SplitVerticalIcon,
255
293
  type: "dialog",
256
- disabled: (data == null ? void 0 : data.metadata) || loading || error || beginning || complete,
294
+ disabled: metadata || loading || error || beginning || complete,
257
295
  label: beginning ? "Beginning..." : "Begin Workflow",
258
296
  onHandle: () => {
259
297
  handle();
260
298
  }
261
299
  };
262
300
  }
263
- function CompleteWorkflow(props, states) {
264
- var _a;
301
+ function CompleteWorkflow(props) {
265
302
  const {
266
303
  id
267
304
  } = props;
268
305
  const {
269
- data,
306
+ metadata,
270
307
  loading,
271
- error
272
- } = useWorkflowMetadata(id, states);
308
+ error,
309
+ states
310
+ } = useWorkflowContext(id);
273
311
  const client = sanity.useClient({
274
312
  apiVersion: API_VERSION
275
313
  });
@@ -279,10 +317,11 @@ function CompleteWorkflow(props, states) {
279
317
  const handle = React.useCallback(() => {
280
318
  client.delete("workflow-metadata.".concat(id));
281
319
  }, [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) {
320
+ if (!metadata) {
284
321
  return null;
285
322
  }
323
+ const state = states.find(s => s.id === metadata.state);
324
+ const isLastState = (state == null ? void 0 : state.id) === states[states.length - 1].id;
286
325
  return {
287
326
  icon: icons.CheckmarkIcon,
288
327
  type: "dialog",
@@ -298,7 +337,7 @@ function CompleteWorkflow(props, states) {
298
337
  function arraysContainMatchingString(one, two) {
299
338
  return one.some(item => two.includes(item));
300
339
  }
301
- function UpdateWorkflow(props, allStates, actionState) {
340
+ function UpdateWorkflow(props, actionState) {
302
341
  var _a, _b, _c, _d;
303
342
  const {
304
343
  id,
@@ -311,16 +350,15 @@ function UpdateWorkflow(props, allStates, actionState) {
311
350
  const toast = ui.useToast();
312
351
  const currentUser = sanity.useCurrentUser();
313
352
  const {
314
- data,
353
+ metadata,
315
354
  loading,
316
- error
317
- } = useWorkflowMetadata(id, allStates);
318
- const {
319
- state: currentState
320
- } = data;
355
+ error,
356
+ states
357
+ } = useWorkflowContext(id);
358
+ const currentState = states.find(s => s.id === (metadata == null ? void 0 : metadata.state));
321
359
  const {
322
360
  assignees = []
323
- } = (_a = data == null ? void 0 : data.metadata) != null ? _a : {};
361
+ } = metadata != null ? metadata : {};
324
362
  const {
325
363
  validation,
326
364
  isValidating
@@ -347,21 +385,21 @@ function UpdateWorkflow(props, allStates, actionState) {
347
385
  });
348
386
  });
349
387
  };
350
- if (!data.metadata || currentState && currentState.id === actionState.id) {
388
+ if (!metadata || currentState && currentState.id === actionState.id) {
351
389
  return null;
352
390
  }
353
- const currentStateIndex = allStates.findIndex(s => s.id === (currentState == null ? void 0 : currentState.id));
354
- const actionStateIndex = allStates.findIndex(s => s.id === actionState.id);
391
+ const currentStateIndex = states.findIndex(s => s.id === (currentState == null ? void 0 : currentState.id));
392
+ const actionStateIndex = states.findIndex(s => s.id === actionState.id);
355
393
  const direction = actionStateIndex > currentStateIndex ? "promote" : "demote";
356
394
  const DirectionIcon = direction === "promote" ? icons.ArrowRightIcon : icons.ArrowLeftIcon;
357
395
  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) ?
396
+ 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) ?
359
397
  // If the Action state is limited to specific roles
360
398
  // check that the current user has one of those roles
361
399
  arraysContainMatchingString(user.roles.map(r => r.name), actionState.roles) :
362
400
  // 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 ?
401
+ ((_c = actionState == null ? void 0 : actionState.roles) == null ? void 0 : _c.length) !== 0;
402
+ const actionStateIsAValidTransition = (currentState == null ? void 0 : currentState.id) && ((_d = currentState == null ? void 0 : currentState.transitions) == null ? void 0 : _d.length) ?
365
403
  // If the Current State limits transitions to specific States
366
404
  // Check that the Action State is in Current State's transitions array
367
405
  currentState.transitions.includes(actionState.id) :
@@ -370,7 +408,7 @@ function UpdateWorkflow(props, allStates, actionState) {
370
408
  const userAssignmentCanUpdateState = actionState.requireAssignment ?
371
409
  // If the Action State requires assigned users
372
410
  // Check the current user ID is in the assignees array
373
- currentUser && assignees.length && assignees.includes(currentUser.id) :
411
+ currentUser && (assignees == null ? void 0 : assignees.length) && assignees.includes(currentUser.id) :
374
412
  // Otherwise this isn't a problem
375
413
  true;
376
414
  let title = "".concat(directionLabel, " State to \"").concat(actionState.title, "\"");
@@ -393,16 +431,13 @@ function UpdateWorkflow(props, allStates, actionState) {
393
431
  onHandle: () => onHandle(id, actionState)
394
432
  };
395
433
  }
396
- function AssigneesBadge(states, documentId, currentUser) {
434
+ function AssigneesBadge(documentId, currentUser) {
397
435
  var _a;
398
436
  const {
399
- data,
437
+ metadata,
400
438
  loading,
401
439
  error
402
- } = useWorkflowMetadata(documentId, states);
403
- const {
404
- metadata
405
- } = data;
440
+ } = useWorkflowContext(documentId);
406
441
  const userList = sanityPluginUtils.useProjectUsers({
407
442
  apiVersion: API_VERSION
408
443
  });
@@ -438,15 +473,14 @@ function AssigneesBadge(states, documentId, currentUser) {
438
473
  color: "primary"
439
474
  };
440
475
  }
441
- function StateBadge(states, documentId) {
476
+ function StateBadge(documentId) {
442
477
  const {
443
- data,
478
+ metadata,
444
479
  loading,
445
- error
446
- } = useWorkflowMetadata(documentId, states);
447
- const {
448
- state
449
- } = data;
480
+ error,
481
+ states
482
+ } = useWorkflowContext(documentId);
483
+ const state = states.find(s => s.id === (metadata == null ? void 0 : metadata.state));
450
484
  if (loading || error) {
451
485
  if (error) {
452
486
  console.error(error);
@@ -462,6 +496,25 @@ function StateBadge(states, documentId) {
462
496
  color: state == null ? void 0 : state.color
463
497
  };
464
498
  }
499
+ function WorkflowSignal(props) {
500
+ var _a;
501
+ const documentId = ((_a = props == null ? void 0 : props.value) == null ? void 0 : _a._id) ? props.value._id.replace("drafts.", "") : null;
502
+ const {
503
+ addId,
504
+ removeId
505
+ } = useWorkflowContext();
506
+ React.useEffect(() => {
507
+ if (documentId) {
508
+ addId(documentId);
509
+ }
510
+ return () => {
511
+ if (documentId) {
512
+ removeId(documentId);
513
+ }
514
+ };
515
+ }, [documentId, addId, removeId]);
516
+ return props.renderDefault(props);
517
+ }
465
518
  function EditButton(props) {
466
519
  const {
467
520
  id,
@@ -1782,7 +1835,7 @@ function WorkflowTool(props) {
1782
1835
  if (destinationStateIndex === 0) {
1783
1836
  newOrder = lexorank.LexoRank.min().toString();
1784
1837
  } else {
1785
- newOrder = lexorank.LexoRank.parse(globalStateMinimumRank).between(lexorank.LexoRank.min()).toString();
1838
+ newOrder = lexorank.LexoRank.min().genNext().toString();
1786
1839
  }
1787
1840
  } else if (destination.index === 0) {
1788
1841
  const firstItemOrderRank = (_b2 = (_a2 = [...destinationStateItems].shift()) == null ? void 0 : _a2._metadata) == null ? void 0 : _b2.orderRank;
@@ -2001,7 +2054,10 @@ const workflow = sanity.definePlugin(function () {
2001
2054
  ...config
2002
2055
  };
2003
2056
  if (!(states == null ? void 0 : states.length)) {
2004
- throw new Error("Workflow: Missing states in config");
2057
+ throw new Error("Workflow plugin: Missing \"states\" in config");
2058
+ }
2059
+ if (!(schemaTypes == null ? void 0 : schemaTypes.length)) {
2060
+ throw new Error("Workflow plugin: Missing \"schemaTypes\" in config");
2005
2061
  }
2006
2062
  return {
2007
2063
  name: "sanity-plugin-workflow",
@@ -2010,12 +2066,33 @@ const workflow = sanity.definePlugin(function () {
2010
2066
  },
2011
2067
  // TODO: Remove 'workflow.metadata' from list of new document types
2012
2068
  // ...
2069
+ studio: {
2070
+ components: {
2071
+ layout: props => WorkflowProvider({
2072
+ ...props,
2073
+ workflow: {
2074
+ schemaTypes,
2075
+ states
2076
+ }
2077
+ })
2078
+ }
2079
+ },
2080
+ form: {
2081
+ components: {
2082
+ input: props => {
2083
+ if (props.id === "root" && sanity.isObjectInputProps(props) && schemaTypes.includes(props.schemaType.name)) {
2084
+ return WorkflowSignal(props);
2085
+ }
2086
+ return props.renderDefault(props);
2087
+ }
2088
+ }
2089
+ },
2013
2090
  document: {
2014
2091
  actions: (prev, context) => {
2015
2092
  if (!schemaTypes.includes(context.schemaType)) {
2016
2093
  return prev;
2017
2094
  }
2018
- return [props => BeginWorkflow(props, states), props => AssignWorkflow(props, states), ...states.map(state => props => UpdateWorkflow(props, states, state)), props => CompleteWorkflow(props, states), ...prev];
2095
+ return [props => BeginWorkflow(props), props => AssignWorkflow(props), ...states.map(state => props => UpdateWorkflow(props, state)), props => CompleteWorkflow(props), ...prev];
2019
2096
  },
2020
2097
  badges: (prev, context) => {
2021
2098
  if (!schemaTypes.includes(context.schemaType)) {
@@ -2028,10 +2105,12 @@ const workflow = sanity.definePlugin(function () {
2028
2105
  if (!documentId) {
2029
2106
  return prev;
2030
2107
  }
2031
- return [() => StateBadge(states, documentId), () => AssigneesBadge(states, documentId, currentUser), ...prev];
2108
+ return [() => StateBadge(documentId), () => AssigneesBadge(documentId, currentUser), ...prev];
2032
2109
  }
2033
2110
  },
2034
- tools: [workflowTool({
2111
+ tools: [
2112
+ // TODO: These configs could be read from Context
2113
+ workflowTool({
2035
2114
  schemaTypes,
2036
2115
  states
2037
2116
  })]