@parallelworks/ui 0.1.0 → 0.2.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.
Files changed (102) hide show
  1. package/dist/assets/editor.worker-CuBRIwi-.js +14776 -0
  2. package/dist/assets/json.worker-8ytS02vz.js +22161 -0
  3. package/dist/assets/yaml.worker-DJC-VUcv.js +48367 -0
  4. package/dist/chunks/Indicator-fF8AVbjQ.js +158 -0
  5. package/dist/chunks/Loader-DQiZ75fa.js +25 -0
  6. package/dist/chunks/MarkdownImpl-Bq4QhrtA.js +33 -0
  7. package/dist/chunks/Monaco-CP_cL9gp.js +745 -0
  8. package/dist/chunks/Provider-C2VtPnh5.js +151 -0
  9. package/dist/chunks/SwitchToggleSmall-FcsaYSTs.js +14 -0
  10. package/dist/chunks/Table-DWuRINhu.js +855 -0
  11. package/dist/chunks/Tooltip-WgbiC_nd.js +71 -0
  12. package/dist/chunks/lib-DTgL8klR.js +191 -0
  13. package/dist/chunks/workers-Cjfjb8E7.js +119 -0
  14. package/dist/components/CollapsiblePanel.d.ts +27 -0
  15. package/dist/components/CopyToClipboard.d.ts +17 -0
  16. package/dist/components/Dropdown.d.ts +24 -0
  17. package/dist/components/Indicator.d.ts +4 -0
  18. package/dist/components/Listbox.d.ts +18 -0
  19. package/dist/components/Markdown.d.ts +7 -0
  20. package/dist/components/MarkdownImpl.d.ts +2 -0
  21. package/dist/components/MultiSelect.d.ts +19 -0
  22. package/dist/components/Overview.d.ts +12 -0
  23. package/dist/components/Provider.d.ts +145 -0
  24. package/dist/components/SectionHeader.d.ts +6 -0
  25. package/dist/components/SwitchToggle.d.ts +12 -0
  26. package/dist/components/SwitchToggleSmall.d.ts +6 -0
  27. package/dist/components/dropdownUtils.d.ts +18 -0
  28. package/dist/editor/Editor.d.ts +2 -0
  29. package/dist/editor/EditorField.d.ts +11 -0
  30. package/dist/editor/FormCodePanel.d.ts +8 -0
  31. package/dist/editor/Monaco.d.ts +31 -0
  32. package/dist/editor/index.d.ts +6 -0
  33. package/dist/editor/index.js +219 -0
  34. package/dist/editor/themes.d.ts +4 -0
  35. package/dist/editor/workers.d.ts +6 -0
  36. package/dist/form/FieldWrapper.d.ts +10 -0
  37. package/dist/form/Fields/BooleanField.d.ts +12 -0
  38. package/dist/form/Fields/BucketReadOnlyField.d.ts +7 -0
  39. package/dist/form/Fields/CheckboxGroupField.d.ts +18 -0
  40. package/dist/form/Fields/ColorField.d.ts +8 -0
  41. package/dist/form/Fields/DropdownField.d.ts +24 -0
  42. package/dist/form/Fields/DurationField.d.ts +13 -0
  43. package/dist/form/Fields/LabelField.d.ts +9 -0
  44. package/dist/form/Fields/MultiDropdownField.d.ts +18 -0
  45. package/dist/form/Fields/NumberField.d.ts +13 -0
  46. package/dist/form/Fields/ObjectField.d.ts +8 -0
  47. package/dist/form/Fields/RadioField.d.ts +9 -0
  48. package/dist/form/Fields/RangeField.d.ts +11 -0
  49. package/dist/form/Fields/ResourceOptionIcon.d.ts +11 -0
  50. package/dist/form/Fields/StepField.d.ts +3 -0
  51. package/dist/form/Fields/StringField.d.ts +17 -0
  52. package/dist/form/Fields/TextAreaField.d.ts +8 -0
  53. package/dist/form/Fields/durationValidity.d.ts +2 -0
  54. package/dist/form/Form.d.ts +105 -0
  55. package/dist/form/SpecialTypes/DropdownWrapper.d.ts +14 -0
  56. package/dist/form/SpecialTypes/FormikCustomDropdown.d.ts +53 -0
  57. package/dist/form/SpecialTypes/FormikCustomInput.d.ts +17 -0
  58. package/dist/form/SpecialTypes/FormikMultiSelectDropdown.d.ts +13 -0
  59. package/dist/form/SpecialTypes/FormikNumericInput.d.ts +9 -0
  60. package/dist/form/SpecialTypes/index.d.ts +3 -0
  61. package/dist/form/Wizard/WizardContainer.d.ts +5 -0
  62. package/dist/form/Wizard/WizardNavigation.d.ts +2 -0
  63. package/dist/form/Wizard/WizardStepContent.d.ts +8 -0
  64. package/dist/form/Wizard/WizardStepIndicator.d.ts +2 -0
  65. package/dist/form/Wizard/types.d.ts +171 -0
  66. package/dist/form/Wizard/useWizardState.d.ts +28 -0
  67. package/dist/form/Wizard/utils.d.ts +2 -0
  68. package/dist/form/fieldRegistry.d.ts +7 -0
  69. package/dist/form/index.d.ts +16 -0
  70. package/dist/form/index.js +2317 -0
  71. package/dist/form/lib.d.ts +15 -0
  72. package/dist/form/types/fieldComponentTypes.d.ts +32 -0
  73. package/dist/form/types/fieldTypes.d.ts +80 -0
  74. package/dist/form/types/workflow-schema.d.ts +1107 -0
  75. package/dist/form/useParsedOpts.d.ts +5 -0
  76. package/dist/form/usePrevious.d.ts +1 -0
  77. package/dist/form/utils/checkShowIfObject.d.ts +1 -0
  78. package/dist/form/utils/getParentValue.d.ts +1 -0
  79. package/dist/form/utils/getPlaceholder.d.ts +2 -0
  80. package/dist/form/utils/getValueUsingPath.d.ts +1 -0
  81. package/dist/form/utils/pwResolver.d.ts +18 -0
  82. package/dist/form/utils/secondaryField.d.ts +8 -0
  83. package/dist/form/utils/slurmDuration.d.ts +15 -0
  84. package/dist/icons.d.ts +1 -1
  85. package/dist/icons.js +2 -2
  86. package/dist/index.d.ts +17 -0
  87. package/dist/index.js +107 -323
  88. package/dist/logviewer/LogViewer.d.ts +18 -0
  89. package/dist/logviewer/index.d.ts +1 -0
  90. package/dist/logviewer/index.js +534 -0
  91. package/dist/logviewer/logviewer.css +70 -0
  92. package/dist/styles.css +1 -1
  93. package/dist/workflow/AnnotationBanner.d.ts +5 -0
  94. package/dist/workflow/Collapse.d.ts +6 -0
  95. package/dist/workflow/DependencyGraph.d.ts +15 -0
  96. package/dist/workflow/JobSummary.d.ts +38 -0
  97. package/dist/workflow/MatrixGroup.d.ts +41 -0
  98. package/dist/workflow/Reveal.d.ts +7 -0
  99. package/dist/workflow/TreeView.d.ts +42 -0
  100. package/dist/workflow/index.d.ts +1 -0
  101. package/dist/workflow/index.js +2330 -0
  102. package/package.json +60 -6
@@ -0,0 +1,2330 @@
1
+ import { ArrowLeftIcon, ArrowRightIcon, ChevronDownIcon, ChevronRightIcon, DocumentIcon, ErrorIcon, InfoIcon, LoaderIcon, OpenInNewGraphIcon, WarningIcon } from "../icons.js";
2
+ import { a as useSlots, i as useRunFile, n as useNavigation, o as useStrings } from "../chunks/Provider-C2VtPnh5.js";
3
+ import { n as TOOLTIP_ID, r as TooltipInfo } from "../chunks/Tooltip-WgbiC_nd.js";
4
+ import { t as Loader } from "../chunks/Loader-DQiZ75fa.js";
5
+ import { n as Modal, t as Indicator } from "../chunks/Indicator-fF8AVbjQ.js";
6
+ import { LogViewer } from "../logviewer/index.js";
7
+ import cx from "classnames";
8
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
+ import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
10
+ import { flushSync } from "react-dom";
11
+ import { buildSubworkflowLogPath, collapseMatrixGroupsInLayout, collectExpansionKeys, computeMatrixAggStatus, detectMatrixGroups, dumpYaml, getAllDeps, getStepLabel, isRunActive, toAbsHumanDuration } from "@parallelworks/workflow-parser";
12
+ import { DateTime } from "luxon";
13
+ import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
14
+ import { AnimatePresence, motion } from "framer-motion";
15
+ //#region src/workflow/AnnotationBanner.tsx
16
+ var typeOrder = {
17
+ error: 0,
18
+ warning: 1,
19
+ notice: 2
20
+ };
21
+ var typeConfig = {
22
+ error: {
23
+ icon: ErrorIcon,
24
+ label: "Error",
25
+ accentClass: "text-red-500",
26
+ bgClass: "bg-red-500/5 dark:bg-red-500/10",
27
+ countBg: "bg-red-500/15 text-red-700 dark:text-red-400"
28
+ },
29
+ warning: {
30
+ icon: WarningIcon,
31
+ label: "Warning",
32
+ accentClass: "text-yellow-500",
33
+ bgClass: "bg-yellow-500/5 dark:bg-yellow-500/10",
34
+ countBg: "bg-yellow-500/15 text-yellow-700 dark:text-yellow-400"
35
+ },
36
+ notice: {
37
+ icon: InfoIcon,
38
+ label: "Notice",
39
+ accentClass: "text-blue-500",
40
+ bgClass: "bg-blue-500/5 dark:bg-blue-500/10",
41
+ countBg: "bg-blue-500/15 text-blue-700 dark:text-blue-400"
42
+ }
43
+ };
44
+ function collectAnnotations(executedJobs, parentPath) {
45
+ const annotations = [];
46
+ for (const [jobName, job] of Object.entries(executedJobs)) {
47
+ const fullJobName = parentPath ? `${parentPath} > ${jobName}` : jobName;
48
+ const steps = job?.steps;
49
+ if (!Array.isArray(steps)) continue;
50
+ for (let stepIndex = 0; stepIndex < steps.length; stepIndex++) {
51
+ const step = steps[stepIndex];
52
+ const stepAnnotations = step?.annotations;
53
+ if (Array.isArray(stepAnnotations)) {
54
+ for (const annotation of stepAnnotations) if (annotation?.message) annotations.push({
55
+ type: annotation.type || "notice",
56
+ message: annotation.message,
57
+ title: annotation.title,
58
+ file: annotation.file,
59
+ line: annotation.line,
60
+ jobName: fullJobName,
61
+ stepName: step?.name,
62
+ stepIndex
63
+ });
64
+ }
65
+ const subJobs = step?.subworkflow?.jobs;
66
+ if (subJobs && typeof subJobs === "object") annotations.push(...collectAnnotations(subJobs, fullJobName));
67
+ }
68
+ }
69
+ annotations.sort((a, b) => (typeOrder[a.type] ?? 2) - (typeOrder[b.type] ?? 2));
70
+ return annotations;
71
+ }
72
+ function AnnotationBanner({ executedJobs }) {
73
+ const [expanded, setExpanded] = useState(false);
74
+ if (!executedJobs) return null;
75
+ const annotations = collectAnnotations(executedJobs);
76
+ if (annotations.length === 0) return null;
77
+ const counts = {
78
+ error: 0,
79
+ warning: 0,
80
+ notice: 0
81
+ };
82
+ for (const a of annotations) counts[a.type]++;
83
+ const ChevronIcon = expanded ? ChevronDownIcon : ChevronRightIcon;
84
+ return /* @__PURE__ */ jsxs("div", {
85
+ className: cx("rounded-lg border border-[var(--theme-border)] overflow-hidden", "bg-[var(--theme-panel-bg)]"),
86
+ children: [/* @__PURE__ */ jsxs("button", {
87
+ type: "button",
88
+ onClick: () => setExpanded((prev) => !prev),
89
+ className: cx("w-full flex items-center gap-3 px-4 py-2.5 text-left", "hover:bg-[var(--theme-hover)] transition-colors", expanded && "border-b border-[var(--theme-border)]"),
90
+ children: [
91
+ /* @__PURE__ */ jsx(ChevronIcon, { className: "w-3.5 h-3.5 text-[var(--theme-muted-text-color)] shrink-0" }),
92
+ /* @__PURE__ */ jsx("span", {
93
+ className: "text-sm font-medium text-[var(--theme-panel-color)]",
94
+ children: "Annotations"
95
+ }),
96
+ /* @__PURE__ */ jsx("div", {
97
+ className: "flex items-center gap-1.5",
98
+ children: [
99
+ "error",
100
+ "warning",
101
+ "notice"
102
+ ].map((type) => {
103
+ if (!counts[type]) return null;
104
+ const config = typeConfig[type];
105
+ const Icon = config.icon;
106
+ return /* @__PURE__ */ jsxs("span", {
107
+ className: cx("inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium", config.countBg),
108
+ children: [/* @__PURE__ */ jsx(Icon, { className: "w-3 h-3" }), counts[type]]
109
+ }, type);
110
+ })
111
+ })
112
+ ]
113
+ }), expanded && /* @__PURE__ */ jsx("div", {
114
+ className: "divide-y divide-[var(--theme-border)] max-h-[400px] overflow-y-auto",
115
+ children: annotations.map((annotation, idx) => {
116
+ const config = typeConfig[annotation.type] || typeConfig.notice;
117
+ const Icon = config.icon;
118
+ return /* @__PURE__ */ jsxs("div", {
119
+ className: cx("flex items-start gap-2.5 px-4 py-2 text-sm", config.bgClass),
120
+ children: [/* @__PURE__ */ jsx(Icon, { className: cx("w-4 h-4 mt-0.5 shrink-0", config.accentClass) }), /* @__PURE__ */ jsxs("div", {
121
+ className: "flex-1 min-w-0",
122
+ children: [
123
+ /* @__PURE__ */ jsxs("span", {
124
+ className: cx("font-medium", config.accentClass),
125
+ children: [annotation.title || config.label, ":"]
126
+ }),
127
+ " ",
128
+ /* @__PURE__ */ jsx("span", {
129
+ className: "text-[var(--theme-panel-color)]",
130
+ children: annotation.message
131
+ }),
132
+ /* @__PURE__ */ jsxs("span", {
133
+ className: "text-[var(--theme-muted-text-color)] ml-2 text-xs",
134
+ children: [
135
+ "in ",
136
+ annotation.jobName,
137
+ " →",
138
+ " ",
139
+ annotation.stepName || `step ${annotation.stepIndex}`
140
+ ]
141
+ })
142
+ ]
143
+ })]
144
+ }, idx);
145
+ })
146
+ })]
147
+ });
148
+ }
149
+ //#endregion
150
+ //#region src/workflow/Collapse.tsx
151
+ function Collapse({ expanded, children, releaseOverflow = true }) {
152
+ const [overflowVisible, setOverflowVisible] = useState(false);
153
+ return /* @__PURE__ */ jsx(AnimatePresence, {
154
+ initial: false,
155
+ children: expanded && /* @__PURE__ */ jsx(motion.div, {
156
+ initial: {
157
+ height: 0,
158
+ opacity: 0
159
+ },
160
+ animate: {
161
+ height: "auto",
162
+ opacity: 1
163
+ },
164
+ exit: {
165
+ height: 0,
166
+ opacity: 0
167
+ },
168
+ transition: {
169
+ duration: .2,
170
+ ease: [
171
+ .25,
172
+ .1,
173
+ .25,
174
+ 1
175
+ ]
176
+ },
177
+ style: { overflow: overflowVisible ? "visible" : "hidden" },
178
+ onAnimationStart: () => setOverflowVisible(false),
179
+ onAnimationComplete: () => {
180
+ if (expanded && releaseOverflow) setOverflowVisible(true);
181
+ },
182
+ children
183
+ }, "collapse")
184
+ });
185
+ }
186
+ //#endregion
187
+ //#region src/workflow/Reveal.tsx
188
+ var EASE = "cubic-bezier(0.25, 0.1, 0.25, 1)";
189
+ var ROWS_TRANSITION = `grid-template-rows 200ms ${EASE}`;
190
+ var FULL_TRANSITION = `${ROWS_TRANSITION}, width 200ms ${EASE}`;
191
+ function Reveal({ open, children, growWidth = false, onSettled }) {
192
+ const contentRef = useRef(null);
193
+ const [width, setWidth] = useState(0);
194
+ const [prevOpen, setPrevOpen] = useState(open);
195
+ const [clipped, setClipped] = useState(!open);
196
+ const [toggling, setToggling] = useState(false);
197
+ const [renderChildren, setRenderChildren] = useState(open);
198
+ if (open && !renderChildren) setRenderChildren(true);
199
+ if (prevOpen !== open) {
200
+ setPrevOpen(open);
201
+ setToggling(true);
202
+ setClipped(true);
203
+ }
204
+ useLayoutEffect(() => {
205
+ if (!growWidth || !renderChildren) return;
206
+ const el = contentRef.current;
207
+ if (!el) return;
208
+ const measure = () => setWidth(el.offsetWidth);
209
+ measure();
210
+ const ro = new ResizeObserver(measure);
211
+ ro.observe(el);
212
+ return () => ro.disconnect();
213
+ }, [growWidth, renderChildren]);
214
+ return /* @__PURE__ */ jsx("div", {
215
+ className: "grid",
216
+ style: {
217
+ gridTemplateRows: open ? "1fr" : "0fr",
218
+ overflow: clipped ? "hidden" : "visible",
219
+ ...growWidth ? {
220
+ width: open ? width : 0,
221
+ transition: toggling ? FULL_TRANSITION : ROWS_TRANSITION
222
+ } : { transition: ROWS_TRANSITION }
223
+ },
224
+ onTransitionEnd: (e) => {
225
+ if (e.target === e.currentTarget && e.propertyName === "grid-template-rows") {
226
+ setToggling(false);
227
+ setClipped(!open);
228
+ if (!open) setRenderChildren(false);
229
+ onSettled?.();
230
+ }
231
+ },
232
+ children: /* @__PURE__ */ jsx("div", {
233
+ ref: contentRef,
234
+ className: "min-h-0",
235
+ ...growWidth ? { style: { width: "max-content" } } : {},
236
+ children: renderChildren ? children : null
237
+ })
238
+ });
239
+ }
240
+ //#endregion
241
+ //#region src/workflow/JobSummary.tsx
242
+ function UsesIcon({ uses, subworkflow }) {
243
+ const navigation = useNavigation();
244
+ const slots = useSlots();
245
+ const { jobActions } = useStrings();
246
+ if (!uses || !slots.workflowIcon) return null;
247
+ const open = () => {
248
+ if (uses.startsWith("workflow/")) navigation.toWorkflow(uses.split("/")[1]);
249
+ else if (uses.startsWith("github/")) {
250
+ const [ownerRepo, ref] = uses.replace("github/", "").split("@");
251
+ navigation.openExternal("https://github.com/" + ownerRepo + "/tree/" + ref);
252
+ } else navigation.toMarketItem(uses.split("/")[1]);
253
+ };
254
+ return /* @__PURE__ */ jsx("div", {
255
+ "data-tooltip-id": TOOLTIP_ID,
256
+ "data-tooltip-content": jobActions.openOriginalWorkflow,
257
+ onClick: open,
258
+ className: "cursor-pointer",
259
+ children: slots.workflowIcon({
260
+ image: subworkflow.image,
261
+ type: subworkflow.type,
262
+ className: "ml-1.5 h-[1.2em] w-[1.2em]"
263
+ })
264
+ });
265
+ }
266
+ function Joblist(inputs) {
267
+ const { jobActions } = useStrings();
268
+ return inputs.jobNames.map((jobName) => {
269
+ if (inputs.jobs[jobName]?._matrixGroup) return null;
270
+ const jobStatus = inputs.jobs[jobName]?.status;
271
+ const matrixMeta = inputs.jobs[jobName]?._matrix;
272
+ const jobLabel = matrixMeta?.originaljob ? `${matrixMeta.originaljob[0].toUpperCase() + matrixMeta.originaljob.slice(1)} (${(matrixMeta.index ?? 0) + 1}/${matrixMeta.totalingroup ?? "?"})` : (jobName[0].toUpperCase() + jobName.slice(1)).replace(/_/g, " ");
273
+ return /* @__PURE__ */ jsxs("div", {
274
+ className: "py-1 select-none",
275
+ children: [/* @__PURE__ */ jsxs("div", {
276
+ onClick: () => inputs.onJobClick(jobName),
277
+ className: cx("flex items-center gap-x-1.5", jobStatus === "skipped" || jobStatus === "skipped-failed" ? "cursor-default" : "cursor-pointer"),
278
+ children: [!inputs.preview && /* @__PURE__ */ jsx(Indicator, { status: inputs.jobs[jobName]?.status }), /* @__PURE__ */ jsx("div", {
279
+ className: "font-medium",
280
+ children: jobLabel
281
+ })]
282
+ }), /* @__PURE__ */ jsx(Reveal, {
283
+ open: inputs.isStepsOpen(jobName),
284
+ growWidth: inputs.growWidth ?? false,
285
+ children: /* @__PURE__ */ jsx("div", {
286
+ className: "flex flex-col gap-y-0.5 mt-0.5",
287
+ children: (inputs.jobs[jobName]?.steps || []).map((step, i) => {
288
+ const stepLabel = getStepLabel(step, i);
289
+ const subJobs = step.subworkflow?.jobs;
290
+ const canExpand = !!inputs.renderSubworkflow && !!subJobs && Object.keys(subJobs).length > 0;
291
+ return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
292
+ className: cx("flex items-center", step.status === "skipped" || step.status === "skipped-failed" ? "cursor-default" : "cursor-pointer"),
293
+ children: [/* @__PURE__ */ jsxs("div", {
294
+ onClick: () => inputs.onStepClick(jobName, i),
295
+ className: cx(inputs.preview ? "ml-1.5" : "ml-4", "gap-x-1.5 flex items-center"),
296
+ "data-testid": stepLabel,
297
+ children: [inputs.preview ? "↳" : /* @__PURE__ */ jsx(Indicator, { status: step.status }), /* @__PURE__ */ jsx("div", { children: stepLabel })]
298
+ }), step.subworkflow && /* @__PURE__ */ jsxs(Fragment, { children: [
299
+ inputs.onStepLogClick && /* @__PURE__ */ jsx("button", {
300
+ type: "button",
301
+ "aria-label": jobActions.openSubworkflowLogs,
302
+ "data-tooltip-id": "global-tooltip",
303
+ "data-tooltip-content": jobActions.openSubworkflowLogs,
304
+ onClick: (e) => {
305
+ e.stopPropagation();
306
+ inputs.onStepLogClick?.(jobName, i);
307
+ },
308
+ className: "link ml-1",
309
+ children: /* @__PURE__ */ jsx(DocumentIcon, { className: "w-[1.2em] h-[1.2em]" })
310
+ }),
311
+ /* @__PURE__ */ jsx(UsesIcon, {
312
+ uses: step.uses,
313
+ subworkflow: step.subworkflow
314
+ }),
315
+ inputs.onSubworkflowIcon && /* @__PURE__ */ jsx("button", {
316
+ type: "button",
317
+ "aria-label": jobActions.openInNewGraph,
318
+ "data-tooltip-id": "global-tooltip",
319
+ "data-tooltip-content": jobActions.openInNewGraph,
320
+ onClick: (e) => {
321
+ e.stopPropagation();
322
+ inputs.onSubworkflowIcon?.(jobName, i);
323
+ },
324
+ className: "link ml-1",
325
+ children: /* @__PURE__ */ jsx(OpenInNewGraphIcon, { className: "w-[1.2em] h-[1.2em]" })
326
+ })
327
+ ] })]
328
+ }), canExpand && inputs.renderSubworkflow?.(jobName, i)] }, "step-" + jobName + "-" + i);
329
+ })
330
+ })
331
+ })]
332
+ }, "job-" + jobName);
333
+ });
334
+ }
335
+ //#endregion
336
+ //#region src/workflow/TreeView.tsx
337
+ function getStepDuration(step) {
338
+ if (!step.startedAt) return "";
339
+ return toAbsHumanDuration(DateTime.fromISO(step.startedAt), step.completedAt ? DateTime.fromISO(step.completedAt) : DateTime.now());
340
+ }
341
+ function getJobDuration(job) {
342
+ if (!job.startedAt) return "";
343
+ return toAbsHumanDuration(DateTime.fromISO(job.startedAt), job.completedAt ? DateTime.fromISO(job.completedAt) : DateTime.now());
344
+ }
345
+ function formatJobLabel(jobName, job) {
346
+ const matrix = job?._matrix;
347
+ if (matrix?.originaljob) return `${matrix.originaljob[0].toUpperCase() + matrix.originaljob.slice(1)} (${(matrix.index ?? 0) + 1}/${matrix.totalingroup ?? "?"})`;
348
+ return (jobName[0].toUpperCase() + jobName.slice(1)).replace(/_/g, " ");
349
+ }
350
+ function InlineLogViewer({ slug, jobName, stepIndex }) {
351
+ const { data: logs, isLoading, error } = useRunFile(slug, buildSubworkflowLogPath("", jobName, stepIndex), { refreshInterval: 2e3 });
352
+ if (isLoading && !logs) return /* @__PURE__ */ jsxs("div", {
353
+ className: "flex items-center gap-2 px-5 py-4 text-sm text-[var(--theme-muted-text-color)]",
354
+ children: [/* @__PURE__ */ jsx(LoaderIcon, { className: "w-4 h-4" }), /* @__PURE__ */ jsx("span", { children: "Loading logs..." })]
355
+ });
356
+ if (error || !logs) return /* @__PURE__ */ jsx("div", {
357
+ className: "px-5 py-4 text-sm text-[var(--theme-muted-text-color)] italic",
358
+ children: "No logs available"
359
+ });
360
+ return /* @__PURE__ */ jsx("div", {
361
+ className: "border-t border-[var(--theme-border)] ",
362
+ children: /* @__PURE__ */ jsx(LogViewer, {
363
+ log: logs,
364
+ width: "100%",
365
+ height: "unset",
366
+ inline: true,
367
+ contentClassName: "bg-[var(--theme-app-bg)]",
368
+ enableWorkflowCommands: true
369
+ })
370
+ });
371
+ }
372
+ function JobRow({ jobName, job, isExpanded, onToggle, dependencies, workflowName, runNumber, slug, expandedJobs, onJobToggle, expandedSteps, onStepToggle }) {
373
+ const hasSteps = job.steps && job.steps.length > 0;
374
+ const duration = getJobDuration(job);
375
+ return /* @__PURE__ */ jsxs("div", {
376
+ className: "border-b border-[var(--theme-border)] last:border-b-0",
377
+ children: [/* @__PURE__ */ jsxs("div", {
378
+ className: cx("flex items-center gap-3 px-5 py-3 transition-colors", job.status === "skipped" || job.status === "skipped-failed" ? "cursor-default" : "cursor-pointer", isExpanded && "bg-[var(--theme-panel-bg)]"),
379
+ onClick: onToggle,
380
+ children: [
381
+ /* @__PURE__ */ jsx(motion.div, {
382
+ animate: { rotate: isExpanded ? 90 : 0 },
383
+ transition: { duration: .15 },
384
+ className: cx("flex-shrink-0", hasSteps ? "text-[var(--theme-muted-text-color)]" : "invisible"),
385
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: cx("w-4 h-4", isExpanded && "text-[var(--theme-element)]") })
386
+ }),
387
+ /* @__PURE__ */ jsx(Indicator, { status: job.status }),
388
+ /* @__PURE__ */ jsxs("div", {
389
+ className: "flex flex-col gap-1 min-w-0 flex-1",
390
+ children: [/* @__PURE__ */ jsx("span", {
391
+ className: "font-mono text-sm text-[var(--theme-app)] truncate",
392
+ children: formatJobLabel(jobName, job)
393
+ }), dependencies && dependencies.length > 0 && /* @__PURE__ */ jsxs("div", {
394
+ className: "flex items-center gap-1.5",
395
+ children: [/* @__PURE__ */ jsx("span", {
396
+ className: "text-[10px] text-[var(--theme-muted-text-color)]",
397
+ children: "depends on:"
398
+ }), dependencies.map((dep) => /* @__PURE__ */ jsx("span", {
399
+ className: "text-[10px] px-1.5 py-0.5 rounded bg-[var(--theme-muted-panel-bg)] text-[var(--theme-muted-text-color)]",
400
+ children: formatJobLabel(dep)
401
+ }, dep))]
402
+ })]
403
+ }),
404
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
405
+ duration && /* @__PURE__ */ jsx("span", {
406
+ className: "font-mono text-xs text-[var(--theme-muted-text-color)] flex-shrink-0",
407
+ children: duration
408
+ })
409
+ ]
410
+ }), /* @__PURE__ */ jsx(AnimatePresence, {
411
+ initial: false,
412
+ children: isExpanded && hasSteps && /* @__PURE__ */ jsx(motion.div, {
413
+ initial: {
414
+ height: 0,
415
+ opacity: 0
416
+ },
417
+ animate: {
418
+ height: "auto",
419
+ opacity: 1
420
+ },
421
+ exit: {
422
+ height: 0,
423
+ opacity: 0
424
+ },
425
+ transition: {
426
+ duration: .2,
427
+ ease: [
428
+ .25,
429
+ .1,
430
+ .25,
431
+ 1
432
+ ]
433
+ },
434
+ className: "overflow-hidden",
435
+ children: /* @__PURE__ */ jsx("div", {
436
+ className: "ml-[38px] border-l-2 border-[var(--theme-border)]",
437
+ children: job.steps.map((step, index) => {
438
+ const stepKey = `${jobName}:${index}`;
439
+ return /* @__PURE__ */ jsx(StepRow, {
440
+ step,
441
+ index,
442
+ isExpanded: expandedSteps.has(stepKey),
443
+ onToggle: () => onStepToggle(stepKey),
444
+ workflowName,
445
+ runNumber,
446
+ slug,
447
+ jobName,
448
+ expandedJobs,
449
+ onJobToggle,
450
+ expandedSteps,
451
+ onStepToggle
452
+ }, index);
453
+ })
454
+ })
455
+ })
456
+ })]
457
+ });
458
+ }
459
+ function StepRow({ step, index, isExpanded, onToggle, workflowName, runNumber, slug, jobName, expandedJobs, onJobToggle, expandedSteps, onStepToggle, depth = 0 }) {
460
+ const slots = useSlots();
461
+ const duration = getStepDuration(step);
462
+ const label = getStepLabel(step, index);
463
+ const isSubworkflow = !!step.subworkflow;
464
+ const subworkflowJobs = isSubworkflow ? step.subworkflow.jobs : null;
465
+ const subworkflowJobNames = subworkflowJobs ? Object.keys(subworkflowJobs).filter((name) => subworkflowJobs[name].if !== false) : [];
466
+ const subworkflowDependenciesMap = useMemo(() => {
467
+ if (!subworkflowJobs) return {};
468
+ const map = {};
469
+ for (const subJobName of subworkflowJobNames) {
470
+ const job = subworkflowJobs[subJobName];
471
+ if (job.needs && Array.isArray(job.needs) && job.needs.length > 0) map[subJobName] = job.needs;
472
+ }
473
+ return map;
474
+ }, [subworkflowJobs, subworkflowJobNames]);
475
+ return /* @__PURE__ */ jsxs("div", { children: [
476
+ /* @__PURE__ */ jsxs("div", {
477
+ className: cx("flex items-center gap-3 px-5 py-2.5 cursor-pointer transition-colors", isExpanded ? "" : "hover:bg-[var(--theme-hover)]"),
478
+ onClick: onToggle,
479
+ children: [
480
+ /* @__PURE__ */ jsx(motion.div, {
481
+ animate: { rotate: isExpanded ? 90 : 0 },
482
+ transition: { duration: .15 },
483
+ className: "flex-shrink-0 text-[var(--theme-muted-text-color)]",
484
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: cx("w-4 h-4", isExpanded && "text-[var(--theme-element)]") })
485
+ }),
486
+ /* @__PURE__ */ jsx(Indicator, { status: step.status }),
487
+ isSubworkflow && slots.workflowIcon?.({
488
+ image: step.subworkflow.image,
489
+ type: step.subworkflow.type,
490
+ className: "h-4 w-4 flex-shrink-0"
491
+ }),
492
+ /* @__PURE__ */ jsxs("span", {
493
+ className: "font-mono text-sm text-[var(--theme-app)] truncate flex-1",
494
+ children: [label, isSubworkflow && /* @__PURE__ */ jsx("span", {
495
+ className: "ml-2 text-xs text-[var(--theme-muted-text-color)]",
496
+ children: "(subworkflow)"
497
+ })]
498
+ }),
499
+ duration && /* @__PURE__ */ jsx("span", {
500
+ className: "font-mono text-xs text-[var(--theme-muted-text-color)] flex-shrink-0",
501
+ children: duration
502
+ })
503
+ ]
504
+ }),
505
+ /* @__PURE__ */ jsx(AnimatePresence, {
506
+ initial: false,
507
+ children: isExpanded && !isSubworkflow && /* @__PURE__ */ jsx(motion.div, {
508
+ initial: {
509
+ height: 0,
510
+ opacity: 0
511
+ },
512
+ animate: {
513
+ height: "auto",
514
+ opacity: 1
515
+ },
516
+ exit: {
517
+ height: 0,
518
+ opacity: 0
519
+ },
520
+ transition: {
521
+ duration: .2,
522
+ ease: [
523
+ .25,
524
+ .1,
525
+ .25,
526
+ 1
527
+ ]
528
+ },
529
+ className: "overflow-hidden",
530
+ children: /* @__PURE__ */ jsx(InlineLogViewer, {
531
+ slug,
532
+ jobName,
533
+ stepIndex: index
534
+ })
535
+ })
536
+ }),
537
+ /* @__PURE__ */ jsx(AnimatePresence, {
538
+ initial: false,
539
+ children: isExpanded && isSubworkflow && subworkflowJobNames.length > 0 && /* @__PURE__ */ jsx(motion.div, {
540
+ initial: {
541
+ height: 0,
542
+ opacity: 0
543
+ },
544
+ animate: {
545
+ height: "auto",
546
+ opacity: 1
547
+ },
548
+ exit: {
549
+ height: 0,
550
+ opacity: 0
551
+ },
552
+ transition: {
553
+ duration: .2,
554
+ ease: [
555
+ .25,
556
+ .1,
557
+ .25,
558
+ 1
559
+ ]
560
+ },
561
+ className: "overflow-hidden",
562
+ children: /* @__PURE__ */ jsx("div", {
563
+ className: "ml-[38px] border-l-2 border-[var(--theme-border)]",
564
+ children: subworkflowJobNames.map((subJobName) => {
565
+ const subJobKey = `${jobName}:${index}:${subJobName}`;
566
+ const isSubJobExpanded = expandedJobs.has(subJobKey);
567
+ return /* @__PURE__ */ jsx(SubworkflowJobRow, {
568
+ jobName: subJobName,
569
+ job: subworkflowJobs[subJobName],
570
+ isExpanded: isSubJobExpanded,
571
+ onToggle: () => onJobToggle(subJobKey),
572
+ dependencies: subworkflowDependenciesMap[subJobName],
573
+ slug,
574
+ parentJobName: jobName,
575
+ parentStepIndex: index,
576
+ expandedJobs,
577
+ onJobToggle,
578
+ expandedSteps,
579
+ onStepToggle,
580
+ depth: depth + 1
581
+ }, subJobName);
582
+ })
583
+ })
584
+ })
585
+ })
586
+ ] });
587
+ }
588
+ function SubworkflowJobRow({ jobName, job, isExpanded, onToggle, dependencies, slug, parentJobName, parentStepIndex, subworkflowPrefix, expandedJobs, onJobToggle, expandedSteps, onStepToggle, depth }) {
589
+ const hasSteps = job.steps && job.steps.length > 0;
590
+ const duration = getJobDuration(job);
591
+ const nestedSubworkflowPrefix = `${subworkflowPrefix || ""}subworkflows/${parentJobName}/step_${parentStepIndex}/`;
592
+ return /* @__PURE__ */ jsxs("div", {
593
+ className: "border-b border-[var(--theme-border)] last:border-b-0",
594
+ children: [/* @__PURE__ */ jsxs("div", {
595
+ className: cx("flex items-center gap-3 px-5 py-3 transition-colors", job.status === "skipped" || job.status === "skipped-failed" ? "cursor-default" : "cursor-pointer", isExpanded && "bg-[var(--theme-panel-bg)]"),
596
+ onClick: onToggle,
597
+ children: [
598
+ /* @__PURE__ */ jsx(motion.div, {
599
+ animate: { rotate: isExpanded ? 90 : 0 },
600
+ transition: { duration: .15 },
601
+ className: cx("flex-shrink-0", hasSteps ? "text-[var(--theme-muted-text-color)]" : "invisible"),
602
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: cx("w-4 h-4", isExpanded && "text-[var(--theme-element)]") })
603
+ }),
604
+ /* @__PURE__ */ jsx(Indicator, { status: job.status }),
605
+ /* @__PURE__ */ jsxs("div", {
606
+ className: "flex flex-col gap-1 min-w-0 flex-1",
607
+ children: [/* @__PURE__ */ jsx("span", {
608
+ className: "font-mono text-sm text-[var(--theme-app)] truncate",
609
+ children: formatJobLabel(jobName, job)
610
+ }), dependencies && dependencies.length > 0 && /* @__PURE__ */ jsxs("div", {
611
+ className: "flex items-center gap-1.5",
612
+ children: [/* @__PURE__ */ jsx("span", {
613
+ className: "text-[10px] text-[var(--theme-muted-text-color)]",
614
+ children: "depends on:"
615
+ }), dependencies.map((dep) => /* @__PURE__ */ jsx("span", {
616
+ className: "text-[10px] px-1.5 py-0.5 rounded bg-[var(--theme-muted-panel-bg)] text-[var(--theme-muted-text-color)]",
617
+ children: formatJobLabel(dep)
618
+ }, dep))]
619
+ })]
620
+ }),
621
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
622
+ duration && /* @__PURE__ */ jsx("span", {
623
+ className: "font-mono text-xs text-[var(--theme-muted-text-color)] flex-shrink-0",
624
+ children: duration
625
+ })
626
+ ]
627
+ }), /* @__PURE__ */ jsx(AnimatePresence, {
628
+ initial: false,
629
+ children: isExpanded && hasSteps && /* @__PURE__ */ jsx(motion.div, {
630
+ initial: {
631
+ height: 0,
632
+ opacity: 0
633
+ },
634
+ animate: {
635
+ height: "auto",
636
+ opacity: 1
637
+ },
638
+ exit: {
639
+ height: 0,
640
+ opacity: 0
641
+ },
642
+ transition: {
643
+ duration: .2,
644
+ ease: [
645
+ .25,
646
+ .1,
647
+ .25,
648
+ 1
649
+ ]
650
+ },
651
+ className: "overflow-hidden",
652
+ children: /* @__PURE__ */ jsx("div", {
653
+ className: "ml-[38px] border-l-2 border-[var(--theme-border)]",
654
+ children: job.steps.map((step, index) => {
655
+ const stepKey = `${nestedSubworkflowPrefix}${jobName}:${index}`;
656
+ return /* @__PURE__ */ jsx(SubworkflowStepRow, {
657
+ step,
658
+ index,
659
+ isExpanded: expandedSteps.has(stepKey),
660
+ onToggle: () => onStepToggle(stepKey),
661
+ slug,
662
+ subworkflowPrefix: nestedSubworkflowPrefix,
663
+ jobName,
664
+ expandedJobs,
665
+ onJobToggle,
666
+ expandedSteps,
667
+ onStepToggle,
668
+ depth
669
+ }, index);
670
+ })
671
+ })
672
+ })
673
+ })]
674
+ });
675
+ }
676
+ function SubworkflowStepRow({ step, index, isExpanded, onToggle, slug, subworkflowPrefix, jobName, expandedJobs, onJobToggle, expandedSteps, onStepToggle, depth }) {
677
+ const slots = useSlots();
678
+ const duration = getStepDuration(step);
679
+ const label = getStepLabel(step, index);
680
+ const isSubworkflow = !!step.subworkflow;
681
+ const subworkflowJobs = isSubworkflow ? step.subworkflow.jobs : null;
682
+ const subworkflowJobNames = subworkflowJobs ? Object.keys(subworkflowJobs).filter((name) => subworkflowJobs[name].if !== false) : [];
683
+ const subworkflowDependenciesMap = useMemo(() => {
684
+ if (!subworkflowJobs) return {};
685
+ const map = {};
686
+ for (const subJobName of subworkflowJobNames) {
687
+ const job = subworkflowJobs[subJobName];
688
+ if (job.needs && Array.isArray(job.needs) && job.needs.length > 0) map[subJobName] = job.needs;
689
+ }
690
+ return map;
691
+ }, [subworkflowJobs, subworkflowJobNames]);
692
+ return /* @__PURE__ */ jsxs("div", { children: [
693
+ /* @__PURE__ */ jsxs("div", {
694
+ className: cx("flex items-center gap-3 px-5 py-2.5 cursor-pointer transition-colors", isExpanded ? "" : "hover:bg-[var(--theme-hover)]"),
695
+ onClick: onToggle,
696
+ children: [
697
+ /* @__PURE__ */ jsx(motion.div, {
698
+ animate: { rotate: isExpanded ? 90 : 0 },
699
+ transition: { duration: .15 },
700
+ className: "flex-shrink-0 text-[var(--theme-muted-text-color)]",
701
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: cx("w-4 h-4", isExpanded && "text-[var(--theme-element)]") })
702
+ }),
703
+ /* @__PURE__ */ jsx(Indicator, { status: step.status }),
704
+ isSubworkflow && slots.workflowIcon?.({
705
+ image: step.subworkflow.image,
706
+ type: step.subworkflow.type,
707
+ className: "h-4 w-4 flex-shrink-0"
708
+ }),
709
+ /* @__PURE__ */ jsxs("span", {
710
+ className: "font-mono text-sm text-[var(--theme-app)] truncate flex-1",
711
+ children: [label, isSubworkflow && /* @__PURE__ */ jsx("span", {
712
+ className: "ml-2 text-xs text-[var(--theme-muted-text-color)]",
713
+ children: "(subworkflow)"
714
+ })]
715
+ }),
716
+ duration && /* @__PURE__ */ jsx("span", {
717
+ className: "font-mono text-xs text-[var(--theme-muted-text-color)] flex-shrink-0",
718
+ children: duration
719
+ })
720
+ ]
721
+ }),
722
+ /* @__PURE__ */ jsx(AnimatePresence, {
723
+ initial: false,
724
+ children: isExpanded && !isSubworkflow && /* @__PURE__ */ jsx(motion.div, {
725
+ initial: {
726
+ height: 0,
727
+ opacity: 0
728
+ },
729
+ animate: {
730
+ height: "auto",
731
+ opacity: 1
732
+ },
733
+ exit: {
734
+ height: 0,
735
+ opacity: 0
736
+ },
737
+ transition: {
738
+ duration: .2,
739
+ ease: [
740
+ .25,
741
+ .1,
742
+ .25,
743
+ 1
744
+ ]
745
+ },
746
+ className: "overflow-hidden",
747
+ children: /* @__PURE__ */ jsx(SubworkflowInlineLogViewer, {
748
+ slug,
749
+ subworkflowPrefix,
750
+ jobName,
751
+ stepIndex: index
752
+ })
753
+ })
754
+ }),
755
+ /* @__PURE__ */ jsx(AnimatePresence, {
756
+ initial: false,
757
+ children: isExpanded && isSubworkflow && subworkflowJobNames.length > 0 && /* @__PURE__ */ jsx(motion.div, {
758
+ initial: {
759
+ height: 0,
760
+ opacity: 0
761
+ },
762
+ animate: {
763
+ height: "auto",
764
+ opacity: 1
765
+ },
766
+ exit: {
767
+ height: 0,
768
+ opacity: 0
769
+ },
770
+ transition: {
771
+ duration: .2,
772
+ ease: [
773
+ .25,
774
+ .1,
775
+ .25,
776
+ 1
777
+ ]
778
+ },
779
+ className: "overflow-hidden",
780
+ children: /* @__PURE__ */ jsx("div", {
781
+ className: "ml-[38px] border-l-2 border-[var(--theme-border)]",
782
+ children: subworkflowJobNames.map((subJobName) => {
783
+ const subJobKey = `${subworkflowPrefix}${jobName}:${index}:${subJobName}`;
784
+ const isSubJobExpanded = expandedJobs.has(subJobKey);
785
+ return /* @__PURE__ */ jsx(SubworkflowJobRow, {
786
+ jobName: subJobName,
787
+ job: subworkflowJobs[subJobName],
788
+ isExpanded: isSubJobExpanded,
789
+ onToggle: () => onJobToggle(subJobKey),
790
+ dependencies: subworkflowDependenciesMap[subJobName],
791
+ slug,
792
+ parentJobName: jobName,
793
+ parentStepIndex: index,
794
+ subworkflowPrefix,
795
+ expandedJobs,
796
+ onJobToggle,
797
+ expandedSteps,
798
+ onStepToggle,
799
+ depth: depth + 1
800
+ }, subJobName);
801
+ })
802
+ })
803
+ })
804
+ })
805
+ ] });
806
+ }
807
+ function SubworkflowInlineLogViewer({ slug, subworkflowPrefix, jobName, stepIndex }) {
808
+ const { data: logs, isLoading, error } = useRunFile(slug, buildSubworkflowLogPath(subworkflowPrefix, jobName, stepIndex), { refreshInterval: 2e3 });
809
+ if (isLoading && !logs) return /* @__PURE__ */ jsxs("div", {
810
+ className: "flex items-center gap-2 px-5 py-4 text-sm text-[var(--theme-muted-text-color)]",
811
+ children: [/* @__PURE__ */ jsx(LoaderIcon, { className: "w-4 h-4" }), /* @__PURE__ */ jsx("span", { children: "Loading logs..." })]
812
+ });
813
+ if (error || !logs) return /* @__PURE__ */ jsx("div", {
814
+ className: "px-5 py-4 text-sm text-[var(--theme-muted-text-color)] italic",
815
+ children: "No logs available"
816
+ });
817
+ return /* @__PURE__ */ jsx("div", {
818
+ className: "border-t border-[var(--theme-border)]",
819
+ children: /* @__PURE__ */ jsx(LogViewer, {
820
+ log: logs,
821
+ width: "100%",
822
+ height: "unset",
823
+ inline: true,
824
+ contentClassName: "bg-[var(--theme-app-bg)]",
825
+ enableWorkflowCommands: true
826
+ })
827
+ });
828
+ }
829
+ function TreeView({ jobs, workflowName, runNumber, slug, expandedJobs, setExpandedJobs }) {
830
+ const visibleJobNames = useMemo(() => {
831
+ return Object.keys(jobs).filter((jobName) => jobs[jobName].if !== false);
832
+ }, [jobs]);
833
+ const [expandedSteps, setExpandedSteps] = useState(/* @__PURE__ */ new Set());
834
+ const [expandedSubworkflowJobs, setExpandedSubworkflowJobs] = useState(/* @__PURE__ */ new Set());
835
+ const toggleJob = useCallback((jobName) => {
836
+ const newExpanded = new Set(expandedJobs);
837
+ if (newExpanded.has(jobName)) newExpanded.delete(jobName);
838
+ else {
839
+ const status = jobs[jobName]?.status;
840
+ if (status === "skipped" || status === "skipped-failed") return;
841
+ newExpanded.add(jobName);
842
+ }
843
+ setExpandedJobs(newExpanded);
844
+ }, [
845
+ expandedJobs,
846
+ setExpandedJobs,
847
+ jobs
848
+ ]);
849
+ const toggleSubworkflowJob = useCallback((jobKey) => {
850
+ setExpandedSubworkflowJobs((prev) => {
851
+ const newSet = new Set(prev);
852
+ if (newSet.has(jobKey)) newSet.delete(jobKey);
853
+ else newSet.add(jobKey);
854
+ return newSet;
855
+ });
856
+ }, []);
857
+ const toggleStep = useCallback((stepKey) => {
858
+ setExpandedSteps((prev) => {
859
+ const newSet = new Set(prev);
860
+ if (newSet.has(stepKey)) newSet.delete(stepKey);
861
+ else newSet.add(stepKey);
862
+ return newSet;
863
+ });
864
+ }, []);
865
+ const expandAll = useCallback(() => {
866
+ setExpandedJobs(new Set(visibleJobNames));
867
+ }, [visibleJobNames, setExpandedJobs]);
868
+ const collapseAll = useCallback(() => {
869
+ setExpandedJobs(/* @__PURE__ */ new Set());
870
+ setExpandedSteps(/* @__PURE__ */ new Set());
871
+ setExpandedSubworkflowJobs(/* @__PURE__ */ new Set());
872
+ }, [setExpandedJobs]);
873
+ const allExpanded = expandedJobs.size === visibleJobNames.length;
874
+ const dependenciesMap = useMemo(() => {
875
+ const map = {};
876
+ for (const jobName of visibleJobNames) {
877
+ const job = jobs[jobName];
878
+ if (job.needs && Array.isArray(job.needs) && job.needs.length > 0) map[jobName] = job.needs;
879
+ }
880
+ return map;
881
+ }, [jobs, visibleJobNames]);
882
+ return /* @__PURE__ */ jsxs("div", {
883
+ className: "flex flex-col",
884
+ children: [/* @__PURE__ */ jsx("div", {
885
+ className: "flex justify-end px-4 py-2 border-b border-[var(--theme-border)]",
886
+ children: /* @__PURE__ */ jsx("button", {
887
+ type: "button",
888
+ onClick: allExpanded ? collapseAll : expandAll,
889
+ className: "text-xs font-mono text-[var(--theme-muted-text-color)] hover:text-[var(--theme-app)] transition-colors px-3 py-1.5 border border-[var(--theme-border)] rounded",
890
+ children: allExpanded ? "Collapse all" : "Expand all"
891
+ })
892
+ }), /* @__PURE__ */ jsx("div", { children: (() => {
893
+ const matrixGroups = {};
894
+ for (const jobName of visibleJobNames) {
895
+ const matrix = jobs[jobName]?._matrix;
896
+ if (matrix?.originaljob) {
897
+ if (!matrixGroups[matrix.originaljob]) matrixGroups[matrix.originaljob] = {
898
+ originaljob: matrix.originaljob,
899
+ members: []
900
+ };
901
+ matrixGroups[matrix.originaljob].members.push(jobName);
902
+ }
903
+ }
904
+ for (const group of Object.values(matrixGroups)) group.members.sort((a, b) => (jobs[a]?._matrix?.index ?? 0) - (jobs[b]?._matrix?.index ?? 0));
905
+ const rendered = [];
906
+ const seenMatrixGroups = /* @__PURE__ */ new Set();
907
+ for (const jobName of visibleJobNames) {
908
+ const matrixOrigin = jobs[jobName]?._matrix?.originaljob;
909
+ if (matrixOrigin) {
910
+ if (seenMatrixGroups.has(matrixOrigin)) continue;
911
+ seenMatrixGroups.add(matrixOrigin);
912
+ const group = matrixGroups[matrixOrigin];
913
+ const isGroupExpanded = expandedJobs.has(`matrix:${matrixOrigin}`);
914
+ const { aggStatus } = computeMatrixAggStatus(group.members.map((m) => jobs[m]?.status));
915
+ rendered.push(/* @__PURE__ */ jsxs("div", {
916
+ className: "border-b border-[var(--theme-border)]",
917
+ children: [/* @__PURE__ */ jsxs("div", {
918
+ className: "flex items-center gap-3 px-5 py-3 cursor-pointer transition-colors",
919
+ onClick: () => toggleJob(`matrix:${matrixOrigin}`),
920
+ children: [
921
+ /* @__PURE__ */ jsx(motion.div, {
922
+ animate: { rotate: isGroupExpanded ? 90 : 0 },
923
+ transition: { duration: .15 },
924
+ className: "flex-shrink-0 text-[var(--theme-muted-text-color)]",
925
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: cx("w-4 h-4", isGroupExpanded && "text-[var(--theme-element)]") })
926
+ }),
927
+ /* @__PURE__ */ jsx(Indicator, { status: aggStatus }),
928
+ /* @__PURE__ */ jsxs("span", {
929
+ className: "font-mono text-sm text-[var(--theme-app)]",
930
+ children: [
931
+ "Matrix: ",
932
+ matrixOrigin,
933
+ " (",
934
+ group.members.length,
935
+ " jobs)"
936
+ ]
937
+ })
938
+ ]
939
+ }), /* @__PURE__ */ jsx(AnimatePresence, { children: isGroupExpanded && /* @__PURE__ */ jsx(motion.div, {
940
+ initial: {
941
+ height: 0,
942
+ opacity: 0
943
+ },
944
+ animate: {
945
+ height: "auto",
946
+ opacity: 1
947
+ },
948
+ exit: {
949
+ height: 0,
950
+ opacity: 0
951
+ },
952
+ transition: { duration: .15 },
953
+ className: "overflow-hidden pl-4",
954
+ children: group.members.map((memberName) => /* @__PURE__ */ jsx(JobRow, {
955
+ jobName: memberName,
956
+ job: jobs[memberName],
957
+ isExpanded: expandedJobs.has(memberName),
958
+ onToggle: () => toggleJob(memberName),
959
+ dependencies: dependenciesMap[memberName],
960
+ workflowName,
961
+ runNumber,
962
+ slug,
963
+ expandedJobs: expandedSubworkflowJobs,
964
+ onJobToggle: toggleSubworkflowJob,
965
+ expandedSteps,
966
+ onStepToggle: toggleStep
967
+ }, memberName))
968
+ }) })]
969
+ }, `matrix-${matrixOrigin}`));
970
+ } else rendered.push(/* @__PURE__ */ jsx(JobRow, {
971
+ jobName,
972
+ job: jobs[jobName],
973
+ isExpanded: expandedJobs.has(jobName),
974
+ onToggle: () => toggleJob(jobName),
975
+ dependencies: dependenciesMap[jobName],
976
+ workflowName,
977
+ runNumber,
978
+ slug,
979
+ expandedJobs: expandedSubworkflowJobs,
980
+ onJobToggle: toggleSubworkflowJob,
981
+ expandedSteps,
982
+ onStepToggle: toggleStep
983
+ }, jobName));
984
+ }
985
+ return rendered;
986
+ })() })]
987
+ });
988
+ }
989
+ //#endregion
990
+ //#region src/workflow/MatrixGroup.tsx
991
+ /** Matrix group item in the summary sidebar. */
992
+ function MatrixGroupSummaryItem({ matrixGroup, status, isExpanded, onToggle, jobs, isStepsOpen, onJobClick, onStepClick, onStepLogClick, onSubworkflowIcon, isSubworkflowExpanded, renderSubworkflow, preview }) {
993
+ return /* @__PURE__ */ jsxs("div", {
994
+ className: "py-1.5",
995
+ children: [/* @__PURE__ */ jsxs("div", {
996
+ onClick: onToggle,
997
+ className: "cursor-pointer flex items-center gap-x-1.5",
998
+ children: [/* @__PURE__ */ jsx(Indicator, { status }), /* @__PURE__ */ jsxs("div", {
999
+ className: "font-semibold",
1000
+ children: [
1001
+ "Matrix: ",
1002
+ matrixGroup.originaljob,
1003
+ /* @__PURE__ */ jsxs("span", {
1004
+ className: "font-normal text-sm ml-1",
1005
+ children: [
1006
+ "(",
1007
+ matrixGroup.members.length,
1008
+ " jobs)"
1009
+ ]
1010
+ })
1011
+ ]
1012
+ })]
1013
+ }), /* @__PURE__ */ jsx(Reveal, {
1014
+ open: isExpanded,
1015
+ children: /* @__PURE__ */ jsx("div", {
1016
+ className: "ml-3 mt-0.5",
1017
+ children: /* @__PURE__ */ jsx(Joblist, {
1018
+ jobs,
1019
+ jobNames: matrixGroup.members,
1020
+ onJobClick,
1021
+ isStepsOpen,
1022
+ onStepClick,
1023
+ onStepLogClick,
1024
+ onSubworkflowIcon,
1025
+ isSubworkflowExpanded,
1026
+ renderSubworkflow,
1027
+ preview
1028
+ })
1029
+ })
1030
+ })]
1031
+ }, "matrix-" + matrixGroup.originaljob);
1032
+ }
1033
+ /** Matrix group node in the DAG visualization. */
1034
+ function MatrixGroupNode({ matrixGroup, jobNames, idPrefix, isExpanded, onToggle, jobs, activeDists, onMouseEnter, onMouseLeave, hoveredRelated, animT, zBase, isStepsOpen, onJobClick, onStepClick, onStepLogClick, onSubworkflowIcon, isSubworkflowExpanded, renderSubworkflow, preview }) {
1035
+ const matrixName = matrixGroup.originaljob;
1036
+ const matrixMembers = matrixGroup.members;
1037
+ const { aggStatus, statusLabel } = computeMatrixAggStatus(matrixMembers.map((m) => jobs[m]?.status));
1038
+ return /* @__PURE__ */ jsxs("div", {
1039
+ id: "node_" + idPrefix + jobNames[0],
1040
+ className: "relative m-24",
1041
+ style: { zIndex: (activeDists?.has(jobNames[0]) ? 25 : 1) + zBase },
1042
+ onMouseEnter,
1043
+ onMouseLeave,
1044
+ children: [/* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-2xl bg-(--theme-panel-bg)" }), /* @__PURE__ */ jsxs("div", {
1045
+ className: "relative border-solid shadow py-4 px-8 rounded-xl border-4 whitespace-nowrap bg-(--theme-panel-bg) text-2xl",
1046
+ style: {
1047
+ opacity: hoveredRelated && !hoveredRelated.has(jobNames[0]) ? .5 : 1,
1048
+ transition: `opacity ${animT}s`
1049
+ },
1050
+ children: [/* @__PURE__ */ jsxs("div", {
1051
+ className: "cursor-pointer",
1052
+ onClick: onToggle,
1053
+ children: [/* @__PURE__ */ jsxs("div", {
1054
+ className: "font-semibold text-md p-0.5",
1055
+ children: [
1056
+ "Matrix:",
1057
+ " ",
1058
+ matrixName.length > 20 ? matrixName.slice(0, 20) + "..." : matrixName
1059
+ ]
1060
+ }), !isExpanded && /* @__PURE__ */ jsxs("div", {
1061
+ className: "mt-1 border-t border-(--theme-border) pt-1 flex items-center gap-x-1",
1062
+ children: [/* @__PURE__ */ jsx(Indicator, { status: aggStatus }), /* @__PURE__ */ jsx("span", { children: statusLabel })]
1063
+ })]
1064
+ }), /* @__PURE__ */ jsx(Reveal, {
1065
+ open: isExpanded,
1066
+ growWidth: true,
1067
+ children: /* @__PURE__ */ jsx("div", {
1068
+ className: "mt-1 border-t border-(--theme-border) pt-1",
1069
+ children: /* @__PURE__ */ jsx(Joblist, {
1070
+ jobs,
1071
+ jobNames: matrixMembers,
1072
+ onJobClick,
1073
+ isStepsOpen,
1074
+ onStepClick,
1075
+ onStepLogClick,
1076
+ onSubworkflowIcon,
1077
+ isSubworkflowExpanded,
1078
+ renderSubworkflow,
1079
+ growWidth: true,
1080
+ preview
1081
+ })
1082
+ })
1083
+ })]
1084
+ })]
1085
+ }, "node_" + jobNames[0]);
1086
+ }
1087
+ //#endregion
1088
+ //#region src/workflow/DependencyGraph.tsx
1089
+ function ViewToggle({ viewMode, setViewMode }) {
1090
+ return /* @__PURE__ */ jsxs("div", {
1091
+ className: "flex border border-[var(--theme-border)] rounded overflow-hidden",
1092
+ children: [/* @__PURE__ */ jsx("button", {
1093
+ type: "button",
1094
+ onClick: () => setViewMode("dag"),
1095
+ className: cx("px-3 py-1.5 text-xs font-mono transition-colors", viewMode === "dag" ? "bg-[var(--theme-element)] text-[var(--theme-element-text)]" : "text-[var(--theme-muted-text-color)] hover:text-[var(--theme-app)] hover:bg-[var(--theme-hover)]"),
1096
+ children: "DAG"
1097
+ }), /* @__PURE__ */ jsx("button", {
1098
+ type: "button",
1099
+ onClick: () => setViewMode("tree"),
1100
+ className: cx("px-3 py-1.5 text-xs font-mono transition-colors", viewMode === "tree" ? "bg-[var(--theme-element)] text-[var(--theme-element-text)]" : "text-[var(--theme-muted-text-color)] hover:text-[var(--theme-app)] hover:bg-[var(--theme-hover)]"),
1101
+ children: "Tree"
1102
+ })]
1103
+ });
1104
+ }
1105
+ var addCleanupSteps = ({ jobs }) => {
1106
+ const clonedJobs = structuredClone(jobs);
1107
+ for (const job in jobs) {
1108
+ if (Array.isArray(jobs[job].steps)) jobs[job].steps.slice().reverse().forEach((step, index) => {
1109
+ if (!step.cleanup) return;
1110
+ const linkedName = step.name ? step.name : `Step ${jobs[job].steps.length - index - 1}`;
1111
+ const newStep = {
1112
+ ...step,
1113
+ name: `POST ${linkedName}`,
1114
+ run: step.cleanup,
1115
+ linkedStep: `${jobs[job].steps.length - index - 1}`
1116
+ };
1117
+ delete newStep.cleanup;
1118
+ delete clonedJobs[job].steps[index].cleanup;
1119
+ clonedJobs[job].steps.push(newStep);
1120
+ });
1121
+ if (Array.isArray(jobs[job].cleanup)) {
1122
+ jobs[job].cleanup.slice().reverse().forEach((step, index) => {
1123
+ if (!step.cleanup) return;
1124
+ const linkedName = step.name ? step.name : `Cleanup ${jobs[job].cleanup.length - index - 1}`;
1125
+ const newStep = {
1126
+ ...step,
1127
+ name: `POST ${linkedName}`,
1128
+ run: step.cleanup,
1129
+ linkedStep: `${jobs[job].cleanup.length - index - 1 + clonedJobs[job].steps.length}`
1130
+ };
1131
+ delete newStep.cleanup;
1132
+ delete clonedJobs[job].cleanup[index].cleanup;
1133
+ clonedJobs[job].cleanup.push(newStep);
1134
+ });
1135
+ for (let i = 0; i < clonedJobs[job].cleanup.length; i++) clonedJobs[job].cleanup[i].linkedStep = ".";
1136
+ clonedJobs[job].steps = [...clonedJobs[job].steps, ...clonedJobs[job].cleanup];
1137
+ delete clonedJobs[job].cleanup;
1138
+ }
1139
+ }
1140
+ return clonedJobs;
1141
+ };
1142
+ function offsetWithin(el, ancestor) {
1143
+ let x = 0;
1144
+ let y = 0;
1145
+ let cur = el;
1146
+ while (cur && cur !== ancestor) {
1147
+ x += cur.offsetLeft;
1148
+ y += cur.offsetTop;
1149
+ cur = cur.offsetParent;
1150
+ }
1151
+ return {
1152
+ x,
1153
+ y
1154
+ };
1155
+ }
1156
+ function parsePrefixToPath(prefix) {
1157
+ const parts = prefix.split("/").filter(Boolean);
1158
+ const path = [];
1159
+ for (let i = 0; i + 2 < parts.length; i += 3) if (parts[i] === "subworkflows" && parts[i + 1] && parts[i + 2]?.startsWith("step_")) path.push(parts[i + 1], "steps", Number(parts[i + 2].slice(5)), "subworkflow", "jobs");
1160
+ return path;
1161
+ }
1162
+ function computeGraphLayout(jobs, matrixGroups) {
1163
+ const visibleJobs = structuredClone(jobs);
1164
+ for (const jobName of Object.keys(jobs ?? {})) if (jobs[jobName].if === false) delete visibleJobs[jobName];
1165
+ collapseMatrixGroupsInLayout(matrixGroups, visibleJobs);
1166
+ const allDeps = {};
1167
+ for (const jobName of Object.keys(visibleJobs)) allDeps[jobName] = getAllDeps(jobName, visibleJobs, allDeps);
1168
+ const filteredDeps = {};
1169
+ for (const jobName of Object.keys(visibleJobs)) {
1170
+ const oldNeeds = visibleJobs[jobName]?.needs && Array.isArray(visibleJobs[jobName].needs) ? visibleJobs[jobName].needs : [];
1171
+ filteredDeps[jobName] = oldNeeds.filter((dep) => {
1172
+ return !oldNeeds.some((other) => {
1173
+ if (other === dep) return false;
1174
+ return allDeps[other].has(dep);
1175
+ });
1176
+ }).sort();
1177
+ }
1178
+ const depended_on = {};
1179
+ Object.keys(visibleJobs).map((jobName) => {
1180
+ if (filteredDeps[jobName] && Array.isArray(filteredDeps[jobName])) filteredDeps[jobName].forEach((dep) => {
1181
+ if (depended_on[dep]) depended_on[dep].push(jobName);
1182
+ else depended_on[dep] = [jobName];
1183
+ });
1184
+ });
1185
+ Object.keys(visibleJobs).map((jobName) => {
1186
+ if (depended_on[jobName]) depended_on[jobName].sort();
1187
+ });
1188
+ const unfinished = structuredClone(visibleJobs);
1189
+ const deps = [[]];
1190
+ Object.keys(visibleJobs).map((jobName) => {
1191
+ if (filteredDeps[jobName].length === 0) {
1192
+ deps[0].push(jobName);
1193
+ delete unfinished[jobName];
1194
+ }
1195
+ });
1196
+ let prevLen;
1197
+ do {
1198
+ const toAppend = [];
1199
+ const unfinishedArr = Object.keys(unfinished);
1200
+ prevLen = unfinishedArr.length;
1201
+ unfinishedArr.map((jobName) => {
1202
+ if (filteredDeps[jobName].every((dep) => !unfinishedArr.includes(dep))) {
1203
+ toAppend.push(jobName);
1204
+ delete unfinished[jobName];
1205
+ }
1206
+ });
1207
+ if (toAppend.length > 0) deps.push(toAppend);
1208
+ } while (prevLen !== Object.keys(unfinished).length);
1209
+ const finalDeps = [];
1210
+ deps.map((col) => {
1211
+ const toAppend = [];
1212
+ while (col.length > 0) {
1213
+ const grouped = [col[0]];
1214
+ const toRm = [0];
1215
+ col.slice(1).map((jobName, j) => {
1216
+ if (visibleJobs[col[0]]?._matrixGroup || visibleJobs[jobName]?._matrixGroup) return;
1217
+ const depsA = depended_on[col[0]];
1218
+ const depsB = depended_on[jobName];
1219
+ const depsA2 = filteredDeps[col[0]];
1220
+ const depsB2 = filteredDeps[jobName];
1221
+ if (!depsA && !depsB || depsA && depsB && depsA.length === depsB.length) {
1222
+ if (!depsA2 && !depsB2 || depsA2 && depsB2 && depsA2.length === depsB2.length) {
1223
+ if ((!depsA || depsA.every((dep, i) => dep === depsB[i])) && (!depsA2 || depsA2.every((dep, i) => dep === depsB2[i]))) {
1224
+ grouped.push(jobName);
1225
+ toRm.push(j + 1);
1226
+ }
1227
+ }
1228
+ }
1229
+ });
1230
+ col.splice(0, col.length, ...col.filter((_, i) => !toRm.includes(i)));
1231
+ toAppend.push(grouped);
1232
+ }
1233
+ finalDeps.push(toAppend);
1234
+ });
1235
+ while (finalDeps.length > 0 && (finalDeps[0].length === 0 || finalDeps[0][0].length === 0)) {
1236
+ if (finalDeps[0].length === 0) finalDeps.splice(0, 1);
1237
+ while (finalDeps.length > 0 && finalDeps[0].length > 0 && finalDeps[0][0].length === 0) finalDeps[0].splice(0, 1);
1238
+ }
1239
+ const ancestorDists = {};
1240
+ const descendantDists = {};
1241
+ const jobNames = Object.keys(visibleJobs);
1242
+ const reverseDeps = {};
1243
+ for (const [job, depList] of Object.entries(filteredDeps)) for (const dep of depList) {
1244
+ if (!reverseDeps[dep]) reverseDeps[dep] = [];
1245
+ reverseDeps[dep].push(job);
1246
+ }
1247
+ for (const start of jobNames) {
1248
+ const aDist = /* @__PURE__ */ new Map();
1249
+ const q1 = [[start, 0]];
1250
+ while (q1.length > 0) {
1251
+ const [job, d] = q1.shift();
1252
+ for (const dep of filteredDeps[job] ?? []) if (!aDist.has(dep)) {
1253
+ aDist.set(dep, d + 1);
1254
+ q1.push([dep, d + 1]);
1255
+ }
1256
+ }
1257
+ ancestorDists[start] = aDist;
1258
+ const dDist = /* @__PURE__ */ new Map();
1259
+ const q2 = [[start, 0]];
1260
+ while (q2.length > 0) {
1261
+ const [job, d] = q2.shift();
1262
+ for (const child of reverseDeps[job] ?? []) if (!dDist.has(child)) {
1263
+ dDist.set(child, d + 1);
1264
+ q2.push([child, d + 1]);
1265
+ }
1266
+ }
1267
+ descendantDists[start] = dDist;
1268
+ }
1269
+ return {
1270
+ dependencyCols: finalDeps,
1271
+ directDeps: filteredDeps,
1272
+ displayJobs: visibleJobs,
1273
+ ancestorDists,
1274
+ descendantDists
1275
+ };
1276
+ }
1277
+ function SubworkflowWell({ expanded, onSettled, className, children }) {
1278
+ return /* @__PURE__ */ jsx(Reveal, {
1279
+ open: expanded,
1280
+ growWidth: true,
1281
+ onSettled,
1282
+ children: /* @__PURE__ */ jsx("div", {
1283
+ className,
1284
+ style: { zoom: .8 },
1285
+ children
1286
+ })
1287
+ });
1288
+ }
1289
+ var SUBGRAPH_Z_STEP = 100;
1290
+ var FIT_ANIMATION_MS = 300;
1291
+ function Subgraph({ jobs, pathPrefix, depth, preview, animT, expandedSubworkflows, toggleSubworkflow, openInNewGraph, setSublogOpen, invalidateConnectors, requestFit, stepsOpen, toggleSteps, matrixOpen, toggleMatrix, onReady }) {
1292
+ const wrapperRef = useRef(null);
1293
+ const zBase = depth * SUBGRAPH_Z_STEP;
1294
+ const [hoveredJob, setHoveredJob] = useState(null);
1295
+ const lastHoveredDistancesRef = useRef(null);
1296
+ const maxHoveredDistRef = useRef(0);
1297
+ const retractPathLensRef = useRef(/* @__PURE__ */ new Map());
1298
+ const matrixGroups = useMemo(() => detectMatrixGroups(jobs), [jobs]);
1299
+ const { dependencyCols, directDeps, displayJobs, ancestorDists, descendantDists } = useMemo(() => computeGraphLayout(jobs, matrixGroups), [jobs, matrixGroups]);
1300
+ const hoveredDistances = useMemo(() => {
1301
+ if (!hoveredJob) return null;
1302
+ const dist = /* @__PURE__ */ new Map();
1303
+ dist.set(hoveredJob, 0);
1304
+ const ancestors = ancestorDists[hoveredJob];
1305
+ if (ancestors) for (const [k, v] of ancestors) dist.set(k, v);
1306
+ const descendants = descendantDists[hoveredJob];
1307
+ if (descendants) for (const [k, v] of descendants) dist.set(k, v);
1308
+ return dist;
1309
+ }, [
1310
+ hoveredJob,
1311
+ ancestorDists,
1312
+ descendantDists
1313
+ ]);
1314
+ if (hoveredDistances) {
1315
+ lastHoveredDistancesRef.current = hoveredDistances;
1316
+ maxHoveredDistRef.current = Math.max(...hoveredDistances.values());
1317
+ }
1318
+ const activeDists = hoveredDistances ?? lastHoveredDistancesRef.current;
1319
+ useEffect(() => {
1320
+ if (hoveredJob || !lastHoveredDistancesRef.current) return;
1321
+ const maxDelay = maxHoveredDistRef.current * animT + animT;
1322
+ const timer = setTimeout(() => {
1323
+ lastHoveredDistancesRef.current = null;
1324
+ maxHoveredDistRef.current = 0;
1325
+ invalidateConnectors();
1326
+ }, maxDelay * 1e3 + 200);
1327
+ return () => clearTimeout(timer);
1328
+ }, [
1329
+ hoveredJob,
1330
+ invalidateConnectors,
1331
+ animT
1332
+ ]);
1333
+ const hoveredRelated = useMemo(() => {
1334
+ if (!hoveredDistances) return null;
1335
+ return new Set(hoveredDistances.keys());
1336
+ }, [hoveredDistances]);
1337
+ useEffect(() => {
1338
+ const raf = requestAnimationFrame(() => {
1339
+ invalidateConnectors();
1340
+ onReady?.();
1341
+ });
1342
+ return () => cancelAnimationFrame(raf);
1343
+ }, [
1344
+ dependencyCols,
1345
+ invalidateConnectors,
1346
+ onReady
1347
+ ]);
1348
+ useEffect(() => {
1349
+ const el = wrapperRef.current;
1350
+ if (!el) return;
1351
+ const ro = new ResizeObserver(() => invalidateConnectors());
1352
+ ro.observe(el);
1353
+ return () => ro.disconnect();
1354
+ }, [invalidateConnectors]);
1355
+ const didRevealMountRef = useRef(false);
1356
+ useEffect(() => {
1357
+ if (!didRevealMountRef.current) {
1358
+ didRevealMountRef.current = true;
1359
+ return;
1360
+ }
1361
+ let raf = 0;
1362
+ const start = performance.now();
1363
+ const tick = () => {
1364
+ flushSync(() => invalidateConnectors());
1365
+ if (performance.now() - start < 260) raf = requestAnimationFrame(tick);
1366
+ };
1367
+ raf = requestAnimationFrame(tick);
1368
+ return () => cancelAnimationFrame(raf);
1369
+ }, [
1370
+ stepsOpen,
1371
+ matrixOpen,
1372
+ expandedSubworkflows,
1373
+ invalidateConnectors
1374
+ ]);
1375
+ const buildBoxLayer = useCallback(() => {
1376
+ const isStepsOpen = (j) => stepsOpen.has(pathPrefix + j);
1377
+ const onJobClick = (j) => {
1378
+ const status = jobs[j]?.status ?? displayJobs[j]?.status;
1379
+ if (!isStepsOpen(j) && (status === "skipped" || status === "skipped-failed")) return;
1380
+ toggleSteps(pathPrefix + j);
1381
+ };
1382
+ const expandInline = (j, s) => {
1383
+ toggleSubworkflow(`${pathPrefix}${j}:${s}`);
1384
+ requestAnimationFrame(invalidateConnectors);
1385
+ };
1386
+ const openNew = (j, s) => openInNewGraph(pathPrefix, j, s);
1387
+ const onStepClick = (j, s) => {
1388
+ const stepStatus = jobs[j]?.steps?.[+s]?.status;
1389
+ if (stepStatus === "skipped" || stepStatus === "skipped-failed") return;
1390
+ if (jobs[j].steps[+s].subworkflow) expandInline(j, +s);
1391
+ else setSublogOpen(buildSubworkflowLogPath(pathPrefix, j, s));
1392
+ };
1393
+ const onStepLogClick = (j, s) => setSublogOpen(buildSubworkflowLogPath(pathPrefix, j, s));
1394
+ const onSubworkflowIcon = (j, s) => openNew(j, s);
1395
+ const isSubworkflowExpanded = (j, s) => expandedSubworkflows.has(`${pathPrefix}${j}:${s}`);
1396
+ const renderSubworkflow = (j, s) => {
1397
+ const sub = jobs[j]?.steps?.[s]?.subworkflow;
1398
+ if (!sub?.jobs) return null;
1399
+ return /* @__PURE__ */ jsx(SubworkflowWell, {
1400
+ expanded: isSubworkflowExpanded(j, s),
1401
+ onSettled: requestFit,
1402
+ className: "my-2 rounded-lg border-4 border-(--theme-border)",
1403
+ children: /* @__PURE__ */ jsx(Subgraph, {
1404
+ jobs: sub.jobs,
1405
+ pathPrefix: `${pathPrefix}subworkflows/${j}/step_${s}/`,
1406
+ depth: depth + 1,
1407
+ preview,
1408
+ animT,
1409
+ expandedSubworkflows,
1410
+ toggleSubworkflow,
1411
+ openInNewGraph,
1412
+ setSublogOpen,
1413
+ invalidateConnectors,
1414
+ requestFit,
1415
+ stepsOpen,
1416
+ toggleSteps,
1417
+ matrixOpen,
1418
+ toggleMatrix
1419
+ })
1420
+ });
1421
+ };
1422
+ return /* @__PURE__ */ jsx("div", {
1423
+ className: "flex relative",
1424
+ children: dependencyCols.map((col) => {
1425
+ return /* @__PURE__ */ jsx("div", { children: col.map((jobNames) => {
1426
+ const matrixGroup = displayJobs[jobNames[0]]?._matrixGroup;
1427
+ if (matrixGroup) return /* @__PURE__ */ jsx(MatrixGroupNode, {
1428
+ matrixGroup,
1429
+ jobNames,
1430
+ idPrefix: pathPrefix,
1431
+ isExpanded: matrixOpen.has(pathPrefix + matrixGroup.originaljob),
1432
+ onToggle: () => toggleMatrix(pathPrefix + matrixGroup.originaljob),
1433
+ jobs,
1434
+ activeDists,
1435
+ onMouseEnter: () => setHoveredJob(jobNames[0]),
1436
+ onMouseLeave: () => setHoveredJob(null),
1437
+ hoveredRelated,
1438
+ animT,
1439
+ zBase,
1440
+ isStepsOpen,
1441
+ onJobClick,
1442
+ onStepClick,
1443
+ onStepLogClick,
1444
+ onSubworkflowIcon,
1445
+ isSubworkflowExpanded,
1446
+ renderSubworkflow,
1447
+ preview
1448
+ }, "node_" + jobNames[0]);
1449
+ return /* @__PURE__ */ jsxs("div", {
1450
+ id: "node_" + pathPrefix + jobNames[0],
1451
+ className: "relative m-24",
1452
+ style: { zIndex: (activeDists?.has(jobNames[0]) ? 25 : 1) + zBase },
1453
+ onMouseEnter: () => setHoveredJob(jobNames[0]),
1454
+ onMouseLeave: () => setHoveredJob(null),
1455
+ children: [/* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-2xl bg-(--theme-panel-bg)" }), /* @__PURE__ */ jsx("div", {
1456
+ className: "relative border-solid shadow py-4 px-8 rounded-xl border-4 whitespace-nowrap bg-(--theme-panel-bg) text-2xl",
1457
+ style: {
1458
+ opacity: hoveredRelated && !hoveredRelated.has(jobNames[0]) ? .5 : 1,
1459
+ transition: `opacity ${animT}s`
1460
+ },
1461
+ children: /* @__PURE__ */ jsx(Joblist, {
1462
+ jobs: displayJobs,
1463
+ jobNames,
1464
+ onJobClick,
1465
+ isStepsOpen,
1466
+ onStepClick,
1467
+ onStepLogClick,
1468
+ onSubworkflowIcon,
1469
+ isSubworkflowExpanded,
1470
+ renderSubworkflow,
1471
+ growWidth: true,
1472
+ preview
1473
+ })
1474
+ })]
1475
+ }, "node_" + jobNames[0]);
1476
+ }) }, col[0][0]);
1477
+ })
1478
+ });
1479
+ }, [
1480
+ dependencyCols,
1481
+ displayJobs,
1482
+ jobs,
1483
+ stepsOpen,
1484
+ matrixOpen,
1485
+ expandedSubworkflows,
1486
+ hoveredRelated,
1487
+ activeDists,
1488
+ animT,
1489
+ zBase,
1490
+ preview,
1491
+ pathPrefix,
1492
+ depth,
1493
+ toggleSteps,
1494
+ toggleSubworkflow,
1495
+ toggleMatrix,
1496
+ openInNewGraph,
1497
+ setSublogOpen,
1498
+ invalidateConnectors,
1499
+ requestFit
1500
+ ]);
1501
+ const memoizedBoxLayer = useMemo(() => buildBoxLayer(), [buildBoxLayer]);
1502
+ const hasLiveNestedGraph = Array.from(expandedSubworkflows).some((k) => k.startsWith(pathPrefix));
1503
+ if (!Array.isArray(dependencyCols) || dependencyCols.length === 0 || !Array.isArray(dependencyCols[0]) || dependencyCols[0].length === 0 || !Array.isArray(dependencyCols[0][0]) || dependencyCols[0][0].length === 0) return null;
1504
+ return /* @__PURE__ */ jsxs("div", {
1505
+ ref: wrapperRef,
1506
+ className: "relative",
1507
+ children: [(() => {
1508
+ const wrapper = wrapperRef.current;
1509
+ const gid = (name) => document.getElementById("node_" + pathPrefix + name);
1510
+ const ox = (el) => offsetWithin(el, wrapper).x;
1511
+ const nodeConnectorY = (el) => offsetWithin(el, wrapper).y + 37;
1512
+ let colGap = 32;
1513
+ if (dependencyCols.length >= 2) {
1514
+ const col0 = gid(dependencyCols[0][0][0]);
1515
+ const col1 = gid(dependencyCols[1][0][0]);
1516
+ if (col0 && col1) colGap = ox(col1) - (ox(col0) + col0.offsetWidth);
1517
+ }
1518
+ let minAdy = Number.POSITIVE_INFINITY;
1519
+ for (const col of dependencyCols) for (const jobNames of col) for (const dep of directDeps[jobNames[0]] ?? []) {
1520
+ const s = gid(dep);
1521
+ const e = gid(jobNames[0]);
1522
+ if (!s || !e) continue;
1523
+ const dy = Math.abs(nodeConnectorY(e) - nodeConnectorY(s));
1524
+ if (dy > 0 && dy < minAdy) minAdy = dy;
1525
+ }
1526
+ const MAX_ARC_R = 16;
1527
+ const arcR = Math.min(minAdy === Number.POSITIVE_INFINITY ? MAX_ARC_R : minAdy / 4, colGap / 2, MAX_ARC_R);
1528
+ return dependencyCols.map((col) => /* @__PURE__ */ jsx(React.Fragment, { children: col.map((jobNames) => /* @__PURE__ */ jsx(React.Fragment, { children: (directDeps[jobNames[0]] ?? []).map((dep) => {
1529
+ const isHighlighted = hoveredRelated?.has(jobNames[0]) && hoveredRelated?.has(dep);
1530
+ const baseColor = "var(--theme-border)";
1531
+ const depDist = activeDists?.get(dep) ?? 0;
1532
+ const targetDist = activeDists?.get(jobNames[0]) ?? 0;
1533
+ const hopDelay = animT;
1534
+ const closerDist = Math.min(depDist, targetDist);
1535
+ const connectorDelay = closerDist * hopDelay;
1536
+ const retractDelay = (maxHoveredDistRef.current - closerDist) * hopDelay;
1537
+ const drawRightToLeft = targetDist < depDist;
1538
+ const start = gid(dep);
1539
+ const end = gid(jobNames[0]);
1540
+ if (!start || !end) return null;
1541
+ const x1 = ox(start) + start.offsetWidth - 2;
1542
+ const y1 = nodeConnectorY(start);
1543
+ const x2 = ox(end) + 2;
1544
+ const y2 = nodeConnectorY(end);
1545
+ const dy = y2 - y1;
1546
+ const ady = Math.abs(dy);
1547
+ let pathD;
1548
+ let pathLen;
1549
+ let pathDReversed;
1550
+ const r = arcR;
1551
+ if (ady === 0) {
1552
+ pathD = `M ${x1} ${y1} L ${x2} ${y2}`;
1553
+ pathDReversed = `M ${x2} ${y2} L ${x1} ${y1}`;
1554
+ pathLen = x2 - x1;
1555
+ } else {
1556
+ const s = dy > 0 ? 1 : -1;
1557
+ const ax2end = x2 - (colGap / 2 - r);
1558
+ const ax = ax2end - 2 * r;
1559
+ const sw1 = dy > 0 ? 1 : 0;
1560
+ const sw2 = dy > 0 ? 0 : 1;
1561
+ pathD = [
1562
+ `M ${x1} ${y1}`,
1563
+ `L ${ax} ${y1}`,
1564
+ `A ${r} ${r} 0 0 ${sw1} ${ax + r} ${y1 + s * r}`,
1565
+ `L ${ax + r} ${y2 - s * r}`,
1566
+ `A ${r} ${r} 0 0 ${sw2} ${ax2end} ${y2}`,
1567
+ `L ${x2} ${y2}`
1568
+ ].join(" ");
1569
+ pathDReversed = [
1570
+ `M ${x2} ${y2}`,
1571
+ `L ${ax2end} ${y2}`,
1572
+ `A ${r} ${r} 0 0 ${sw1} ${ax + r} ${y2 - s * r}`,
1573
+ `L ${ax + r} ${y1 + s * r}`,
1574
+ `A ${r} ${r} 0 0 ${sw2} ${ax} ${y1}`,
1575
+ `L ${x1} ${y1}`
1576
+ ].join(" ");
1577
+ const arcLen = Math.PI * r / 2;
1578
+ const vSeg = Math.max(0, ady - 2 * r);
1579
+ pathLen = ax - x1 + arcLen + vSeg + arcLen + (x2 - ax2end);
1580
+ }
1581
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
1582
+ /* @__PURE__ */ jsxs("svg", {
1583
+ overflow: "visible",
1584
+ className: "absolute pointer-events-none",
1585
+ style: { zIndex: (isHighlighted || activeDists?.has(dep) && activeDists?.has(jobNames[0]) ? 30 : 10) + zBase },
1586
+ children: [
1587
+ /* @__PURE__ */ jsx("rect", {
1588
+ x: x1 - 16,
1589
+ y: y1 - 16,
1590
+ width: 32,
1591
+ height: 32,
1592
+ fill: "var(--theme-panel-bg)"
1593
+ }),
1594
+ /* @__PURE__ */ jsx("rect", {
1595
+ x: x2 - 16,
1596
+ y: y2 - 16,
1597
+ width: 32,
1598
+ height: 32,
1599
+ fill: "var(--theme-panel-bg)"
1600
+ }),
1601
+ /* @__PURE__ */ jsx("circle", {
1602
+ cx: x1,
1603
+ cy: y1,
1604
+ r: "8",
1605
+ fill: isHighlighted ? "#3B82F6" : baseColor,
1606
+ style: {
1607
+ opacity: hoveredRelated && !isHighlighted ? .3 : 1,
1608
+ transition: isHighlighted ? `fill ${animT}s ${depDist * hopDelay}s, opacity 0s` : `fill ${animT}s ${(maxHoveredDistRef.current - depDist) * hopDelay}s, opacity 0s`
1609
+ }
1610
+ }),
1611
+ /* @__PURE__ */ jsx("circle", {
1612
+ cx: x2,
1613
+ cy: y2,
1614
+ r: "8",
1615
+ fill: isHighlighted ? "#3B82F6" : baseColor,
1616
+ style: {
1617
+ opacity: hoveredRelated && !isHighlighted ? .3 : 1,
1618
+ transition: isHighlighted ? `fill ${animT}s ${targetDist * hopDelay}s, opacity 0s` : `fill ${animT}s ${(maxHoveredDistRef.current - targetDist) * hopDelay}s, opacity 0s`
1619
+ }
1620
+ })
1621
+ ]
1622
+ }),
1623
+ /* @__PURE__ */ jsxs("svg", {
1624
+ overflow: "visible",
1625
+ className: "absolute pointer-events-none",
1626
+ style: { zIndex: (isHighlighted ? 20 : -1) + zBase },
1627
+ children: [/* @__PURE__ */ jsx("path", {
1628
+ d: pathD,
1629
+ stroke: "var(--theme-panel-bg)",
1630
+ strokeWidth: "6",
1631
+ fill: "transparent"
1632
+ }), /* @__PURE__ */ jsx("path", {
1633
+ d: pathD,
1634
+ stroke: baseColor,
1635
+ strokeWidth: "6",
1636
+ fill: "transparent",
1637
+ style: {
1638
+ opacity: hoveredRelated && !isHighlighted ? .3 : 1,
1639
+ transition: `opacity 0s`
1640
+ }
1641
+ })]
1642
+ }),
1643
+ /* @__PURE__ */ jsxs("svg", {
1644
+ overflow: "visible",
1645
+ className: "absolute pointer-events-none",
1646
+ style: { zIndex: 20 + zBase },
1647
+ children: [/* @__PURE__ */ jsx("path", {
1648
+ d: drawRightToLeft ? pathDReversed : pathD,
1649
+ fill: "transparent",
1650
+ style: {
1651
+ stroke: "#3B82F6",
1652
+ strokeWidth: 6,
1653
+ strokeDasharray: `${pathLen} ${pathLen}`,
1654
+ strokeDashoffset: isHighlighted ? 0 : pathLen,
1655
+ opacity: isHighlighted ? 1 : 0,
1656
+ transition: isHighlighted ? `stroke-dashoffset ${animT}s ease ${connectorDelay}s, opacity 0s ease ${connectorDelay}s` : "stroke-dashoffset 0s, opacity 0s"
1657
+ }
1658
+ }), (() => {
1659
+ const connKey = `${dep}→${jobNames[0]}`;
1660
+ if (isHighlighted) retractPathLensRef.current.set(connKey, pathLen);
1661
+ const isRetracting = !isHighlighted && !!activeDists?.has(dep) && !!activeDists?.has(jobNames[0]);
1662
+ if (!isHighlighted && !isRetracting) return null;
1663
+ const frozenLen = retractPathLensRef.current.get(connKey) ?? pathLen;
1664
+ return /* @__PURE__ */ jsx("path", {
1665
+ d: drawRightToLeft ? pathDReversed : pathD,
1666
+ fill: "transparent",
1667
+ style: {
1668
+ stroke: "#3B82F6",
1669
+ strokeWidth: 6,
1670
+ strokeDasharray: `${frozenLen} ${frozenLen}`,
1671
+ strokeDashoffset: isHighlighted ? 0 : frozenLen,
1672
+ opacity: isHighlighted ? 0 : 1,
1673
+ transition: isHighlighted ? "stroke-dashoffset 0s, opacity 0s" : `stroke-dashoffset ${animT}s ease ${retractDelay}s, opacity 0s`
1674
+ }
1675
+ });
1676
+ })()]
1677
+ })
1678
+ ] }, dep);
1679
+ }) }, jobNames[0])) }, col[0][0]));
1680
+ })(), hasLiveNestedGraph ? buildBoxLayer() : memoizedBoxLayer]
1681
+ });
1682
+ }
1683
+ function SidebarWell({ expanded, children }) {
1684
+ return /* @__PURE__ */ jsx(Collapse, {
1685
+ expanded,
1686
+ releaseOverflow: false,
1687
+ children
1688
+ });
1689
+ }
1690
+ function SidebarJobs({ jobs, pathPrefix, preview, stepsOpen, toggleSteps, expandedSubworkflows, toggleSubworkflow, matrixOpen, toggleMatrix, openInNewGraph, setSublogOpen }) {
1691
+ const matrixGroups = useMemo(() => detectMatrixGroups(jobs), [jobs]);
1692
+ const { displayJobs } = useMemo(() => computeGraphLayout(jobs, matrixGroups), [jobs, matrixGroups]);
1693
+ const isStepsOpen = (j) => stepsOpen.has(pathPrefix + j);
1694
+ const onJobClick = (j) => {
1695
+ const status = jobs[j]?.status ?? displayJobs[j]?.status;
1696
+ if (!isStepsOpen(j) && (status === "skipped" || status === "skipped-failed")) return;
1697
+ toggleSteps(pathPrefix + j);
1698
+ };
1699
+ const expandInline = (j, s) => toggleSubworkflow(`${pathPrefix}${j}:${s}`);
1700
+ const openNew = (j, s) => openInNewGraph(pathPrefix, j, s);
1701
+ const onStepClick = (j, s) => {
1702
+ const stepStatus = jobs[j]?.steps?.[+s]?.status;
1703
+ if (stepStatus === "skipped" || stepStatus === "skipped-failed") return;
1704
+ if (jobs[j].steps[+s].subworkflow) expandInline(j, +s);
1705
+ else setSublogOpen(buildSubworkflowLogPath(pathPrefix, j, s));
1706
+ };
1707
+ const onStepLogClick = (j, s) => setSublogOpen(buildSubworkflowLogPath(pathPrefix, j, s));
1708
+ const onSubworkflowIcon = (j, s) => openNew(j, s);
1709
+ const isSubworkflowExpanded = (j, s) => expandedSubworkflows.has(`${pathPrefix}${j}:${s}`);
1710
+ const renderSubworkflow = (j, s) => {
1711
+ const sub = jobs[j]?.steps?.[s]?.subworkflow;
1712
+ if (!sub?.jobs) return null;
1713
+ return /* @__PURE__ */ jsx(SidebarWell, {
1714
+ expanded: isSubworkflowExpanded(j, s),
1715
+ children: /* @__PURE__ */ jsx("div", {
1716
+ className: "ml-6 mt-0.5 border-l border-(--theme-border) pl-4",
1717
+ children: /* @__PURE__ */ jsx(SidebarJobs, {
1718
+ jobs: sub.jobs,
1719
+ pathPrefix: `${pathPrefix}subworkflows/${j}/step_${s}/`,
1720
+ preview,
1721
+ stepsOpen,
1722
+ toggleSteps,
1723
+ expandedSubworkflows,
1724
+ toggleSubworkflow,
1725
+ matrixOpen,
1726
+ toggleMatrix,
1727
+ openInNewGraph,
1728
+ setSublogOpen
1729
+ })
1730
+ })
1731
+ });
1732
+ };
1733
+ const seenMatrixGroups = /* @__PURE__ */ new Set();
1734
+ return /* @__PURE__ */ jsx(Fragment, { children: Object.keys(jobs).flatMap((jobName) => {
1735
+ const originaljob = jobs[jobName]?._matrix?.originaljob;
1736
+ if (!originaljob) return [jobName];
1737
+ if (seenMatrixGroups.has(originaljob)) return [];
1738
+ seenMatrixGroups.add(originaljob);
1739
+ return [originaljob];
1740
+ }).map((jobName) => {
1741
+ const mg = displayJobs[jobName]?._matrixGroup;
1742
+ if (mg) return /* @__PURE__ */ jsx(MatrixGroupSummaryItem, {
1743
+ matrixGroup: mg,
1744
+ status: displayJobs[jobName]?.status,
1745
+ isExpanded: matrixOpen.has(pathPrefix + mg.originaljob),
1746
+ onToggle: () => toggleMatrix(pathPrefix + mg.originaljob),
1747
+ jobs,
1748
+ isStepsOpen,
1749
+ onJobClick,
1750
+ onStepClick,
1751
+ onStepLogClick,
1752
+ onSubworkflowIcon,
1753
+ isSubworkflowExpanded,
1754
+ renderSubworkflow,
1755
+ preview
1756
+ }, "matrix-" + pathPrefix + mg.originaljob);
1757
+ return /* @__PURE__ */ jsx(Joblist, {
1758
+ jobs,
1759
+ jobNames: [jobName],
1760
+ onJobClick,
1761
+ isStepsOpen,
1762
+ onStepClick,
1763
+ onStepLogClick,
1764
+ onSubworkflowIcon,
1765
+ isSubworkflowExpanded,
1766
+ renderSubworkflow,
1767
+ preview
1768
+ }, "job-" + pathPrefix + jobName);
1769
+ }) });
1770
+ }
1771
+ function DependencyGraph({ run, preview = false, initialScale = 1, removeBorder, viewMode = "dag", setViewMode }) {
1772
+ const animT = .125;
1773
+ const minScale = .2;
1774
+ const maxScale = .6;
1775
+ let jobs = run.executedJobs;
1776
+ const runNumber = run.number;
1777
+ const runSlug = run.slug;
1778
+ const workflowName = run.workflowName;
1779
+ const [sublogOpen, setSublogOpen] = useState("");
1780
+ const [stepsOpen, setStepsOpen] = useState(/* @__PURE__ */ new Set());
1781
+ const [expandedSubworkflows, setExpandedSubworkflows] = useState(/* @__PURE__ */ new Set());
1782
+ const containerRef = useRef(null);
1783
+ const graphContentRef = useRef(null);
1784
+ const transformRef = useRef(null);
1785
+ const [, forceRerender] = useState(0);
1786
+ const invalidateConnectors = useCallback(() => forceRerender((c) => c + 1), []);
1787
+ const [windowSize, setWindowSize] = useState(() => ({
1788
+ w: typeof window !== "undefined" ? window.innerWidth : 800,
1789
+ h: typeof window !== "undefined" ? window.innerHeight : 600
1790
+ }));
1791
+ useEffect(() => {
1792
+ const onResize = () => setWindowSize({
1793
+ w: window.innerWidth,
1794
+ h: window.innerHeight
1795
+ });
1796
+ window.addEventListener("resize", onResize);
1797
+ return () => window.removeEventListener("resize", onResize);
1798
+ }, []);
1799
+ const [topOffset, setTopOffset] = useState(null);
1800
+ const topObserverRef = useRef(null);
1801
+ const setContainerElement = useCallback((el) => {
1802
+ containerRef.current = el;
1803
+ topObserverRef.current?.disconnect();
1804
+ topObserverRef.current = null;
1805
+ if (!el || preview) return;
1806
+ const measure = () => {
1807
+ const cur = containerRef.current;
1808
+ if (!cur) return;
1809
+ setTopOffset((prev) => {
1810
+ const next = cur.getBoundingClientRect().top;
1811
+ return prev === next ? prev : next;
1812
+ });
1813
+ };
1814
+ requestAnimationFrame(measure);
1815
+ const parent = el.parentElement;
1816
+ if (parent) {
1817
+ const ro = new ResizeObserver(measure);
1818
+ ro.observe(parent);
1819
+ topObserverRef.current = ro;
1820
+ }
1821
+ }, [preview]);
1822
+ const containerHeight = topOffset !== null ? Math.max(windowSize.h - topOffset - 16, 160) : null;
1823
+ const { dag: dagStrings } = useStrings();
1824
+ const slots = useSlots();
1825
+ const [executedJobsPath, setExecutedJobsPath] = useState([[]]);
1826
+ const [executedJobsPathIdx, setExecutedJobsPathIdx] = useState(0);
1827
+ const [expandedJobs, setExpandedJobs] = useState(/* @__PURE__ */ new Set());
1828
+ const [matrixOpen, setMatrixOpen] = useState(/* @__PURE__ */ new Set());
1829
+ const isRunFinished = !!run.completedAt || run.status === "completed" || run.status === "error" || run.status === "canceled";
1830
+ const [now, setNow] = useState(() => DateTime.now());
1831
+ useEffect(() => {
1832
+ if (isRunFinished) return;
1833
+ const timer = setInterval(() => setNow(DateTime.now()), 1e3);
1834
+ return () => clearInterval(timer);
1835
+ }, [isRunFinished]);
1836
+ if (executedJobsPath[executedJobsPathIdx].length > 0) executedJobsPath[executedJobsPathIdx].map((prop) => {
1837
+ jobs = jobs[prop];
1838
+ });
1839
+ const resetGraph = useCallback(() => {
1840
+ setSublogOpen("");
1841
+ setStepsOpen(/* @__PURE__ */ new Set());
1842
+ setExpandedSubworkflows(/* @__PURE__ */ new Set());
1843
+ setMatrixOpen(/* @__PURE__ */ new Set());
1844
+ }, []);
1845
+ const openInNewGraph = useCallback((prefix, job, step) => {
1846
+ resetGraph();
1847
+ setExecutedJobsPath((prev) => [[
1848
+ ...parsePrefixToPath(prefix),
1849
+ job,
1850
+ "steps",
1851
+ step,
1852
+ "subworkflow",
1853
+ "jobs"
1854
+ ], ...prev.slice(executedJobsPathIdx, 10)]);
1855
+ setExecutedJobsPathIdx(0);
1856
+ }, [executedJobsPathIdx, resetGraph]);
1857
+ const toggleSubworkflow = useCallback((key) => {
1858
+ setExpandedSubworkflows((prev) => {
1859
+ const next = new Set(prev);
1860
+ if (next.has(key)) next.delete(key);
1861
+ else next.add(key);
1862
+ return next;
1863
+ });
1864
+ }, []);
1865
+ const toggleSteps = useCallback((key) => {
1866
+ setStepsOpen((prev) => {
1867
+ const next = new Set(prev);
1868
+ if (next.has(key)) next.delete(key);
1869
+ else next.add(key);
1870
+ return next;
1871
+ });
1872
+ requestAnimationFrame(invalidateConnectors);
1873
+ }, [invalidateConnectors]);
1874
+ const toggleMatrix = useCallback((key) => {
1875
+ setMatrixOpen((prev) => {
1876
+ const next = new Set(prev);
1877
+ if (next.has(key)) next.delete(key);
1878
+ else next.add(key);
1879
+ return next;
1880
+ });
1881
+ requestAnimationFrame(invalidateConnectors);
1882
+ }, [invalidateConnectors]);
1883
+ const { data: sublogs, isLoading: sublogsLoading, error: sublogsError } = useRunFile(runSlug, sublogOpen || null, preview ? void 0 : { refreshInterval: 2e3 });
1884
+ const jobAndStep = sublogOpen.split("/").slice(-3, -1);
1885
+ if (jobAndStep.length > 1) jobAndStep[1] = jobAndStep[1].split("_")[1];
1886
+ const logName = jobAndStep.length > 1 ? "Job: " + (jobAndStep[0][0].toUpperCase() + jobAndStep[0].slice(1)).replace(/_/g, " ") + " \nStep: " + (jobs[jobAndStep[0]]?.steps[jobAndStep[1]]?.name || "Step " + jobAndStep[1]) : "";
1887
+ const sublogIsSubworkflow = jobAndStep.length > 1 && jobs[jobAndStep[0]]?.steps[jobAndStep[1]]?.subworkflow;
1888
+ const currentSublog = sublogOpen === "" ? "" : preview ? dumpYaml(jobs[jobAndStep[0]]?.steps[jobAndStep[1]]) : sublogsError || !sublogs ? sublogIsSubworkflow ? "Subworkflows generally have no logs/scripts at this level.\nClick on the \"Open in new graph\" icon on the subworkflow step to open the subworkflow graph." : "No log found" : sublogs;
1889
+ const matrixGroups = useMemo(() => detectMatrixGroups(jobs), [jobs]);
1890
+ const { dependencyCols } = useMemo(() => computeGraphLayout(jobs, matrixGroups), [jobs, matrixGroups]);
1891
+ const fitGraph = useCallback((animationMs = 0) => {
1892
+ const container = containerRef.current;
1893
+ const content = graphContentRef.current;
1894
+ const transform = transformRef.current;
1895
+ if (!container || !content || !transform) return;
1896
+ const topNodes = content.querySelectorAll(":scope > div > div > div > div[id^=\"node_\"]");
1897
+ const nodes = topNodes.length > 0 ? topNodes : content.querySelectorAll("[id^=\"node_\"]");
1898
+ if (nodes.length === 0) return;
1899
+ let minX = Number.POSITIVE_INFINITY;
1900
+ let minY = Number.POSITIVE_INFINITY;
1901
+ let maxX = 0;
1902
+ let maxY = 0;
1903
+ for (const node of nodes) {
1904
+ const el = node;
1905
+ const pos = offsetWithin(el, content);
1906
+ minX = Math.min(minX, pos.x);
1907
+ minY = Math.min(minY, pos.y);
1908
+ maxX = Math.max(maxX, pos.x + el.offsetWidth);
1909
+ maxY = Math.max(maxY, pos.y + el.offsetHeight);
1910
+ }
1911
+ const graphW = maxX - minX;
1912
+ const graphH = maxY - minY;
1913
+ const containerW = container.clientWidth;
1914
+ const containerH = container.clientHeight;
1915
+ if (graphW === 0 || graphH === 0) return;
1916
+ const margin = Math.min(containerW, containerH) * .05;
1917
+ const scaleW = (containerW - margin * 2) / graphW;
1918
+ const scaleH = (containerH - margin * 2) / graphH;
1919
+ const scale = Math.min(Math.max(Math.min(scaleW, scaleH), minScale), maxScale);
1920
+ const offsetX = margin - minX * scale;
1921
+ const offsetY = margin - minY * scale;
1922
+ if (animationMs > 0) {
1923
+ transform.setTransform(offsetX, offsetY, scale, animationMs, "easeOut");
1924
+ return;
1925
+ }
1926
+ const wrapperEl = container.querySelector(".react-transform-component");
1927
+ if (wrapperEl) wrapperEl.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;
1928
+ requestAnimationFrame(() => {
1929
+ transform.setTransform(offsetX, offsetY, scale, 0);
1930
+ invalidateConnectors();
1931
+ });
1932
+ }, [invalidateConnectors]);
1933
+ const animatedFit = useCallback(() => fitGraph(FIT_ANIMATION_MS), [fitGraph]);
1934
+ useEffect(() => {
1935
+ if (preview) return;
1936
+ const raf = requestAnimationFrame(() => fitGraph());
1937
+ return () => cancelAnimationFrame(raf);
1938
+ }, [
1939
+ preview,
1940
+ fitGraph,
1941
+ windowSize,
1942
+ executedJobsPathIdx
1943
+ ]);
1944
+ const currentPath = executedJobsPath[executedJobsPathIdx];
1945
+ const nestingDepth = currentPath.length / 5;
1946
+ let pathAppend = "";
1947
+ for (let i = 0; i < nestingDepth; i++) pathAppend += `subworkflows/${currentPath[5 * i]}/step_${currentPath[5 * i + 2].toString()}/`;
1948
+ let generalLogPath = "logs.out";
1949
+ if (nestingDepth > 0) {
1950
+ let parentPrefix = "";
1951
+ for (let i = 0; i < nestingDepth - 1; i++) parentPrefix += `subworkflows/${currentPath[5 * i]}/step_${currentPath[5 * i + 2].toString()}/`;
1952
+ const lastJob = currentPath[5 * (nestingDepth - 1)];
1953
+ const lastStep = currentPath[5 * (nestingDepth - 1) + 2];
1954
+ generalLogPath = buildSubworkflowLogPath(parentPrefix, lastJob, lastStep);
1955
+ }
1956
+ const expansionKeys = useMemo(() => collectExpansionKeys(jobs, pathAppend), [jobs, pathAppend]);
1957
+ const hasExpandable = expansionKeys.subworkflowKeys.length > 0 || expansionKeys.matrixKeys.length > 0;
1958
+ const allExpanded = hasExpandable && expansionKeys.subworkflowKeys.every((k) => expandedSubworkflows.has(k)) && expansionKeys.matrixKeys.every((k) => matrixOpen.has(k));
1959
+ const toggleExpandAll = () => {
1960
+ if (allExpanded) {
1961
+ setExpandedSubworkflows(/* @__PURE__ */ new Set());
1962
+ setStepsOpen(/* @__PURE__ */ new Set());
1963
+ setMatrixOpen(/* @__PURE__ */ new Set());
1964
+ } else {
1965
+ setExpandedSubworkflows(new Set(expansionKeys.subworkflowKeys));
1966
+ setStepsOpen(new Set(expansionKeys.jobKeys));
1967
+ setMatrixOpen(new Set(expansionKeys.matrixKeys));
1968
+ }
1969
+ requestAnimationFrame(() => {
1970
+ invalidateConnectors();
1971
+ fitGraph();
1972
+ });
1973
+ };
1974
+ const nextStep = useCallback((backward) => {
1975
+ const sublogSplit = sublogOpen.split("/");
1976
+ let job = sublogSplit[sublogSplit.length - 3];
1977
+ let step = parseInt(sublogSplit[sublogSplit.length - 2].slice(5), 10);
1978
+ const jobArr = Object.keys(jobs);
1979
+ let idx = jobArr.indexOf(job);
1980
+ if (backward) if (0 < step) step -= 1;
1981
+ else {
1982
+ if (idx === 0) idx = jobArr.length;
1983
+ job = jobArr[idx - 1];
1984
+ step = jobs[job].steps.length - 1;
1985
+ }
1986
+ else if (jobs[job].steps.length - 1 > step) step += 1;
1987
+ else {
1988
+ if (idx === jobArr.length - 1) idx = -1;
1989
+ job = jobArr[idx + 1];
1990
+ step = 0;
1991
+ }
1992
+ setSublogOpen(sublogSplit.slice(0, -3).join("/") + `/${job}/step_${step}/` + sublogSplit.slice(-1)[0]);
1993
+ }, [jobs, sublogOpen]);
1994
+ const handleKeyDown = useCallback((e) => {
1995
+ if (e.key === "ArrowRight") nextStep(false);
1996
+ else if (e.key === "ArrowLeft") nextStep(true);
1997
+ }, [nextStep]);
1998
+ useEffect(() => {
1999
+ if ([`${pathAppend}/logs.out`, ""].includes(sublogOpen)) return;
2000
+ window.addEventListener("keydown", handleKeyDown);
2001
+ return () => window.removeEventListener("keydown", handleKeyDown);
2002
+ }, [
2003
+ handleKeyDown,
2004
+ sublogOpen,
2005
+ pathAppend
2006
+ ]);
2007
+ const summarySidebar = useMemo(() => /* @__PURE__ */ jsx(SidebarJobs, {
2008
+ jobs,
2009
+ pathPrefix: pathAppend,
2010
+ preview,
2011
+ stepsOpen,
2012
+ toggleSteps,
2013
+ expandedSubworkflows,
2014
+ toggleSubworkflow,
2015
+ matrixOpen,
2016
+ toggleMatrix,
2017
+ openInNewGraph,
2018
+ setSublogOpen
2019
+ }), [
2020
+ jobs,
2021
+ pathAppend,
2022
+ preview,
2023
+ stepsOpen,
2024
+ toggleSteps,
2025
+ expandedSubworkflows,
2026
+ toggleSubworkflow,
2027
+ matrixOpen,
2028
+ toggleMatrix,
2029
+ openInNewGraph
2030
+ ]);
2031
+ if (!Array.isArray(dependencyCols) || dependencyCols.length === 0 || !Array.isArray(dependencyCols[0]) || dependencyCols[0].length === 0 || !Array.isArray(dependencyCols[0][0]) || dependencyCols[0][0].length === 0) return;
2032
+ const startDt = run.createdAt ? DateTime.fromISO(run.createdAt) : now;
2033
+ const runtime = toAbsHumanDuration(startDt, run.completedAt ? DateTime.fromISO(run.completedAt) : now);
2034
+ const notGeneralLogs = `${pathAppend}/logs.out` !== sublogOpen;
2035
+ const additionalBottomLeftBarComponents = notGeneralLogs ? /* @__PURE__ */ jsxs(Fragment, { children: [
2036
+ /* @__PURE__ */ jsxs("button", {
2037
+ type: "button",
2038
+ className: "btn btn-neutral",
2039
+ onClick: () => nextStep(true),
2040
+ children: [/* @__PURE__ */ jsx(ArrowLeftIcon, {}), /* @__PURE__ */ jsx("span", { children: "Prev" })]
2041
+ }),
2042
+ /* @__PURE__ */ jsxs("button", {
2043
+ type: "button",
2044
+ className: "btn btn-neutral",
2045
+ onClick: () => nextStep(false),
2046
+ children: [/* @__PURE__ */ jsx(ArrowRightIcon, {}), /* @__PURE__ */ jsx("span", { children: "Next" })]
2047
+ }),
2048
+ /* @__PURE__ */ jsx("span", {
2049
+ className: "pt-0.5 whitespace-pre-wrap text-xs",
2050
+ children: logName
2051
+ })
2052
+ ] }) : void 0;
2053
+ const additionalBottomRightBarComponents = (!preview || sublogIsSubworkflow) && sublogOpen && notGeneralLogs ? /* @__PURE__ */ jsx("button", {
2054
+ type: "button",
2055
+ className: "btn btn-neutral",
2056
+ onClick: () => {
2057
+ if (sublogIsSubworkflow) {
2058
+ openInNewGraph(pathAppend, jobAndStep[0], parseInt(jobAndStep[1], 10));
2059
+ setSublogOpen("");
2060
+ } else setSublogOpen(sublogOpen.split("/").slice(-1)[0] === "logs.out" ? [...sublogOpen.split("/").slice(0, -1), "script-unstable.sh"].join("/") : [...sublogOpen.split("/").slice(0, -1), "logs.out"].join("/"));
2061
+ },
2062
+ children: /* @__PURE__ */ jsx("span", { children: sublogIsSubworkflow ? "Open Subworkflow" : sublogOpen.split("/").slice(-1)[0] === "logs.out" ? "Show Script" : "Show Logs" })
2063
+ }) : void 0;
2064
+ return /* @__PURE__ */ jsxs("div", {
2065
+ className: "flex flex-col lg:flex-row w-full",
2066
+ children: [
2067
+ !preview && /* @__PURE__ */ jsxs("div", {
2068
+ className: "hidden lg:block lg:w-[280px] flex-shrink-0 lg:mr-1",
2069
+ children: [
2070
+ slots.runSessions && /* @__PURE__ */ jsx("div", {
2071
+ className: "mb-4 overflow-y-auto",
2072
+ children: slots.runSessions({
2073
+ runId: run.id,
2074
+ runNumber: run.number,
2075
+ runActive: isRunActive(run.status)
2076
+ })
2077
+ }),
2078
+ /* @__PURE__ */ jsxs("div", {
2079
+ className: "font-bold flex items-center gap-x-2",
2080
+ children: ["Summary", /* @__PURE__ */ jsx("button", {
2081
+ type: "button",
2082
+ "aria-label": "General log",
2083
+ onClick: () => setSublogOpen(generalLogPath),
2084
+ className: "link",
2085
+ children: /* @__PURE__ */ jsx(DocumentIcon, { className: "w-4 h-4" })
2086
+ })]
2087
+ }),
2088
+ /* @__PURE__ */ jsx("div", {
2089
+ className: "mx-4 mb-4 overflow-y-auto",
2090
+ children: summarySidebar
2091
+ })
2092
+ ]
2093
+ }),
2094
+ /* @__PURE__ */ jsxs("div", {
2095
+ className: cx(preview ? "w-full" : "flex-1 min-w-0 mt-2", "h-full flex flex-col gap-y-2"),
2096
+ children: [
2097
+ !preview && /* @__PURE__ */ jsxs("div", {
2098
+ className: "panel w-full border rounded-lg px-4 py-2 flex flex-wrap items-center gap-x-16 gap-y-2",
2099
+ children: [
2100
+ /* @__PURE__ */ jsxs("div", {
2101
+ className: "flex flex-col",
2102
+ children: [/* @__PURE__ */ jsx("div", {
2103
+ className: "text-xs",
2104
+ children: dagStrings.workflow
2105
+ }), /* @__PURE__ */ jsx("div", {
2106
+ className: "text-sm font-semibold flex items-center gap-x-1",
2107
+ children: workflowName
2108
+ })]
2109
+ }),
2110
+ /* @__PURE__ */ jsxs("div", {
2111
+ className: "flex flex-col",
2112
+ children: [/* @__PURE__ */ jsx("div", {
2113
+ className: "text-xs",
2114
+ children: dagStrings.run
2115
+ }), /* @__PURE__ */ jsx("div", {
2116
+ className: "text-sm font-semibold flex items-center gap-x-1",
2117
+ children: runNumber
2118
+ })]
2119
+ }),
2120
+ /* @__PURE__ */ jsxs("div", {
2121
+ className: "flex flex-col",
2122
+ children: [/* @__PURE__ */ jsx("div", {
2123
+ className: "text-xs",
2124
+ children: dagStrings.status
2125
+ }), /* @__PURE__ */ jsx("div", {
2126
+ className: "text-sm font-semibold flex items-center gap-x-1",
2127
+ children: /* @__PURE__ */ jsxs(TooltipInfo, {
2128
+ className: "flex items-center gap-x-1",
2129
+ text: dagStrings.statusReason(run.statusReason) ?? "",
2130
+ children: [/* @__PURE__ */ jsx(Indicator, { status: run.status }), dagStrings.statusLabel(run.status)]
2131
+ })
2132
+ })]
2133
+ }),
2134
+ /* @__PURE__ */ jsxs("div", {
2135
+ className: "flex flex-col",
2136
+ children: [/* @__PURE__ */ jsx("div", {
2137
+ className: "text-xs",
2138
+ children: dagStrings.runtime
2139
+ }), /* @__PURE__ */ jsx("div", {
2140
+ className: "text-sm font-semibold flex items-center gap-x-1",
2141
+ children: runtime
2142
+ })]
2143
+ }),
2144
+ /* @__PURE__ */ jsxs("div", {
2145
+ className: "flex flex-col",
2146
+ children: [/* @__PURE__ */ jsx("div", {
2147
+ className: "text-xs",
2148
+ children: dagStrings.submitted
2149
+ }), /* @__PURE__ */ jsx("div", {
2150
+ className: "text-sm font-semibold flex items-center gap-x-1",
2151
+ children: startDt ? startDt.toLocaleString(DateTime.DATETIME_SHORT) : ""
2152
+ })]
2153
+ }),
2154
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
2155
+ setViewMode && /* @__PURE__ */ jsx(ViewToggle, {
2156
+ viewMode,
2157
+ setViewMode: (mode) => {
2158
+ if (mode === "dag") {
2159
+ setStepsOpen(/* @__PURE__ */ new Set());
2160
+ requestAnimationFrame(fitGraph);
2161
+ }
2162
+ setViewMode?.(mode);
2163
+ }
2164
+ })
2165
+ ]
2166
+ }),
2167
+ !preview && /* @__PURE__ */ jsx(AnnotationBanner, { executedJobs: run.executedJobs }),
2168
+ viewMode === "tree" && !preview ? /* @__PURE__ */ jsx("div", {
2169
+ className: "panel w-full border",
2170
+ children: /* @__PURE__ */ jsx(TreeView, {
2171
+ jobs,
2172
+ workflowName,
2173
+ runNumber,
2174
+ slug: runSlug,
2175
+ expandedJobs,
2176
+ setExpandedJobs
2177
+ })
2178
+ }) : /* @__PURE__ */ jsx("div", {
2179
+ ref: setContainerElement,
2180
+ className: "panel w-full " + (removeBorder ? "border-none" : "border") + " overflow-hidden relative",
2181
+ style: {
2182
+ height: containerHeight !== null ? `${containerHeight}px` : "62vh",
2183
+ minHeight: 160
2184
+ },
2185
+ children: /* @__PURE__ */ jsx(TransformWrapper, {
2186
+ ref: transformRef,
2187
+ minScale,
2188
+ maxScale,
2189
+ initialScale: maxScale,
2190
+ doubleClick: { disabled: true },
2191
+ limitToBounds: false,
2192
+ onInit: () => {
2193
+ setTimeout(invalidateConnectors, 0);
2194
+ },
2195
+ onPanning: invalidateConnectors,
2196
+ onPinch: invalidateConnectors,
2197
+ onTransform: invalidateConnectors,
2198
+ onWheel: invalidateConnectors,
2199
+ onZoom: invalidateConnectors,
2200
+ children: () => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
2201
+ className: "absolute z-10 top-2 right-2 flex flex-col items-end gap-y-1",
2202
+ children: [
2203
+ hasExpandable && /* @__PURE__ */ jsx("button", {
2204
+ type: "button",
2205
+ className: "btn btn-info cursor-pointer h-6",
2206
+ onClick: toggleExpandAll,
2207
+ children: allExpanded ? dagStrings.collapseAll : dagStrings.expandAll
2208
+ }),
2209
+ /* @__PURE__ */ jsx("button", {
2210
+ className: "btn btn-info w-21 cursor-pointer h-6",
2211
+ "aria-label": "Reset view",
2212
+ onClick: () => {
2213
+ if (executedJobsPath[executedJobsPathIdx].length > 0) {
2214
+ resetGraph();
2215
+ setExecutedJobsPath([[], ...executedJobsPath.slice(executedJobsPathIdx, 10)]);
2216
+ setExecutedJobsPathIdx(0);
2217
+ } else fitGraph(FIT_ANIMATION_MS);
2218
+ },
2219
+ children: "Reset View"
2220
+ }),
2221
+ executedJobsPath.length > 1 && /* @__PURE__ */ jsxs("div", {
2222
+ className: "flex",
2223
+ children: [/* @__PURE__ */ jsx("button", {
2224
+ onClick: () => {
2225
+ if (executedJobsPath.length > executedJobsPathIdx + 1) {
2226
+ resetGraph();
2227
+ setExecutedJobsPathIdx(executedJobsPathIdx + 1);
2228
+ }
2229
+ },
2230
+ className: "btn btn-info mr-1 cursor-pointer w-10 h-6",
2231
+ children: "<"
2232
+ }), /* @__PURE__ */ jsx("button", {
2233
+ onClick: () => {
2234
+ if (executedJobsPathIdx - 1 >= 0) {
2235
+ resetGraph();
2236
+ setExecutedJobsPathIdx(executedJobsPathIdx - 1);
2237
+ }
2238
+ },
2239
+ className: "btn btn-info cursor-pointer w-10 h-6",
2240
+ children: ">"
2241
+ })]
2242
+ })
2243
+ ]
2244
+ }), /* @__PURE__ */ jsx(TransformComponent, { children: /* @__PURE__ */ jsx("div", {
2245
+ ref: graphContentRef,
2246
+ className: "flex relative mr-[8000px]",
2247
+ style: { minHeight: 4e3 },
2248
+ children: /* @__PURE__ */ jsx(Subgraph, {
2249
+ jobs,
2250
+ pathPrefix: pathAppend,
2251
+ depth: 0,
2252
+ preview,
2253
+ animT,
2254
+ expandedSubworkflows,
2255
+ toggleSubworkflow,
2256
+ openInNewGraph,
2257
+ setSublogOpen,
2258
+ invalidateConnectors,
2259
+ requestFit: animatedFit,
2260
+ stepsOpen,
2261
+ toggleSteps,
2262
+ matrixOpen,
2263
+ toggleMatrix,
2264
+ onReady: fitGraph
2265
+ }, `sg-${executedJobsPathIdx}-${pathAppend}`)
2266
+ }) })] })
2267
+ })
2268
+ })
2269
+ ]
2270
+ }),
2271
+ /* @__PURE__ */ jsx(Modal, {
2272
+ isOpen: sublogOpen !== "",
2273
+ close: () => setSublogOpen(""),
2274
+ fitWidth: true,
2275
+ showButtons: false,
2276
+ children: /* @__PURE__ */ jsx("div", {
2277
+ className: "flex w-full",
2278
+ children: sublogsLoading && (!sublogs || !runNumber) ? /* @__PURE__ */ jsx(Loader, {
2279
+ text: "Loading logs",
2280
+ size: 70,
2281
+ textSize: 18
2282
+ }) : preview ? /* @__PURE__ */ jsxs("div", { children: [slots.yamlViewer?.({
2283
+ value: currentSublog,
2284
+ height: window.innerHeight / 1.4,
2285
+ width: window.innerWidth / 1.4,
2286
+ contentKey: "editor_" + sublogOpen
2287
+ }) ?? /* @__PURE__ */ jsx("pre", {
2288
+ className: "h-full overflow-auto text-xs p-4",
2289
+ style: {
2290
+ height: window.innerHeight / 1.4,
2291
+ width: window.innerWidth / 1.4
2292
+ },
2293
+ children: currentSublog
2294
+ }), /* @__PURE__ */ jsxs("div", {
2295
+ className: "flex theme-panel select-none items-center gap-x-2 p-2",
2296
+ children: [/* @__PURE__ */ jsx("div", {
2297
+ className: "w-full flex flex-row gap-x-2 items-center",
2298
+ children: /* @__PURE__ */ jsx("div", {
2299
+ className: "flex gap-x-2 w-full",
2300
+ children: additionalBottomLeftBarComponents
2301
+ })
2302
+ }), additionalBottomRightBarComponents]
2303
+ })] }) : /* @__PURE__ */ jsx(LogViewer, {
2304
+ log: currentSublog?.length > 1e3 ? currentSublog.slice(-1e5) : currentSublog,
2305
+ height: windowSize.h / 1.2,
2306
+ width: windowSize.w / 1.2,
2307
+ hideExpand: true,
2308
+ additionalBottomLeftBarComponents,
2309
+ additionalBottomRightBarComponents,
2310
+ enableWorkflowCommands: true
2311
+ })
2312
+ })
2313
+ })
2314
+ ]
2315
+ });
2316
+ }
2317
+ function DependencyGraphPreview(inputs) {
2318
+ return /* @__PURE__ */ jsx(DependencyGraph, {
2319
+ run: {
2320
+ executedJobs: addCleanupSteps({ jobs: inputs.yml?.jobs || {} }),
2321
+ number: 0,
2322
+ workflowName: ""
2323
+ },
2324
+ preview: true,
2325
+ initialScale: inputs.initialScale || 1,
2326
+ removeBorder: inputs.removeBorder
2327
+ });
2328
+ }
2329
+ //#endregion
2330
+ export { DependencyGraph, DependencyGraphPreview };