@sanity/workflow-studio-plugin 0.4.0 → 0.20.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.
@@ -1,18 +1,6 @@
1
1
  "use strict";
2
2
 
3
- var jsxRuntime = require("react/jsx-runtime"), ui = require("@sanity/ui"), react = require("react"), router = require("sanity/router"), index = require("./index.cjs"), ChevronDown = require("@sanity/icons/ChevronDown"), ChevronRight = require("@sanity/icons/ChevronRight"), workflowEngine = require("@sanity/workflow-engine"), Checkmark = require("@sanity/icons/Checkmark"), Close = require("@sanity/icons/Close"), Filter = require("@sanity/icons/Filter"), workflowComponents = require("@sanity/workflow-components"), endOfDay = require("date-fns/endOfDay"), sanity = require("sanity"), workflowStudio = require("@sanity/workflow-studio"), ArrowLeft = require("@sanity/icons/ArrowLeft"), workflowDiagram = require("@sanity/workflow-diagram"), Add = require("@sanity/icons/Add"), Search = require("@sanity/icons/Search"), EllipsisHorizontal = require("@sanity/icons/EllipsisHorizontal");
4
-
5
- function MutedNote({text: text}) {
6
- /* @__PURE__ */
7
- return jsxRuntime.jsx(ui.Box, {
8
- paddingY: 3,
9
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
10
- muted: !0,
11
- size: 1,
12
- children: text
13
- })
14
- });
15
- }
3
+ var jsxRuntime = require("react/jsx-runtime"), ui = require("@sanity/ui"), react = require("react"), router = require("sanity/router"), index = require("./index.cjs"), ArrowLeft = require("@sanity/icons/ArrowLeft"), workflowDiagram = require("@sanity/workflow-diagram"), workflowEngine = require("@sanity/workflow-engine"), Add = require("@sanity/icons/Add"), Search = require("@sanity/icons/Search"), Checkmark = require("@sanity/icons/Checkmark"), Close = require("@sanity/icons/Close"), Filter = require("@sanity/icons/Filter"), workflowComponents = require("@sanity/workflow-components"), endOfDay = require("date-fns/endOfDay"), reactDom = require("react-dom"), sanity = require("sanity"), Calendar = require("@sanity/icons/Calendar"), workflowStudio = require("@sanity/workflow-studio");
16
4
 
17
5
  function documentRefsOf(instance) {
18
6
  const seen = /* @__PURE__ */ new Map;
@@ -20,1476 +8,1336 @@ function documentRefsOf(instance) {
20
8
  return [ ...seen.values() ];
21
9
  }
22
10
 
23
- function RefChips({refs: refs, max: max = 2}) {
24
- if (refs.length === 0) return null;
25
- const shown = refs.slice(0, max), overflow = refs.length - shown.length;
26
- /* @__PURE__ */
27
- return jsxRuntime.jsxs(ui.Flex, {
28
- align: "center",
29
- gap: 3,
30
- wrap: "wrap",
31
- children: [ shown.map(ref => /* @__PURE__ */ jsxRuntime.jsx(index.DocPreviewLink, {
32
- gdr: ref,
33
- layout: "inline"
34
- }, ref.id)), overflow > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
35
- muted: !0,
36
- size: 1,
37
- children: [ "+", overflow, " more" ]
38
- }) : null ]
11
+ function InstanceTaskLists({entry: entry, onOpenActivity: onOpenActivity}) {
12
+ return index.instanceNotice(entry) || /* @__PURE__ */ jsxRuntime.jsx(index.StaleLock, {
13
+ stale: index.isEvaluationStale(entry),
14
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
15
+ children: [
16
+ /* @__PURE__ */ jsxRuntime.jsx(index.ActivitiesList, {
17
+ entry: entry,
18
+ onOpenActivity: onOpenActivity
19
+ }),
20
+ /* @__PURE__ */ jsxRuntime.jsx(index.TodoItemsList, {
21
+ entry: entry
22
+ }) ]
23
+ })
39
24
  });
40
25
  }
41
26
 
42
- function DocRefChips({instance: instance, max: max = 2}) {
43
- const refs = react.useMemo(() => documentRefsOf(instance), [ instance ]);
44
- /* @__PURE__ */
45
- return jsxRuntime.jsx(RefChips, {
46
- max: max,
47
- refs: refs
27
+ function WorkflowInstanceDetail({instanceId: instanceId, onBack: onBack}) {
28
+ const entry = index.useWorkflowInstanceEntry(instanceId), [graceOver, setGraceOver] = react.useState(!1);
29
+ return react.useEffect(() => {
30
+ const timer = setTimeout(() => setGraceOver(!0), 2500);
31
+ return () => clearTimeout(timer);
32
+ }, [ instanceId ]), entry?.invalid ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
33
+ padding: 4,
34
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.InvalidDocNotice, {
35
+ invalid: entry.invalid
36
+ })
37
+ }) : entry ? /* @__PURE__ */ jsxRuntime.jsx(InstanceDetailPanel, {
38
+ entry: entry,
39
+ onBack: onBack
40
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
41
+ padding: 4,
42
+ children: graceOver ? /* @__PURE__ */ jsxRuntime.jsx(NotFound, {
43
+ id: instanceId,
44
+ onBack: onBack
45
+ }) : /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
46
+ label: "Loading workflow…"
47
+ })
48
48
  });
49
49
  }
50
50
 
51
- function datesOf(state) {
52
- return state.filter(entry => entry._type === "date" || entry._type === "datetime").map(entry => entry.value).filter(value => typeof value == "string" && value !== "");
53
- }
54
-
55
- function spawnOrigins(instances) {
56
- const origins = /* @__PURE__ */ new Map;
57
- for (const parent of instances) for (const row of parent.subworkflows ?? []) origins.set(workflowEngine.extractDocumentId(row.ref.id), {
58
- parent: parent,
59
- activityName: row.activity,
60
- cohortStage: row.cohortStage
51
+ function NotFound({id: id, onBack: onBack}) {
52
+ /* @__PURE__ */
53
+ return jsxRuntime.jsxs(ui.Stack, {
54
+ gap: 4,
55
+ children: [
56
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
57
+ muted: !0,
58
+ size: 1,
59
+ children: [ "No workflow with id “", id, "” — it may have been deleted." ]
60
+ }),
61
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
62
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
63
+ fontSize: 1,
64
+ icon: ArrowLeft.ArrowLeftIcon,
65
+ mode: "ghost",
66
+ onClick: onBack,
67
+ padding: 2,
68
+ text: "Back to workflows"
69
+ })
70
+ }) ]
61
71
  });
62
- return origins;
63
- }
64
-
65
- function rootOf(instance, byId) {
66
- const rootRef = instance.ancestors[0];
67
- return rootRef ? byId.get(workflowEngine.extractDocumentId(rootRef.id)) ?? instance : instance;
68
72
  }
69
73
 
70
- function taskRowsOf(args) {
71
- const {instance: instance, breadcrumb: breadcrumb, identity: identity} = args, open = workflowEngine.findOpenStageEntry(instance);
72
- if (!open) return [];
73
- const definition = index.definitionSnapshotOf(instance), state = index.stateByActivity(instance), rows = (open.activities ?? []).map(activity => {
74
- const fields = state.get(activity.name) ?? [], declared = index.findActivity({
74
+ function InstanceDetailPanel({entry: entry, onBack: onBack}) {
75
+ const definition = index.useDefinition(entry), [openActivity, setOpenActivity] = react.useState(null), {instance: instance} = entry;
76
+ return react.useEffect(() => {
77
+ openActivity && index.openActivityGone({
78
+ entry: entry,
79
+ target: openActivity
80
+ }) && setOpenActivity(null);
81
+ }, [ entry, openActivity ]), /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
82
+ direction: "column",
83
+ height: "fill",
84
+ overflow: "hidden",
85
+ children: [
86
+ /* @__PURE__ */ jsxRuntime.jsx(DetailHeader, {
75
87
  definition: definition,
76
- stageName: open.name,
77
- activityName: activity.name
78
- });
79
- return {
80
- instanceId: instance._id,
81
- activityName: activity.name,
82
- title: declared.title,
83
- description: declared.description,
84
- breadcrumb: breadcrumb,
85
- stage: open.name,
86
- stageTitle: index.stageTitle(definition, open.name),
87
- status: activity.status,
88
- open: index.isOpenActivityStatus(activity.status),
89
- dates: datesOf(fields),
90
- assigneeIds: index.assigneeUserIdsOf(fields),
91
- assignedToMe: identity !== void 0 && index.isActivityAssignedTo({
92
- state: fields,
93
- who: identity
88
+ entry: entry,
89
+ onBack: onBack
90
+ }),
91
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
92
+ flex: 1,
93
+ overflow: "auto",
94
+ paddingX: 4,
95
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
96
+ gap: 4,
97
+ paddingBottom: 5,
98
+ children: [
99
+ /* @__PURE__ */ jsxRuntime.jsx(StudioDocumentsSection, {
100
+ instance: instance
101
+ }),
102
+ /* @__PURE__ */ jsxRuntime.jsx(StageDiagramSection, {
103
+ definition: definition,
104
+ entry: entry
105
+ }),
106
+ /* @__PURE__ */ jsxRuntime.jsx(index.GroupHeading, {
107
+ title: "All tasks"
108
+ }),
109
+ /* @__PURE__ */ jsxRuntime.jsx(InstanceTaskLists, {
110
+ entry: entry,
111
+ onOpenActivity: name => setOpenActivity({
112
+ stage: instance.currentStage,
113
+ activityName: name
114
+ })
115
+ }) ]
94
116
  })
95
- };
117
+ }), openActivity ? /* @__PURE__ */ jsxRuntime.jsx(index.ActivityDetailDialog, {
118
+ activityName: openActivity.activityName,
119
+ breadcrumb: index.instanceBreadcrumb(instance, definition),
120
+ definition: definition,
121
+ entry: entry,
122
+ onClose: () => setOpenActivity(null)
123
+ }) : null ]
96
124
  });
97
- return [ ...rows.filter(r => r.open), ...rows.filter(r => !r.open) ];
98
125
  }
99
126
 
100
- function deriveTaskGroups(args) {
101
- const {instances: instances, identity: identity} = args, byId = new Map(instances.map(instance => [ instance._id, instance ])), origins = spawnOrigins(instances), groups = /* @__PURE__ */ new Map, ordered = [ ...instances ].sort((a, b) => a.startedAt.localeCompare(b.startedAt));
102
- for (const instance of ordered) {
103
- const origin = origins.get(instance._id), breadcrumb = origin ? index.findActivity({
104
- definition: index.definitionSnapshotOf(origin.parent),
105
- stageName: origin.cohortStage,
106
- activityName: origin.activityName
107
- }).title : void 0, rows = taskRowsOf({
108
- instance: instance,
109
- breadcrumb: breadcrumb,
110
- identity: identity
111
- });
112
- if (rows.length === 0) continue;
113
- const root = rootOf(instance, byId), documents = documentRefsOf(root), document = documents[0], key = document ? `doc:${document.id}` : `def:${root.definition}`, held = groups.get(key);
114
- if (held) {
115
- held.rows.push(...rows);
116
- continue;
117
- }
118
- const definition = index.definitionSnapshotOf(root);
119
- groups.set(key, {
120
- group: {
121
- key: key,
122
- document: document,
123
- documents: documents,
124
- instanceId: root._id,
125
- workflowName: root.definition,
126
- workflowTitle: index.instanceTitle(root, definition),
127
- workflowDescription: definition?.description
128
- },
129
- rows: rows
130
- });
131
- }
132
- const assembled = [ ...groups.values() ].map(({group: group, rows: rows}) => ({
133
- ...group,
134
- rows: rows
135
- }));
136
- return {
137
- withDocuments: assembled.filter(g => g.document !== void 0),
138
- withoutDocuments: assembled.filter(g => g.document === void 0)
139
- };
127
+ function DetailHeader({entry: entry, definition: definition, onBack: onBack}) {
128
+ const {instance: instance, ready: ready} = entry;
129
+ /* @__PURE__ */
130
+ return jsxRuntime.jsx(ui.Card, {
131
+ flex: "none",
132
+ paddingX: 4,
133
+ paddingY: 3,
134
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
135
+ align: "center",
136
+ gap: 3,
137
+ wrap: "wrap",
138
+ children: [
139
+ /* @__PURE__ */ jsxRuntime.jsx(index.HoverHint, {
140
+ text: "Back to workflows",
141
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
142
+ "aria-label": "Back to workflows",
143
+ fontSize: 1,
144
+ icon: ArrowLeft.ArrowLeftIcon,
145
+ mode: "bleed",
146
+ onClick: onBack,
147
+ padding: 2
148
+ })
149
+ }),
150
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
151
+ gap: 2,
152
+ children: [
153
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
154
+ size: 1,
155
+ weight: "semibold",
156
+ children: index.instanceTitle(instance, definition)
157
+ }),
158
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
159
+ muted: !0,
160
+ size: 1,
161
+ children: [ "Started ", index.formatDate(instance.startedAt) ]
162
+ }) ]
163
+ }), workflowEngine.terminalState(instance) === "in-flight" ? /* @__PURE__ */ jsxRuntime.jsx(index.StageChip, {
164
+ title: index.stageTitle(definition, instance.currentStage)
165
+ }) : /* @__PURE__ */ jsxRuntime.jsx(index.AbortedBadge, {
166
+ instance: instance
167
+ }),
168
+ /* @__PURE__ */ jsxRuntime.jsx(index.SpinnerSlot, {
169
+ busy: !ready || index.isEvaluationStale(entry)
170
+ }) ]
171
+ })
172
+ });
140
173
  }
141
174
 
142
- function onlyMyTasks(groups) {
143
- const cut = list => list.map(group => ({
144
- ...group,
145
- rows: group.rows.filter(row => row.assignedToMe)
146
- })).filter(group => group.rows.length > 0);
147
- return {
148
- withDocuments: cut(groups.withDocuments),
149
- withoutDocuments: cut(groups.withoutDocuments)
150
- };
175
+ function StudioDocumentsSection({instance: instance}) {
176
+ const refs = documentRefsOf(instance);
177
+ return refs.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
178
+ gap: 3,
179
+ marginTop: 3,
180
+ children: [
181
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
182
+ muted: !0,
183
+ size: 1,
184
+ weight: "medium",
185
+ children: refs.length === 1 ? "Studio document" : "Studio documents"
186
+ }),
187
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
188
+ gap: 2,
189
+ children: refs.map(ref => /* @__PURE__ */ jsxRuntime.jsx(index.DocPreviewLink, {
190
+ gdr: ref
191
+ }, ref.id))
192
+ }) ]
193
+ });
151
194
  }
152
195
 
153
- function taskFilterOptions(groups) {
154
- const all = [ ...groups.withDocuments, ...groups.withoutDocuments ], workflows = /* @__PURE__ */ new Map, documents = /* @__PURE__ */ new Map;
155
- for (const group of all) workflows.set(group.workflowName, group.workflowTitle),
156
- group.document && documents.set(group.document.id, group.document);
157
- return {
158
- workflows: [ ...workflows ].map(([name, title]) => ({
159
- name: name,
160
- title: title
161
- })),
162
- documents: [ ...documents.values() ]
163
- };
164
- }
165
-
166
- function hasActiveFilters(filters) {
167
- return Object.values(filters).some(value => value !== void 0 && (!Array.isArray(value) || value.length > 0));
168
- }
169
-
170
- function toggledValue(args) {
171
- const {filters: filters, key: key, value: value} = args, current = filters[key] ?? [], next = current.includes(value) ? current.filter(v => v !== value) : [ ...current, value ];
172
- if (next.length > 0) return {
173
- ...filters,
174
- [key]: next
175
- };
176
- const {[key]: _emptied, ...rest} = filters;
177
- return rest;
178
- }
179
-
180
- const STATUS_LABEL = {
181
- open: "Open",
182
- completed: "Completed"
183
- }, DATE_PRESET_LABEL = {
184
- past: "In the past",
185
- none: "No date"
186
- };
187
-
188
- function dateLabel(date) {
189
- return typeof date == "string" ? DATE_PRESET_LABEL[date] : `Before ${index.formatDate(date.before)}`;
190
- }
191
-
192
- function withoutChip(filters, chip) {
193
- if (chip.key === "date") {
194
- const {date: _cleared, ...rest} = filters;
195
- return rest;
196
- }
197
- return chip.value === void 0 ? filters : toggledValue({
198
- filters: filters,
199
- key: chip.key,
200
- value: chip.value
196
+ function StageDiagramSection({entry: entry, definition: definition}) {
197
+ const {instance: instance} = entry;
198
+ return definition ?
199
+ /* @__PURE__ */ jsxRuntime.jsx(workflowDiagram.WorkflowDiagram, {
200
+ currentStage: workflowEngine.terminalState(instance) === "in-flight" ? instance.currentStage : void 0,
201
+ definition: definition,
202
+ evaluation: entry.evaluation,
203
+ explain: !0,
204
+ guardCount: entry.guards?.length,
205
+ height: 340,
206
+ history: instance.history
207
+ }, instance._id) : /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
208
+ border: !0,
209
+ marginTop: 3,
210
+ padding: 3,
211
+ radius: 2,
212
+ tone: "caution",
213
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
214
+ muted: !0,
215
+ size: 1,
216
+ children: "Couldn’t load this workflow’s full details — the stage diagram is unavailable, and technical names show instead of titles."
217
+ })
201
218
  });
202
219
  }
203
220
 
204
- function chipsFor(args) {
205
- const {key: key, values: values, labelOf: labelOf} = args;
206
- return (values ?? []).map(value => ({
207
- key: key,
208
- value: value,
209
- label: labelOf(value)
210
- }));
211
- }
212
-
213
- function shownHits(hits, cap) {
214
- return hits.length <= cap + 1 ? [ ...hits ] : hits.slice(0, cap);
215
- }
216
-
217
- const PENDING_LABEL = "…";
218
-
219
- function activeFilterChips(args) {
220
- const {filters: filters, options: options, assigneeNames: assigneeNames, documentTitles: documentTitles} = args, chips = [ ...chipsFor({
221
- key: "documentIds",
222
- values: filters.documentIds,
223
- labelOf: id => documentTitles?.get(id) ?? PENDING_LABEL
224
- }), ...chipsFor({
225
- key: "statuses",
226
- values: filters.statuses,
227
- labelOf: status => STATUS_LABEL[status]
228
- }), ...chipsFor({
229
- key: "assigneeIds",
230
- values: filters.assigneeIds,
231
- labelOf: id => assigneeNames?.get(id) ?? PENDING_LABEL
232
- }), ...chipsFor({
233
- key: "workflows",
234
- values: filters.workflows,
235
- labelOf: name => options.workflows.find(w => w.name === name)?.title ?? name
236
- }) ];
237
- return filters.date && chips.push({
238
- key: "date",
239
- label: dateLabel(filters.date)
240
- }), chips;
241
- }
242
-
243
- function dateMatches(args) {
244
- const {row: row, date: date, now: now} = args, parsed = row.dates.map(value => index.parseStoredDateValue(value)?.getTime()).filter(at => at !== void 0);
245
- return date === "none" ? parsed.length === 0 : date === "past" ? parsed.some(at => at < now.getTime()) : parsed.some(at => at <= Date.parse(date.before));
246
- }
247
-
248
- function activeArm(values) {
249
- return values !== void 0 && values.length > 0 ? values : void 0;
250
- }
251
-
252
- function statusArm(row, statuses) {
253
- const arm = activeArm(statuses);
254
- if (!arm) return !0;
255
- const rowStatus = row.open ? "open" : "completed";
256
- return arm.includes(rowStatus);
257
- }
258
-
259
- function assigneeArm(row, assigneeIds) {
260
- const arm = activeArm(assigneeIds);
261
- return arm ? arm.some(id => row.assigneeIds.includes(id)) : !0;
262
- }
263
-
264
- function rowMatches(args) {
265
- const {row: row, filters: filters, now: now} = args;
266
- return !(!statusArm(row, filters.statuses) || !assigneeArm(row, filters.assigneeIds) || filters.date !== void 0 && !dateMatches({
267
- row: row,
268
- date: filters.date,
269
- now: now
270
- }));
271
- }
272
-
273
- function groupMatches(group, filters) {
274
- const workflows = activeArm(filters.workflows);
275
- if (workflows && !workflows.includes(group.workflowName)) return !1;
276
- const documentIds = activeArm(filters.documentIds);
277
- return !(documentIds && (!group.document || !documentIds.includes(group.document.id)));
278
- }
279
-
280
- function applyTaskFilters(args) {
281
- const {groups: groups, filters: filters, now: now} = args, cut = list => list.filter(group => groupMatches(group, filters)).map(group => ({
282
- ...group,
283
- rows: group.rows.filter(row => rowMatches({
284
- row: row,
285
- filters: filters,
286
- now: now
287
- }))
288
- })).filter(group => group.rows.length > 0);
289
- return {
290
- withDocuments: cut(groups.withDocuments),
291
- withoutDocuments: cut(groups.withoutDocuments)
292
- };
293
- }
294
-
295
- function instanceFilterOptions(rows) {
296
- const workflows = /* @__PURE__ */ new Map, documents = /* @__PURE__ */ new Map;
297
- for (const row of rows) {
298
- workflows.set(row.instance.definition, row.workflowTitle);
299
- for (const doc of row.documents) documents.set(doc.id, doc);
300
- }
301
- return {
302
- workflows: [ ...workflows ].map(([name, title]) => ({
303
- name: name,
304
- title: title
305
- })),
306
- documents: [ ...documents.values() ]
307
- };
308
- }
309
-
310
- function applyInstanceFilters(args) {
311
- const {rows: rows, filters: filters} = args, workflows = activeArm(filters.workflows), documentIds = activeArm(filters.documentIds);
312
- return rows.filter(row => !workflows || workflows.includes(row.instance.definition)).filter(row => !documentIds || row.documents.some(doc => documentIds.includes(doc.id))).map(row => row.instance);
313
- }
314
-
315
- function hasTaskArms(filters) {
316
- return (filters.assigneeIds?.length ?? 0) > 0 || filters.date !== void 0;
317
- }
318
-
319
- function instanceStatusMatches(instance, statuses) {
320
- const arm = activeArm(statuses);
321
- if (!arm) return !0;
322
- const state = workflowEngine.terminalState(instance);
323
- return state === "in-flight" ? arm.includes("open") : state === "completed" && arm.includes("completed");
324
- }
325
-
326
- function instanceIdsMatchingTaskArms(args) {
327
- const {instances: instances, identity: identity, filters: filters, now: now} = args;
328
- if (!hasTaskArms(filters)) return null;
329
- const groups = deriveTaskGroups({
330
- instances: instances,
331
- identity: identity
332
- }), {statuses: _workflowLevel, ...taskArms} = filters, cut = applyTaskFilters({
333
- groups: groups,
334
- filters: taskArms,
335
- now: now
336
- }), ids = /* @__PURE__ */ new Set;
337
- for (const group of [ ...cut.withDocuments, ...cut.withoutDocuments ]) for (const row of group.rows) ids.add(row.instanceId);
338
- return ids;
221
+ function MutedNote({text: text}) {
222
+ /* @__PURE__ */
223
+ return jsxRuntime.jsx(ui.Box, {
224
+ paddingY: 3,
225
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
226
+ muted: !0,
227
+ size: 1,
228
+ children: text
229
+ })
230
+ });
339
231
  }
340
232
 
341
- const NOTHING = {
342
- titles: /* @__PURE__ */ new Map,
343
- resolved: /* @__PURE__ */ new Set
344
- };
345
-
346
- function useDocPreviewTitles(documents) {
347
- const previewStore = sanity.useDocumentPreviewStore(), schema = sanity.useSchema(), {binding: binding} = index.useWorkflowContext(), [state, setState] = react.useState(NOTHING), key = documents.map(doc => doc.id).join("|");
233
+ function useStartableDefinitions() {
234
+ const {engine: engine} = index.useWorkflowContext(), [definitions, setDefinitions] = react.useState(void 0), [error, setError] = react.useState(void 0);
348
235
  return react.useEffect(() => {
349
- const titles = /* @__PURE__ */ new Map, resolved = /* @__PURE__ */ new Set, publish = () => setState({
350
- titles: new Map(titles),
351
- resolved: new Set(resolved)
352
- }), subscriptions = documents.flatMap(doc => {
353
- const {parsed: parsedRef, local: local} = index.gdrLocality(doc.id, binding.contentResource), schemaType = schema.get(doc.type);
354
- return !parsedRef || !local || !schemaType ? (resolved.add(doc.id), []) : [ previewStore.observeForPreview({
355
- _type: "reference",
356
- _ref: parsedRef.documentId
357
- }, schemaType).subscribe({
358
- next: event => {
359
- resolved.add(doc.id);
360
- const title = event.snapshot?.title;
361
- typeof title == "string" && titles.set(doc.id, title), publish();
362
- },
363
- error: err => {
364
- console.error(`[workflow-studio-plugin] preview for "${doc.id}" failed:`, err),
365
- resolved.add(doc.id), publish();
366
- }
367
- }) ];
368
- });
369
- return publish(), () => subscriptions.forEach(sub => sub.unsubscribe());
370
- }, [ key, previewStore, schema, binding.contentResource.id ]), state;
236
+ let cancelled = !1;
237
+ return (async () => {
238
+ const rows = await index.readDeployedDefinitions(engine), startable = workflowEngine.latestDeployedDefinitions(rows).filter(row => workflowEngine.isStartableDefinition(row)).map(row => ({
239
+ name: row.name,
240
+ title: row.title ?? row.name,
241
+ description: row.description
242
+ }));
243
+ cancelled || (setDefinitions(startable), setError(void 0));
244
+ })().catch(err => {
245
+ cancelled || setError(index.describeError(err));
246
+ }), () => {
247
+ cancelled = !0;
248
+ };
249
+ }, [ engine ]), {
250
+ definitions: definitions,
251
+ error: error
252
+ };
371
253
  }
372
254
 
373
- const HITS_CAP = 6;
374
-
375
- function TaskFilterMenu({filters: filters, options: options, onChange: onChange}) {
376
- const [open, setOpen] = react.useState(!1), active = hasActiveFilters(filters);
255
+ function NewWorkflowButton() {
256
+ const {openStartDialog: openStartDialog} = index.useWorkflowContext(), [open, setOpen] = react.useState(!1);
377
257
  /* @__PURE__ */
378
258
  return jsxRuntime.jsx(index.DismissablePopover, {
379
- content: open ? /* @__PURE__ */ jsxRuntime.jsx(FilterPanel, {
380
- filters: filters,
381
- onChange: onChange,
382
- options: options
259
+ content: open ? /* @__PURE__ */ jsxRuntime.jsx(DefinitionPickerPanel, {
260
+ onPick: definition => {
261
+ setOpen(!1), openStartDialog({
262
+ definition: definition.name,
263
+ label: definition.title
264
+ });
265
+ }
383
266
  }) : null,
384
267
  onDismiss: () => setOpen(!1),
385
268
  open: open,
386
- overflow: "visible",
387
- children: /* @__PURE__ */ jsxRuntime.jsx(index.HoverHint, {
388
- disabled: open,
389
- text: "Filter",
390
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
391
- "aria-label": "Filter",
392
- fontSize: 1,
393
- icon: Filter.FilterIcon,
394
- mode: "bleed",
395
- onClick: () => setOpen(v => !v),
396
- padding: 2,
397
- selected: active || open
398
- })
269
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
270
+ fontSize: 1,
271
+ gap: 2,
272
+ icon: Add.AddIcon,
273
+ onClick: () => setOpen(v => !v),
274
+ padding: 2,
275
+ selected: open,
276
+ text: "New workflow"
399
277
  })
400
278
  });
401
279
  }
402
280
 
403
- function FilterPanel({filters: filters, options: options, onChange: onChange}) {
404
- const [openList, setOpenList] = react.useState(null), toggle = (key, value) => onChange(toggledValue({
405
- filters: filters,
406
- key: key,
407
- value: value
408
- })), fieldProps = category => ({
409
- open: openList === category,
410
- onOpen: next => setOpenList(next ? category : null)
411
- });
281
+ function DefinitionPickerPanel({onPick: onPick}) {
282
+ const {definitions: definitions, error: error} = useStartableDefinitions(), [query, setQuery] = react.useState(""), visible = react.useMemo(() => {
283
+ const q = query.trim().toLowerCase();
284
+ return definitions ? q ? definitions.filter(d => `${d.title} ${d.name}`.toLowerCase().includes(q)) : definitions : [];
285
+ }, [ definitions, query ]);
412
286
  /* @__PURE__ */
413
287
  return jsxRuntime.jsx(ui.Box, {
414
- padding: 3,
288
+ padding: 2,
415
289
  style: {
416
- width: 360
290
+ width: 340
417
291
  },
418
292
  children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
419
- gap: 4,
293
+ gap: 2,
420
294
  children: [
421
- /* @__PURE__ */ jsxRuntime.jsx(DocumentField, {
422
- ...fieldProps("document"),
423
- activeIds: filters.documentIds ?? [],
424
- documents: options.documents,
425
- onToggle: id => toggle("documentIds", id)
426
- }),
427
- /* @__PURE__ */ jsxRuntime.jsx(TagField, {
428
- ...fieldProps("status"),
429
- activeKeys: filters.statuses ?? [],
430
- label: "Status",
431
- onToggle: key => toggle("statuses", key),
432
- pillLabel: key => STATUS_LABEL[key],
433
- placeholder: "Any status",
434
- rows: statusRows()
435
- }),
436
- /* @__PURE__ */ jsxRuntime.jsx(AssigneeField, {
437
- ...fieldProps("assignee"),
438
- activeIds: filters.assigneeIds ?? [],
439
- onToggle: id => toggle("assigneeIds", id)
440
- }),
441
- /* @__PURE__ */ jsxRuntime.jsx(TagField, {
442
- ...fieldProps("workflow"),
443
- activeKeys: filters.workflows ?? [],
444
- label: "Workflow",
445
- onToggle: key => toggle("workflows", key),
446
- pillLabel: key => options.workflows.find(w => w.name === key)?.title ?? key,
447
- placeholder: "Any workflow",
448
- rows: options.workflows.map(w => ({
449
- key: w.name,
450
- text: w.title
451
- }))
452
- }),
453
- /* @__PURE__ */ jsxRuntime.jsx(DateField, {
454
- ...fieldProps("date"),
455
- date: filters.date,
456
- onChange: next => {
457
- const {date: _cleared, ...rest} = filters;
458
- onChange(next === void 0 ? rest : {
459
- ...rest,
460
- date: next
461
- });
462
- }
463
- }), hasActiveFilters(filters) ? /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
464
- justify: "flex-end",
465
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
466
- fontSize: 1,
467
- mode: "ghost",
468
- onClick: () => onChange({}),
469
- padding: 2,
470
- text: "Clear filters"
471
- })
472
- }) : null ]
295
+ /* @__PURE__ */ jsxRuntime.jsx(ui.TextInput, {
296
+ autoFocus: !0,
297
+ fontSize: 1,
298
+ icon: Search.SearchIcon,
299
+ onChange: event => setQuery(event.currentTarget.value),
300
+ placeholder: "Filter workflows…",
301
+ value: query
302
+ }), error ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
303
+ text: `Could not load the deployed workflows: ${error}`
304
+ }) : null, !definitions && !error ? /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
305
+ label: "Loading workflows…"
306
+ }) : null, definitions && definitions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
307
+ text: "No startable workflows are deployed"
308
+ }) : null, definitions && definitions.length > 0 && visible.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
309
+ text: "No workflows match"
310
+ }) : null,
311
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
312
+ gap: 1,
313
+ style: {
314
+ maxHeight: 320,
315
+ overflowY: "auto"
316
+ },
317
+ children: visible.map(definition => /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
318
+ as: "button",
319
+ onClick: () => onPick(definition),
320
+ padding: 3,
321
+ radius: 2,
322
+ style: {
323
+ cursor: "pointer",
324
+ textAlign: "left"
325
+ },
326
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
327
+ gap: 2,
328
+ children: [
329
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
330
+ size: 1,
331
+ weight: "medium",
332
+ children: definition.title
333
+ }), definition.description ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
334
+ muted: !0,
335
+ size: 1,
336
+ textOverflow: "ellipsis",
337
+ children: definition.description
338
+ }) : null ]
339
+ })
340
+ }, definition.name))
341
+ }) ]
473
342
  })
474
343
  });
475
344
  }
476
345
 
477
- const statusRows = () => Object.keys(STATUS_LABEL).map(key => ({
478
- key: key,
479
- text: STATUS_LABEL[key]
480
- }));
346
+ function ToolActivityDialog({target: target, onClose: onClose}) {
347
+ const entry = index.useWorkflowInstanceEntry(target.instanceId), definition = index.useDefinition(entry), resolvedOnce = react.useRef(!1);
348
+ return entry && (resolvedOnce.current = !0), react.useEffect(() => {
349
+ resolvedOnce.current && index.openActivityGone({
350
+ entry: entry,
351
+ target: target
352
+ }) && onClose();
353
+ }, [ entry, target, onClose ]), entry?.invalid ? /* @__PURE__ */ jsxRuntime.jsx(ui.Dialog, {
354
+ header: "Workflow unavailable",
355
+ id: "workflow-activity-detail",
356
+ onClose: onClose,
357
+ width: 1,
358
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
359
+ padding: 4,
360
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.InvalidDocNotice, {
361
+ invalid: entry.invalid
362
+ })
363
+ })
364
+ }) : entry?.evaluation ? /* @__PURE__ */ jsxRuntime.jsx(index.ActivityDetailDialog, {
365
+ activityName: target.activityName,
366
+ breadcrumb: index.instanceBreadcrumb(entry.instance, definition),
367
+ definition: definition,
368
+ document: documentRefsOf(entry.instance)[0],
369
+ entry: entry,
370
+ onClose: onClose
371
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Dialog, {
372
+ header: "Loading activity…",
373
+ id: "workflow-activity-detail",
374
+ onClose: onClose,
375
+ width: 1,
376
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
377
+ padding: 4,
378
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
379
+ label: "Fetching the live workflow state…"
380
+ })
381
+ })
382
+ });
383
+ }
384
+
385
+ function datesOf(state) {
386
+ return state.filter(entry => entry._type === "date" || entry._type === "datetime").map(entry => entry.value).filter(value => typeof value == "string" && value !== "");
387
+ }
388
+
389
+ function spawnOrigins(instances) {
390
+ const origins = /* @__PURE__ */ new Map;
391
+ for (const parent of instances) for (const row of parent.subworkflows ?? []) origins.set(workflowEngine.toBareId(row.ref.id), {
392
+ parent: parent,
393
+ activityName: row.activity,
394
+ cohortStage: row.cohortStage
395
+ });
396
+ return origins;
397
+ }
398
+
399
+ function rootOf(instance, byId) {
400
+ const rootRef = instance.ancestors[0];
401
+ return rootRef ? byId.get(workflowEngine.toBareId(rootRef.id)) ?? instance : instance;
402
+ }
403
+
404
+ function taskRowsOf(args) {
405
+ const {instance: instance, identity: identity} = args, open = workflowEngine.findOpenStageEntry(instance);
406
+ if (!open) return [];
407
+ const definition = index.definitionSnapshotOf(instance), state = index.stateByActivity(instance), rows = (open.activities ?? []).map(activity => {
408
+ const fields = state.get(activity.name) ?? [], declared = index.findActivityNode({
409
+ definition: definition,
410
+ stageName: open.name,
411
+ activityName: activity.name
412
+ }) ?? {
413
+ name: activity.name
414
+ }, controlArgs = {
415
+ activity: declared,
416
+ status: activity.status,
417
+ editableFields: void 0
418
+ };
419
+ return {
420
+ instanceId: instance._id,
421
+ activityName: activity.name,
422
+ title: declared.title ?? activity.name,
423
+ stage: open.name,
424
+ status: activity.status,
425
+ open: index.isOpenActivityStatus(activity.status),
426
+ dates: datesOf(fields),
427
+ assigneeIds: index.assigneeUserIdsOf(fields),
428
+ assignedToMe: identity !== void 0 && index.isActivityAssignedTo({
429
+ state: fields,
430
+ who: identity
431
+ }),
432
+ assignControl: index.rowAssignControlState(controlArgs),
433
+ dateControl: index.rowDateControlState(controlArgs)
434
+ };
435
+ });
436
+ return [ ...rows.filter(r => r.open), ...rows.filter(r => !r.open) ];
437
+ }
438
+
439
+ function deriveTaskGroups(args) {
440
+ const {instances: instances, identity: identity} = args, byId = new Map(instances.map(instance => [ instance._id, instance ])), origins = spawnOrigins(instances), docGroups = /* @__PURE__ */ new Map, withoutDocuments = [], ordered = [ ...instances ].sort((a, b) => a.startedAt.localeCompare(b.startedAt));
441
+ for (const instance of ordered) {
442
+ const rows = taskRowsOf({
443
+ instance: instance,
444
+ identity: identity
445
+ }), first = rows[0];
446
+ if (!first) continue;
447
+ const origin = origins.get(instance._id), definition = index.definitionSnapshotOf(instance), entry = {
448
+ instanceId: instance._id,
449
+ workflowName: instance.definition,
450
+ title: index.instanceTitle(instance, definition),
451
+ stageTitle: index.stageTitle(definition, first.stage),
452
+ breadcrumb: origin ? index.findActivity({
453
+ definition: index.definitionSnapshotOf(origin.parent),
454
+ stageName: origin.cohortStage,
455
+ activityName: origin.activityName
456
+ }).title : void 0,
457
+ rows: rows
458
+ }, root = rootOf(instance, byId), documents = documentRefsOf(root), document = documents[0];
459
+ if (!document) {
460
+ withoutDocuments.push(entry);
461
+ continue;
462
+ }
463
+ const key = `doc:${document.id}`, held = docGroups.get(key);
464
+ if (held) {
465
+ held.instances.push(entry);
466
+ continue;
467
+ }
468
+ docGroups.set(key, {
469
+ group: {
470
+ key: key,
471
+ document: document,
472
+ documents: documents
473
+ },
474
+ instances: [ entry ]
475
+ });
476
+ }
477
+ return {
478
+ withDocuments: [ ...docGroups.values() ].map(({group: group, instances: bands}) => ({
479
+ ...group,
480
+ instances: bands
481
+ })),
482
+ withoutDocuments: withoutDocuments
483
+ };
484
+ }
485
+
486
+ function cutTaskGroups(groups, cut) {
487
+ const cutBands = bands => bands.filter(band => cut.keepBand?.(band) ?? !0).map(band => ({
488
+ ...band,
489
+ rows: band.rows.filter(cut.keepRow)
490
+ })).filter(band => band.rows.length > 0);
491
+ return {
492
+ withDocuments: groups.withDocuments.filter(group => cut.keepGroup?.(group) ?? !0).map(group => ({
493
+ ...group,
494
+ instances: cutBands(group.instances)
495
+ })).filter(group => group.instances.length > 0),
496
+ withoutDocuments: cut.dropUngrouped ? [] : cutBands(groups.withoutDocuments)
497
+ };
498
+ }
481
499
 
482
- function FieldLabel({text: text}) {
483
- /* @__PURE__ */
484
- return jsxRuntime.jsx(ui.Text, {
485
- muted: !0,
486
- size: 1,
487
- weight: "medium",
488
- children: text
500
+ function onlyMyTasks(groups) {
501
+ return cutTaskGroups(groups, {
502
+ keepRow: row => row.assignedToMe
489
503
  });
490
504
  }
491
505
 
492
- function FilterField({label: label, placeholder: placeholder, pills: pills, onClearPill: onClearPill, onFocus: onFocus, onQuery: onQuery, query: query, open: open, onOpen: onOpen, children: children}) {
493
- const rootRef = react.useRef(null), inputRef = react.useRef(null);
494
- /* @__PURE__ */
495
- return jsxRuntime.jsxs(ui.Stack, {
496
- gap: 2,
497
- children: [
498
- /* @__PURE__ */ jsxRuntime.jsx(FieldLabel, {
499
- text: label
500
- }),
501
- /* @__PURE__ */ jsxRuntime.jsxs("div", {
502
- ref: rootRef,
503
- style: {
504
- position: "relative"
505
- },
506
- children: [
507
- /* @__PURE__ */ jsxRuntime.jsx(TagBox, {
508
- inputRef: inputRef,
509
- label: label,
510
- onBlur: event => {
511
- rootRef.current?.contains(event.relatedTarget) || onOpen(!1);
512
- },
513
- onClearPill: onClearPill,
514
- onFocus: onFocus,
515
- onQuery: onQuery,
516
- pills: pills,
517
- placeholder: placeholder,
518
- query: query
519
- }),
520
- /* @__PURE__ */ jsxRuntime.jsx(FieldOverlay, {
521
- open: open,
522
- children: children
523
- }) ]
524
- }) ]
506
+ function rowActivityEval(entry, row) {
507
+ const evaluation = entry?.evaluation;
508
+ if (!(evaluation === void 0 || evaluation.currentStage.stage.name !== row.stage)) return evaluation.currentStage.activities.find(a => a.activity.name === row.activityName);
509
+ }
510
+
511
+ function taskRowRender(row, entry) {
512
+ const activityEval = rowActivityEval(entry, row);
513
+ if (entry?.evaluation === void 0 || activityEval === void 0) return {
514
+ face: index.committedRowFace(row),
515
+ assignState: row.assignControl,
516
+ assigneeIds: row.assigneeIds,
517
+ dates: row.dates,
518
+ dateState: row.dateControl,
519
+ terminalActions: [],
520
+ stale: !1
521
+ };
522
+ const activityState = index.stateByActivity(entry.instance), {face: face, assignState: assignState, assigneeIds: assigneeIds, terminalActions: terminalActions} = index.activityRowProps({
523
+ activityEval: activityEval,
524
+ activityState: activityState,
525
+ editableFields: entry.evaluation.editableFields
525
526
  });
527
+ return {
528
+ face: face,
529
+ assignState: assignState,
530
+ assigneeIds: assigneeIds,
531
+ dates: datesOf(activityState.get(row.activityName) ?? []),
532
+ dateState: index.rowDateControlState({
533
+ activity: activityEval.activity,
534
+ status: activityEval.status,
535
+ editableFields: entry.evaluation.editableFields
536
+ }),
537
+ terminalActions: terminalActions,
538
+ stale: index.isEvaluationStale(entry)
539
+ };
526
540
  }
527
541
 
528
- function TagField({label: label, placeholder: placeholder, rows: rows, activeKeys: activeKeys, onToggle: onToggle, pillLabel: pillLabel, open: open, onOpen: onOpen, emptyNote: emptyNote = "Nothing matches"}) {
529
- const [query, setQuery] = react.useState(""), q = query.trim().toLowerCase(), hits = q ? rows.filter(row => row.text.toLowerCase().includes(q)) : rows, shown = shownHits(hits, HITS_CAP);
530
- /* @__PURE__ */
531
- return jsxRuntime.jsx(FilterField, {
532
- label: label,
533
- onClearPill: onToggle,
534
- onFocus: () => {
535
- setQuery(""), onOpen(!0);
536
- },
537
- onOpen: onOpen,
538
- onQuery: setQuery,
539
- open: open,
540
- pills: activeKeys.map(key => ({
541
- key: key,
542
- label: pillLabel(key)
542
+ function taskFilterOptions(groups) {
543
+ const workflows = /* @__PURE__ */ new Map, documents = /* @__PURE__ */ new Map;
544
+ for (const group of groups.withDocuments) {
545
+ documents.set(group.document.id, group.document);
546
+ for (const band of group.instances) workflows.set(band.workflowName, band.title);
547
+ }
548
+ for (const band of groups.withoutDocuments) workflows.set(band.workflowName, band.title);
549
+ return {
550
+ workflows: [ ...workflows ].map(([name, title]) => ({
551
+ name: name,
552
+ title: title
543
553
  })),
544
- placeholder: placeholder,
545
- query: open ? query : "",
546
- children: /* @__PURE__ */ jsxRuntime.jsx(OptionsList, {
547
- activeKeys: activeKeys,
548
- emptyNote: emptyNote,
549
- hits: hits,
550
- onToggle: onToggle,
551
- shown: shown
552
- })
553
- });
554
+ documents: [ ...documents.values() ]
555
+ };
554
556
  }
555
557
 
556
- function FieldOverlay({open: open, children: children}) {
557
- return open ? /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
558
- radius: 2,
559
- shadow: 3,
560
- style: {
561
- insetInline: 0,
562
- position: "absolute",
563
- top: "calc(100% + 4px)",
564
- zIndex: 2
565
- },
566
- children: children
567
- }) : null;
558
+ function hasActiveFilters(filters) {
559
+ return Object.values(filters).some(value => value !== void 0 && (!Array.isArray(value) || value.length > 0));
568
560
  }
569
561
 
570
- function useTagInputStyle() {
571
- const {font: font} = ui.useTheme_v2(), textSize = font.text.sizes[1];
572
- if (!textSize) throw new Error("useTagInputStyle: theme is missing text size 1");
573
- return {
574
- background: "transparent",
575
- border: "none",
576
- color: "var(--card-fg-color)",
577
- fontFamily: font.text.family,
578
- fontSize: textSize.fontSize,
579
- outline: "none",
580
- padding: "4px 6px",
581
- width: "100%"
562
+ function toggledValue(args) {
563
+ const {filters: filters, key: key, value: value} = args, current = filters[key] ?? [], next = current.includes(value) ? current.filter(v => v !== value) : [ ...current, value ];
564
+ if (next.length > 0) return {
565
+ ...filters,
566
+ [key]: next
582
567
  };
568
+ const {[key]: _emptied, ...rest} = filters;
569
+ return rest;
583
570
  }
584
571
 
585
- function TagBox({pills: pills, onClearPill: onClearPill, query: query, onQuery: onQuery, placeholder: placeholder, label: label, onFocus: onFocus, onBlur: onBlur, inputRef: inputRef}) {
586
- const inputStyle = useTagInputStyle();
587
- /* @__PURE__ */
588
- return jsxRuntime.jsx(ui.Card, {
589
- border: !0,
590
- onClick: () => inputRef.current?.focus(),
591
- padding: 1,
592
- radius: 2,
593
- style: {
594
- cursor: "text"
595
- },
596
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
597
- align: "center",
598
- gap: 1,
599
- wrap: "wrap",
600
- children: [ pills.map(pill => /* @__PURE__ */ jsxRuntime.jsx(FilterPill, {
601
- label: pill.label,
602
- onClear: () => onClearPill(pill.key)
603
- }, pill.key)),
604
- /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
605
- flex: 1,
606
- style: {
607
- minWidth: 90
608
- },
609
- children: /* @__PURE__ */ jsxRuntime.jsx("input", {
610
- "aria-label": label,
611
- onBlur: onBlur,
612
- onChange: event => onQuery(event.currentTarget.value),
613
- onFocus: onFocus,
614
- placeholder: pills.length === 0 ? placeholder : void 0,
615
- ref: inputRef,
616
- style: inputStyle,
617
- value: query
618
- })
619
- }) ]
620
- })
621
- });
572
+ const STATUS_LABEL = {
573
+ open: "Open",
574
+ completed: "Completed"
575
+ }, DATE_PRESET_LABEL = {
576
+ past: "In the past",
577
+ none: "No date"
578
+ };
579
+
580
+ function dateLabel(date) {
581
+ return typeof date == "string" ? DATE_PRESET_LABEL[date] : `Before ${index.formatDate(date.before)}`;
622
582
  }
623
583
 
624
- function OptionsList({hits: hits, shown: shown, activeKeys: activeKeys, onToggle: onToggle, emptyNote: emptyNote}) {
625
- /* @__PURE__ */
626
- return jsxRuntime.jsxs(ui.Stack, {
627
- space: 1,
628
- children: [ hits.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
629
- padding: 2,
630
- children: /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
631
- text: emptyNote
632
- })
633
- }) : null, shown.map(row => /* @__PURE__ */ jsxRuntime.jsx(OptionRow, {
634
- active: activeKeys.includes(row.key),
635
- onPick: () => onToggle(row.key),
636
- children: row.node ?? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
637
- size: 1,
638
- children: row.text
639
- })
640
- }, row.key)), hits.length > shown.length ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
641
- padding: 2,
642
- children: /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
643
- text: `…and ${hits.length - shown.length} more — keep typing to narrow`
644
- })
645
- }) : null ]
584
+ function withoutChip(filters, chip) {
585
+ if (chip.key === "date") {
586
+ const {date: _cleared, ...rest} = filters;
587
+ return rest;
588
+ }
589
+ return chip.value === void 0 ? filters : toggledValue({
590
+ filters: filters,
591
+ key: chip.key,
592
+ value: chip.value
646
593
  });
647
594
  }
648
595
 
649
- function FilterPill({label: label, onClear: onClear}) {
650
- /* @__PURE__ */
651
- return jsxRuntime.jsx(ui.Badge, {
652
- fontSize: 1,
653
- mode: "outline",
654
- radius: "full",
655
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
656
- align: "center",
657
- gap: 1,
658
- children: [ label,
659
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
660
- "aria-label": `Clear ${label}`,
661
- fontSize: 0,
662
- icon: Close.CloseIcon,
663
- mode: "bleed",
664
- onClick: onClear,
665
- onMouseDown: event => event.preventDefault(),
666
- padding: 1
667
- }) ]
668
- })
669
- });
596
+ function chipsFor(args) {
597
+ const {key: key, values: values, labelOf: labelOf} = args;
598
+ return (values ?? []).map(value => ({
599
+ key: key,
600
+ value: value,
601
+ label: labelOf(value)
602
+ }));
603
+ }
604
+
605
+ function shownHits(hits, cap) {
606
+ return hits.length <= cap + 1 ? [ ...hits ] : hits.slice(0, cap);
607
+ }
608
+
609
+ const PENDING_LABEL = "…";
610
+
611
+ function activeFilterChips(args) {
612
+ const {filters: filters, options: options, assigneeNames: assigneeNames, documentTitles: documentTitles} = args, chips = [ ...chipsFor({
613
+ key: "documentIds",
614
+ values: filters.documentIds,
615
+ labelOf: id => documentTitles?.get(id) ?? PENDING_LABEL
616
+ }), ...chipsFor({
617
+ key: "statuses",
618
+ values: filters.statuses,
619
+ labelOf: status => STATUS_LABEL[status]
620
+ }), ...chipsFor({
621
+ key: "assigneeIds",
622
+ values: filters.assigneeIds,
623
+ labelOf: id => assigneeNames?.get(id) ?? PENDING_LABEL
624
+ }), ...chipsFor({
625
+ key: "workflows",
626
+ values: filters.workflows,
627
+ labelOf: name => options.workflows.find(w => w.name === name)?.title ?? name
628
+ }) ];
629
+ return filters.date && chips.push({
630
+ key: "date",
631
+ label: dateLabel(filters.date)
632
+ }), chips;
633
+ }
634
+
635
+ function dateMatches(args) {
636
+ const {row: row, date: date, now: now} = args, parsed = row.dates.map(value => index.parseStoredDateValue(value)?.getTime()).filter(at => at !== void 0);
637
+ return date === "none" ? parsed.length === 0 : date === "past" ? parsed.some(at => at < now.getTime()) : parsed.some(at => at <= Date.parse(date.before));
638
+ }
639
+
640
+ function activeArm(values) {
641
+ return values !== void 0 && values.length > 0 ? values : void 0;
642
+ }
643
+
644
+ function statusArm(row, statuses) {
645
+ const arm = activeArm(statuses);
646
+ if (!arm) return !0;
647
+ const rowStatus = row.open ? "open" : "completed";
648
+ return arm.includes(rowStatus);
670
649
  }
671
650
 
672
- function OptionRow({active: active, onPick: onPick, children: children}) {
673
- /* @__PURE__ */
674
- return jsxRuntime.jsx(ui.Card, {
675
- as: "button",
676
- onMouseDown: event => {
677
- event.preventDefault(), onPick();
678
- },
679
- padding: 2,
680
- radius: 2,
681
- style: {
682
- cursor: "pointer",
683
- textAlign: "left"
684
- },
685
- tone: active ? "primary" : "default",
686
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
687
- align: "center",
688
- gap: 2,
689
- children: [
690
- /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
691
- flex: 1,
692
- children: children
693
- }), active ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
694
- size: 1,
695
- children: /* @__PURE__ */ jsxRuntime.jsx(Checkmark.CheckmarkIcon, {})
696
- }) : null ]
697
- })
698
- });
651
+ function assigneeArm(row, assigneeIds) {
652
+ const arm = activeArm(assigneeIds);
653
+ return arm ? arm.some(id => row.assigneeIds.includes(id)) : !0;
699
654
  }
700
655
 
701
- function AssigneeField({activeIds: activeIds, onToggle: onToggle, open: open, onOpen: onOpen}) {
702
- const {members: members, loading: loading} = index.useProjectMembers(), displays = index.useUserDisplays(activeIds), names = new Map(displays.map(d => [ d.id, d.name ])), rows = members.map(member => ({
703
- key: member.id,
704
- text: `${member.displayName} ${member.email ?? ""}`.trim(),
705
- node: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
706
- align: "center",
707
- gap: 2,
708
- children: [
709
- /* @__PURE__ */ jsxRuntime.jsx(workflowComponents.MemberAvatar, {
710
- imageUrl: member.imageUrl,
711
- name: member.displayName
712
- }),
713
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
714
- size: 1,
715
- children: member.displayName
716
- }) ]
717
- })
656
+ function rowMatches(args) {
657
+ const {row: row, filters: filters, now: now} = args;
658
+ return !(!statusArm(row, filters.statuses) || !assigneeArm(row, filters.assigneeIds) || filters.date !== void 0 && !dateMatches({
659
+ row: row,
660
+ date: filters.date,
661
+ now: now
718
662
  }));
719
- /* @__PURE__ */
720
- return jsxRuntime.jsx(TagField, {
721
- activeKeys: activeIds,
722
- label: "Assignee",
723
- onOpen: onOpen,
724
- onToggle: onToggle,
725
- open: open,
726
- pillLabel: id => loading ? PENDING_LABEL : names.get(id) ?? id,
727
- placeholder: "Anyone",
728
- rows: rows
663
+ }
664
+
665
+ function applyTaskFilters(args) {
666
+ const {groups: groups, filters: filters, now: now} = args, documentIds = activeArm(filters.documentIds), workflows = activeArm(filters.workflows);
667
+ return cutTaskGroups(groups, {
668
+ keepRow: row => rowMatches({
669
+ row: row,
670
+ filters: filters,
671
+ now: now
672
+ }),
673
+ keepBand: band => !workflows || workflows.includes(band.workflowName),
674
+ keepGroup: group => !documentIds || documentIds.includes(group.document.id),
675
+ dropUngrouped: documentIds !== void 0
729
676
  });
730
677
  }
731
678
 
732
- function DocumentField({documents: documents, activeIds: activeIds, onToggle: onToggle, open: open, onOpen: onOpen}) {
733
- const labels = useDocumentChipLabels(activeIds, documents), {titles: titles} = useDocPreviewTitles(documents);
734
- if (documents.length === 0) return null;
735
- const rows = documents.map(doc => ({
736
- key: doc.id,
737
- text: `${titles.get(doc.id) ?? ""} ${doc.type} ${workflowEngine.extractDocumentId(doc.id)}`,
738
- node: /* @__PURE__ */ jsxRuntime.jsx(index.DocRefFace, {
739
- gdr: doc
740
- })
741
- }));
679
+ const NOTHING = {
680
+ titles: /* @__PURE__ */ new Map,
681
+ resolved: /* @__PURE__ */ new Set
682
+ };
683
+
684
+ function useDocPreviewTitles(documents) {
685
+ const previewStore = sanity.useDocumentPreviewStore(), schema = sanity.useSchema(), {binding: binding} = index.useWorkflowContext(), [state, setState] = react.useState(NOTHING), key = documents.map(doc => doc.id).join("|");
686
+ return react.useEffect(() => {
687
+ const titles = /* @__PURE__ */ new Map, resolved = /* @__PURE__ */ new Set, publish = () => setState({
688
+ titles: new Map(titles),
689
+ resolved: new Set(resolved)
690
+ }), subscriptions = documents.flatMap(doc => {
691
+ const {parsed: parsedRef, local: local} = index.gdrLocality(doc.id, binding.contentResource), schemaType = schema.get(doc.type);
692
+ return !parsedRef || !local || !schemaType ? (resolved.add(doc.id), []) : [ previewStore.observeForPreview({
693
+ _type: "reference",
694
+ _ref: parsedRef.documentId
695
+ }, schemaType).subscribe({
696
+ next: event => {
697
+ resolved.add(doc.id);
698
+ const title = event.snapshot?.title;
699
+ typeof title == "string" && titles.set(doc.id, title), publish();
700
+ },
701
+ error: err => {
702
+ console.error(`[workflow-studio-plugin] preview for "${doc.id}" failed:`, err),
703
+ resolved.add(doc.id), publish();
704
+ }
705
+ }) ];
706
+ });
707
+ return publish(), () => subscriptions.forEach(sub => sub.unsubscribe());
708
+ }, [ key, previewStore, schema, binding.contentResource.id ]), state;
709
+ }
710
+
711
+ const HITS_CAP = 6;
712
+
713
+ function TaskFilterMenu({filters: filters, options: options, onChange: onChange}) {
714
+ const [open, setOpen] = react.useState(!1), active = hasActiveFilters(filters);
742
715
  /* @__PURE__ */
743
- return jsxRuntime.jsx(TagField, {
744
- activeKeys: activeIds,
745
- emptyNote: "No documents match",
746
- label: "Document",
747
- onOpen: onOpen,
748
- onToggle: onToggle,
716
+ return jsxRuntime.jsx(index.DismissablePopover, {
717
+ content: open ? /* @__PURE__ */ jsxRuntime.jsx(FilterPanel, {
718
+ filters: filters,
719
+ onChange: onChange,
720
+ options: options
721
+ }) : null,
722
+ onDismiss: () => setOpen(!1),
749
723
  open: open,
750
- pillLabel: id => labels.get(id) ?? PENDING_LABEL,
751
- placeholder: "Any document",
752
- rows: rows
724
+ overflow: "visible",
725
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.HoverHint, {
726
+ disabled: open,
727
+ text: "Filter",
728
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
729
+ "aria-label": "Filter",
730
+ fontSize: 1,
731
+ icon: Filter.FilterIcon,
732
+ mode: "bleed",
733
+ onClick: () => setOpen(v => !v),
734
+ padding: 2,
735
+ selected: active || open
736
+ })
737
+ })
753
738
  });
754
739
  }
755
740
 
756
- function DateField({date: date, onChange: onChange, open: open, onOpen: onOpen}) {
757
- const pickedDate = typeof date == "object" ? new Date(date.before) : void 0, pick = next => {
758
- onChange(next), onOpen(!1);
759
- };
741
+ function FilterPanel({filters: filters, options: options, onChange: onChange}) {
742
+ const [openList, setOpenList] = react.useState(null), toggle = (key, value) => onChange(toggledValue({
743
+ filters: filters,
744
+ key: key,
745
+ value: value
746
+ })), fieldProps = category => ({
747
+ open: openList === category,
748
+ onOpen: next => setOpenList(next ? category : null)
749
+ });
760
750
  /* @__PURE__ */
761
- return jsxRuntime.jsx(FilterField, {
762
- label: "Date",
763
- onClearPill: () => onChange(void 0),
764
- onFocus: () => onOpen(!0),
765
- onOpen: onOpen,
766
- onQuery: () => {},
767
- open: open,
768
- pills: date === void 0 ? [] : [ {
769
- key: "date",
770
- label: dateLabel(date)
771
- } ],
772
- placeholder: "Any time",
773
- query: "",
751
+ return jsxRuntime.jsx(ui.Box, {
752
+ padding: 3,
753
+ style: {
754
+ width: 360
755
+ },
774
756
  children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
775
- space: 1,
757
+ gap: 4,
776
758
  children: [
777
- /* @__PURE__ */ jsxRuntime.jsx(OptionRow, {
778
- active: date === "past",
779
- onPick: () => pick("past"),
780
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
781
- size: 1,
782
- children: "In the past"
783
- })
759
+ /* @__PURE__ */ jsxRuntime.jsx(DocumentField, {
760
+ ...fieldProps("document"),
761
+ activeIds: filters.documentIds ?? [],
762
+ documents: options.documents,
763
+ onToggle: id => toggle("documentIds", id)
784
764
  }),
785
- /* @__PURE__ */ jsxRuntime.jsx(OptionRow, {
786
- active: date === "none",
787
- onPick: () => pick("none"),
788
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
789
- size: 1,
790
- children: "No date"
791
- })
765
+ /* @__PURE__ */ jsxRuntime.jsx(TagField, {
766
+ ...fieldProps("status"),
767
+ activeKeys: filters.statuses ?? [],
768
+ label: "Status",
769
+ onToggle: key => toggle("statuses", key),
770
+ pillLabel: key => STATUS_LABEL[key],
771
+ placeholder: "Any status",
772
+ rows: statusRows()
792
773
  }),
793
- /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
794
- paddingTop: 1,
795
- paddingX: 2,
796
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
797
- muted: !0,
798
- size: 1,
799
- children: "Before…"
800
- })
774
+ /* @__PURE__ */ jsxRuntime.jsx(AssigneeField, {
775
+ ...fieldProps("assignee"),
776
+ activeIds: filters.assigneeIds ?? [],
777
+ onToggle: id => toggle("assigneeIds", id)
778
+ }),
779
+ /* @__PURE__ */ jsxRuntime.jsx(TagField, {
780
+ ...fieldProps("workflow"),
781
+ activeKeys: filters.workflows ?? [],
782
+ label: "Workflow",
783
+ onToggle: key => toggle("workflows", key),
784
+ pillLabel: key => options.workflows.find(w => w.name === key)?.title ?? key,
785
+ placeholder: "Any workflow",
786
+ rows: options.workflows.map(w => ({
787
+ key: w.name,
788
+ text: w.title
789
+ }))
801
790
  }),
802
- /* @__PURE__ */ jsxRuntime.jsx(workflowComponents.DatePicker, {
803
- onSelect: next => pick({
804
- before: endOfDayIso(next)
805
- }),
806
- value: pickedDate
807
- }) ]
791
+ /* @__PURE__ */ jsxRuntime.jsx(DateField, {
792
+ ...fieldProps("date"),
793
+ date: filters.date,
794
+ onChange: next => {
795
+ const {date: _cleared, ...rest} = filters;
796
+ onChange(next === void 0 ? rest : {
797
+ ...rest,
798
+ date: next
799
+ });
800
+ }
801
+ }), hasActiveFilters(filters) ? /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
802
+ justify: "flex-end",
803
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
804
+ fontSize: 1,
805
+ mode: "ghost",
806
+ onClick: () => onChange({}),
807
+ padding: 2,
808
+ text: "Clear filters"
809
+ })
810
+ }) : null ]
808
811
  })
809
812
  });
810
813
  }
811
814
 
812
- const endOfDayIso = date => endOfDay.endOfDay(date).toISOString();
813
-
814
- function schemaTypeTitle(schema, type) {
815
- return schema.get(type)?.title;
816
- }
817
-
818
- function useDocumentChipLabels(documentIds, documents) {
819
- const schema = sanity.useSchema(), activeDocs = documents.filter(doc => documentIds.includes(doc.id)), {titles: titles, resolved: resolved} = useDocPreviewTitles(activeDocs), labels = /* @__PURE__ */ new Map;
820
- for (const doc of activeDocs) {
821
- if (!resolved.has(doc.id)) continue;
822
- const label = titles.get(doc.id) ?? schemaTypeTitle(schema, doc.type) ?? workflowEngine.extractDocumentId(doc.id);
823
- labels.set(doc.id, label);
824
- }
825
- return labels;
826
- }
827
-
828
- function ActiveFilterChips({filters: filters, options: options, onChange: onChange}) {
829
- const displays = index.useUserDisplays(filters.assigneeIds ?? []), {loading: membersLoading} = index.useProjectMembers(), documentTitles = useDocumentChipLabels(filters.documentIds ?? [], options.documents), chips = activeFilterChips({
830
- filters: filters,
831
- options: options,
832
- assigneeNames: membersLoading ? /* @__PURE__ */ new Map : new Map(displays.map(d => [ d.id, d.name ])),
833
- documentTitles: documentTitles
834
- });
835
- return chips.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
836
- align: "center",
837
- gap: 2,
838
- wrap: "wrap",
839
- children: chips.map(chip => /* @__PURE__ */ jsxRuntime.jsx(FilterPill, {
840
- label: chip.label,
841
- onClear: () => onChange(withoutChip(filters, chip))
842
- }, `${chip.key}:${chip.value ?? ""}`))
843
- });
844
- }
845
-
846
- const INSTANCE_CAP = 200, NEWEST_INSTANCES = {
847
- includeCompleted: !0,
848
- limit: INSTANCE_CAP
849
- };
815
+ const statusRows = () => Object.keys(STATUS_LABEL).map(key => ({
816
+ key: key,
817
+ text: STATUS_LABEL[key]
818
+ }));
850
819
 
851
- function useToolInstances() {
852
- const {engine: engine} = index.useWorkflowContext(), {instances: instances, loading: loading, invalid: invalid} = workflowStudio.useWorkflowInstances({
853
- engine: engine,
854
- filter: NEWEST_INSTANCES
820
+ function FieldLabel({text: text}) {
821
+ /* @__PURE__ */
822
+ return jsxRuntime.jsx(ui.Text, {
823
+ muted: !0,
824
+ size: 1,
825
+ weight: "medium",
826
+ children: text
855
827
  });
856
- return react.useMemo(() => {
857
- const rows = instances ?? [];
858
- return {
859
- inFlight: rows.filter(instance => workflowEngine.terminalState(instance) === "in-flight"),
860
- settled: rows.filter(instance => workflowEngine.terminalState(instance) !== "in-flight"),
861
- truncated: rows.length === INSTANCE_CAP,
862
- loading: loading,
863
- invalid: invalid
864
- };
865
- }, [ instances, loading, invalid ]);
866
- }
867
-
868
- function toFilterRows(instances) {
869
- return instances.map(instance => ({
870
- instance: instance,
871
- workflowTitle: index.instanceTitle(instance, index.definitionSnapshotOf(instance)),
872
- documents: documentRefsOf(instance)
873
- }));
874
828
  }
875
829
 
876
- function cutInstances(args) {
877
- const {rows: rows, filters: filters, taskArmIds: taskArmIds} = args, kept = applyInstanceFilters({
878
- rows: rows,
879
- filters: filters
880
- }).filter(instance => instanceStatusMatches(instance, filters.statuses));
881
- return taskArmIds === null ? kept : kept.filter(instance => taskArmIds.has(instance._id));
882
- }
883
-
884
- function WorkflowsDashboard({instances: instances, onOpenInstance: onOpenInstance}) {
885
- const {inFlight: inFlight, settled: settled, truncated: truncated, loading: loading} = instances, [filters, setFilters] = react.useState({}), identity = index.useAssignmentIdentity(), inFlightRows = react.useMemo(() => toFilterRows(inFlight), [ inFlight ]), settledRows = react.useMemo(() => toFilterRows(settled), [ settled ]), options = react.useMemo(() => instanceFilterOptions([ ...inFlightRows, ...settledRows ]), [ inFlightRows, settledRows ]), taskArmIds = react.useMemo(() => instanceIdsMatchingTaskArms({
886
- instances: [ ...inFlight, ...settled ],
887
- identity: identity,
888
- filters: filters,
889
- now: /* @__PURE__ */ new Date
890
- }), [ inFlight, settled, identity, filters ]), visibleInFlight = react.useMemo(() => cutInstances({
891
- rows: inFlightRows,
892
- filters: filters,
893
- taskArmIds: taskArmIds
894
- }), [ inFlightRows, filters, taskArmIds ]), visibleSettled = react.useMemo(() => cutInstances({
895
- rows: settledRows,
896
- filters: filters,
897
- taskArmIds: taskArmIds
898
- }), [ settledRows, filters, taskArmIds ]);
899
- return loading ? /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
900
- label: "Loading workflows…"
901
- }) : inFlight.length === 0 && settled.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
902
- text: "No workflows yet — start one with “New workflow”"
903
- }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
830
+ function FilterField({label: label, placeholder: placeholder, pills: pills, onClearPill: onClearPill, onFocus: onFocus, onQuery: onQuery, query: query, open: open, onOpen: onOpen, children: children}) {
831
+ const rootRef = react.useRef(null), inputRef = react.useRef(null);
832
+ /* @__PURE__ */
833
+ return jsxRuntime.jsxs(ui.Stack, {
904
834
  gap: 2,
905
835
  children: [
906
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
907
- align: "center",
908
- gap: 2,
909
- justify: "flex-end",
836
+ /* @__PURE__ */ jsxRuntime.jsx(FieldLabel, {
837
+ text: label
838
+ }),
839
+ /* @__PURE__ */ jsxRuntime.jsxs("div", {
840
+ ref: rootRef,
841
+ style: {
842
+ position: "relative"
843
+ },
910
844
  children: [
911
- /* @__PURE__ */ jsxRuntime.jsx(ActiveFilterChips, {
912
- filters: filters,
913
- onChange: setFilters,
914
- options: options
845
+ /* @__PURE__ */ jsxRuntime.jsx(TagBox, {
846
+ inputRef: inputRef,
847
+ label: label,
848
+ onBlur: event => {
849
+ rootRef.current?.contains(event.relatedTarget) || onOpen(!1);
850
+ },
851
+ onClearPill: onClearPill,
852
+ onFocus: onFocus,
853
+ onQuery: onQuery,
854
+ pills: pills,
855
+ placeholder: placeholder,
856
+ query: query
915
857
  }),
916
- /* @__PURE__ */ jsxRuntime.jsx(TaskFilterMenu, {
917
- filters: filters,
918
- onChange: setFilters,
919
- options: options
858
+ /* @__PURE__ */ jsxRuntime.jsx(FieldOverlay, {
859
+ open: open,
860
+ children: children
920
861
  }) ]
921
- }), visibleInFlight.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
922
- text: "No running workflows match"
923
- }) : /* @__PURE__ */ jsxRuntime.jsx(InstanceRows, {
924
- instances: visibleInFlight,
925
- onOpenInstance: onOpenInstance
926
- }),
927
- /* @__PURE__ */ jsxRuntime.jsx(SettledRows, {
928
- defaultShown: filters.statuses?.includes("completed") ?? !1,
929
- instances: visibleSettled,
930
- onOpenInstance: onOpenInstance
931
- }), truncated ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
932
- text: `Showing the latest ${INSTANCE_CAP} workflows.`
933
- }) : null ]
862
+ }) ]
934
863
  });
935
864
  }
936
865
 
937
- function InstanceRows({instances: instances, onOpenInstance: onOpenInstance}) {
866
+ function TagField({label: label, placeholder: placeholder, rows: rows, activeKeys: activeKeys, onToggle: onToggle, pillLabel: pillLabel, open: open, onOpen: onOpen, emptyNote: emptyNote = "Nothing matches"}) {
867
+ const [query, setQuery] = react.useState(""), q = query.trim().toLowerCase(), hits = q ? rows.filter(row => row.text.toLowerCase().includes(q)) : rows, shown = shownHits(hits, HITS_CAP);
938
868
  /* @__PURE__ */
939
- return jsxRuntime.jsx(jsxRuntime.Fragment, {
940
- children: instances.map(instance => /* @__PURE__ */ jsxRuntime.jsx(InstanceRow, {
941
- instance: instance,
942
- onOpen: () => onOpenInstance(instance)
943
- }, instance._id))
869
+ return jsxRuntime.jsx(FilterField, {
870
+ label: label,
871
+ onClearPill: onToggle,
872
+ onFocus: () => {
873
+ setQuery(""), onOpen(!0);
874
+ },
875
+ onOpen: onOpen,
876
+ onQuery: setQuery,
877
+ open: open,
878
+ pills: activeKeys.map(key => ({
879
+ key: key,
880
+ label: pillLabel(key)
881
+ })),
882
+ placeholder: placeholder,
883
+ query: open ? query : "",
884
+ children: /* @__PURE__ */ jsxRuntime.jsx(OptionsList, {
885
+ activeKeys: activeKeys,
886
+ emptyNote: emptyNote,
887
+ hits: hits,
888
+ onToggle: onToggle,
889
+ shown: shown
890
+ })
944
891
  });
945
892
  }
946
893
 
947
- function SettledRows({instances: instances, onOpenInstance: onOpenInstance, defaultShown: defaultShown}) {
948
- const [toggled, setToggled] = react.useState(void 0), shown = toggled ?? defaultShown;
949
- return instances.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
950
- gap: 2,
951
- marginTop: 3,
952
- children: [
953
- /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
954
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
955
- fontSize: 1,
956
- icon: shown ? ChevronDown.ChevronDownIcon : ChevronRight.ChevronRightIcon,
957
- mode: "bleed",
958
- onClick: () => setToggled(!shown),
959
- padding: 2,
960
- text: `${shown ? "Hide" : "Show"} ${instances.length} finished`
961
- })
962
- }), shown ? /* @__PURE__ */ jsxRuntime.jsx(InstanceRows, {
963
- instances: instances,
964
- onOpenInstance: onOpenInstance
965
- }) : null ]
894
+ function FieldOverlay({open: open, children: children}) {
895
+ return open ? /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
896
+ radius: 2,
897
+ shadow: 3,
898
+ style: {
899
+ insetInline: 0,
900
+ position: "absolute",
901
+ top: "calc(100% + 4px)",
902
+ zIndex: 2
903
+ },
904
+ children: children
905
+ }) : null;
906
+ }
907
+
908
+ function useTagInputStyle() {
909
+ const {font: font} = ui.useTheme_v2(), textSize = font.text.sizes[1];
910
+ if (!textSize) throw new Error("useTagInputStyle: theme is missing text size 1");
911
+ return {
912
+ background: "transparent",
913
+ border: "none",
914
+ color: "var(--card-fg-color)",
915
+ fontFamily: font.text.family,
916
+ fontSize: textSize.fontSize,
917
+ outline: "none",
918
+ padding: "4px 6px",
919
+ width: "100%"
920
+ };
921
+ }
922
+
923
+ function TagBox({pills: pills, onClearPill: onClearPill, query: query, onQuery: onQuery, placeholder: placeholder, label: label, onFocus: onFocus, onBlur: onBlur, inputRef: inputRef}) {
924
+ const inputStyle = useTagInputStyle();
925
+ /* @__PURE__ */
926
+ return jsxRuntime.jsx(ui.Card, {
927
+ border: !0,
928
+ onClick: () => inputRef.current?.focus(),
929
+ padding: 1,
930
+ radius: 2,
931
+ style: {
932
+ cursor: "text"
933
+ },
934
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
935
+ align: "center",
936
+ gap: 1,
937
+ wrap: "wrap",
938
+ children: [ pills.map(pill => /* @__PURE__ */ jsxRuntime.jsx(FilterPill, {
939
+ label: pill.label,
940
+ onClear: () => onClearPill(pill.key)
941
+ }, pill.key)),
942
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
943
+ flex: 1,
944
+ style: {
945
+ minWidth: 90
946
+ },
947
+ children: /* @__PURE__ */ jsxRuntime.jsx("input", {
948
+ "aria-label": label,
949
+ onBlur: onBlur,
950
+ onChange: event => onQuery(event.currentTarget.value),
951
+ onFocus: onFocus,
952
+ placeholder: pills.length === 0 ? placeholder : void 0,
953
+ ref: inputRef,
954
+ style: inputStyle,
955
+ value: query
956
+ })
957
+ }) ]
958
+ })
966
959
  });
967
960
  }
968
961
 
969
- function InstanceRow({instance: instance, onOpen: onOpen}) {
970
- const definition = react.useMemo(() => index.definitionSnapshotOf(instance), [ instance ]);
962
+ function OptionsList({hits: hits, shown: shown, activeKeys: activeKeys, onToggle: onToggle, emptyNote: emptyNote}) {
971
963
  /* @__PURE__ */
972
- return jsxRuntime.jsxs(ui.Flex, {
973
- align: "center",
974
- gap: 2,
975
- children: [
976
- /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
977
- as: "button",
978
- flex: 1,
979
- onClick: onOpen,
980
- padding: 3,
981
- paddingLeft: 4,
982
- radius: 2,
983
- style: {
984
- cursor: "pointer",
985
- textAlign: "left"
986
- },
987
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
988
- align: "center",
989
- gap: 3,
990
- children: [
991
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
992
- size: 1,
993
- weight: "medium",
994
- children: index.instanceTitle(instance, definition)
995
- }),
996
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
997
- muted: !0,
998
- size: 1,
999
- children: [ "Started ", index.formatDate(instance.startedAt) ]
1000
- }),
1001
- /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
1002
- flex: 1
1003
- }),
1004
- /* @__PURE__ */ jsxRuntime.jsx(index.AbortedBadge, {
1005
- instance: instance
1006
- }) ]
964
+ return jsxRuntime.jsxs(ui.Stack, {
965
+ space: 1,
966
+ children: [ hits.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
967
+ padding: 2,
968
+ children: /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
969
+ text: emptyNote
1007
970
  })
1008
- }),
1009
- /* @__PURE__ */ jsxRuntime.jsx(DocRefChips, {
1010
- instance: instance
1011
- }) ]
971
+ }) : null, shown.map(row => /* @__PURE__ */ jsxRuntime.jsx(OptionRow, {
972
+ active: activeKeys.includes(row.key),
973
+ onPick: () => onToggle(row.key),
974
+ children: row.node ?? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
975
+ size: 1,
976
+ children: row.text
977
+ })
978
+ }, row.key)), hits.length > shown.length ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
979
+ padding: 2,
980
+ children: /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
981
+ text: `…and ${hits.length - shown.length} more — keep typing to narrow`
982
+ })
983
+ }) : null ]
1012
984
  });
1013
985
  }
1014
986
 
1015
- function SpinnerSlot({busy: busy}) {
987
+ function FilterPill({label: label, onClear: onClear}) {
1016
988
  /* @__PURE__ */
1017
- return jsxRuntime.jsx(ui.Flex, {
1018
- align: "center",
1019
- flex: "none",
1020
- justify: "center",
989
+ return jsxRuntime.jsx(ui.Badge, {
990
+ fontSize: 1,
991
+ mode: "outline",
992
+ radius: "full",
993
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
994
+ align: "center",
995
+ gap: 1,
996
+ children: [ label,
997
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
998
+ "aria-label": `Clear ${label}`,
999
+ fontSize: 0,
1000
+ icon: Close.CloseIcon,
1001
+ mode: "bleed",
1002
+ onClick: onClear,
1003
+ onMouseDown: event => event.preventDefault(),
1004
+ padding: 1
1005
+ }) ]
1006
+ })
1007
+ });
1008
+ }
1009
+
1010
+ function OptionRow({active: active, onPick: onPick, children: children}) {
1011
+ /* @__PURE__ */
1012
+ return jsxRuntime.jsx(ui.Card, {
1013
+ as: "button",
1014
+ onMouseDown: event => {
1015
+ event.preventDefault(), onPick();
1016
+ },
1017
+ padding: 2,
1018
+ radius: 2,
1021
1019
  style: {
1022
- width: 19,
1023
- height: 19
1020
+ cursor: "pointer",
1021
+ textAlign: "left"
1024
1022
  },
1025
- children: busy ? /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {
1026
- muted: !0
1027
- }) : null
1023
+ tone: active ? "primary" : "default",
1024
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1025
+ align: "center",
1026
+ gap: 2,
1027
+ children: [
1028
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1029
+ flex: 1,
1030
+ children: children
1031
+ }), active ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1032
+ size: 1,
1033
+ children: /* @__PURE__ */ jsxRuntime.jsx(Checkmark.CheckmarkIcon, {})
1034
+ }) : null ]
1035
+ })
1028
1036
  });
1029
1037
  }
1030
1038
 
1031
- function InstanceTaskLists({entry: entry, onOpenActivity: onOpenActivity}) {
1032
- return index.instanceNotice(entry) || /* @__PURE__ */ jsxRuntime.jsx(index.StaleLock, {
1033
- stale: index.isEvaluationStale(entry),
1034
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1039
+ function AssigneeField({activeIds: activeIds, onToggle: onToggle, open: open, onOpen: onOpen}) {
1040
+ const {members: members, loading: loading} = index.useProjectMembers(), displays = index.useUserDisplays(activeIds), names = new Map(displays.map(d => [ d.id, d.name ])), rows = members.map(member => ({
1041
+ key: member.id,
1042
+ text: `${member.displayName} ${member.email ?? ""}`.trim(),
1043
+ node: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1044
+ align: "center",
1045
+ gap: 2,
1035
1046
  children: [
1036
- /* @__PURE__ */ jsxRuntime.jsx(index.ActivitiesList, {
1037
- entry: entry,
1038
- onOpenActivity: onOpenActivity
1047
+ /* @__PURE__ */ jsxRuntime.jsx(workflowComponents.MemberAvatar, {
1048
+ imageUrl: member.imageUrl,
1049
+ name: member.displayName
1039
1050
  }),
1040
- /* @__PURE__ */ jsxRuntime.jsx(index.TodoItemsList, {
1041
- entry: entry
1051
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1052
+ size: 1,
1053
+ children: member.displayName
1042
1054
  }) ]
1043
1055
  })
1056
+ }));
1057
+ /* @__PURE__ */
1058
+ return jsxRuntime.jsx(TagField, {
1059
+ activeKeys: activeIds,
1060
+ label: "Assignee",
1061
+ onOpen: onOpen,
1062
+ onToggle: onToggle,
1063
+ open: open,
1064
+ pillLabel: id => loading ? PENDING_LABEL : names.get(id) ?? id,
1065
+ placeholder: "Anyone",
1066
+ rows: rows
1044
1067
  });
1045
1068
  }
1046
1069
 
1047
- function WorkflowInstanceDetail({instanceId: instanceId, onBack: onBack}) {
1048
- const entry = index.useWorkflowInstanceEntry(instanceId), [graceOver, setGraceOver] = react.useState(!1);
1049
- return react.useEffect(() => {
1050
- const timer = setTimeout(() => setGraceOver(!0), 2500);
1051
- return () => clearTimeout(timer);
1052
- }, [ instanceId ]), entry?.invalid ? /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1053
- padding: 4,
1054
- children: /* @__PURE__ */ jsxRuntime.jsx(index.InvalidDocNotice, {
1055
- invalid: entry.invalid
1056
- })
1057
- }) : entry ? /* @__PURE__ */ jsxRuntime.jsx(InstanceDetailPanel, {
1058
- entry: entry,
1059
- onBack: onBack
1060
- }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1061
- padding: 4,
1062
- children: graceOver ? /* @__PURE__ */ jsxRuntime.jsx(NotFound, {
1063
- id: instanceId,
1064
- onBack: onBack
1065
- }) : /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
1066
- label: "Loading workflow…"
1070
+ function DocumentField({documents: documents, activeIds: activeIds, onToggle: onToggle, open: open, onOpen: onOpen}) {
1071
+ const labels = useDocumentChipLabels(activeIds, documents), {titles: titles} = useDocPreviewTitles(documents);
1072
+ if (documents.length === 0) return null;
1073
+ const rows = documents.map(doc => ({
1074
+ key: doc.id,
1075
+ text: `${titles.get(doc.id) ?? ""} ${doc.type} ${workflowEngine.toBareId(doc.id)}`,
1076
+ node: /* @__PURE__ */ jsxRuntime.jsx(index.DocRefFace, {
1077
+ gdr: doc
1067
1078
  })
1068
- });
1069
- }
1070
-
1071
- function NotFound({id: id, onBack: onBack}) {
1079
+ }));
1072
1080
  /* @__PURE__ */
1073
- return jsxRuntime.jsxs(ui.Stack, {
1074
- gap: 4,
1075
- children: [
1076
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
1077
- muted: !0,
1078
- size: 1,
1079
- children: [ "No workflow with id “", id, "” — it may have been deleted." ]
1080
- }),
1081
- /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
1082
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1083
- fontSize: 1,
1084
- icon: ArrowLeft.ArrowLeftIcon,
1085
- mode: "ghost",
1086
- onClick: onBack,
1087
- padding: 2,
1088
- text: "Back to workflows"
1089
- })
1090
- }) ]
1091
- });
1092
- }
1093
-
1094
- function InstanceDetailPanel({entry: entry, onBack: onBack}) {
1095
- const definition = index.useDefinition(entry), [openActivity, setOpenActivity] = react.useState(null), {instance: instance} = entry;
1096
- return react.useEffect(() => {
1097
- openActivity && index.openActivityGone({
1098
- entry: entry,
1099
- target: openActivity
1100
- }) && setOpenActivity(null);
1101
- }, [ entry, openActivity ]), /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1102
- direction: "column",
1103
- height: "fill",
1104
- overflow: "hidden",
1105
- children: [
1106
- /* @__PURE__ */ jsxRuntime.jsx(DetailHeader, {
1107
- definition: definition,
1108
- entry: entry,
1109
- onBack: onBack
1110
- }),
1111
- /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1112
- flex: 1,
1113
- overflow: "auto",
1114
- paddingX: 4,
1115
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1116
- gap: 4,
1117
- paddingBottom: 5,
1118
- children: [
1119
- /* @__PURE__ */ jsxRuntime.jsx(StudioDocumentsSection, {
1120
- instance: instance
1121
- }),
1122
- /* @__PURE__ */ jsxRuntime.jsx(StageDiagramSection, {
1123
- definition: definition,
1124
- entry: entry
1125
- }),
1126
- /* @__PURE__ */ jsxRuntime.jsx(index.GroupHeading, {
1127
- title: "All tasks"
1128
- }),
1129
- /* @__PURE__ */ jsxRuntime.jsx(InstanceTaskLists, {
1130
- entry: entry,
1131
- onOpenActivity: name => setOpenActivity({
1132
- stage: instance.currentStage,
1133
- activityName: name
1134
- })
1135
- }) ]
1136
- })
1137
- }), openActivity ? /* @__PURE__ */ jsxRuntime.jsx(index.ActivityDetailDialog, {
1138
- activityName: openActivity.activityName,
1139
- breadcrumb: index.instanceBreadcrumb(instance, definition),
1140
- definition: definition,
1141
- entry: entry,
1142
- onClose: () => setOpenActivity(null)
1143
- }) : null ]
1081
+ return jsxRuntime.jsx(TagField, {
1082
+ activeKeys: activeIds,
1083
+ emptyNote: "No documents match",
1084
+ label: "Document",
1085
+ onOpen: onOpen,
1086
+ onToggle: onToggle,
1087
+ open: open,
1088
+ pillLabel: id => labels.get(id) ?? PENDING_LABEL,
1089
+ placeholder: "Any document",
1090
+ rows: rows
1144
1091
  });
1145
1092
  }
1146
1093
 
1147
- function DetailHeader({entry: entry, definition: definition, onBack: onBack}) {
1148
- const {instance: instance, ready: ready} = entry;
1094
+ function DateField({date: date, onChange: onChange, open: open, onOpen: onOpen}) {
1095
+ const pickedDate = typeof date == "object" ? new Date(date.before) : void 0, pick = next => {
1096
+ onChange(next), onOpen(!1);
1097
+ };
1149
1098
  /* @__PURE__ */
1150
- return jsxRuntime.jsx(ui.Card, {
1151
- flex: "none",
1152
- paddingX: 4,
1153
- paddingY: 3,
1154
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1155
- align: "center",
1156
- gap: 3,
1157
- wrap: "wrap",
1099
+ return jsxRuntime.jsx(FilterField, {
1100
+ label: "Date",
1101
+ onClearPill: () => onChange(void 0),
1102
+ onFocus: () => onOpen(!0),
1103
+ onOpen: onOpen,
1104
+ onQuery: () => {},
1105
+ open: open,
1106
+ pills: date === void 0 ? [] : [ {
1107
+ key: "date",
1108
+ label: dateLabel(date)
1109
+ } ],
1110
+ placeholder: "Any time",
1111
+ query: "",
1112
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1113
+ space: 1,
1158
1114
  children: [
1159
- /* @__PURE__ */ jsxRuntime.jsx(index.HoverHint, {
1160
- text: "Back to workflows",
1161
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1162
- "aria-label": "Back to workflows",
1163
- fontSize: 1,
1164
- icon: ArrowLeft.ArrowLeftIcon,
1165
- mode: "bleed",
1166
- onClick: onBack,
1167
- padding: 2
1115
+ /* @__PURE__ */ jsxRuntime.jsx(OptionRow, {
1116
+ active: date === "past",
1117
+ onPick: () => pick("past"),
1118
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1119
+ size: 1,
1120
+ children: "In the past"
1121
+ })
1122
+ }),
1123
+ /* @__PURE__ */ jsxRuntime.jsx(OptionRow, {
1124
+ active: date === "none",
1125
+ onPick: () => pick("none"),
1126
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1127
+ size: 1,
1128
+ children: "No date"
1168
1129
  })
1169
1130
  }),
1170
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1171
- gap: 2,
1172
- children: [
1173
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1174
- size: 1,
1175
- weight: "semibold",
1176
- children: index.instanceTitle(instance, definition)
1177
- }),
1178
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
1131
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1132
+ paddingTop: 1,
1133
+ paddingX: 2,
1134
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1179
1135
  muted: !0,
1180
1136
  size: 1,
1181
- children: [ "Started ", index.formatDate(instance.startedAt) ]
1182
- }) ]
1183
- }), workflowEngine.terminalState(instance) === "in-flight" ? /* @__PURE__ */ jsxRuntime.jsx(index.StageChip, {
1184
- title: index.stageTitle(definition, instance.currentStage)
1185
- }) : /* @__PURE__ */ jsxRuntime.jsx(index.AbortedBadge, {
1186
- instance: instance
1137
+ children: "Before…"
1138
+ })
1187
1139
  }),
1188
- /* @__PURE__ */ jsxRuntime.jsx(SpinnerSlot, {
1189
- busy: !ready || index.isEvaluationStale(entry)
1140
+ /* @__PURE__ */ jsxRuntime.jsx(workflowComponents.DatePicker, {
1141
+ onSelect: next => pick({
1142
+ before: endOfDayIso(next)
1143
+ }),
1144
+ value: pickedDate
1190
1145
  }) ]
1191
1146
  })
1192
1147
  });
1193
1148
  }
1194
1149
 
1195
- function StudioDocumentsSection({instance: instance}) {
1196
- const refs = documentRefsOf(instance);
1197
- return refs.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1198
- gap: 3,
1199
- marginTop: 3,
1200
- children: [
1201
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1202
- muted: !0,
1203
- size: 1,
1204
- weight: "medium",
1205
- children: refs.length === 1 ? "Studio document" : "Studio documents"
1206
- }),
1207
- /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
1208
- gap: 2,
1209
- children: refs.map(ref => /* @__PURE__ */ jsxRuntime.jsx(index.DocPreviewLink, {
1210
- gdr: ref
1211
- }, ref.id))
1212
- }) ]
1213
- });
1214
- }
1150
+ const endOfDayIso = date => endOfDay.endOfDay(date).toISOString();
1215
1151
 
1216
- function StageDiagramSection({entry: entry, definition: definition}) {
1217
- const {instance: instance} = entry;
1218
- return definition ?
1219
- /* @__PURE__ */ jsxRuntime.jsx(workflowDiagram.WorkflowDiagram, {
1220
- currentStage: workflowEngine.terminalState(instance) === "in-flight" ? instance.currentStage : void 0,
1221
- definition: definition,
1222
- evaluation: entry.evaluation,
1223
- explain: !0,
1224
- guardCount: entry.guards?.length,
1225
- height: 340,
1226
- history: instance.history
1227
- }, instance._id) : /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
1228
- border: !0,
1229
- marginTop: 3,
1230
- padding: 3,
1231
- radius: 2,
1232
- tone: "caution",
1233
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1234
- muted: !0,
1235
- size: 1,
1236
- children: "Couldn’t load this workflow’s full details — the stage diagram is unavailable, and technical names show instead of titles."
1237
- })
1238
- });
1152
+ function schemaTypeTitle(schema, type) {
1153
+ return schema.get(type)?.title;
1239
1154
  }
1240
1155
 
1241
- function useStartableDefinitions() {
1242
- const {engine: engine} = index.useWorkflowContext(), [definitions, setDefinitions] = react.useState(void 0), [error, setError] = react.useState(void 0);
1243
- return react.useEffect(() => {
1244
- let cancelled = !1;
1245
- return (async () => {
1246
- const rows = await index.readDeployedDefinitions(engine), startable = workflowEngine.latestDeployedDefinitions(rows).filter(row => workflowEngine.isStartableDefinition(row)).map(row => ({
1247
- name: row.name,
1248
- title: row.title ?? row.name,
1249
- description: row.description
1250
- }));
1251
- cancelled || (setDefinitions(startable), setError(void 0));
1252
- })().catch(err => {
1253
- cancelled || setError(index.describeError(err));
1254
- }), () => {
1255
- cancelled = !0;
1256
- };
1257
- }, [ engine ]), {
1258
- definitions: definitions,
1259
- error: error
1260
- };
1156
+ function useDocumentChipLabels(documentIds, documents) {
1157
+ const schema = sanity.useSchema(), activeDocs = documents.filter(doc => documentIds.includes(doc.id)), {titles: titles, resolved: resolved} = useDocPreviewTitles(activeDocs), labels = /* @__PURE__ */ new Map;
1158
+ for (const doc of activeDocs) {
1159
+ if (!resolved.has(doc.id)) continue;
1160
+ const label = titles.get(doc.id) ?? schemaTypeTitle(schema, doc.type) ?? workflowEngine.toBareId(doc.id);
1161
+ labels.set(doc.id, label);
1162
+ }
1163
+ return labels;
1261
1164
  }
1262
1165
 
1263
- function NewWorkflowButton() {
1264
- const {openStartDialog: openStartDialog} = index.useWorkflowContext(), [open, setOpen] = react.useState(!1);
1265
- /* @__PURE__ */
1266
- return jsxRuntime.jsx(index.DismissablePopover, {
1267
- content: open ? /* @__PURE__ */ jsxRuntime.jsx(DefinitionPickerPanel, {
1268
- onPick: definition => {
1269
- setOpen(!1), openStartDialog({
1270
- definition: definition.name,
1271
- label: definition.title
1272
- });
1273
- }
1274
- }) : null,
1275
- onDismiss: () => setOpen(!1),
1276
- open: open,
1277
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1278
- fontSize: 1,
1279
- icon: Add.AddIcon,
1280
- onClick: () => setOpen(v => !v),
1281
- padding: 2,
1282
- selected: open,
1283
- text: "New workflow",
1284
- tone: "primary"
1285
- })
1166
+ function ActiveFilterChips({filters: filters, options: options, onChange: onChange}) {
1167
+ const displays = index.useUserDisplays(filters.assigneeIds ?? []), {loading: membersLoading} = index.useProjectMembers(), documentTitles = useDocumentChipLabels(filters.documentIds ?? [], options.documents), chips = activeFilterChips({
1168
+ filters: filters,
1169
+ options: options,
1170
+ assigneeNames: membersLoading ? /* @__PURE__ */ new Map : new Map(displays.map(d => [ d.id, d.name ])),
1171
+ documentTitles: documentTitles
1172
+ });
1173
+ return chips.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
1174
+ align: "center",
1175
+ gap: 2,
1176
+ wrap: "wrap",
1177
+ children: chips.map(chip => /* @__PURE__ */ jsxRuntime.jsx(FilterPill, {
1178
+ label: chip.label,
1179
+ onClear: () => onChange(withoutChip(filters, chip))
1180
+ }, `${chip.key}:${chip.value ?? ""}`))
1286
1181
  });
1287
1182
  }
1288
1183
 
1289
- function DefinitionPickerPanel({onPick: onPick}) {
1290
- const {definitions: definitions, error: error} = useStartableDefinitions(), [query, setQuery] = react.useState(""), visible = react.useMemo(() => {
1291
- const q = query.trim().toLowerCase();
1292
- return definitions ? q ? definitions.filter(d => `${d.title} ${d.name}`.toLowerCase().includes(q)) : definitions : [];
1293
- }, [ definitions, query ]);
1294
- /* @__PURE__ */
1295
- return jsxRuntime.jsx(ui.Box, {
1296
- padding: 2,
1297
- style: {
1298
- width: 340
1299
- },
1300
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1301
- gap: 2,
1302
- children: [
1303
- /* @__PURE__ */ jsxRuntime.jsx(ui.TextInput, {
1304
- autoFocus: !0,
1305
- fontSize: 1,
1306
- icon: Search.SearchIcon,
1307
- onChange: event => setQuery(event.currentTarget.value),
1308
- placeholder: "Filter workflows…",
1309
- value: query
1310
- }), error ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
1311
- text: `Could not load the deployed workflows: ${error}`
1312
- }) : null, !definitions && !error ? /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
1313
- label: "Loading workflows…"
1314
- }) : null, definitions && definitions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
1315
- text: "No startable workflows are deployed"
1316
- }) : null, definitions && definitions.length > 0 && visible.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
1317
- text: "No workflows match"
1318
- }) : null,
1319
- /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
1320
- gap: 1,
1321
- style: {
1322
- maxHeight: 320,
1323
- overflowY: "auto"
1324
- },
1325
- children: visible.map(definition => /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
1326
- as: "button",
1327
- onClick: () => onPick(definition),
1328
- padding: 3,
1329
- radius: 2,
1330
- style: {
1331
- cursor: "pointer",
1332
- textAlign: "left"
1333
- },
1334
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1335
- gap: 2,
1336
- children: [
1337
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1338
- size: 1,
1339
- weight: "medium",
1340
- children: definition.title
1341
- }), definition.description ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1342
- muted: !0,
1343
- size: 1,
1344
- textOverflow: "ellipsis",
1345
- children: definition.description
1346
- }) : null ]
1347
- })
1348
- }, definition.name))
1349
- }) ]
1350
- })
1351
- });
1184
+ function FilterRowPortal({filters: filters, onChange: onChange, options: options, slot: slot}) {
1185
+ return slot ? reactDom.createPortal(
1186
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1187
+ align: "center",
1188
+ gap: 2,
1189
+ justify: "flex-end",
1190
+ children: [
1191
+ /* @__PURE__ */ jsxRuntime.jsx(ActiveFilterChips, {
1192
+ filters: filters,
1193
+ onChange: onChange,
1194
+ options: options
1195
+ }),
1196
+ /* @__PURE__ */ jsxRuntime.jsx(TaskFilterMenu, {
1197
+ filters: filters,
1198
+ onChange: onChange,
1199
+ options: options
1200
+ }) ]
1201
+ }), slot) : null;
1352
1202
  }
1353
1203
 
1354
- function ToolActivityDialog({target: target, onClose: onClose}) {
1355
- const entry = index.useWorkflowInstanceEntry(target.instanceId), definition = index.useDefinition(entry), resolvedOnce = react.useRef(!1);
1356
- return entry && (resolvedOnce.current = !0), react.useEffect(() => {
1357
- resolvedOnce.current && index.openActivityGone({
1358
- entry: entry,
1359
- target: target
1360
- }) && onClose();
1361
- }, [ entry, target, onClose ]), entry?.invalid ? /* @__PURE__ */ jsxRuntime.jsx(ui.Dialog, {
1362
- header: "Workflow unavailable",
1363
- id: "workflow-activity-detail",
1364
- onClose: onClose,
1365
- width: 1,
1366
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1367
- padding: 4,
1368
- children: /* @__PURE__ */ jsxRuntime.jsx(index.InvalidDocNotice, {
1369
- invalid: entry.invalid
1370
- })
1371
- })
1372
- }) : entry?.evaluation ? /* @__PURE__ */ jsxRuntime.jsx(index.ActivityDetailDialog, {
1373
- activityName: target.activityName,
1374
- breadcrumb: index.instanceBreadcrumb(entry.instance, definition),
1375
- definition: definition,
1376
- document: documentRefsOf(entry.instance)[0],
1377
- entry: entry,
1378
- onClose: onClose
1379
- }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Dialog, {
1380
- header: "Loading activity…",
1381
- id: "workflow-activity-detail",
1382
- onClose: onClose,
1383
- width: 1,
1384
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1385
- padding: 4,
1386
- children: /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
1387
- label: "Fetching the live workflow state…"
1204
+ function ActivityDateControl({instanceId: instanceId, state: state, dates: dates}) {
1205
+ const {editFieldFor: editFieldFor} = index.useWorkflowContext(), {save: save} = index.useSaveField(), [open, setOpen] = react.useState(!1), raw = index.dateControlValue({
1206
+ state: state,
1207
+ dates: dates
1208
+ }), display = raw === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1209
+ muted: !0,
1210
+ size: 1,
1211
+ children: index.formatDate(raw)
1212
+ });
1213
+ if (state.kind === "none") return display;
1214
+ if (state.kind === "closed") return display === null ? null : /* @__PURE__ */ jsxRuntime.jsx(index.HoverHint, {
1215
+ text: state.reason,
1216
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1217
+ "aria-label": "Date",
1218
+ as: "span",
1219
+ fontSize: 1,
1220
+ mode: "bleed",
1221
+ onClick: index.stopRowClick,
1222
+ onMouseDown: index.stopRowMouseDown,
1223
+ padding: 2,
1224
+ children: display
1225
+ })
1226
+ });
1227
+ const kind = index.dateControlKind(state), commit = (mode, value) => {
1228
+ state.kind === "editable" && save(() => editFieldFor(instanceId, {
1229
+ target: index.editTargetOf(state.field),
1230
+ ...mode === "set" ? {
1231
+ mode: mode,
1232
+ value: value
1233
+ } : {
1234
+ mode: mode
1235
+ }
1236
+ }).then(() => {}));
1237
+ }, handleClick = e => {
1238
+ e.stopPropagation(), setOpen(v => !v);
1239
+ };
1240
+ /* @__PURE__ */
1241
+ return jsxRuntime.jsx(index.DismissablePopover, {
1242
+ content: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1243
+ onClick: index.stopRowClick,
1244
+ children: state.kind === "loading" || kind === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
1245
+ label: "Loading…",
1246
+ padding: 3
1247
+ }) : /* @__PURE__ */ jsxRuntime.jsx(workflowComponents.ClearableDatePicker, {
1248
+ clearLabel: "Clear date",
1249
+ onClear: () => {
1250
+ setOpen(!1), commit("unset");
1251
+ },
1252
+ onPick: next => {
1253
+ kind === "date" && setOpen(!1), commit("set", index.serializeDateFieldValue(next, kind));
1254
+ },
1255
+ selectTime: kind === "datetime",
1256
+ value: index.parseDateFieldValue(state.field.value, kind)
1257
+ })
1258
+ }),
1259
+ onDismiss: () => setOpen(!1),
1260
+ open: open,
1261
+ children: display ? /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1262
+ "aria-label": "Change date",
1263
+ as: "span",
1264
+ fontSize: 1,
1265
+ mode: "bleed",
1266
+ onClick: handleClick,
1267
+ onMouseDown: index.stopRowMouseDown,
1268
+ padding: 2,
1269
+ children: display
1270
+ }) : /* @__PURE__ */ jsxRuntime.jsx(index.HoverHint, {
1271
+ disabled: open,
1272
+ text: "Set date",
1273
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1274
+ "aria-label": "Set date",
1275
+ as: "span",
1276
+ fontSize: 1,
1277
+ icon: Calendar.CalendarIcon,
1278
+ mode: "bleed",
1279
+ onClick: handleClick,
1280
+ onMouseDown: index.stopRowMouseDown,
1281
+ padding: 2
1388
1282
  })
1389
1283
  })
1390
1284
  });
1391
1285
  }
1392
1286
 
1393
- function TaskGroupList({groups: groups, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow}) {
1394
- return groups.withDocuments.length === 0 && groups.withoutDocuments.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
1395
- text: "No tasks match — adjust the filters, or start a workflow"
1396
- }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1397
- gap: 2,
1398
- children: [ groups.withDocuments.map(group => /* @__PURE__ */ jsxRuntime.jsx(DocumentTaskGroup, {
1399
- group: group,
1400
- onOpenTask: onOpenTask,
1401
- onOpenWorkflow: onOpenWorkflow
1402
- }, group.key)), groups.withoutDocuments.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(CollapsibleGroup, {
1403
- header: /* @__PURE__ */ jsxRuntime.jsx(GroupTitle, {
1404
- text: "Workflows without Studio documents"
1405
- }),
1406
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, {
1407
- gap: 2,
1408
- paddingLeft: 2,
1409
- children: groups.withoutDocuments.map(group => /* @__PURE__ */ jsxRuntime.jsx(CollapsibleGroup, {
1410
- header: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
1411
- children: [
1412
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1413
- muted: !0,
1414
- size: 1,
1415
- children: /* @__PURE__ */ jsxRuntime.jsx(index.StageGlyph, {})
1416
- }),
1417
- /* @__PURE__ */ jsxRuntime.jsx(WorkflowBadgeTitle, {
1418
- group: group
1419
- }),
1420
- /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1421
- flex: 1
1422
- }),
1423
- /* @__PURE__ */ jsxRuntime.jsx(GroupMenu, {
1424
- group: group,
1425
- onOpenWorkflow: onOpenWorkflow
1426
- }) ]
1427
- }),
1428
- children: /* @__PURE__ */ jsxRuntime.jsx(TaskRows, {
1429
- onOpenTask: onOpenTask,
1430
- rows: group.rows
1431
- })
1432
- }, group.key))
1433
- })
1434
- }) : null ]
1435
- });
1287
+ const PREWARM_MARGIN = "600px 0px";
1288
+
1289
+ function useNearViewport() {
1290
+ const [element, setElement] = react.useState(null), [near, setNear] = react.useState(!1);
1291
+ return react.useEffect(() => {
1292
+ if (element === null) return;
1293
+ if (typeof IntersectionObserver > "u") {
1294
+ setNear(!0);
1295
+ return;
1296
+ }
1297
+ const observer = new IntersectionObserver(entries => {
1298
+ setNear(entries.at(-1)?.isIntersecting ?? !1);
1299
+ }, {
1300
+ rootMargin: PREWARM_MARGIN
1301
+ });
1302
+ return observer.observe(element), () => observer.disconnect();
1303
+ }, [ element ]), {
1304
+ ref: setElement,
1305
+ near: near
1306
+ };
1436
1307
  }
1437
1308
 
1438
- function GroupTitle({text: text}) {
1309
+ function RefChips({refs: refs, max: max = 2}) {
1310
+ if (refs.length === 0) return null;
1311
+ const shown = refs.slice(0, max), overflow = refs.length - shown.length;
1439
1312
  /* @__PURE__ */
1440
- return jsxRuntime.jsx(ui.Text, {
1441
- size: 1,
1442
- weight: "semibold",
1443
- children: text
1313
+ return jsxRuntime.jsxs(ui.Flex, {
1314
+ align: "center",
1315
+ gap: 3,
1316
+ wrap: "wrap",
1317
+ children: [ shown.map(ref => /* @__PURE__ */ jsxRuntime.jsx(index.DocPreviewLink, {
1318
+ gdr: ref,
1319
+ layout: "inline"
1320
+ }, ref.id)), overflow > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, {
1321
+ muted: !0,
1322
+ size: 1,
1323
+ children: [ "+", overflow, " more" ]
1324
+ }) : null ]
1444
1325
  });
1445
1326
  }
1446
1327
 
1447
- function WorkflowBadgeTitle({group: group}) {
1448
- const badge = /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, {
1449
- fontSize: 1,
1450
- mode: "outline",
1451
- children: group.workflowTitle
1452
- });
1453
- return group.workflowDescription ? /* @__PURE__ */ jsxRuntime.jsx(index.HoverHint, {
1454
- text: group.workflowDescription,
1455
- children: badge
1456
- }) : badge;
1457
- }
1328
+ const EMPTY_NOTE = "No tasks match — adjust the filters, or start a workflow", UNGROUPED_TITLE = "Workflows without Studio documents";
1458
1329
 
1459
- function CollapsibleGroup({header: header, children: children}) {
1460
- const [open, setOpen] = react.useState(!0);
1461
- /* @__PURE__ */
1462
- return jsxRuntime.jsxs(ui.Stack, {
1330
+ function TaskGroupList({groups: groups, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, showTerminalActions: showTerminalActions}) {
1331
+ return groups.withDocuments.length === 0 && groups.withoutDocuments.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
1332
+ text: EMPTY_NOTE
1333
+ }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1463
1334
  gap: 2,
1464
- children: [
1465
- /* @__PURE__ */ jsxRuntime.jsx(ui.Card, {
1466
- paddingX: 2,
1467
- paddingY: 2,
1468
- radius: 2,
1469
- tone: "transparent",
1470
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1335
+ children: [ groups.withDocuments.map(group => /* @__PURE__ */ jsxRuntime.jsx(index.CollapsibleBand, {
1336
+ background: !0,
1337
+ sticky: !0,
1338
+ title: workflowEngine.toBareId(group.document.id),
1339
+ header: /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
1471
1340
  align: "center",
1472
- gap: 2,
1473
- children: [
1474
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1475
- "aria-label": open ? "Collapse" : "Expand",
1476
- fontSize: 1,
1477
- icon: open ? ChevronDown.ChevronDownIcon : ChevronRight.ChevronRightIcon,
1478
- mode: "bleed",
1479
- onClick: () => setOpen(v => !v),
1480
- padding: 1
1481
- }), header ]
1482
- })
1483
- }), open ? children : null ]
1484
- });
1485
- }
1486
-
1487
- function DocumentTaskGroup({group: group, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow}) {
1488
- const document = group.document;
1489
- return document ? /* @__PURE__ */ jsxRuntime.jsx(CollapsibleGroup, {
1490
- header: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
1491
- children: [
1492
- /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1493
1341
  flex: "none",
1494
1342
  style: {
1495
1343
  maxWidth: "60%"
@@ -1498,323 +1346,338 @@ function DocumentTaskGroup({group: group, onOpenTask: onOpenTask, onOpenWorkflow
1498
1346
  max: 2,
1499
1347
  refs: group.documents
1500
1348
  })
1501
- }),
1502
- /* @__PURE__ */ jsxRuntime.jsx(WorkflowBadgeTitle, {
1503
- group: group
1504
- }),
1505
- /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1506
- flex: 1
1507
- }),
1508
- /* @__PURE__ */ jsxRuntime.jsx(GroupMenu, {
1509
- document: document,
1510
- group: group,
1511
- onOpenWorkflow: onOpenWorkflow
1512
- }) ]
1513
- }),
1514
- children: /* @__PURE__ */ jsxRuntime.jsx(TaskRows, {
1349
+ }),
1350
+ children: /* @__PURE__ */ jsxRuntime.jsx(InstanceBands, {
1351
+ instances: group.instances,
1352
+ onOpenTask: onOpenTask,
1353
+ onOpenWorkflow: onOpenWorkflow,
1354
+ showTerminalActions: showTerminalActions
1355
+ })
1356
+ }, group.key)), groups.withoutDocuments.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(index.CollapsibleBand, {
1357
+ background: !0,
1358
+ sticky: !0,
1359
+ title: UNGROUPED_TITLE,
1360
+ header: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1361
+ size: 1,
1362
+ weight: "semibold",
1363
+ children: UNGROUPED_TITLE
1364
+ }),
1365
+ children: /* @__PURE__ */ jsxRuntime.jsx(InstanceBands, {
1366
+ instances: groups.withoutDocuments,
1367
+ onOpenTask: onOpenTask,
1368
+ onOpenWorkflow: onOpenWorkflow,
1369
+ showTerminalActions: showTerminalActions
1370
+ })
1371
+ }) : null ]
1372
+ });
1373
+ }
1374
+
1375
+ function targetOf(row) {
1376
+ return {
1377
+ instanceId: row.instanceId,
1378
+ stage: row.stage,
1379
+ activityName: row.activityName
1380
+ };
1381
+ }
1382
+
1383
+ function InstanceBands({instances: instances, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, showTerminalActions: showTerminalActions}) {
1384
+ /* @__PURE__ */
1385
+ return jsxRuntime.jsx(ui.Stack, {
1386
+ gap: 1,
1387
+ children: instances.map(band => /* @__PURE__ */ jsxRuntime.jsx(InstanceBand, {
1388
+ band: band,
1515
1389
  onOpenTask: onOpenTask,
1516
- rows: group.rows
1517
- })
1518
- }) : null;
1390
+ onOpenWorkflow: onOpenWorkflow,
1391
+ showTerminalActions: showTerminalActions
1392
+ }, band.instanceId))
1393
+ });
1519
1394
  }
1520
1395
 
1521
- function GroupMenu({group: group, document: document, onOpenWorkflow: onOpenWorkflow}) {
1522
- const router$1 = router.useRouter(), state = index.useDocLink(document);
1396
+ function InstanceBand({band: band, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, showTerminalActions: showTerminalActions}) {
1397
+ const {resolvePathFromState: resolvePathFromState} = router.useRouter();
1523
1398
  /* @__PURE__ */
1524
- return jsxRuntime.jsx(index.HintedMenuButton, {
1525
- button: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, {
1526
- "aria-label": "Group actions",
1527
- fontSize: 1,
1528
- icon: EllipsisHorizontal.EllipsisHorizontalIcon,
1529
- mode: "bleed",
1530
- padding: 2
1531
- }),
1532
- hint: "Group actions",
1533
- id: `task-group-menu-${group.key}`,
1534
- menu: /* @__PURE__ */ jsxRuntime.jsxs(ui.Menu, {
1399
+ return jsxRuntime.jsx(index.CollapsibleBand, {
1400
+ title: band.title,
1401
+ header: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, {
1535
1402
  children: [
1536
- /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, {
1537
- onClick: () => onOpenWorkflow(group.instanceId),
1538
- text: "View workflow"
1539
- }), state.kind === "linked" ? /* @__PURE__ */ jsxRuntime.jsx(ui.MenuItem, {
1540
- onClick: () => router$1.navigateIntent("edit", {
1541
- id: state.bareId
1403
+ /* @__PURE__ */ jsxRuntime.jsx(index.LinkChip, {
1404
+ hint: "View workflow",
1405
+ href: resolvePathFromState({
1406
+ instanceId: band.instanceId
1542
1407
  }),
1543
- text: "Open document"
1544
- }) : null ]
1408
+ onClick: event => {
1409
+ event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || (event.preventDefault(),
1410
+ onOpenWorkflow(band.instanceId));
1411
+ },
1412
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1413
+ align: "center",
1414
+ gap: 2,
1415
+ style: {
1416
+ minWidth: 0
1417
+ },
1418
+ children: [
1419
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1420
+ size: 1,
1421
+ style: {
1422
+ minWidth: 0
1423
+ },
1424
+ textOverflow: "ellipsis",
1425
+ weight: "medium",
1426
+ children: band.title
1427
+ }), band.breadcrumb === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(index.BreadcrumbTail, {
1428
+ segments: [ band.breadcrumb ]
1429
+ }),
1430
+ /* @__PURE__ */ jsxRuntime.jsx(index.StageFace, {
1431
+ title: band.stageTitle
1432
+ }) ]
1433
+ })
1434
+ }),
1435
+ /* @__PURE__ */ jsxRuntime.jsx(index.TrailingHairline, {}) ]
1545
1436
  }),
1546
- popover: {
1547
- portal: !0
1548
- }
1437
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1438
+ paddingLeft: 5,
1439
+ children: /* @__PURE__ */ jsxRuntime.jsx(TaskRows, {
1440
+ onOpenTask: onOpenTask,
1441
+ rows: band.rows,
1442
+ showTerminalActions: showTerminalActions
1443
+ })
1444
+ })
1549
1445
  });
1550
1446
  }
1551
1447
 
1552
- function TaskRows({rows: rows, onOpenTask: onOpenTask}) {
1448
+ function TaskRows({rows: rows, onOpenTask: onOpenTask, showTerminalActions: showTerminalActions}) {
1553
1449
  /* @__PURE__ */
1554
1450
  return jsxRuntime.jsx(ui.Stack, {
1555
- children: rows.map(row => /* @__PURE__ */ jsxRuntime.jsx(TaskRowButton, {
1556
- onOpen: () => onOpenTask({
1557
- instanceId: row.instanceId,
1558
- stage: row.stage,
1559
- activityName: row.activityName
1560
- }),
1561
- row: row
1451
+ gap: 1,
1452
+ children: rows.map(row => /* @__PURE__ */ jsxRuntime.jsx(TaskActivityRow, {
1453
+ onOpen: () => onOpenTask(targetOf(row)),
1454
+ row: row,
1455
+ showTerminalActions: showTerminalActions
1562
1456
  }, `${row.instanceId}:${row.activityName}`))
1563
1457
  });
1564
1458
  }
1565
1459
 
1566
- function RowDate({dates: dates}) {
1567
- const first = dates.find(value => index.parseStoredDateValue(value) !== void 0);
1568
- return first === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1569
- muted: !0,
1570
- size: 1,
1571
- children: index.formatDate(first)
1572
- });
1573
- }
1574
-
1575
- function RowTitle({row: row}) {
1576
- const title = /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1577
- size: 1,
1578
- style: row.open ? void 0 : {
1579
- opacity: .66
1580
- },
1581
- weight: "medium",
1582
- children: row.title
1460
+ function TaskActivityRow({row: row, onOpen: onOpen, showTerminalActions: showTerminalActions}) {
1461
+ const {ref: ref, near: near} = useNearViewport(), entry = index.useWorkflowInstanceEntry(row.instanceId, {
1462
+ evaluate: near
1463
+ }), render = taskRowRender(row, entry);
1464
+ /* @__PURE__ */
1465
+ return jsxRuntime.jsx(ui.Box, {
1466
+ ref: ref,
1467
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.StaleLock, {
1468
+ stale: render.stale,
1469
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.ActivityRow, {
1470
+ assignState: render.assignState,
1471
+ assigneeIds: render.assigneeIds,
1472
+ dateControl: /* @__PURE__ */ jsxRuntime.jsx(ActivityDateControl, {
1473
+ dates: render.dates,
1474
+ instanceId: row.instanceId,
1475
+ state: render.dateState
1476
+ }),
1477
+ face: render.face,
1478
+ instanceId: row.instanceId,
1479
+ onOpen: onOpen,
1480
+ terminalActions: showTerminalActions ? render.terminalActions : []
1481
+ })
1482
+ })
1583
1483
  });
1584
- return row.description ? /* @__PURE__ */ jsxRuntime.jsx(index.HoverHint, {
1585
- text: row.description,
1586
- children: title
1587
- }) : title;
1588
1484
  }
1589
1485
 
1590
- function RowBreadcrumb({breadcrumb: breadcrumb}) {
1591
- return breadcrumb ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1592
- align: "center",
1593
- gap: 2,
1594
- children: [
1595
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1596
- muted: !0,
1597
- size: 1,
1598
- children: breadcrumb
1599
- }),
1600
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1601
- muted: !0,
1602
- size: 1,
1603
- children: /* @__PURE__ */ jsxRuntime.jsx(ChevronRight.ChevronRightIcon, {})
1604
- }) ]
1605
- }) : null;
1606
- }
1486
+ const INSTANCE_CAP = 200, NEWEST_INSTANCES = {
1487
+ includeCompleted: !0,
1488
+ limit: INSTANCE_CAP
1489
+ };
1607
1490
 
1608
- function TaskRowButton({row: row, onOpen: onOpen}) {
1609
- /* @__PURE__ */
1610
- return jsxRuntime.jsx(ui.Card, {
1611
- as: "button",
1612
- onClick: onOpen,
1613
- padding: 3,
1614
- paddingLeft: 4,
1615
- radius: 2,
1616
- style: {
1617
- cursor: "pointer",
1618
- textAlign: "left"
1619
- },
1620
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1621
- align: "center",
1622
- gap: 3,
1623
- children: [
1624
- /* @__PURE__ */ jsxRuntime.jsx(index.ActivityStatusIcon, {
1625
- status: row.status
1626
- }),
1627
- /* @__PURE__ */ jsxRuntime.jsx(RowBreadcrumb, {
1628
- breadcrumb: row.breadcrumb
1629
- }),
1630
- /* @__PURE__ */ jsxRuntime.jsx(RowTitle, {
1631
- row: row
1632
- }),
1633
- /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, {
1634
- fontSize: 1,
1635
- mode: "outline",
1636
- tone: "default",
1637
- children: row.stageTitle
1638
- }),
1639
- /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, {
1640
- flex: 1
1641
- }),
1642
- /* @__PURE__ */ jsxRuntime.jsx(RowDate, {
1643
- dates: row.dates
1644
- }), row.assigneeIds.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(index.UserAvatarGroup, {
1645
- ids: row.assigneeIds
1646
- }) : null ]
1647
- })
1491
+ function useToolInstances() {
1492
+ const {engine: engine} = index.useWorkflowContext(), {instances: instances, loading: loading, unreadable: unreadable} = workflowStudio.useWorkflowInstances({
1493
+ engine: engine,
1494
+ filter: NEWEST_INSTANCES
1648
1495
  });
1496
+ return react.useMemo(() => {
1497
+ const rows = instances ?? [];
1498
+ return {
1499
+ inFlight: rows.filter(instance => workflowEngine.terminalState(instance) === "in-flight"),
1500
+ settled: rows.filter(instance => workflowEngine.terminalState(instance) !== "in-flight"),
1501
+ truncated: rows.length + unreadable.length === INSTANCE_CAP,
1502
+ loading: loading,
1503
+ unreadable: unreadable
1504
+ };
1505
+ }, [ instances, loading, unreadable ]);
1649
1506
  }
1650
1507
 
1651
- function TasksTab({instances: instances, loading: loading, identity: identity, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, truncated: truncated}) {
1652
- const [segment, setSegment] = react.useState("mine"), [filters, setFilters] = react.useState({}), groups = react.useMemo(() => deriveTaskGroups({
1508
+ function TasksTab({filterSlot: filterSlot, instances: instances, loading: loading, identity: identity, mineOnly: mineOnly, onOpenTask: onOpenTask, onOpenWorkflow: onOpenWorkflow, truncated: truncated, unreadable: unreadable}) {
1509
+ const [filters, setFilters] = react.useState({}), groups = react.useMemo(() => deriveTaskGroups({
1653
1510
  instances: instances,
1654
1511
  identity: identity
1655
1512
  }), [ instances, identity ]), options = react.useMemo(() => taskFilterOptions(groups), [ groups ]), visible = react.useMemo(() => {
1656
- const segmented = segment === "mine" ? onlyMyTasks(groups) : groups;
1657
- return applyTaskFilters({
1658
- groups: segmented,
1513
+ const filtered = applyTaskFilters({
1514
+ groups: groups,
1659
1515
  filters: filters,
1660
1516
  now: /* @__PURE__ */ new Date
1661
1517
  });
1662
- }, [ groups, segment, filters ]);
1663
- return loading ? /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
1664
- label: "Loading tasks…"
1518
+ return mineOnly ? onlyMyTasks(filtered) : filtered;
1519
+ }, [ groups, filters, mineOnly ]);
1520
+ return loading ?
1521
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1522
+ paddingLeft: 2,
1523
+ children: /* @__PURE__ */ jsxRuntime.jsx(index.LoadingRow, {
1524
+ label: "Loading tasks…"
1525
+ })
1665
1526
  }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Box, {
1666
1527
  children: [
1667
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1668
- align: "center",
1528
+ /* @__PURE__ */ jsxRuntime.jsx(FilterRowPortal, {
1529
+ filters: filters,
1530
+ onChange: setFilters,
1531
+ options: options,
1532
+ slot: filterSlot
1533
+ }),
1534
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1669
1535
  gap: 2,
1670
- paddingBottom: 3,
1671
1536
  children: [
1672
- /* @__PURE__ */ jsxRuntime.jsxs(ui.TabList, {
1673
- gap: 1,
1674
- children: [
1675
- /* @__PURE__ */ jsxRuntime.jsx(ui.Tab, {
1676
- "aria-controls": "workflow-tasks-panel",
1677
- fontSize: 1,
1678
- id: "workflow-tasks-mine",
1679
- label: "My tasks",
1680
- onClick: () => setSegment("mine"),
1681
- padding: 2,
1682
- selected: segment === "mine"
1683
- }),
1684
- /* @__PURE__ */ jsxRuntime.jsx(ui.Tab, {
1685
- "aria-controls": "workflow-tasks-panel",
1686
- fontSize: 1,
1687
- id: "workflow-tasks-all",
1688
- label: "All tasks",
1689
- onClick: () => setSegment("all"),
1690
- padding: 2,
1691
- selected: segment === "all"
1692
- }) ]
1693
- }),
1694
- /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1695
- flex: 1
1696
- }),
1697
- /* @__PURE__ */ jsxRuntime.jsx(ActiveFilterChips, {
1698
- filters: filters,
1699
- onChange: setFilters,
1700
- options: options
1537
+ /* @__PURE__ */ jsxRuntime.jsx(index.UnreadableDocsNote, {
1538
+ unreadable: unreadable
1701
1539
  }),
1702
- /* @__PURE__ */ jsxRuntime.jsx(TaskFilterMenu, {
1703
- filters: filters,
1704
- onChange: setFilters,
1705
- options: options
1706
- }) ]
1707
- }),
1708
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Box, {
1709
- id: "workflow-tasks-panel",
1710
- children: [
1711
1540
  /* @__PURE__ */ jsxRuntime.jsx(TaskGroupList, {
1712
1541
  groups: visible,
1713
1542
  onOpenTask: onOpenTask,
1714
- onOpenWorkflow: onOpenWorkflow
1715
- }), truncated ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
1716
- text: `Tasks from the latest ${INSTANCE_CAP} workflows — older ones aren't listed.`
1717
- }) : null ]
1718
- }) ]
1543
+ onOpenWorkflow: onOpenWorkflow,
1544
+ showTerminalActions: mineOnly
1545
+ }) ]
1546
+ }), truncated ? /* @__PURE__ */ jsxRuntime.jsx(MutedNote, {
1547
+ text: `Tasks from the latest ${INSTANCE_CAP} workflows — older ones aren't listed.`
1548
+ }) : null ]
1719
1549
  });
1720
1550
  }
1721
1551
 
1552
+ const TOOL_TABS = [ {
1553
+ value: "overview",
1554
+ label: "Overview"
1555
+ }, {
1556
+ value: "for-me",
1557
+ label: "For me"
1558
+ } ], [firstToolTab, ...restToolTabs] = TOOL_TABS, TOOL_TAB_CODEC = index.workflowsTabCodec([ firstToolTab.value, ...restToolTabs.map(tab => tab.value) ]);
1559
+
1560
+ function useToolTab() {
1561
+ const router$1 = router.useRouter(), params = Object.fromEntries(router$1.state._searchParams ?? []);
1562
+ return {
1563
+ tab: TOOL_TAB_CODEC.read(params),
1564
+ setTab: next => router$1.navigate({
1565
+ _searchParams: definedEntries(TOOL_TAB_CODEC.write(params, next))
1566
+ })
1567
+ };
1568
+ }
1569
+
1570
+ function definedEntries(params) {
1571
+ return Object.entries(params).filter(entry => entry[1] !== void 0);
1572
+ }
1573
+
1574
+ function carryingSearchParams(state, searchParams) {
1575
+ return searchParams === void 0 ? state : {
1576
+ ...state,
1577
+ _searchParams: searchParams
1578
+ };
1579
+ }
1580
+
1722
1581
  function WorkflowsToolRoot() {
1723
1582
  const router$1 = router.useRouter(), {seedInstance: seedInstance} = index.useWorkflowContext(), instanceId = typeof router$1.state.instanceId == "string" ? router$1.state.instanceId : void 0;
1724
1583
  return instanceId ? /* @__PURE__ */ jsxRuntime.jsx(WorkflowInstanceDetail, {
1725
1584
  instanceId: instanceId,
1726
- onBack: () => router$1.navigate({})
1585
+ onBack: () => router$1.navigate(carryingSearchParams({}, router$1.state._searchParams))
1727
1586
  }, instanceId) : /* @__PURE__ */ jsxRuntime.jsx(WorkflowsHome, {
1728
1587
  onOpenInstance: instance => {
1729
- typeof instance != "string" && seedInstance(instance), router$1.navigate({
1588
+ typeof instance != "string" && seedInstance(instance), router$1.navigate(carryingSearchParams({
1730
1589
  instanceId: typeof instance == "string" ? instance : instance._id
1731
- });
1590
+ }, router$1.state._searchParams));
1732
1591
  }
1733
1592
  });
1734
1593
  }
1735
1594
 
1736
- function ToolPanel({identity: identity, instances: instances, onOpenInstance: onOpenInstance, onOpenTask: onOpenTask, tab: tab}) {
1737
- return instances.invalid ? /* @__PURE__ */ jsxRuntime.jsx(index.InvalidDocNotice, {
1738
- invalid: instances.invalid
1739
- }) : tab === "tasks" ? /* @__PURE__ */ jsxRuntime.jsx(TasksTab, {
1740
- identity: identity,
1741
- instances: [ ...instances.inFlight, ...instances.settled ],
1742
- loading: instances.loading,
1743
- onOpenTask: onOpenTask,
1744
- onOpenWorkflow: onOpenInstance,
1745
- truncated: instances.truncated
1746
- }) : /* @__PURE__ */ jsxRuntime.jsx(WorkflowsDashboard, {
1747
- instances: instances,
1748
- onOpenInstance: onOpenInstance
1749
- });
1750
- }
1751
-
1752
1595
  function WorkflowsHome({onOpenInstance: onOpenInstance}) {
1753
- const [tab, setTab] = react.useState("tasks"), instances = useToolInstances(), identity = index.useAssignmentIdentity(), [openTask, setOpenTask] = react.useState(null);
1754
- /* @__PURE__ */
1755
- return jsxRuntime.jsxs(ui.Card, {
1596
+ const {tab: tab, setTab: setTab} = useToolTab(), instances = useToolInstances(), identity = index.useAssignmentIdentity(), [openTask, setOpenTask] = react.useState(null), openInstance = target => {
1597
+ if (typeof target != "string") return onOpenInstance(target);
1598
+ const held = [ ...instances.inFlight, ...instances.settled ].find(i => i._id === target);
1599
+ onOpenInstance(held ?? target);
1600
+ }, [filterSlot, setFilterSlot] = react.useState(null), scrollerRef = react.useRef(null);
1601
+ return react.useEffect(() => {
1602
+ scrollerRef.current?.scrollTo({
1603
+ top: 0
1604
+ });
1605
+ }, [ tab ]),
1606
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Card, {
1756
1607
  height: "fill",
1757
- overflow: "auto",
1758
1608
  children: [
1759
- /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1760
- padding: 4,
1761
- children: /* @__PURE__ */ jsxRuntime.jsx(ui.Container, {
1762
- width: 2,
1763
- children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1764
- gap: 4,
1765
- children: [
1766
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1767
- align: "center",
1609
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1610
+ direction: "column",
1611
+ height: "fill",
1612
+ overflow: "hidden",
1613
+ children: [
1614
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1615
+ flex: "none",
1616
+ padding: 3,
1617
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Container, {
1618
+ width: 2,
1619
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, {
1768
1620
  gap: 3,
1769
1621
  children: [
1770
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1771
- size: 2,
1772
- weight: "semibold",
1773
- children: "Workflows"
1774
- }),
1775
- /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1776
- flex: 1
1622
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, {
1623
+ align: "center",
1624
+ gap: 3,
1625
+ paddingLeft: 2,
1626
+ children: [
1627
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, {
1628
+ size: 2,
1629
+ weight: "semibold",
1630
+ children: "Workflows"
1631
+ }),
1632
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1633
+ flex: 1
1634
+ }),
1635
+ /* @__PURE__ */ jsxRuntime.jsx(NewWorkflowButton, {}) ]
1777
1636
  }),
1778
- /* @__PURE__ */ jsxRuntime.jsx(NewWorkflowButton, {}) ]
1779
- }),
1780
- /* @__PURE__ */ jsxRuntime.jsxs(ui.TabList, {
1781
- gap: 1,
1782
- children: [
1783
- /* @__PURE__ */ jsxRuntime.jsx(ui.Tab, {
1784
- "aria-controls": "workflows-tool-panel",
1785
- fontSize: 1,
1786
- id: "workflows-tool-tab-tasks",
1787
- label: "Tasks",
1788
- onClick: () => setTab("tasks"),
1789
- padding: 2,
1790
- selected: tab === "tasks"
1637
+ /* @__PURE__ */ jsxRuntime.jsx(index.TabSwitch, {
1638
+ ariaControls: "workflows-tool-panel",
1639
+ idPrefix: "workflows-tool-tab",
1640
+ onSelect: setTab,
1641
+ options: TOOL_TABS,
1642
+ selected: tab
1791
1643
  }),
1792
- /* @__PURE__ */ jsxRuntime.jsx(ui.Tab, {
1793
- "aria-controls": "workflows-tool-panel",
1794
- fontSize: 1,
1795
- id: "workflows-tool-tab-workflows",
1796
- label: "All workflows",
1797
- onClick: () => setTab("workflows"),
1798
- padding: 2,
1799
- selected: tab === "workflows"
1644
+ /* @__PURE__ */ jsxRuntime.jsx("div", {
1645
+ ref: setFilterSlot
1800
1646
  }) ]
1801
- }),
1802
- /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1647
+ })
1648
+ })
1649
+ }),
1650
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1651
+ ref: scrollerRef,
1652
+ flex: 1,
1653
+ overflow: "auto",
1654
+ paddingX: 3,
1655
+ style: {
1656
+ scrollbarGutter: "stable"
1657
+ },
1658
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Container, {
1659
+ width: 2,
1660
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.TabPanel, {
1661
+ "aria-labelledby": `workflows-tool-tab-${tab}`,
1803
1662
  id: "workflows-tool-panel",
1804
- children: /* @__PURE__ */ jsxRuntime.jsx(ToolPanel, {
1805
- identity: identity,
1806
- instances: instances,
1807
- onOpenInstance: target => {
1808
- if (typeof target != "string") return onOpenInstance(target);
1809
- const held = [ ...instances.inFlight, ...instances.settled ].find(i => i._id === target);
1810
- onOpenInstance(held ?? target);
1811
- },
1812
- onOpenTask: setOpenTask,
1813
- tab: tab
1663
+ children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, {
1664
+ paddingBottom: 4,
1665
+ paddingTop: 1,
1666
+ children: /* @__PURE__ */ jsxRuntime.jsx(TasksTab, {
1667
+ filterSlot: filterSlot,
1668
+ identity: identity,
1669
+ instances: [ ...instances.inFlight, ...instances.settled ],
1670
+ loading: instances.loading,
1671
+ mineOnly: tab === "for-me",
1672
+ onOpenTask: setOpenTask,
1673
+ onOpenWorkflow: openInstance,
1674
+ truncated: instances.truncated,
1675
+ unreadable: instances.unreadable
1676
+ })
1814
1677
  })
1815
- }) ]
1678
+ })
1816
1679
  })
1817
- })
1680
+ }) ]
1818
1681
  }), openTask ? /* @__PURE__ */ jsxRuntime.jsx(ToolActivityDialog, {
1819
1682
  onClose: () => setOpenTask(null),
1820
1683
  target: openTask