@parallelworks/ui 0.5.3 → 0.7.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.
@@ -73,10 +73,15 @@ export interface CreateModalSelectOption {
73
73
  icon?: ReactNode;
74
74
  /** Category header to group this option under in the dropdown. */
75
75
  group?: string;
76
+ /** Hidden behind the group's toggle row until the group is expanded. */
77
+ collapsible?: boolean;
76
78
  }
79
+ /** Callers gating collapse on list size must use this same threshold so
80
+ * collapsed rows always come with a filter to find them. */
81
+ export declare const CREATE_MODAL_SELECT_FILTER_THRESHOLD = 8;
77
82
  /** Compact property pill with a dropdown of options. Options may carry leading
78
83
  * icons and group headers (e.g. Clusters vs Kubernetes). */
79
- export declare function CreateModalSelect({ label, icon, value, options, onChange, disabled, disabledHint, tooltip, }: {
84
+ export declare function CreateModalSelect({ label, icon, value, options, onChange, disabled, disabledHint, tooltip, collapsedLabel, }: {
80
85
  label: string;
81
86
  icon?: ReactNode;
82
87
  value?: string;
@@ -88,5 +93,7 @@ export declare function CreateModalSelect({ label, icon, value, options, onChang
88
93
  disabledHint?: string;
89
94
  /** Help text surfaced on hover (explains what the pill selects). */
90
95
  tooltip?: string;
96
+ /** Label for the toggle row hiding a group's collapsible options. */
97
+ collapsedLabel?: (count: number) => string;
91
98
  }): import("react").JSX.Element;
92
99
  export {};
@@ -19,6 +19,8 @@ export interface IProps {
19
19
  loading?: boolean;
20
20
  allowCustomValue?: boolean;
21
21
  customValueLabel?: string;
22
+ /** Label for the toggle row hiding a category's collapsible options. */
23
+ collapsedLabel?: (count: number) => string;
22
24
  portalEl?: HTMLElement;
23
25
  }
24
26
  export default function Dropdown(props: IProps): import("react").JSX.Element;
@@ -69,6 +69,22 @@ export interface UIStrings {
69
69
  showDebug: string;
70
70
  filterLogs: string;
71
71
  download: string;
72
+ beginningOfLog: string;
73
+ loadingOlderEntries: string;
74
+ loadOlderEntries: string;
75
+ timeRange: string;
76
+ timeRangeActive: string;
77
+ timeRangeOn: string;
78
+ filterByTimeRange: string;
79
+ timeRangeHelp: string;
80
+ timeRangeFrom: string;
81
+ timeRangeTo: string;
82
+ apply: string;
83
+ clear: string;
84
+ invalidStartTime: string;
85
+ invalidEndTime: string;
86
+ endBeforeStart: string;
87
+ endInFuture: string;
72
88
  };
73
89
  dag: {
74
90
  workflow: string;
@@ -332,6 +348,7 @@ export interface UIStrings {
332
348
  filter: string;
333
349
  clear: string;
334
350
  pager: (start: number, end: number, total: number) => string;
351
+ perPage: string;
335
352
  paginationPrevious: string;
336
353
  paginationNext: string;
337
354
  paginationShowing: (name: string, start: number | string, end: number | string) => ReactNode;
@@ -6,6 +6,8 @@ export interface IOptions<T = unknown> {
6
6
  disabled?: boolean;
7
7
  icon?: React.ReactNode;
8
8
  description?: string;
9
+ /** Hidden behind the category's toggle row until the category is expanded. */
10
+ collapsible?: boolean;
9
11
  }
10
12
  export interface ICategory<T = unknown> {
11
13
  category?: string;
@@ -15,14 +17,19 @@ export declare function isCategory<T>(option: ICategory<T> | IOptions<T> | strin
15
17
  export declare function isOption<T>(option: ICategory<T> | IOptions<T> | string): option is IOptions<T>;
16
18
  export declare function flattenOptions<T>(options: (ICategory<T> | IOptions<T> | string)[]): (IOptions<T> | ICategory<T>)[];
17
19
  export declare function filterOption<T>(option: IOptions<T>, query: string): boolean;
18
- export type FlatOption = IOptions & {
20
+ export declare function toggleSetMember<T>(set: ReadonlySet<T>, member: T): Set<T>;
21
+ export type FlatDisplayOption = IOptions & {
19
22
  category?: boolean;
23
+ /** Set on a category's toggle row to the category it expands/collapses. */
24
+ toggle?: string;
20
25
  };
21
- export declare function flattenFilteredOptions({ options, query, selectedValue, allowCustomValue, customValueLabel, }: {
26
+ export declare function flattenDisplayOptions({ options, query, expandedCategories, collapsedLabel, selectedValue, allowCustomValue, customValueLabel, }: {
22
27
  options: (ICategory | IOptions)[];
23
28
  query: string;
29
+ expandedCategories?: ReadonlySet<string>;
30
+ collapsedLabel?: (count: number) => string;
24
31
  selectedValue?: unknown;
25
32
  allowCustomValue?: boolean;
26
33
  customValueLabel?: string;
27
- }): FlatOption[];
34
+ }): FlatDisplayOption[];
28
35
  export declare function DropdownCaret(): React.JSX.Element;
@@ -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-4ZuZruLz.js";
1
+ import { a as EditorField, i as FormCodePanel, n as defineEditorThemes, o as Editor, r as getThemeName, t as setupMonacoWorkers } from "../chunks/editor-BH81U5-C.js";
2
2
  export { Editor, EditorField, FormCodePanel, defineEditorThemes, getThemeName, setupMonacoWorkers };
@@ -1,12 +1,12 @@
1
1
  import { AccessIcon, AlertIcon, AngleDownIcon, AngleRightIcon, AngleUpIcon, ChevronLeftIcon, ChevronRightIcon, ClipboardIcon, CloseIcon, CloudUploadIcon, CollapseIcon, CustomizeIcon, DiskIcon, DocumentIcon, DownloadFileIcon, ExpandIcon, ExpandVerticalIcon, EyeIcon, FileIcon, FileTextIcon, FolderIcon, FolderOpenIcon, ImageIcon, LinkIcon, LockIcon, MinimizeIcon, MusicIcon, PdfIcon, RefreshIcon, ShareIcon, SpreadsheetIcon, SuccessIcon, TrashIcon, UploadIcon, UserIcon, VideoIcon, ZipIcon, ZoomInIcon, ZoomOutIcon } from "../icons.js";
2
- import { d as Markdown, f as CopyCodeBlock, m as Callout, p as CopyToClipboard, s as ConfirmModal, t as CreateModal } from "../chunks/CreateModal-DEvWkdFl.js";
3
- import { a as useProvisionCorsRules, c as useStrings, i as useNotify, s as useSlots } from "../chunks/Provider-BIL5347a.js";
2
+ import { c as ConfirmModal, f as Markdown, h as Callout, m as CopyToClipboard, n as CreateModal, p as CopyCodeBlock } from "../chunks/CreateModal-Lkl3Eek4.js";
3
+ import { a as useProvisionCorsRules, c as useStrings, i as useNotify, s as useSlots } from "../chunks/Provider-CfFV-vc5.js";
4
4
  import { n as TOOLTIP_ID, r as TooltipInfo } from "../chunks/Tooltip-WgbiC_nd.js";
5
+ import { t as StatusBadge } from "../chunks/StatusBadge-P6C2tv7Z.js";
5
6
  import { t as Loader } from "../chunks/Loader-BXoXTZGc.js";
6
- import { d as copySubmenu, g as useRowMenuActive, h as useRowMenu, l as MoreButton, u as assembleRowMenu } from "../chunks/PageHeader-CSo3LVId.js";
7
- import { t as StatusBadge } from "../chunks/StatusBadge-DXp4zkSd.js";
7
+ import { d as copySubmenu, g as useRowMenuActive, h as useRowMenu, l as MoreButton, u as assembleRowMenu } from "../chunks/PageHeader-BKYjvH2E.js";
8
8
  import { n as Table } from "../chunks/Table-ByziK93x.js";
9
- import { D as listTableFixedProps, O as listTableProps, S as ListSkeleton, T as RowSelectCheckbox, b as ListActionsHeader } from "../chunks/list-CgzqVlao.js";
9
+ import { C as ListSkeleton, E as RowSelectCheckbox, O as listTableFixedProps, k as listTableProps, x as ListActionsHeader } from "../chunks/list-BhZVliAv.js";
10
10
  import cx from "classnames";
11
11
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
12
  import { Suspense, createContext, lazy, memo, use, useCallback, useEffect, useEffectEvent, useId, useLayoutEffect, useMemo, useRef, useState, useTransition } from "react";
@@ -2515,7 +2515,7 @@ function useObjectText(url, { enabled, maxBytes, maxLines, knownSize, cacheKey,
2515
2515
  }
2516
2516
  //#endregion
2517
2517
  //#region src/file-explorer/FilePreview/Viewer.tsx
2518
- var CodePreviewEditor = lazy(() => import("../chunks/CodePreviewEditor-Uij_Npp4.js"));
2518
+ var CodePreviewEditor = lazy(() => import("../chunks/CodePreviewEditor-hwjZof0M.js"));
2519
2519
  var PREVIEW_MARKDOWN_CONTROLS = {
2520
2520
  table: { fullscreen: false },
2521
2521
  mermaid: { fullscreen: false }
@@ -1,17 +1,17 @@
1
1
  import { AngleRightIcon, EyeIcon, EyeOffIcon, SuccessCheckmark, TrashIcon } from "../icons.js";
2
- import { c as useStrings, s as useSlots } from "../chunks/Provider-BIL5347a.js";
2
+ import { c as useStrings, s as useSlots } from "../chunks/Provider-CfFV-vc5.js";
3
3
  import { n as TOOLTIP_ID, r as TooltipInfo } from "../chunks/Tooltip-WgbiC_nd.js";
4
- import { a as UncontrolledCollapsiblePanel, c as Dropdown, f as isCategory, n as SectionHeader, o as MultiSelectDropdown, r as ControlledCollapsiblePanel, s as CustomListbox, t as SwitchToggle } from "../chunks/SwitchToggle-D_H1iJEe.js";
4
+ import { n as StatusDot, s as isCategory } from "../chunks/StatusBadge-P6C2tv7Z.js";
5
+ import { a as UncontrolledCollapsiblePanel, c as Dropdown, n as SectionHeader, o as MultiSelectDropdown, r as ControlledCollapsiblePanel, s as CustomListbox, t as SwitchToggle } from "../chunks/SwitchToggle-ByKNCE2m.js";
5
6
  import { t as Loader } from "../chunks/Loader-BXoXTZGc.js";
6
- import { n as StatusDot } from "../chunks/StatusBadge-DXp4zkSd.js";
7
7
  import { n as Table } from "../chunks/Table-ByziK93x.js";
8
8
  import { a as resolvedFlag, c as FieldWrapper, i as initializeValues, n as flattenGroups, o as applySecondaryField, r as impureSetValueFromPath, s as findSecondaryOption, t as enforceOneMustBeTrue } from "../chunks/lib-DTgL8klR.js";
9
9
  import cx from "classnames";
10
10
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
11
  import React, { Suspense, createContext, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
12
12
  import deepEqual from "fast-deep-equal";
13
- import { callWhenParserInitialized, convertToDynamicForm, extractInputDeps, initializeParseStringsOfObj, parseStringsOfObj, parseStringsOfObjReady } from "@parallelworks/workflow-parser";
14
13
  import { Duration } from "luxon";
14
+ import { callWhenParserInitialized, convertToDynamicForm, extractInputDeps, initializeParseStringsOfObj, parseStringsOfObj, parseStringsOfObjReady } from "@parallelworks/workflow-parser";
15
15
  import { Field, Form, Formik, getIn, useField, useFormikContext } from "formik";
16
16
  import { NumericFormat } from "react-number-format";
17
17
  //#region src/form/utils/getValueUsingPath.ts
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ export { default as DescriptionList } from './components/DescriptionList.js';
6
6
  export { DescriptionListItem } from './components/DescriptionList.js';
7
7
  export { default as Dropdown } from './components/Dropdown.js';
8
8
  export type { IProps as DropdownProps } from './components/Dropdown.js';
9
- export { DropdownCaret, filterOption, flattenOptions, isCategory, isOption, } from './components/dropdownUtils.js';
9
+ export { DropdownCaret, filterOption, flattenOptions, isCategory, isOption, toggleSetMember, } from './components/dropdownUtils.js';
10
10
  export type { ICategory, IOptions } from './components/dropdownUtils.js';
11
11
  export { default as EmptyState } from './components/EmptyState.js';
12
12
  export { default as CustomListbox } from './components/Listbox.js';
@@ -15,7 +15,7 @@ export { default as Markdown } from './components/Markdown.js';
15
15
  export type { MarkdownProps } from './components/Markdown.js';
16
16
  export { BareModal, modalPanelClasses } from './components/BareModal.js';
17
17
  export { ConfirmModal } from './components/ConfirmModal.js';
18
- export { CreateModal, CreateModalAutocompleteField, CreateModalField, CreateModalSelect, type CreateModalSelectOption, CreateModalSwitch, CreateModalTextarea, } from './components/CreateModal.js';
18
+ export { CREATE_MODAL_SELECT_FILTER_THRESHOLD, CreateModal, CreateModalAutocompleteField, CreateModalField, CreateModalSelect, type CreateModalSelectOption, CreateModalSwitch, CreateModalTextarea, } from './components/CreateModal.js';
19
19
  export { AddMenu, type HeaderAddMenuItem } from './components/AddMenu.js';
20
20
  export { dangerButtonClasses, ghostButtonClasses, primaryButtonClasses, } from './components/ghostButton.js';
21
21
  export { default as MultiSelectDropdown } from './components/MultiSelect.js';
package/dist/index.js CHANGED
@@ -1,16 +1,16 @@
1
1
  import { DEFAULT_PRESET, THEME_PRESETS, THEME_TOKENS, applyTheme, contrastRatio, deriveTheme, isDarkColor } from "./theme/index.js";
2
2
  import { AlertIcon, AngleRightIcon, EyeIcon, EyeOffIcon, FilterIcon, LoaderIcon, SortDownIcon, SortUnsortedIcon, SortUpIcon } from "./icons.js";
3
- import { a as CreateModalSwitch, c as dangerButtonClasses, d as Markdown, f as CopyCodeBlock, i as CreateModalSelect, l as ghostButtonClasses, m as Callout, n as CreateModalAutocompleteField, o as CreateModalTextarea, p as CopyToClipboard, r as CreateModalField, s as ConfirmModal, t as CreateModal, u as primaryButtonClasses } from "./chunks/CreateModal-DEvWkdFl.js";
4
- import { a as useProvisionCorsRules, c as useStrings, i as useNotify, l as useWorkflowJsonResolver, n as useLink, o as useRunFile, r as useNavigation, s as useSlots, t as UIProvider } from "./chunks/Provider-BIL5347a.js";
3
+ import { a as CreateModalSelect, c as ConfirmModal, d as primaryButtonClasses, f as Markdown, h as Callout, i as CreateModalField, l as dangerButtonClasses, m as CopyToClipboard, n as CreateModal, o as CreateModalSwitch, p as CopyCodeBlock, r as CreateModalAutocompleteField, s as CreateModalTextarea, t as CREATE_MODAL_SELECT_FILTER_THRESHOLD, u as ghostButtonClasses } from "./chunks/CreateModal-Lkl3Eek4.js";
4
+ import { a as useProvisionCorsRules, c as useStrings, i as useNotify, l as useWorkflowJsonResolver, n as useLink, o as useRunFile, r as useNavigation, s as useSlots, t as UIProvider } from "./chunks/Provider-CfFV-vc5.js";
5
5
  import { i as formatTooltipContent, n as TOOLTIP_ID, r as TooltipInfo, t as GlobalTooltip } from "./chunks/Tooltip-WgbiC_nd.js";
6
- import { a as UncontrolledCollapsiblePanel, c as Dropdown, d as flattenOptions, f as isCategory, i as MaybeCollapsible, l as DropdownCaret, n as SectionHeader, o as MultiSelectDropdown, p as isOption, r as ControlledCollapsiblePanel, s as CustomListbox, t as SwitchToggle, u as filterOption } from "./chunks/SwitchToggle-D_H1iJEe.js";
6
+ import { c as isOption, i as filterOption, l as toggleSetMember, n as StatusDot, o as flattenOptions, r as DropdownCaret, s as isCategory, t as StatusBadge } from "./chunks/StatusBadge-P6C2tv7Z.js";
7
+ import { a as UncontrolledCollapsiblePanel, c as Dropdown, i as MaybeCollapsible, n as SectionHeader, o as MultiSelectDropdown, r as ControlledCollapsiblePanel, s as CustomListbox, t as SwitchToggle } from "./chunks/SwitchToggle-ByKNCE2m.js";
7
8
  import { t as Loader } from "./chunks/Loader-BXoXTZGc.js";
8
9
  import { n as modalPanelClasses, t as BareModal } from "./chunks/BareModal-DBa84JZO.js";
9
- import { S as AddMenu, _ as BreadcrumbProvider, a as HeaderAddButton, b as useHideBreadcrumbs, c as PageHeader, i as HeaderActionMenu, n as BreadcrumbsRightAction, o as HeaderEditButton, r as BreadcrumbsTitleAction, s as HeaderRowMenu, t as BreadcrumbsActionButton, v as useBreadcrumb, x as useSetBreadcrumbs, y as useClearBreadcrumbs } from "./chunks/PageHeader-CSo3LVId.js";
10
+ import { S as AddMenu, _ as BreadcrumbProvider, a as HeaderAddButton, b as useHideBreadcrumbs, c as PageHeader, i as HeaderActionMenu, n as BreadcrumbsRightAction, o as HeaderEditButton, r as BreadcrumbsTitleAction, s as HeaderRowMenu, t as BreadcrumbsActionButton, v as useBreadcrumb, x as useSetBreadcrumbs, y as useClearBreadcrumbs } from "./chunks/PageHeader-BKYjvH2E.js";
10
11
  import { t as useCssIsDark } from "./chunks/useCssIsDark-DHr05dG2.js";
11
12
  import { t as Indicator } from "./chunks/Indicator-hBSIxOPK.js";
12
13
  import { t as SwitchToggleSmall } from "./chunks/SwitchToggleSmall-FcsaYSTs.js";
13
- import { n as StatusDot, t as StatusBadge } from "./chunks/StatusBadge-DXp4zkSd.js";
14
14
  import { n as Table, t as CompactTable } from "./chunks/Table-ByziK93x.js";
15
15
  import cx from "classnames";
16
16
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -536,4 +536,4 @@ function SettingModeOption({ name, checked, onSelect, label, description, isFirs
536
536
  });
537
537
  }
538
538
  //#endregion
539
- export { AddMenu, BareModal, BreadcrumbProvider, BreadcrumbsActionButton, BreadcrumbsRightAction, BreadcrumbsTitleAction, Button, Callout, Card, CardHeader, CompactTable, ConfirmModal, ControlledCollapsiblePanel, CopyCodeBlock, CopyToClipboard, CreateModal, CreateModalAutocompleteField, CreateModalField, CreateModalSelect, CreateModalSwitch, CreateModalTextarea, CustomListbox, DEFAULT_PRESET, DescriptionList, DescriptionListItem, Dropdown, DropdownCaret, EmptyState, Filter, FilterPill, GlobalTooltip, HeaderActionMenu, HeaderAddButton, HeaderEditButton, HeaderRowMenu, IconButton, Indicator, Input, LetterBadge, Loader, Markdown, MaybeCollapsible, MethodButton, MultiSelectDropdown, Overview, PageHeader, Pagination, SectionHeader, SettingModeOption, SettingRow, SettingSection, SettingsCard, SettingsGroup, Sort, StatusBadge, StatusDot, SwitchToggle, SwitchToggleSmall, THEME_PRESETS, THEME_TOKENS, TOOLTIP_ID, Table, Textarea, Toggle, TooltipInfo, UIProvider, UncontrolledCollapsiblePanel, applyTheme, contrastRatio, dangerButtonClasses, deriveTheme, filterOption, flattenOptions, formatTooltipContent, ghostButtonClasses, inputClasses, isCategory, isDarkColor, isOption, modalPanelClasses, primaryButtonClasses, useBreadcrumb, useClearBreadcrumbs, useCssIsDark, useHideBreadcrumbs, useNavigation, useNotify, useProvisionCorsRules, useRunFile, useSetBreadcrumbs, useSlots, useStrings, useWorkflowJsonResolver };
539
+ export { AddMenu, BareModal, BreadcrumbProvider, BreadcrumbsActionButton, BreadcrumbsRightAction, BreadcrumbsTitleAction, Button, CREATE_MODAL_SELECT_FILTER_THRESHOLD, Callout, Card, CardHeader, CompactTable, ConfirmModal, ControlledCollapsiblePanel, CopyCodeBlock, CopyToClipboard, CreateModal, CreateModalAutocompleteField, CreateModalField, CreateModalSelect, CreateModalSwitch, CreateModalTextarea, CustomListbox, DEFAULT_PRESET, DescriptionList, DescriptionListItem, Dropdown, DropdownCaret, EmptyState, Filter, FilterPill, GlobalTooltip, HeaderActionMenu, HeaderAddButton, HeaderEditButton, HeaderRowMenu, IconButton, Indicator, Input, LetterBadge, Loader, Markdown, MaybeCollapsible, MethodButton, MultiSelectDropdown, Overview, PageHeader, Pagination, SectionHeader, SettingModeOption, SettingRow, SettingSection, SettingsCard, SettingsGroup, Sort, StatusBadge, StatusDot, SwitchToggle, SwitchToggleSmall, THEME_PRESETS, THEME_TOKENS, TOOLTIP_ID, Table, Textarea, Toggle, TooltipInfo, UIProvider, UncontrolledCollapsiblePanel, applyTheme, contrastRatio, dangerButtonClasses, deriveTheme, filterOption, flattenOptions, formatTooltipContent, ghostButtonClasses, inputClasses, isCategory, isDarkColor, isOption, modalPanelClasses, primaryButtonClasses, toggleSetMember, useBreadcrumb, useClearBreadcrumbs, useCssIsDark, useHideBreadcrumbs, useNavigation, useNotify, useProvisionCorsRules, useRunFile, useSetBreadcrumbs, useSlots, useStrings, useWorkflowJsonResolver };
@@ -1,7 +1,12 @@
1
+ /** Page sizes offered by the pager's per-page select; first entry is the default. */
2
+ export declare const LIST_PAGE_SIZES: readonly [number, ...number[]];
1
3
  /** Footer pager bar for paginated new-nav lists: "{start}–{end} of {total}" with prev/next. Renders nothing for a single page. */
2
- export declare function ListPager({ page, pageSize, total, onPageChange, }: {
4
+ export declare function ListPager({ page, pageSize, total, onPageChange, pageSizes, onPageSizeChange, }: {
3
5
  page: number;
4
6
  pageSize: number;
5
7
  total: number;
6
8
  onPageChange: (page: number) => void;
9
+ /** Selectable page sizes; shown only when onPageSizeChange is provided. */
10
+ pageSizes?: readonly number[];
11
+ onPageSizeChange?: (size: number) => void;
7
12
  }): import("react").JSX.Element | null;
@@ -5,6 +5,6 @@ export { actionsColumnWidth, GroupRow, ListActionsHeader, ListError, ListSkeleto
5
5
  export { DEFAULT_ORDER, FAVORITE_ORDER, useListView, type ActionDef, type ColumnDef, type FilterFacet, type ListView, type ListViewChrome, type ListViewConfig, type OrderByOption, } from './listView.js';
6
6
  export { ListCount, ListSearchControl, useListSearch, type ListSearch, } from './listSearch.js';
7
7
  export { listControlButtonClasses, ListDisplayMenu, ListFilterMenu, } from './ListViewControls.js';
8
- export { ListPager } from './ListPager.js';
8
+ export { LIST_PAGE_SIZES, ListPager } from './ListPager.js';
9
9
  export { HoverCardSurface, useHoverCard } from './HoverCard.js';
10
10
  export { isTextEntryContext } from './textEntry.js';
@@ -1,3 +1,3 @@
1
- import { d as copySubmenu, f as rowContextMenuProps, g as useRowMenuActive, h as useRowMenu, l as MoreButton, m as toolbarActionToRowAction, p as toRowMenuItem, u as assembleRowMenu } from "../chunks/PageHeader-CSo3LVId.js";
2
- import { A as ToolbarButton, C as NAME_MIN_WIDTH_PX, D as listTableFixedProps, E as actionsColumnWidth, M as stickyTabsUnderHeaderClasses, N as stickyToolbarClasses, O as listTableProps, S as ListSkeleton, T as RowSelectCheckbox, _ as ListRowActionsProvider, a as ListSearchControl, b as ListActionsHeader, c as ListFilterMenu, d as DEFAULT_ORDER, f as FAVORITE_ORDER, g as ListRow, h as ListPage, i as ListCount, j as stickyTabsClasses, k as ActionToolbar, l as listControlButtonClasses, m as ListColumns, n as useHoverCard, o as useListSearch, p as useListView, r as ListPager, s as ListDisplayMenu, t as HoverCardSurface, u as isTextEntryContext, v as useListNavigate, w as NameCell, x as ListError, y as GroupRow } from "../chunks/list-CgzqVlao.js";
3
- export { ActionToolbar, DEFAULT_ORDER, FAVORITE_ORDER, GroupRow, HoverCardSurface, ListActionsHeader, ListColumns, ListCount, ListDisplayMenu, ListError, ListFilterMenu, ListPage, ListPager, ListRow, ListRowActionsProvider, ListSearchControl, ListSkeleton, MoreButton, NAME_MIN_WIDTH_PX, NameCell, RowSelectCheckbox, ToolbarButton, actionsColumnWidth, assembleRowMenu, copySubmenu, isTextEntryContext, listControlButtonClasses, listTableFixedProps, listTableProps, rowContextMenuProps, stickyTabsClasses, stickyTabsUnderHeaderClasses, stickyToolbarClasses, toRowMenuItem, toolbarActionToRowAction, useHoverCard, useListNavigate, useListSearch, useListView, useRowMenu, useRowMenuActive };
1
+ import { d as copySubmenu, f as rowContextMenuProps, g as useRowMenuActive, h as useRowMenu, l as MoreButton, m as toolbarActionToRowAction, p as toRowMenuItem, u as assembleRowMenu } from "../chunks/PageHeader-BKYjvH2E.js";
2
+ import { A as ActionToolbar, C as ListSkeleton, D as actionsColumnWidth, E as RowSelectCheckbox, M as stickyTabsClasses, N as stickyTabsUnderHeaderClasses, O as listTableFixedProps, P as stickyToolbarClasses, S as ListError, T as NameCell, _ as ListRow, a as ListCount, b as GroupRow, c as ListDisplayMenu, d as isTextEntryContext, f as DEFAULT_ORDER, g as ListPage, h as ListColumns, i as ListPager, j as ToolbarButton, k as listTableProps, l as ListFilterMenu, m as useListView, n as useHoverCard, o as ListSearchControl, p as FAVORITE_ORDER, r as LIST_PAGE_SIZES, s as useListSearch, t as HoverCardSurface, u as listControlButtonClasses, v as ListRowActionsProvider, w as NAME_MIN_WIDTH_PX, x as ListActionsHeader, y as useListNavigate } from "../chunks/list-BhZVliAv.js";
3
+ export { ActionToolbar, DEFAULT_ORDER, FAVORITE_ORDER, GroupRow, HoverCardSurface, LIST_PAGE_SIZES, ListActionsHeader, ListColumns, ListCount, ListDisplayMenu, ListError, ListFilterMenu, ListPage, ListPager, ListRow, ListRowActionsProvider, ListSearchControl, ListSkeleton, MoreButton, NAME_MIN_WIDTH_PX, NameCell, RowSelectCheckbox, ToolbarButton, actionsColumnWidth, assembleRowMenu, copySubmenu, isTextEntryContext, listControlButtonClasses, listTableFixedProps, listTableProps, rowContextMenuProps, stickyTabsClasses, stickyTabsUnderHeaderClasses, stickyToolbarClasses, toRowMenuItem, toolbarActionToRowAction, useHoverCard, useListNavigate, useListSearch, useListView, useRowMenu, useRowMenuActive };
@@ -1,4 +1,4 @@
1
- export declare function LogViewer({ log, height, width, roundedBottom, lineNumbers, additionalLeftBarComponents, additionalRightBarComponents, additionalBottomLeftBarComponents, additionalBottomRightBarComponents, hideExpand, inline, contentClassName, enableWorkflowCommands, }: {
1
+ export declare function LogViewer({ log, height, width, roundedBottom, lineNumbers, additionalLeftBarComponents, additionalRightBarComponents, additionalBottomLeftBarComponents, additionalBottomRightBarComponents, hideExpand, inline, contentClassName, enableWorkflowCommands, onReachTop, loadingTop, endOfLog, prependedSignal, enableTimeRangeFilter, onTimeRangeChange, initialTimeRangeFrom, initialTimeRangeTo, }: {
2
2
  log: string;
3
3
  height?: string | number;
4
4
  width: string | number;
@@ -15,4 +15,12 @@ export declare function LogViewer({ log, height, width, roundedBottom, lineNumbe
15
15
  contentClassName?: string;
16
16
  /** When true, parses workflow commands and applies visual styling */
17
17
  enableWorkflowCommands?: boolean;
18
+ onReachTop?: (() => void) | undefined;
19
+ loadingTop?: boolean | undefined;
20
+ endOfLog?: boolean | undefined;
21
+ prependedSignal?: number | undefined;
22
+ enableTimeRangeFilter?: boolean;
23
+ onTimeRangeChange?: ((from: string, to: string) => void) | undefined;
24
+ initialTimeRangeFrom?: string | undefined;
25
+ initialTimeRangeTo?: string | undefined;
18
26
  }): import("react").JSX.Element;
@@ -1,8 +1,10 @@
1
- import { ArrowDownIcon, ArrowUpIcon, CheckIcon, ChevronDownIcon, ChevronRightIcon, CopyIcon, DownloadIcon, ExpandIcon, ShrinkIcon, XIcon } from "../icons.js";
2
- import { c as useStrings, i as useNotify } from "../chunks/Provider-BIL5347a.js";
1
+ import { ArrowDownIcon, ArrowUpIcon, CheckIcon, ChevronDownIcon, ChevronRightIcon, ClockIcon, CopyIcon, DownloadIcon, ExpandIcon, ShrinkIcon, XIcon } from "../icons.js";
2
+ import { c as useStrings, i as useNotify } from "../chunks/Provider-CfFV-vc5.js";
3
+ import { r as TooltipInfo } from "../chunks/Tooltip-WgbiC_nd.js";
3
4
  import cx from "classnames";
4
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
- import { Fragment as Fragment$1, memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
6
+ import { Fragment as Fragment$1, memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
7
+ import { DateTime } from "luxon";
6
8
  import { highlightSegments, parseAnsiSegments, plainSegments, processLinesWithCommands } from "@parallelworks/workflow-parser";
7
9
  //#region src/logviewer/LogViewer.tsx
8
10
  function LogSegments({ segments }) {
@@ -61,7 +63,38 @@ var LogLine = memo(function LogLine({ index, segments, lineNumbers, isSelected,
61
63
  })]
62
64
  });
63
65
  });
64
- function LogViewer({ log, height = "unset", width, roundedBottom = false, lineNumbers = true, additionalLeftBarComponents = null, additionalRightBarComponents = null, additionalBottomLeftBarComponents = null, additionalBottomRightBarComponents = null, hideExpand = false, inline = false, contentClassName, enableWorkflowCommands = false }) {
66
+ function validateTimeRange(from, to) {
67
+ if (from === "" && to === "") return "";
68
+ const fromTime = from === "" ? null : DateTime.fromISO(from);
69
+ const toTime = to === "" ? null : DateTime.fromISO(to);
70
+ if (fromTime && !fromTime.isValid) return "invalidStartTime";
71
+ if (toTime && !toTime.isValid) return "invalidEndTime";
72
+ if (fromTime && toTime && toTime <= fromTime) return "endBeforeStart";
73
+ if (toTime && toTime > DateTime.now()) return "endInFuture";
74
+ return "";
75
+ }
76
+ var TopPaginationBanner = memo(function TopPaginationBanner({ endOfLog, loadingTop, onLoadOlder }) {
77
+ const { logviewer: t } = useStrings();
78
+ const baseCx = cx("flex items-center justify-center gap-2 h-7 text-xs font-mono select-none", "text-[var(--theme-muted-text-color)]", "border-b border-[var(--theme-border)]");
79
+ if (endOfLog) return /* @__PURE__ */ jsx("div", {
80
+ className: cx(baseCx, "opacity-70"),
81
+ children: t.beginningOfLog
82
+ });
83
+ if (loadingTop) return /* @__PURE__ */ jsxs("div", {
84
+ className: baseCx,
85
+ children: [/* @__PURE__ */ jsx("span", {
86
+ className: "w-3 h-3 rounded-full border-2 border-current border-t-transparent animate-spin",
87
+ "aria-hidden": true
88
+ }), /* @__PURE__ */ jsx("span", { children: t.loadingOlderEntries })]
89
+ });
90
+ return /* @__PURE__ */ jsx("button", {
91
+ type: "button",
92
+ onClick: onLoadOlder,
93
+ className: cx(baseCx, "w-full hover:text-[var(--theme-app)] hover:bg-[var(--theme-hover)] transition-colors"),
94
+ children: t.loadOlderEntries
95
+ });
96
+ });
97
+ function LogViewer({ log, height = "unset", width, roundedBottom = false, lineNumbers = true, additionalLeftBarComponents = null, additionalRightBarComponents = null, additionalBottomLeftBarComponents = null, additionalBottomRightBarComponents = null, hideExpand = false, inline = false, contentClassName, enableWorkflowCommands = false, onReachTop, loadingTop = false, endOfLog = false, prependedSignal = 0, enableTimeRangeFilter = false, onTimeRangeChange, initialTimeRangeFrom = "", initialTimeRangeTo = "" }) {
65
98
  const notify = useNotify();
66
99
  const { common: tCommon, logviewer: t } = useStrings();
67
100
  const annotationLabels = useMemo(() => ({
@@ -83,6 +116,17 @@ function LogViewer({ log, height = "unset", width, roundedBottom = false, lineNu
83
116
  const [showDebug, setShowDebug] = useState(false);
84
117
  const [collapsedGroups, setCollapsedGroups] = useState(/* @__PURE__ */ new Set());
85
118
  const [filter, setFilter] = useState("");
119
+ const [timeRangeFrom, setTimeRangeFrom] = useState(initialTimeRangeFrom);
120
+ const [timeRangeTo, setTimeRangeTo] = useState(initialTimeRangeTo);
121
+ const [timeRangeOpen, setTimeRangeOpen] = useState(false);
122
+ const timeRangePopoverRef = useRef(null);
123
+ const timeRangeError = validateTimeRange(timeRangeFrom, timeRangeTo);
124
+ useEffect(() => {
125
+ setTimeRangeFrom(initialTimeRangeFrom);
126
+ }, [initialTimeRangeFrom]);
127
+ useEffect(() => {
128
+ setTimeRangeTo(initialTimeRangeTo);
129
+ }, [initialTimeRangeTo]);
86
130
  const [visibleStart, setVisibleStart] = useState(0);
87
131
  const [visibleEnd, setVisibleEnd] = useState(0);
88
132
  const [selectedLines, setSelectedLines] = useState(/* @__PURE__ */ new Set());
@@ -95,6 +139,10 @@ function LogViewer({ log, height = "unset", width, roundedBottom = false, lineNu
95
139
  const scrollAnimationRef = useRef(null);
96
140
  const prevLogLengthRef = useRef(0);
97
141
  const userScrollUpRef = useRef(false);
142
+ const reachTopFiredRef = useRef(false);
143
+ const prevScrollHeightRef = useRef(0);
144
+ const prevPrependedSignalRef = useRef(prependedSignal);
145
+ const pendingPrependRef = useRef(false);
98
146
  const LINE_HEIGHT = 20;
99
147
  const OVERSCAN = 20;
100
148
  const handleLineClick = useCallback((lineNum, e) => {
@@ -174,6 +222,24 @@ function LogViewer({ log, height = "unset", width, roundedBottom = false, lineNu
174
222
  document.removeEventListener("keydown", handleKeyDown);
175
223
  };
176
224
  }, [highlightCopyPosition]);
225
+ useEffect(() => {
226
+ if (!timeRangeOpen) return;
227
+ const handleClickOutside = (e) => {
228
+ const target = e.target;
229
+ if (timeRangePopoverRef.current?.contains(target)) return;
230
+ if (target.closest("[data-time-range-trigger]")) return;
231
+ setTimeRangeOpen(false);
232
+ };
233
+ const handleKey = (e) => {
234
+ if (e.key === "Escape") setTimeRangeOpen(false);
235
+ };
236
+ document.addEventListener("mousedown", handleClickOutside);
237
+ document.addEventListener("keydown", handleKey);
238
+ return () => {
239
+ document.removeEventListener("mousedown", handleClickOutside);
240
+ document.removeEventListener("keydown", handleKey);
241
+ };
242
+ }, [timeRangeOpen]);
177
243
  const copySelectedLines = useCallback(async () => {
178
244
  if (selectedLines.size === 0) return;
179
245
  const text = Array.from(selectedLines).sort((a, b) => a - b).map((ln) => rawLines[ln - 1] || "").join("\n");
@@ -275,6 +341,17 @@ function LogViewer({ log, height = "unset", width, roundedBottom = false, lineNu
275
341
  container.removeEventListener("touchstart", onTouchStart);
276
342
  };
277
343
  }, [inline]);
344
+ const maybeFireReachTop = useCallback((scrollTop) => {
345
+ if (!onReachTop || loadingTop || endOfLog) return;
346
+ if (scrollTop < 50 && !reachTopFiredRef.current) {
347
+ reachTopFiredRef.current = true;
348
+ onReachTop();
349
+ } else if (scrollTop > 100) reachTopFiredRef.current = false;
350
+ }, [
351
+ onReachTop,
352
+ loadingTop,
353
+ endOfLog
354
+ ]);
278
355
  const handleLogScroll = (event) => {
279
356
  const { scrollTop, clientHeight, scrollHeight } = event.currentTarget;
280
357
  const isAnimating = scrollAnimationRef.current !== null;
@@ -284,6 +361,7 @@ function LogViewer({ log, height = "unset", width, roundedBottom = false, lineNu
284
361
  else if (userScrollUpRef.current && follow) updateFollow(false);
285
362
  userScrollUpRef.current = false;
286
363
  }
364
+ maybeFireReachTop(scrollTop);
287
365
  if (!followRef.current) {
288
366
  const visibleCount = Math.ceil(clientHeight / LINE_HEIGHT) + OVERSCAN * 2;
289
367
  const maxStart = Math.max(0, parsedLog.length - visibleCount);
@@ -357,6 +435,28 @@ function LogViewer({ log, height = "unset", width, roundedBottom = false, lineNu
357
435
  observer.observe(logContainerRef.current);
358
436
  return () => observer.disconnect();
359
437
  }, [smoothScrollToBottom]);
438
+ useLayoutEffect(() => {
439
+ if (prependedSignal > prevPrependedSignalRef.current) pendingPrependRef.current = true;
440
+ else if (prependedSignal < prevPrependedSignalRef.current) pendingPrependRef.current = false;
441
+ prevPrependedSignalRef.current = prependedSignal;
442
+ if (!pendingPrependRef.current) return;
443
+ const container = logContainerRef.current;
444
+ if (!container) return;
445
+ const delta = container.scrollHeight - prevScrollHeightRef.current;
446
+ if (delta <= 0) return;
447
+ pendingPrependRef.current = false;
448
+ container.scrollTop += delta;
449
+ if (!followRef.current) {
450
+ const visibleCount = Math.ceil(container.clientHeight / LINE_HEIGHT) + OVERSCAN * 2;
451
+ const maxStart = Math.max(0, parsedLog.length - visibleCount);
452
+ const startIndex = Math.min(maxStart, Math.max(0, Math.floor(container.scrollTop / LINE_HEIGHT) - OVERSCAN));
453
+ setVisibleStart(startIndex);
454
+ setVisibleEnd(startIndex + visibleCount);
455
+ }
456
+ }, [prependedSignal, parsedLog.length]);
457
+ useLayoutEffect(() => {
458
+ if (logContainerRef.current) prevScrollHeightRef.current = logContainerRef.current.scrollHeight;
459
+ });
360
460
  const handleFilterChange = (event) => {
361
461
  const newFilter = event.target.value;
362
462
  setFilter(newFilter);
@@ -447,6 +547,87 @@ function LogViewer({ log, height = "unset", width, roundedBottom = false, lineNu
447
547
  className: cx("btn btn-neutral", showDebug && "btn-active"),
448
548
  onClick: () => setShowDebug((prev) => !prev),
449
549
  children: /* @__PURE__ */ jsx("span", { children: showDebug ? t.hideDebug : t.showDebug })
550
+ }),
551
+ enableTimeRangeFilter && /* @__PURE__ */ jsxs("div", {
552
+ className: "relative",
553
+ children: [/* @__PURE__ */ jsxs("button", {
554
+ type: "button",
555
+ "data-time-range-trigger": true,
556
+ className: "btn btn-neutral",
557
+ onClick: () => setTimeRangeOpen((o) => !o),
558
+ children: [
559
+ /* @__PURE__ */ jsx(ClockIcon, {}),
560
+ /* @__PURE__ */ jsx("span", { children: t.timeRange }),
561
+ (timeRangeFrom !== "" || timeRangeTo !== "") && /* @__PURE__ */ jsx("span", {
562
+ title: t.timeRangeActive,
563
+ className: "ml-1 px-1.5 py-0.5 text-[10px] rounded-full bg-[var(--theme-element)]/15 text-[var(--theme-element)]",
564
+ children: t.timeRangeOn
565
+ })
566
+ ]
567
+ }), timeRangeOpen && /* @__PURE__ */ jsxs("div", {
568
+ ref: timeRangePopoverRef,
569
+ className: cx("absolute top-full left-0 mt-1 z-50 w-80 p-3 rounded-lg shadow-lg", "border border-[var(--theme-border)] bg-[var(--theme-panel-bg)]", "flex flex-col gap-y-3"),
570
+ children: [
571
+ /* @__PURE__ */ jsxs("div", {
572
+ className: "flex items-center gap-x-1.5",
573
+ children: [/* @__PURE__ */ jsx("span", {
574
+ className: "text-xs font-semibold text-[var(--theme-app)]",
575
+ children: t.filterByTimeRange
576
+ }), /* @__PURE__ */ jsx(TooltipInfo, { text: t.timeRangeHelp })]
577
+ }),
578
+ /* @__PURE__ */ jsxs("label", {
579
+ className: "flex flex-col gap-y-1",
580
+ children: [/* @__PURE__ */ jsx("span", {
581
+ className: "text-[11px] uppercase tracking-wide text-[var(--theme-muted-text-color)]",
582
+ children: t.timeRangeFrom
583
+ }), /* @__PURE__ */ jsx("input", {
584
+ type: "datetime-local",
585
+ value: timeRangeFrom,
586
+ onChange: (e) => setTimeRangeFrom(e.target.value),
587
+ className: "rounded-md text-sm h-8 px-2 bg-[var(--theme-input-bg)] text-[var(--theme-input)] border border-[var(--theme-border)]"
588
+ })]
589
+ }),
590
+ /* @__PURE__ */ jsxs("label", {
591
+ className: "flex flex-col gap-y-1",
592
+ children: [/* @__PURE__ */ jsx("span", {
593
+ className: "text-[11px] uppercase tracking-wide text-[var(--theme-muted-text-color)]",
594
+ children: t.timeRangeTo
595
+ }), /* @__PURE__ */ jsx("input", {
596
+ type: "datetime-local",
597
+ value: timeRangeTo,
598
+ onChange: (e) => setTimeRangeTo(e.target.value),
599
+ className: "rounded-md text-sm h-8 px-2 bg-[var(--theme-input-bg)] text-[var(--theme-input)] border border-[var(--theme-border)]"
600
+ })]
601
+ }),
602
+ timeRangeError !== "" && /* @__PURE__ */ jsx("span", {
603
+ className: "text-[11px] text-red-500",
604
+ children: t[timeRangeError]
605
+ }),
606
+ /* @__PURE__ */ jsxs("div", {
607
+ className: "flex gap-x-2",
608
+ children: [/* @__PURE__ */ jsx("button", {
609
+ type: "button",
610
+ disabled: timeRangeError !== "",
611
+ className: "btn btn-primary flex-1 justify-center",
612
+ onClick: () => {
613
+ onTimeRangeChange?.(timeRangeFrom, timeRangeTo);
614
+ setTimeRangeOpen(false);
615
+ },
616
+ children: t.apply
617
+ }), (timeRangeFrom !== "" || timeRangeTo !== "") && /* @__PURE__ */ jsx("button", {
618
+ type: "button",
619
+ className: "btn btn-neutral flex-1 justify-center",
620
+ onClick: () => {
621
+ setTimeRangeFrom("");
622
+ setTimeRangeTo("");
623
+ onTimeRangeChange?.("", "");
624
+ setTimeRangeOpen(false);
625
+ },
626
+ children: t.clear
627
+ })]
628
+ })
629
+ ]
630
+ })]
450
631
  })
451
632
  ]
452
633
  }),
@@ -471,7 +652,7 @@ function LogViewer({ log, height = "unset", width, roundedBottom = false, lineNu
471
652
  ]
472
653
  }), additionalRightBarComponents]
473
654
  }),
474
- /* @__PURE__ */ jsx("div", {
655
+ /* @__PURE__ */ jsxs("div", {
475
656
  "data-testid": "logviewer",
476
657
  className: cx(roundedBottom && "rounded-b-lg", "w-full overflow-x-auto", !inline && "h-full grow overflow-y-auto", !inline && "bg-(--theme-muted-panel-bg)", contentClassName),
477
658
  style: inline ? void 0 : {
@@ -482,7 +663,11 @@ function LogViewer({ log, height = "unset", width, roundedBottom = false, lineNu
482
663
  ref: logContainerRef,
483
664
  onScroll: inline ? void 0 : handleLogScroll,
484
665
  onMouseUp: handleTextMouseUp,
485
- children: /* @__PURE__ */ jsx("div", {
666
+ children: [!inline && onReachTop && /* @__PURE__ */ jsx(TopPaginationBanner, {
667
+ endOfLog,
668
+ loadingTop,
669
+ onLoadOlder: onReachTop
670
+ }), /* @__PURE__ */ jsx("div", {
486
671
  className: "min-w-max",
487
672
  style: inline ? void 0 : {
488
673
  paddingTop,
@@ -503,7 +688,7 @@ function LogViewer({ log, height = "unset", width, roundedBottom = false, lineNu
503
688
  onGroupToggle: enableWorkflowCommands ? handleGroupToggle : void 0,
504
689
  annotationLabels
505
690
  }, item.index))
506
- })
691
+ })]
507
692
  }),
508
693
  highlightCopyPosition && highlightedText && /* @__PURE__ */ jsx("button", {
509
694
  type: "button",