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