@parallelworks/ui 0.15.0 → 0.17.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 (38) hide show
  1. package/dist/chunks/{CodePreviewEditor-DuMZMA_k.js → CodePreviewEditor-UAtnpmOV.js} +1 -1
  2. package/dist/chunks/{CreateModal-B7smkHkv.js → CreateModal-BbV5cTj7.js} +3 -3
  3. package/dist/chunks/{Monaco-BXDH2jVj.js → Monaco-DVyIpyv4.js} +485 -172
  4. package/dist/chunks/{PageHeader-CoZWF0pJ.js → PageHeader-MxuOs9cH.js} +1 -1
  5. package/dist/chunks/{Provider-CbFVFB7O.js → Provider-DHmZ3v9i.js} +28 -2
  6. package/dist/chunks/{StatusBadge-zvEtK16a.js → StatusBadge-C1Blx_cW.js} +23 -6
  7. package/dist/chunks/{SwitchToggle-3V836Cn7.js → SwitchToggle-BRQoEwLZ.js} +7 -4
  8. package/dist/chunks/{Table-CutP1bRt.js → Table-C6JdFFiJ.js} +1 -1
  9. package/dist/chunks/{editor-Ko3zX22Y.js → editor-mNznodJN.js} +2 -2
  10. package/dist/chunks/{list-CduoUsMW.js → list-7k0eaI5N.js} +21 -19
  11. package/dist/components/Button.d.ts +2 -1
  12. package/dist/components/Provider.d.ts +57 -2
  13. package/dist/components/SectionHeader.d.ts +3 -1
  14. package/dist/components/StatusBadge.d.ts +7 -2
  15. package/dist/components/relativeTime.d.ts +15 -0
  16. package/dist/editor/index.js +1 -1
  17. package/dist/editor/usesGitlab.d.ts +97 -0
  18. package/dist/file-explorer/index.js +253 -253
  19. package/dist/file-explorer/lib/paging.d.ts +27 -28
  20. package/dist/file-explorer/lib/types.d.ts +0 -1
  21. package/dist/file-explorer/lib/utils.d.ts +16 -5
  22. package/dist/form/index.js +5 -5
  23. package/dist/form/types/workflow-schema.d.ts +38 -0
  24. package/dist/index.d.ts +4 -2
  25. package/dist/index.js +42 -11
  26. package/dist/list/ListPage.d.ts +3 -1
  27. package/dist/list/ListPager.d.ts +3 -1
  28. package/dist/list/ListTable.d.ts +2 -1
  29. package/dist/list/index.js +2 -2
  30. package/dist/list/listView.d.ts +1 -1
  31. package/dist/logviewer/index.js +1 -1
  32. package/dist/styles/theme.css +14 -12
  33. package/dist/styles.css +1 -1
  34. package/dist/workflow/DependencyGraph.d.ts +2 -0
  35. package/dist/workflow/index.d.ts +1 -0
  36. package/dist/workflow/index.js +3 -2
  37. package/dist/workflow/types.d.ts +5 -0
  38. package/package.json +4 -4
@@ -1,5 +1,5 @@
1
1
  import { AddIcon, CheckIcon, ChevronRightIcon, ClipboardIcon, CopyIcon, EditIcon, IdCardIcon, KeyIcon, LinkIcon, MailIcon, MoreIcon, UserIcon } from "../icons.js";
2
- import { c as useStrings, i as useNotify, n as useLink, s as useSlots } from "./Provider-CbFVFB7O.js";
2
+ import { i as useNotify, l as useSlots, n as useLink, u as useStrings } from "./Provider-DHmZ3v9i.js";
3
3
  import { n as TOOLTIP_ID } from "./Tooltip-n9w7Tkt1.js";
4
4
  import cx from "classnames";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -1,5 +1,6 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { createContext, useContext, useMemo } from "react";
3
+ import { useLocalStorage } from "usehooks-ts";
3
4
  //#region src/components/Provider.tsx
4
5
  var DefaultLink = ({ to, onClick, className, children, ...rest }) => /* @__PURE__ */ jsx("a", {
5
6
  href: to,
@@ -17,6 +18,10 @@ var defaultUseRunFile = () => ({
17
18
  isLoading: false,
18
19
  error: void 0
19
20
  });
21
+ function useLocalStoragePersistedState(key, defaultValue) {
22
+ const [value, setValue] = useLocalStorage(key, defaultValue);
23
+ return [value, setValue];
24
+ }
20
25
  var DEFAULTS = {
21
26
  notify: {
22
27
  info: () => {},
@@ -319,6 +324,13 @@ var DEFAULTS = {
319
324
  readOnlyDescription: "Read-only mounts can list and download objects, but cannot upload, modify, or delete them.",
320
325
  readOnlyForced: "Your access to this bucket is read-only, so this mount cannot be writable."
321
326
  },
327
+ time: {
328
+ now: "now",
329
+ minutes: (n) => `${n}m`,
330
+ hours: (n) => `${n}h`,
331
+ days: (n) => `${n}d`,
332
+ years: (n) => `${n}y`
333
+ },
322
334
  list: {
323
335
  copy: (label) => `Copy ${label}`,
324
336
  copied: (label) => `Copied ${label}`,
@@ -393,7 +405,10 @@ var DEFAULTS = {
393
405
  toMarketItem: () => {},
394
406
  openExternal: (href) => window.open(href, "_blank")
395
407
  },
396
- data: { useRunFile: defaultUseRunFile },
408
+ data: {
409
+ useRunFile: defaultUseRunFile,
410
+ usePersistedState: useLocalStoragePersistedState
411
+ },
397
412
  slots: {}
398
413
  };
399
414
  var UIContext = createContext(DEFAULTS);
@@ -415,10 +430,17 @@ function useWorkflowJsonResolver() {
415
430
  function useProvisionCorsRules() {
416
431
  return useContext(UIContext).data.provisionCorsRules;
417
432
  }
433
+ function useRepoSuggestions() {
434
+ return useContext(UIContext).data.repoSuggestions;
435
+ }
418
436
  function useRunFile(slug, path, options) {
419
437
  const { useRunFile: boundUseRunFile } = useContext(UIContext).data;
420
438
  return boundUseRunFile(slug, path, options);
421
439
  }
440
+ function usePersistedState(key, defaultValue) {
441
+ const { usePersistedState: boundUsePersistedState } = useContext(UIContext).data;
442
+ return boundUsePersistedState(key, defaultValue);
443
+ }
422
444
  function UIProvider({ notify, strings, navigation, data, slots, children }) {
423
445
  const value = useMemo(() => ({
424
446
  notify: {
@@ -459,6 +481,10 @@ function UIProvider({ notify, strings, navigation, data, slots, children }) {
459
481
  ...DEFAULTS.strings.form,
460
482
  ...strings?.form
461
483
  },
484
+ time: {
485
+ ...DEFAULTS.strings.time,
486
+ ...strings?.time
487
+ },
462
488
  list: {
463
489
  ...DEFAULTS.strings.list,
464
490
  ...strings?.list
@@ -486,4 +512,4 @@ function UIProvider({ notify, strings, navigation, data, slots, children }) {
486
512
  });
487
513
  }
488
514
  //#endregion
489
- export { useProvisionCorsRules as a, useStrings as c, useNotify as i, useWorkflowJsonResolver as l, useLink as n, useRunFile as o, useNavigation as r, useSlots as s, UIProvider as t };
515
+ export { usePersistedState as a, useRunFile as c, useWorkflowJsonResolver as d, useNotify as i, useSlots as l, useLink as n, useProvisionCorsRules as o, useNavigation as r, useRepoSuggestions as s, UIProvider as t, useStrings as u };
@@ -115,13 +115,30 @@ var pulseColors = {
115
115
  info: "bg-blue-500",
116
116
  muted: "bg-(--theme-muted-text-color)"
117
117
  };
118
- function StatusBadge({ children, variant = "default", pulse = false, size = "sm", className }) {
118
+ var textColors = {
119
+ default: "text-(--theme-app)",
120
+ success: "text-emerald-600 dark:text-emerald-400",
121
+ warning: "text-amber-600 dark:text-amber-400",
122
+ error: "text-red-600 dark:text-red-400",
123
+ info: "text-blue-600 dark:text-blue-400",
124
+ muted: "text-(--theme-muted-text-color)"
125
+ };
126
+ /** A line of text in a variant's colour, for a status that stands beside a
127
+ * dot rather than inside a badge. */
128
+ function statusTextClass(variant) {
129
+ return textColors[variant];
130
+ }
131
+ function StatusBadge({ children, variant = "default", pulse = false, dot = false, size = "sm", className }) {
119
132
  return /* @__PURE__ */ jsxs("span", {
120
133
  className: cx("inline-flex items-center gap-1.5 rounded-full border font-medium", size === "sm" && "px-2 py-0.5 text-[10px]", size === "md" && "px-2.5 py-1 text-xs", variantStyles[variant], className),
121
- children: [pulse && /* @__PURE__ */ jsxs("span", {
122
- className: "relative flex h-2 w-2",
123
- children: [/* @__PURE__ */ jsx("span", { className: cx("absolute inline-flex h-full w-full animate-ping rounded-full opacity-75", pulseColors[variant]) }), /* @__PURE__ */ jsx("span", { className: cx("relative inline-flex h-2 w-2 rounded-full", pulseColors[variant]) })]
124
- }), children]
134
+ children: [
135
+ pulse && /* @__PURE__ */ jsxs("span", {
136
+ className: "relative flex h-2 w-2",
137
+ children: [/* @__PURE__ */ jsx("span", { className: cx("absolute inline-flex h-full w-full animate-ping rounded-full opacity-75", pulseColors[variant]) }), /* @__PURE__ */ jsx("span", { className: cx("relative inline-flex h-2 w-2 rounded-full", pulseColors[variant]) })]
138
+ }),
139
+ dot && !pulse && /* @__PURE__ */ jsx("span", { className: cx("inline-flex h-1.5 w-1.5 rounded-full", pulseColors[variant]) }),
140
+ children
141
+ ]
125
142
  });
126
143
  }
127
144
  function StatusDot({ variant = "default", pulse = false, size = 8, className }) {
@@ -141,4 +158,4 @@ function StatusDot({ variant = "default", pulse = false, size = 8, className })
141
158
  });
142
159
  }
143
160
  //#endregion
144
- export { flattenDisplayOptions as a, isOption as c, filterOption as i, toggleSetMember as l, StatusDot as n, flattenOptions as o, DropdownCaret as r, isCategory as s, StatusBadge as t };
161
+ export { filterOption as a, isCategory as c, DropdownCaret as i, isOption as l, StatusDot as n, flattenDisplayOptions as o, statusTextClass as r, flattenOptions as s, StatusBadge as t, toggleSetMember as u };
@@ -1,7 +1,7 @@
1
1
  import { AngleDownIcon, AngleRightIcon, CaretDownIcon } from "../icons.js";
2
- import { c as useStrings } from "./Provider-CbFVFB7O.js";
2
+ import { u as useStrings } from "./Provider-DHmZ3v9i.js";
3
3
  import { a as truncationTooltipProps, n as TOOLTIP_ID } from "./Tooltip-n9w7Tkt1.js";
4
- import { a as flattenDisplayOptions, c as isOption, i as filterOption, l as toggleSetMember, o as flattenOptions, r as DropdownCaret, s as isCategory } from "./StatusBadge-zvEtK16a.js";
4
+ import { a as filterOption, c as isCategory, i as DropdownCaret, l as isOption, o as flattenDisplayOptions, s as flattenOptions, u as toggleSetMember } from "./StatusBadge-C1Blx_cW.js";
5
5
  import cx from "classnames";
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
7
  import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
@@ -254,6 +254,7 @@ function DropdownHelper({ wrapperClassName = "w-full", className, textBoxClassNa
254
254
  customValueLabel
255
255
  ]);
256
256
  const comboOptions = /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(ComboboxOptions, {
257
+ modal: false,
257
258
  ref: setPopperElement,
258
259
  style: styles["popper"],
259
260
  className: "bg-(--theme-input-bg) border-(--theme-border) text-(--theme-app) max-h-60! empty:invisible border overflow-auto rounded-md shadow-lg focus:outline-hidden sm:text-sm z-9999 data-[popper-reference-hidden=true]:invisible data-[popper-reference-hidden=true]:pointer-events-none",
@@ -368,6 +369,7 @@ function CustomListbox({ options = [], handleOptionSelected, type, allowSearch =
368
369
  const wrapperRef = useRef(null);
369
370
  useOutsideAlerter(wrapperRef);
370
371
  const optionsElement = /* @__PURE__ */ jsxs(ListboxOptions, {
372
+ modal: false,
371
373
  static: true,
372
374
  className: "absolute w-full py-1 z-50 theme-input rounded-md",
373
375
  children: [allowSearch && /* @__PURE__ */ jsx("div", {
@@ -545,6 +547,7 @@ function MultiSelectDropdown({ options, value, onChange, id, "aria-labelledby":
545
547
  }),
546
548
  /* @__PURE__ */ jsx(DropdownCaret, {}),
547
549
  open && /* @__PURE__ */ jsxs(ComboboxOptions, {
550
+ modal: false,
548
551
  ref: setPopperEl,
549
552
  static: true,
550
553
  "data-testid": "msd-list",
@@ -689,9 +692,9 @@ function MaybeCollapsible({ className, initialState, children, title, customized
689
692
  }
690
693
  //#endregion
691
694
  //#region src/components/SectionHeader.tsx
692
- function SectionHeader({ children, className = "" }) {
695
+ function SectionHeader({ children, className = "", size = "md" }) {
693
696
  return /* @__PURE__ */ jsx("div", {
694
- className: cx("w-full text-lg font-semibold", className),
697
+ className: cx("w-full font-semibold", size === "sm" ? "mb-2 text-sm theme-text" : "text-lg", className),
695
698
  children
696
699
  });
697
700
  }
@@ -1,4 +1,4 @@
1
- import { c as useStrings } from "./Provider-CbFVFB7O.js";
1
+ import { u as useStrings } from "./Provider-DHmZ3v9i.js";
2
2
  import { t as Loader } from "./Loader-BXoXTZGc.js";
3
3
  import cx from "classnames";
4
4
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,5 +1,5 @@
1
1
  import { CheckIcon, CopyIcon, EyeIcon, EyeOffIcon } from "../icons.js";
2
- import { c as useStrings, i as useNotify } from "./Provider-CbFVFB7O.js";
2
+ import { i as useNotify, u as useStrings } from "./Provider-DHmZ3v9i.js";
3
3
  import { t as Loader } from "./Loader-BXoXTZGc.js";
4
4
  import { t as SwitchToggleSmall } from "./SwitchToggleSmall-FcsaYSTs.js";
5
5
  import { c as FieldWrapper, n as flattenGroups, r as impureSetValueFromPath } from "./lib-CUTRDpa6.js";
@@ -9,7 +9,7 @@ import { Suspense, lazy, useId, useMemo, useRef, useState } from "react";
9
9
  import { convertToDynamicForm, parseStringsOfObj, parseStringsOfObjReady } from "@parallelworks/workflow-parser";
10
10
  import { useField, useFormikContext } from "formik";
11
11
  //#region src/editor/Editor.tsx
12
- var MonacoEditor = lazy(() => import("./Monaco-BXDH2jVj.js"));
12
+ var MonacoEditor = lazy(() => import("./Monaco-DVyIpyv4.js"));
13
13
  function Editor({ ...props }) {
14
14
  return /* @__PURE__ */ jsx(Suspense, {
15
15
  fallback: /* @__PURE__ */ jsx(Loader, { text: "Loading editor..." }),
@@ -1,14 +1,14 @@
1
1
  import { CheckIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, DisplayIcon, ErrorIcon, FilterIcon, MenuIcon, SearchIcon, XIcon } from "../icons.js";
2
- import { c as useStrings, n as useLink, r as useNavigation } from "./Provider-CbFVFB7O.js";
2
+ import { a as usePersistedState, n as useLink, r as useNavigation, u as useStrings } from "./Provider-DHmZ3v9i.js";
3
3
  import { n as TOOLTIP_ID, r as TooltipInfo } from "./Tooltip-n9w7Tkt1.js";
4
- import { _ as useRowMenuActive, c as PageHeader, p as rowContextMenuProps, u as MoreButton } from "./PageHeader-CoZWF0pJ.js";
5
- import { n as Table } from "./Table-CutP1bRt.js";
4
+ import { _ as useRowMenuActive, c as PageHeader, p as rowContextMenuProps, u as MoreButton } from "./PageHeader-MxuOs9cH.js";
5
+ import { n as Table } from "./Table-C6JdFFiJ.js";
6
6
  import cx from "classnames";
7
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
8
  import { Children, Fragment as Fragment$1, cloneElement, createContext, isValidElement, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
9
+ import { useLocalStorage } from "usehooks-ts";
9
10
  import { createPortal } from "react-dom";
10
11
  import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react";
11
- import { useLocalStorage } from "usehooks-ts";
12
12
  //#region src/list/ActionToolbar.tsx
13
13
  var OverflowMenuContext = createContext(false);
14
14
  /** Omits data-tooltip-content when absent — react-tooltip treats an explicit undefined as an empty tooltip anchor. */
@@ -203,7 +203,7 @@ var listTableFixedProps = {
203
203
  ...listTableProps,
204
204
  className: listTableProps.className.replace("table-auto", "table-fixed")
205
205
  };
206
- function NameCell({ icon, name, subtitle, href, badge }) {
206
+ function NameCell({ icon, name, subtitle, href, badge, title }) {
207
207
  const Link = useLink();
208
208
  const inner = /* @__PURE__ */ jsxs(Fragment, { children: [icon, /* @__PURE__ */ jsxs("div", {
209
209
  className: "min-w-0",
@@ -211,6 +211,7 @@ function NameCell({ icon, name, subtitle, href, badge }) {
211
211
  className: "flex min-w-0 items-center gap-1.5",
212
212
  children: [/* @__PURE__ */ jsx("div", {
213
213
  className: cx(NAME_TEXT, href && "group-hover/namecell:text-(--theme-link)"),
214
+ title,
214
215
  children: name
215
216
  }), badge]
216
217
  }), subtitle && /* @__PURE__ */ jsx("div", {
@@ -401,7 +402,7 @@ function ListPage({ section = "main", shellPadded, embedded = false, breadcrumbs
401
402
  })]
402
403
  });
403
404
  }
404
- function ListRow({ href, items, goTo, openMenu, pinnedActions, selected, selectionActive, onRowSelect, leading, className, children }) {
405
+ function ListRow({ href, onActivate, items, goTo, openMenu, pinnedActions, selected, selectionActive, onRowSelect, leading, className, children }) {
405
406
  const { active: menuActive, openMenu: openRowMenu } = useRowMenuActive(openMenu);
406
407
  const { show: showActions, always: alwaysShowActions, columnClasses } = useContext(RowActionsContext);
407
408
  const styledCells = Children.toArray(children).map((child, i) => {
@@ -417,11 +418,12 @@ function ListRow({ href, items, goTo, openMenu, pinnedActions, selected, selecti
417
418
  return;
418
419
  }
419
420
  if (href) goTo(href);
421
+ else onActivate?.();
420
422
  };
421
423
  const pinnedLabels = new Set((pinnedActions ?? []).map((a) => a.label));
422
424
  const hasOverflowItems = items.some((item) => item.kind !== "divider" && (item.kind === "submenu" || !pinnedLabels.has(item.label)));
423
425
  return /* @__PURE__ */ jsxs("tr", {
424
- className: cx("group", selected ? "bg-blue-500/5" : menuActive ? "theme-hover" : "hover:theme-hover", (href || selectionActive) && "cursor-pointer", className),
426
+ className: cx("group", selected ? "bg-blue-500/5" : menuActive ? "theme-hover" : "hover:theme-hover", (href || onActivate || selectionActive) && "cursor-pointer", className),
425
427
  onClick: handleRowClick,
426
428
  ...rowContextMenuProps(openRowMenu, items),
427
429
  children: [
@@ -518,16 +520,16 @@ function useListView({ storageKey, sharedFiltersKey, columns, groupBys = [], def
518
520
  const defaultOrderByValue = isFavorite ? DEFAULT_ORDER : defaultOrderBy ?? orderBys[0]?.value ?? "";
519
521
  const defaultHiddenKeys = useMemo(() => columns.filter((c) => c.defaultHidden && !c.alwaysVisible).map((c) => c.key), [columns]);
520
522
  const defaultPinnedKeys = useMemo(() => actions.filter((a) => a.defaultPinned).map((a) => a.key), [actions]);
521
- const [hiddenKeys, setHiddenKeys] = useLocalStorage(`${storageKey}:hiddenColumns`, defaultHiddenKeys);
522
- const [pinnedKeys, setPinnedKeys] = useLocalStorage(`${storageKey}:pinnedActions`, defaultPinnedKeys);
523
- const [groupBy, setGroupBy] = useLocalStorage(`${storageKey}:groupBy`, defaultGroupByValue);
524
- const [showColumnHeaders, setShowColumnHeaders] = useLocalStorage(`${storageKey}:showColumnHeaders`, defaultShowColumnHeaders);
525
- const [showActions, setShowActions] = useLocalStorage(`${storageKey}:showActions`, defaultShowActions);
526
- const [alwaysShowActions, setAlwaysShowActions] = useLocalStorage(`${storageKey}:alwaysShowActions`, false);
527
- const [orderBy, setOrderBy] = useLocalStorage(`${storageKey}:orderBy`, defaultOrderByValue);
528
- const [orderDir, setOrderDir] = useLocalStorage(`${storageKey}:orderDir`, "asc");
529
- const [localFilters, setLocalFilters] = useLocalStorage(`${storageKey}:filters`, {});
530
- const [sharedFilters, setSharedFilters] = useLocalStorage(`${sharedFiltersKey ?? storageKey}:sharedFilters`, {});
523
+ const [hiddenKeys, setHiddenKeys] = usePersistedState(`${storageKey}:hiddenColumns`, defaultHiddenKeys);
524
+ const [pinnedKeys, setPinnedKeys] = usePersistedState(`${storageKey}:pinnedActions`, defaultPinnedKeys);
525
+ const [groupBy, setGroupBy] = usePersistedState(`${storageKey}:groupBy`, defaultGroupByValue);
526
+ const [showColumnHeaders, setShowColumnHeaders] = usePersistedState(`${storageKey}:showColumnHeaders`, defaultShowColumnHeaders);
527
+ const [showActions, setShowActions] = usePersistedState(`${storageKey}:showActions`, defaultShowActions);
528
+ const [alwaysShowActions, setAlwaysShowActions] = usePersistedState(`${storageKey}:alwaysShowActions`, false);
529
+ const [orderBy, setOrderBy] = usePersistedState(`${storageKey}:orderBy`, defaultOrderByValue);
530
+ const [orderDir, setOrderDir] = usePersistedState(`${storageKey}:orderDir`, "asc");
531
+ const [localFilters, setLocalFilters] = usePersistedState(`${storageKey}:filters`, {});
532
+ const [sharedFilters, setSharedFilters] = usePersistedState(`${sharedFiltersKey ?? storageKey}:sharedFilters`, {});
531
533
  const sharedKeys = useMemo(() => new Set(facets.filter((f) => f.shared).map((f) => f.key)), [facets]);
532
534
  const filters = useMemo(() => {
533
535
  if (sharedKeys.size === 0) return localFilters;
@@ -1184,7 +1186,7 @@ var LIST_PAGE_SIZES = [
1184
1186
  200
1185
1187
  ];
1186
1188
  /** Footer pager bar for paginated new-nav lists: "{start}–{end} of {total}" with prev/next. Renders nothing for a single page. */
1187
- function ListPager({ page, pageSize, total, onPageChange, pageSizes = LIST_PAGE_SIZES, onPageSizeChange }) {
1189
+ function ListPager({ page, pageSize, total, onPageChange, pageSizes = LIST_PAGE_SIZES, onPageSizeChange, hasNext }) {
1188
1190
  const t = useStrings().list;
1189
1191
  const pageCount = Math.max(1, Math.ceil(total / pageSize));
1190
1192
  const start = page * pageSize + 1;
@@ -1220,7 +1222,7 @@ function ListPager({ page, pageSize, total, onPageChange, pageSizes = LIST_PAGE_
1220
1222
  }), /* @__PURE__ */ jsx("button", {
1221
1223
  type: "button",
1222
1224
  "aria-label": t.paginationNext,
1223
- disabled: page >= pageCount - 1,
1225
+ disabled: hasNext === void 0 ? page >= pageCount - 1 : !hasNext,
1224
1226
  onClick: () => onPageChange(page + 1),
1225
1227
  className: cx(listControlButtonClasses, "disabled:opacity-40 disabled:pointer-events-none"),
1226
1228
  children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: "w-3.5 h-3.5" })
@@ -13,6 +13,7 @@ interface MethodButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
13
13
  label: string;
14
14
  description?: string;
15
15
  href?: string;
16
+ loading?: boolean;
16
17
  }
17
- export declare function MethodButton({ icon, label, description, href, className, ...props }: MethodButtonProps): import("react").JSX.Element;
18
+ export declare function MethodButton({ icon, label, description, href, loading, disabled, className, ...props }: MethodButtonProps): import("react").JSX.Element;
18
19
  export {};
@@ -1,4 +1,5 @@
1
- import type { ReactNode } from 'react';
1
+ import type { Dispatch, ReactNode, SetStateAction } from 'react';
2
+ import type { RunLink } from '../workflow/types.js';
2
3
  export type UIToastId = string | number;
3
4
  export interface UIToastOptions {
4
5
  toastId?: UIToastId;
@@ -321,6 +322,13 @@ export interface UIStrings {
321
322
  readOnlyDescription: string;
322
323
  readOnlyForced: string;
323
324
  };
325
+ time: {
326
+ now: string;
327
+ minutes: (n: number) => string;
328
+ hours: (n: number) => string;
329
+ days: (n: number) => string;
330
+ years: (n: number) => string;
331
+ };
324
332
  list: {
325
333
  copy: (label: string) => string;
326
334
  copied: (label: string) => string;
@@ -380,6 +388,7 @@ export interface RunFileResult {
380
388
  isLoading: boolean;
381
389
  error: unknown;
382
390
  }
391
+ export type PersistedStateHook = <T>(key: string, defaultValue: T) => [T, Dispatch<SetStateAction<T>>];
383
392
  export interface UIData {
384
393
  /**
385
394
  * Host-bound hook resolving a file in a run's artifact tree (logs, scripts).
@@ -406,6 +415,49 @@ export interface UIData {
406
415
  }) => Promise<{
407
416
  error?: string;
408
417
  }>;
418
+ usePersistedState: PersistedStateHook;
419
+ /**
420
+ * Repository completions for `uses:`, bound by the host because the credential
421
+ * lives on the platform. Absent, those completions no-op.
422
+ */
423
+ repoSuggestions?: {
424
+ refs: (repo: string) => Promise<RepoRef[]>;
425
+ files: (repo: string, ref: string, path: string) => Promise<RepoEntry[]>;
426
+ file: (repo: string, ref: string, path: string) => Promise<string>;
427
+ repos: (owner: string) => Promise<RepoSummary[]>;
428
+ gitlabProjects: (search: string, host?: string) => Promise<GitlabProject[]>;
429
+ owners: (provider: 'github' | 'gitlab', search: string, host?: string) => Promise<RepoOwner[]>;
430
+ };
431
+ }
432
+ /** One branch, tag or commit a `uses:` reference can name. */
433
+ export interface RepoRef {
434
+ name: string;
435
+ kind: string;
436
+ detail: string;
437
+ }
438
+ /** One repository in an owner listing. */
439
+ export interface RepoSummary {
440
+ name: string;
441
+ description: string;
442
+ private: boolean;
443
+ }
444
+ /** One account, organization or group a repository can live under. */
445
+ export interface RepoOwner {
446
+ name: string;
447
+ kind: string;
448
+ host?: string | undefined;
449
+ }
450
+ /** One GitLab project, with the server a $host must name to reach it. */
451
+ export interface GitlabProject {
452
+ path: string;
453
+ host: string;
454
+ description: string;
455
+ private: boolean;
456
+ }
457
+ /** One entry in a repository directory listing. */
458
+ export interface RepoEntry {
459
+ path: string;
460
+ type: 'blob' | 'tree';
409
461
  }
410
462
  import type { FieldComponent } from '../form/fieldRegistry.js';
411
463
  import type { PageHeaderCrumb } from './PageHeader.js';
@@ -431,11 +483,12 @@ export interface UISlots {
431
483
  * renders fields inside a Suspense boundary.
432
484
  */
433
485
  formFields?: Record<string, FieldComponent>;
434
- /** Sessions block for a run's sidebar; absent hides the section. */
486
+ /** Links and sessions block for a run's sidebar; absent hides the section. */
435
487
  runSessions?: (args: {
436
488
  runId: string;
437
489
  runNumber: number;
438
490
  runActive: boolean;
491
+ links?: Record<string, RunLink> | undefined;
439
492
  }) => ReactNode;
440
493
  /** Rich read-only YAML viewer; absent falls back to a plain <pre>. */
441
494
  yamlViewer?: (args: {
@@ -488,9 +541,11 @@ export declare function useNavigation(): UINavigation;
488
541
  export declare function useSlots(): UISlots;
489
542
  export declare function useWorkflowJsonResolver(): ((ref: WorkflowJsonRef) => Promise<unknown>) | undefined;
490
543
  export declare function useProvisionCorsRules(): UIData['provisionCorsRules'];
544
+ export declare function useRepoSuggestions(): UIData['repoSuggestions'];
491
545
  export declare function useRunFile(slug: string, path: string | null, options?: {
492
546
  refreshInterval?: number;
493
547
  }): RunFileResult;
548
+ export declare function usePersistedState<T>(key: string, defaultValue: T): [T, Dispatch<SetStateAction<T>>];
494
549
  export declare function UIProvider({ notify, strings, navigation, data, slots, children, }: {
495
550
  notify?: Partial<UINotify>;
496
551
  strings?: {
@@ -1,6 +1,8 @@
1
1
  interface ISectionHeaderProps {
2
2
  className?: string;
3
+ /** 'sm' heads a group inside a page rather than the page's own section. */
4
+ size?: 'md' | 'sm';
3
5
  children: React.ReactNode;
4
6
  }
5
- export default function SectionHeader({ children, className, }: ISectionHeaderProps): import("react").JSX.Element;
7
+ export default function SectionHeader({ children, className, size, }: ISectionHeaderProps): import("react").JSX.Element;
6
8
  export {};
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from 'react';
2
- type BadgeVariant = 'default' | 'success' | 'warning' | 'error' | 'info' | 'muted';
2
+ export type BadgeVariant = 'default' | 'success' | 'warning' | 'error' | 'info' | 'muted';
3
3
  interface StatusBadgeProps {
4
4
  /** Badge content */
5
5
  children: ReactNode;
@@ -7,12 +7,17 @@ interface StatusBadgeProps {
7
7
  variant?: BadgeVariant;
8
8
  /** Show animated pulse indicator */
9
9
  pulse?: boolean;
10
+ /** Show a still indicator in the variant's colour */
11
+ dot?: boolean;
10
12
  /** Size */
11
13
  size?: 'sm' | 'md';
12
14
  /** Additional class */
13
15
  className?: string;
14
16
  }
15
- export declare function StatusBadge({ children, variant, pulse, size, className, }: StatusBadgeProps): import("react").JSX.Element;
17
+ /** A line of text in a variant's colour, for a status that stands beside a
18
+ * dot rather than inside a badge. */
19
+ export declare function statusTextClass(variant: BadgeVariant): string;
20
+ export declare function StatusBadge({ children, variant, pulse, dot, size, className, }: StatusBadgeProps): import("react").JSX.Element;
16
21
  interface StatusDotProps {
17
22
  variant?: BadgeVariant;
18
23
  pulse?: boolean;
@@ -0,0 +1,15 @@
1
+ import { DateTime } from 'luxon';
2
+ export declare function compactRelativeMinutes(minutes: number, t: {
3
+ now: string;
4
+ minutes: (n: number) => string;
5
+ hours: (n: number) => string;
6
+ days: (n: number) => string;
7
+ years: (n: number) => string;
8
+ }): string;
9
+ /** A clock that re-reads itself, so "5m" ages while the page sits open, with
10
+ * the abbreviated form a row has room for. */
11
+ export declare function useRelativeTime(): {
12
+ now: DateTime;
13
+ compact: (iso: string | undefined) => string;
14
+ compactMinutes: (minutes: number) => string;
15
+ };
@@ -1,2 +1,2 @@
1
- import { a as EditorField, i as FormCodePanel, n as defineEditorThemes, o as Editor, r as getThemeName, t as setupMonacoWorkers } from "../chunks/editor-Ko3zX22Y.js";
1
+ import { a as EditorField, i as FormCodePanel, n as defineEditorThemes, o as Editor, r as getThemeName, t as setupMonacoWorkers } from "../chunks/editor-mNznodJN.js";
2
2
  export { Editor, EditorField, FormCodePanel, defineEditorThemes, getThemeName, setupMonacoWorkers };
@@ -0,0 +1,97 @@
1
+ /** Pure helpers behind the gitlab/ uses: completion, kept out of Monaco.tsx so they can
2
+ * be tested without loading the editor. */
3
+ export interface MonacoRange {
4
+ startLineNumber: number;
5
+ startColumn: number;
6
+ endLineNumber: number;
7
+ endColumn: number;
8
+ }
9
+ interface LineReader {
10
+ getLineContent: (line: number) => string;
11
+ }
12
+ /** Splits GROUP/PROJECT@REF; the ref is null when no @ has been typed yet. */
13
+ export declare function splitAtRef(typed: string): [string, string | null];
14
+ /** What to insert for a chosen project, given what is already typed. A typed prefix is
15
+ * completed like the github one; a mid-path match has to replace what is there. */
16
+ export declare function projectInsert(typed: string, path: string): {
17
+ text: string;
18
+ replace: boolean;
19
+ };
20
+ /** The $host the step pins, or undefined. Quotes and a trailing comment are stripped,
21
+ * so a pinned host is recognised however it was written. */
22
+ export declare function stepHostAbove(model: LineReader, usesLine: number, maxLines?: number): string | undefined;
23
+ /** A YAML scalar as written: quotes removed, a trailing comment dropped. */
24
+ export declare function unquoteScalar(raw: string): string;
25
+ /** A missing $host already means gitlab.com, so naming it adds nothing. */
26
+ export declare const DEFAULT_GITLAB_HOST = "gitlab.com";
27
+ /** How to add `$host` to a step. Always an edit of its own: monaco re-indents a
28
+ * multi-line insertText, and the cursor has to stay after the project. */
29
+ export declare function hostCompletion(model: LineReader & {
30
+ getLineCount: () => number;
31
+ }, usesLine: number, host: string): {
32
+ appendText: string;
33
+ edits: {
34
+ range: MonacoRange;
35
+ text: string;
36
+ }[];
37
+ };
38
+ /** A trailing slash or @ after a known project is the ref separator, the way a trailing
39
+ * slash is for github/OWNER/REPO. The slash is swapped for @ when a ref is accepted. */
40
+ export declare function refTrigger(typed: string, knownPaths: string[]): {
41
+ projectPath: string;
42
+ replaceSlash: boolean;
43
+ } | undefined;
44
+ /** What to ask the platform for when completing a project path: everything before the
45
+ * first slash. Typing deeper then filters that answer instead of asking again. */
46
+ export declare function projectSearchKey(typed: string): string;
47
+ /** Where a `$yaml:`/`$thumbnail:` value's files come from: the nearest `uses:` above,
48
+ * addressed the way the platform's file endpoint expects. A gitlab/ path carries no
49
+ * host, so the step's $host supplies it. */
50
+ export declare function usesFileSource(model: LineReader, fromLine: number, maxLines?: number): {
51
+ repo: string;
52
+ ref: string;
53
+ } | undefined;
54
+ /** One entry of a repository tree. */
55
+ export interface RepoTreeEntry {
56
+ path: string;
57
+ type: 'tree' | 'blob';
58
+ }
59
+ declare const FILE_VALUE_KINDS: {
60
+ $yaml: {
61
+ detail: string;
62
+ keep: (path: string) => boolean;
63
+ };
64
+ $thumbnail: {
65
+ detail: string;
66
+ keep: (path: string) => boolean;
67
+ };
68
+ };
69
+ /** Which of the two file-valued keys the cursor sits on, if either. */
70
+ export declare function fileValueKey(textUntilCursor: string): keyof typeof FILE_VALUE_KINDS | undefined;
71
+ /** The files worth offering for a `$yaml:`/`$thumbnail:` value, from whichever
72
+ * repository the step's `uses:` names. Empty when the line is not one of those keys,
73
+ * no `uses:` is above it, or the tree cannot be read. */
74
+ export declare function fileValueSuggestions(model: LineReader, line: number, textUntilCursor: string, loadTree: (repo: string, ref: string) => Promise<RepoTreeEntry[]>): Promise<{
75
+ path: string;
76
+ detail: string;
77
+ }[]>;
78
+ /** Every $-prefixed key a step already sets, mapped to its value. Nothing already
79
+ * written is offered a second time, and an existing one must not suppress the rest. */
80
+ export declare function specialKeysNearUses(model: LineReader & {
81
+ getLineCount: () => number;
82
+ }, usesLineNumber: number): Map<string, string>;
83
+ /** The same, for a with: block located by its own indentation. */
84
+ export declare function specialKeysInWithBlock(model: LineReader & {
85
+ getLineCount: () => number;
86
+ }, withLineNumber: number): Map<string, string>;
87
+ /** Text to be inserted as a snippet, with the characters monaco's snippet parser would
88
+ * otherwise read as placeholders escaped. */
89
+ export declare function snippetLiteral(text: string): string;
90
+ /** How a completion inserts its text when a `$host` block has to travel with it. The
91
+ * cursor stop is what keeps the caret on the uses: line rather than after the block;
92
+ * without a block there is nothing to escape and it stays a plain insert. */
93
+ export declare function completionInsert(text: string, appendText: string): {
94
+ insertText: string;
95
+ isSnippet: boolean;
96
+ };
97
+ export {};