@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,113 @@
1
+ import type { ComponentType, MouseEvent as ReactMouseEvent, ReactNode } from 'react';
2
+ export type RowMenuItem = {
3
+ kind: 'link';
4
+ label: string;
5
+ to: string;
6
+ icon?: ReactNode;
7
+ destructive?: boolean;
8
+ } | {
9
+ kind: 'action';
10
+ label: string;
11
+ onSelect?: () => void;
12
+ icon?: ReactNode;
13
+ destructive?: boolean;
14
+ disabled?: boolean;
15
+ /** Trailing checkmark, e.g. the active option in a radio-style submenu. */
16
+ selected?: boolean;
17
+ /** Declarative clipboard copy: the menu performs it and notifies, so
18
+ * builders like copySubmenu need no toast or clipboard access. */
19
+ copy?: {
20
+ text: string;
21
+ label: string;
22
+ };
23
+ } | {
24
+ kind: 'submenu';
25
+ label: string;
26
+ icon?: ReactNode;
27
+ items: RowMenuItem[];
28
+ } | {
29
+ kind: 'divider';
30
+ };
31
+ export type OpenMenu = (x: number, y: number, items: RowMenuItem[], onClose?: () => void) => void;
32
+ /** The shared shape every entity action hook's per-row actions boil down to.
33
+ * Each hook's bespoke `*RowAction` type is structurally assignable to this. */
34
+ export interface RowAction {
35
+ key: string;
36
+ label: string;
37
+ icon: ReactNode;
38
+ onSelect?: () => void;
39
+ to?: string;
40
+ disabled?: boolean;
41
+ destructive?: boolean;
42
+ }
43
+ /** Converts a single row action to a menu item: a `to` makes it a link, otherwise an action. */
44
+ export declare function toRowMenuItem(action: RowAction): RowMenuItem;
45
+ /** Bridges a toolbar action (icon component, `onClick`/`href`) onto the shared
46
+ * `RowAction` shape. Typed structurally so any toolbar action shape matching
47
+ * these fields is assignable. */
48
+ export declare function toolbarActionToRowAction(action: {
49
+ key: string;
50
+ label: string;
51
+ icon: ComponentType<{
52
+ className?: string;
53
+ }>;
54
+ onClick?: () => void;
55
+ href?: string;
56
+ disabled?: boolean;
57
+ }): RowAction;
58
+ /** Assembles a row's context menu in the shared order: leading link(s) → middle
59
+ * actions → extra items → Copy submenu → trailing (destructive) actions last. */
60
+ export declare function assembleRowMenu({ lead, actions, exclude, order, trailing, extra, copy, }: {
61
+ /** Pre-built leading link(s), e.g. "View details". */
62
+ lead?: RowMenuItem[];
63
+ /** The row's full action list. */
64
+ actions: RowAction[];
65
+ /** Keys already rendered in `lead`/`extra`; dropped from the middle. */
66
+ exclude?: string[];
67
+ /** Explicit middle order; keys absent from `actions` are skipped, keys absent from `order` are dropped. */
68
+ order?: string[];
69
+ /** Keys rendered last, in this order (default `['delete']`). */
70
+ trailing?: string[];
71
+ /** Items inserted after the middle actions and before Copy, e.g. a marketplace link. */
72
+ extra?: RowMenuItem[];
73
+ /** The Copy submenu from `copySubmenu(...)`. */
74
+ copy?: RowMenuItem;
75
+ }): RowMenuItem[];
76
+ export declare function useRowMenu(): {
77
+ openMenu: OpenMenu;
78
+ contextMenu: import("react").JSX.Element;
79
+ };
80
+ /** The full-screen menu overlay drops CSS :hover on the triggering row; wrap openMenu here to substitute a JS-driven `active` highlight. */
81
+ export declare function useRowMenuActive(openMenu: OpenMenu): {
82
+ active: boolean;
83
+ openMenu: OpenMenu;
84
+ };
85
+ export declare function rowContextMenuProps(openMenu: OpenMenu, items: RowMenuItem[]): {
86
+ onContextMenu: (e: ReactMouseEvent) => void;
87
+ };
88
+ /** Builds the Copy submenu entries for whichever fields a row has, in stable
89
+ * order. The menu itself performs the copy and notifies; `t` is an optional
90
+ * host translator for the field labels (falls back to the provider defaults'
91
+ * English). */
92
+ export declare function copySubmenu({ name, nameLabel, email, username, uid, id, url, extra, t, }: {
93
+ name?: string | null;
94
+ /** Overrides the "name" label (raw, pre-translated) for entities whose primary field isn't a name, e.g. "domain". */
95
+ nameLabel?: string;
96
+ email?: string | null;
97
+ username?: string | null;
98
+ uid?: string | number | null;
99
+ id?: string | null;
100
+ url?: string | null;
101
+ extra?: {
102
+ label: string;
103
+ value?: string | null;
104
+ icon?: ReactNode;
105
+ }[];
106
+ t?: (key: string, values?: Record<string, string | number>) => string;
107
+ }): RowMenuItem;
108
+ /** Stays visible while active (menu open) so it doesn't vanish behind the overlay. */
109
+ export declare function MoreButton({ onOpen, active, className, }: {
110
+ onOpen: (x: number, y: number) => void;
111
+ active?: boolean;
112
+ className?: string;
113
+ }): import("react").JSX.Element;
@@ -0,0 +1,2 @@
1
+ export { assembleRowMenu, copySubmenu, MoreButton, rowContextMenuProps, toolbarActionToRowAction, toRowMenuItem, useRowMenu, useRowMenuActive, type OpenMenu, type RowAction, type RowMenuItem, } from './RowContextMenu.js';
2
+ export { actionsColumnWidth, GroupRow, ListActionsHeader, ListError, ListSkeleton, listTableFixedProps, listTableProps, NAME_MIN_WIDTH_PX, NameCell, RowSelectCheckbox, } from './ListTable.js';
@@ -0,0 +1,2 @@
1
+ import { _ as useRowMenu, a as NAME_MIN_WIDTH_PX, c as actionsColumnWidth, d as MoreButton, f as assembleRowMenu, g as toolbarActionToRowAction, h as toRowMenuItem, i as ListSkeleton, l as listTableFixedProps, m as rowContextMenuProps, n as ListActionsHeader, o as NameCell, p as copySubmenu, r as ListError, s as RowSelectCheckbox, t as GroupRow, u as listTableProps, v as useRowMenuActive } from "../chunks/list-7PyDF1tz.js";
2
+ export { GroupRow, ListActionsHeader, ListError, ListSkeleton, MoreButton, NAME_MIN_WIDTH_PX, NameCell, RowSelectCheckbox, actionsColumnWidth, assembleRowMenu, copySubmenu, listTableFixedProps, listTableProps, rowContextMenuProps, toRowMenuItem, toolbarActionToRowAction, useRowMenu, useRowMenuActive };
@@ -0,0 +1,18 @@
1
+ export declare function LogViewer({ log, height, width, roundedBottom, lineNumbers, additionalLeftBarComponents, additionalRightBarComponents, additionalBottomLeftBarComponents, additionalBottomRightBarComponents, hideExpand, inline, contentClassName, enableWorkflowCommands, }: {
2
+ log: string;
3
+ height?: string | number;
4
+ width: string | number;
5
+ roundedBottom?: boolean;
6
+ lineNumbers?: boolean;
7
+ additionalLeftBarComponents?: React.ReactNode;
8
+ additionalRightBarComponents?: React.ReactNode;
9
+ additionalBottomLeftBarComponents?: React.ReactNode;
10
+ additionalBottomRightBarComponents?: React.ReactNode;
11
+ hideExpand?: boolean;
12
+ /** When true, renders all lines without virtualization or overflow scrolling */
13
+ inline?: boolean;
14
+ /** Additional className for the log content area */
15
+ contentClassName?: string;
16
+ /** When true, parses workflow commands and applies visual styling */
17
+ enableWorkflowCommands?: boolean;
18
+ }): import("react").JSX.Element;
@@ -0,0 +1 @@
1
+ export * from './LogViewer.js';
@@ -0,0 +1,534 @@
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-DvVvScTQ.js";
3
+ import cx from "classnames";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ import { Fragment as Fragment$1, memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
6
+ import { highlightSegments, parseAnsiSegments, plainSegments, processLinesWithCommands } from "@parallelworks/workflow-parser";
7
+ //#region src/logviewer/LogViewer.tsx
8
+ function LogSegments({ segments }) {
9
+ return segments.map((segment, i) => segment.highlighted ? /* @__PURE__ */ jsx("mark", {
10
+ className: cx("search-highlight", segment.className),
11
+ children: segment.content
12
+ }, i) : segment.className ? /* @__PURE__ */ jsx("span", {
13
+ className: segment.className,
14
+ children: segment.content
15
+ }, i) : /* @__PURE__ */ jsx(Fragment$1, { children: segment.content }, i));
16
+ }
17
+ var LogLine = memo(function LogLine({ index, segments, lineNumbers, isSelected, lineHeight, onLineClick, searchTerm, inline = false, command, groupDepth = 0, isGroupCollapsed, onGroupToggle, annotationLabels }) {
18
+ const displaySegments = useMemo(() => highlightSegments(command?.message ? plainSegments(command.message) : segments, searchTerm), [
19
+ segments,
20
+ searchTerm,
21
+ command
22
+ ]);
23
+ const annotationLabel = useMemo(() => {
24
+ if (command?.type === "error") return /* @__PURE__ */ jsx("span", {
25
+ className: "text-red-500 font-semibold mr-1",
26
+ children: annotationLabels.error
27
+ });
28
+ if (command?.type === "warning") return /* @__PURE__ */ jsx("span", {
29
+ className: "text-yellow-500 font-semibold mr-1",
30
+ children: annotationLabels.warning
31
+ });
32
+ if (command?.type === "notice") return /* @__PURE__ */ jsx("span", {
33
+ className: "text-blue-500 font-semibold mr-1",
34
+ children: annotationLabels.notice
35
+ });
36
+ return null;
37
+ }, [command?.type, annotationLabels]);
38
+ return /* @__PURE__ */ jsxs("div", {
39
+ "data-line-number": index + 1,
40
+ className: cx("flex items-start text-sm font-mono group", isSelected ? "bg-(--theme-link)/15" : "hover:bg-(--theme-hover)", "text-(--theme-app)", command?.type === "error" && "bg-red-500/10", command?.type === "warning" && "bg-yellow-500/10", command?.type === "notice" && "bg-blue-500/10", command?.type === "debug" && "opacity-60", command?.type === "group" && "bg-(--theme-hover) cursor-pointer"),
41
+ style: {
42
+ minHeight: `${lineHeight}px`,
43
+ lineHeight: `${lineHeight}px`,
44
+ paddingLeft: groupDepth > 0 ? `${groupDepth * 16}px` : void 0
45
+ },
46
+ onClick: command?.type === "group" && onGroupToggle ? () => onGroupToggle(index) : void 0,
47
+ children: [lineNumbers && /* @__PURE__ */ jsx("span", {
48
+ onClick: (e) => onLineClick(index + 1, e),
49
+ className: cx("inline-flex items-center justify-end w-14 pr-3 shrink-0 cursor-pointer select-none", "hover:underline hover:text-(--theme-link) transition-colors", "text-(--theme-muted-text-color)", isSelected && "text-(--theme-link)"),
50
+ children: index + 1
51
+ }), /* @__PURE__ */ jsxs("pre", {
52
+ className: cx("grow text-left whitespace-pre m-0 py-0.5 pr-2 flex items-center", lineNumbers && "pl-2 border-l border-(--theme-border)", command?.type === "error" && "border-l-red-500 border-l-2", command?.type === "warning" && "border-l-yellow-500 border-l-2", command?.type === "notice" && "border-l-blue-500 border-l-2", command?.type === "group" && "font-bold"),
53
+ children: [
54
+ command?.type === "group" && /* @__PURE__ */ jsx("span", {
55
+ className: "mr-1 shrink-0 inline-flex items-center",
56
+ children: isGroupCollapsed ? /* @__PURE__ */ jsx(ChevronRightIcon, { className: "w-3 h-3" }) : /* @__PURE__ */ jsx(ChevronDownIcon, { className: "w-3 h-3" })
57
+ }),
58
+ annotationLabel,
59
+ /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(LogSegments, { segments: displaySegments }) })
60
+ ]
61
+ })]
62
+ });
63
+ });
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 }) {
65
+ const notify = useNotify();
66
+ const { common: tCommon, logviewer: t } = useStrings();
67
+ const annotationLabels = useMemo(() => ({
68
+ error: t.error,
69
+ warning: t.warning,
70
+ notice: t.notice
71
+ }), [t]);
72
+ const [fullscreen, setFullscreen] = useState(false);
73
+ const [follow, _setFollow] = useState(true);
74
+ const followRef = useRef(true);
75
+ const updateFollow = useCallback((value) => {
76
+ followRef.current = value;
77
+ _setFollow(value);
78
+ }, []);
79
+ const [logUrl, setLogUrl] = useState("");
80
+ const [fullParsedLog, setFullParsedLog] = useState([]);
81
+ const [processedLines, setProcessedLines] = useState([]);
82
+ const [rawLines, setRawLines] = useState([]);
83
+ const [showDebug, setShowDebug] = useState(false);
84
+ const [collapsedGroups, setCollapsedGroups] = useState(/* @__PURE__ */ new Set());
85
+ const [filter, setFilter] = useState("");
86
+ const [visibleStart, setVisibleStart] = useState(0);
87
+ const [visibleEnd, setVisibleEnd] = useState(0);
88
+ const [selectedLines, setSelectedLines] = useState(/* @__PURE__ */ new Set());
89
+ const [lastClickedLine, setLastClickedLine] = useState(null);
90
+ const [copied, setCopied] = useState(false);
91
+ const [highlightCopyPosition, setHighlightCopyPosition] = useState(null);
92
+ const [highlightedText, setHighlightedText] = useState("");
93
+ const logContainerRef = useRef(null);
94
+ const ansiCache = useRef({});
95
+ const scrollAnimationRef = useRef(null);
96
+ const prevLogLengthRef = useRef(0);
97
+ const userScrollUpRef = useRef(false);
98
+ const LINE_HEIGHT = 20;
99
+ const OVERSCAN = 20;
100
+ const handleLineClick = useCallback((lineNum, e) => {
101
+ e.preventDefault();
102
+ updateFollow(false);
103
+ if (e.shiftKey && lastClickedLine !== null) {
104
+ const start = Math.min(lastClickedLine, lineNum);
105
+ const end = Math.max(lastClickedLine, lineNum);
106
+ const newSelected = /* @__PURE__ */ new Set();
107
+ for (let i = start; i <= end; i++) newSelected.add(i);
108
+ setSelectedLines(newSelected);
109
+ } else if (selectedLines.has(lineNum) && selectedLines.size === 1) setSelectedLines(/* @__PURE__ */ new Set());
110
+ else {
111
+ setSelectedLines(new Set([lineNum]));
112
+ setLastClickedLine(lineNum);
113
+ }
114
+ }, [
115
+ lastClickedLine,
116
+ selectedLines,
117
+ updateFollow
118
+ ]);
119
+ const copyAllLogs = useCallback(async () => {
120
+ const text = rawLines.join("\n");
121
+ try {
122
+ await navigator.clipboard.writeText(text);
123
+ setCopied(true);
124
+ setTimeout(() => setCopied(false), 2e3);
125
+ } catch (_err) {
126
+ notify.error("Failed to copy logs to clipboard");
127
+ }
128
+ }, [rawLines]);
129
+ const handleTextMouseUp = useCallback((e) => {
130
+ const selectedText = window.getSelection()?.toString().trim();
131
+ if (selectedText && selectedText.length > 0) {
132
+ setHighlightCopyPosition({
133
+ x: e.clientX,
134
+ y: e.clientY
135
+ });
136
+ setHighlightedText(selectedText);
137
+ } else {
138
+ setHighlightCopyPosition(null);
139
+ setHighlightedText("");
140
+ }
141
+ }, []);
142
+ const copyHighlightedText = useCallback(async () => {
143
+ if (!highlightedText) return;
144
+ try {
145
+ await navigator.clipboard.writeText(highlightedText);
146
+ notify.success("Text copied to clipboard");
147
+ window.getSelection()?.removeAllRanges();
148
+ setHighlightCopyPosition(null);
149
+ setHighlightedText("");
150
+ } catch (_err) {
151
+ notify.error("Failed to copy text to clipboard");
152
+ }
153
+ }, [highlightedText]);
154
+ useEffect(() => {
155
+ const handleClickOutside = (e) => {
156
+ if (e.target.closest("[data-highlight-copy-button]")) return;
157
+ setTimeout(() => {
158
+ setHighlightCopyPosition(null);
159
+ setHighlightedText("");
160
+ }, 100);
161
+ };
162
+ const handleKeyDown = (e) => {
163
+ if (e.key === "Escape") {
164
+ setHighlightCopyPosition(null);
165
+ setHighlightedText("");
166
+ }
167
+ };
168
+ if (highlightCopyPosition) {
169
+ document.addEventListener("mousedown", handleClickOutside);
170
+ document.addEventListener("keydown", handleKeyDown);
171
+ }
172
+ return () => {
173
+ document.removeEventListener("mousedown", handleClickOutside);
174
+ document.removeEventListener("keydown", handleKeyDown);
175
+ };
176
+ }, [highlightCopyPosition]);
177
+ const copySelectedLines = useCallback(async () => {
178
+ if (selectedLines.size === 0) return;
179
+ const text = Array.from(selectedLines).sort((a, b) => a - b).map((ln) => rawLines[ln - 1] || "").join("\n");
180
+ try {
181
+ await navigator.clipboard.writeText(text);
182
+ setCopied(true);
183
+ setTimeout(() => setCopied(false), 2e3);
184
+ } catch (_err) {
185
+ notify.error("Failed to copy selected logs to clipboard");
186
+ }
187
+ }, [selectedLines, rawLines]);
188
+ const handleGroupToggle = useCallback((lineIndex) => {
189
+ setCollapsedGroups((prev) => {
190
+ const next = new Set(prev);
191
+ if (next.has(lineIndex)) next.delete(lineIndex);
192
+ else next.add(lineIndex);
193
+ return next;
194
+ });
195
+ }, []);
196
+ const parsedLog = useMemo(() => {
197
+ let filtered = enableWorkflowCommands ? processedLines : fullParsedLog;
198
+ if (enableWorkflowCommands) filtered = filtered.filter((item) => {
199
+ if (item.command?.type === "endgroup") return false;
200
+ if (!showDebug && item.command?.type === "debug") return false;
201
+ if (item.groupId !== void 0 && item.command?.type !== "group" && collapsedGroups.has(item.groupId)) return false;
202
+ return true;
203
+ });
204
+ if (filter !== "") {
205
+ const normalizedFilter = filter.toLowerCase();
206
+ filtered = filtered.filter((item) => {
207
+ const command = enableWorkflowCommands ? item.command : void 0;
208
+ return (command?.message ? command.message : item.segments.map((segment) => segment.content).join("")).toLowerCase().includes(normalizedFilter);
209
+ });
210
+ }
211
+ return filtered;
212
+ }, [
213
+ fullParsedLog,
214
+ processedLines,
215
+ filter,
216
+ enableWorkflowCommands,
217
+ showDebug,
218
+ collapsedGroups
219
+ ]);
220
+ const smoothScrollToBottom = useCallback(() => {
221
+ if (!logContainerRef.current) return;
222
+ if (scrollAnimationRef.current) {
223
+ cancelAnimationFrame(scrollAnimationRef.current);
224
+ scrollAnimationRef.current = null;
225
+ }
226
+ const container = logContainerRef.current;
227
+ const targetScroll = container.scrollHeight - container.clientHeight;
228
+ const currentScroll = container.scrollTop;
229
+ const distance = targetScroll - currentScroll;
230
+ if (Math.abs(distance) < 5) {
231
+ container.scrollTop = targetScroll;
232
+ return;
233
+ }
234
+ const startTime = performance.now();
235
+ const duration = 150;
236
+ const animate = (currentTime) => {
237
+ const elapsed = currentTime - startTime;
238
+ const progress = Math.min(elapsed / duration, 1);
239
+ container.scrollTop = currentScroll + distance * (1 - (1 - progress) ** 3);
240
+ if (progress < 1) scrollAnimationRef.current = requestAnimationFrame(animate);
241
+ else scrollAnimationRef.current = null;
242
+ };
243
+ scrollAnimationRef.current = requestAnimationFrame(animate);
244
+ }, []);
245
+ const scrollLogViewer = useCallback((location, ref) => {
246
+ if (!ref?.current) return;
247
+ if (location === "top") ref.current.scroll({
248
+ top: 0,
249
+ left: 0,
250
+ behavior: "smooth"
251
+ });
252
+ else smoothScrollToBottom();
253
+ }, [smoothScrollToBottom]);
254
+ useEffect(() => {
255
+ const container = logContainerRef.current;
256
+ if (!container || inline) return;
257
+ const onWheel = (e) => {
258
+ if (e.deltaY < 0) userScrollUpRef.current = true;
259
+ if (scrollAnimationRef.current) {
260
+ cancelAnimationFrame(scrollAnimationRef.current);
261
+ scrollAnimationRef.current = null;
262
+ }
263
+ };
264
+ const onTouchStart = () => {
265
+ userScrollUpRef.current = true;
266
+ if (scrollAnimationRef.current) {
267
+ cancelAnimationFrame(scrollAnimationRef.current);
268
+ scrollAnimationRef.current = null;
269
+ }
270
+ };
271
+ container.addEventListener("wheel", onWheel, { passive: true });
272
+ container.addEventListener("touchstart", onTouchStart, { passive: true });
273
+ return () => {
274
+ container.removeEventListener("wheel", onWheel);
275
+ container.removeEventListener("touchstart", onTouchStart);
276
+ };
277
+ }, [inline]);
278
+ const handleLogScroll = (event) => {
279
+ const { scrollTop, clientHeight, scrollHeight } = event.currentTarget;
280
+ const isAnimating = scrollAnimationRef.current !== null;
281
+ const atBottom = scrollHeight - clientHeight - scrollTop < 2;
282
+ if (!isAnimating) {
283
+ if (atBottom && !follow) updateFollow(true);
284
+ else if (userScrollUpRef.current && follow) updateFollow(false);
285
+ userScrollUpRef.current = false;
286
+ }
287
+ if (!followRef.current) {
288
+ const visibleCount = Math.ceil(clientHeight / LINE_HEIGHT) + OVERSCAN * 2;
289
+ const maxStart = Math.max(0, parsedLog.length - visibleCount);
290
+ const startIndex = Math.min(maxStart, Math.max(0, Math.floor(scrollTop / LINE_HEIGHT) - OVERSCAN));
291
+ setVisibleStart(startIndex);
292
+ setVisibleEnd(startIndex + visibleCount);
293
+ }
294
+ };
295
+ useEffect(() => {
296
+ if (!log) return;
297
+ const lines = (typeof log === "object" ? JSON.stringify(log) : log.toString()).split("\n");
298
+ const cleanedLines = lines[lines.length - 1] === "" ? lines.slice(0, -1) : lines;
299
+ const isAppending = cleanedLines.length > prevLogLengthRef.current;
300
+ prevLogLengthRef.current = cleanedLines.length;
301
+ setRawLines(cleanedLines);
302
+ const processed = cleanedLines.map((line, index) => {
303
+ if (ansiCache.current[line] === void 0) ansiCache.current[line] = parseAnsiSegments(line);
304
+ return {
305
+ index,
306
+ segments: ansiCache.current[line]
307
+ };
308
+ });
309
+ setFullParsedLog(processed);
310
+ if (enableWorkflowCommands) setProcessedLines(processLinesWithCommands(processed, cleanedLines));
311
+ if (followRef.current && logContainerRef.current) {
312
+ setVisibleEnd((prev) => Math.max(prev, cleanedLines.length));
313
+ if (isAppending) requestAnimationFrame(() => {
314
+ requestAnimationFrame(() => {
315
+ smoothScrollToBottom();
316
+ });
317
+ });
318
+ }
319
+ const file = new Blob([log], { type: "text/plaintext" });
320
+ const url = URL.createObjectURL(file);
321
+ setLogUrl(url);
322
+ return () => {
323
+ URL.revokeObjectURL(url);
324
+ if (scrollAnimationRef.current) cancelAnimationFrame(scrollAnimationRef.current);
325
+ };
326
+ }, [
327
+ log,
328
+ smoothScrollToBottom,
329
+ enableWorkflowCommands
330
+ ]);
331
+ const parsedLogLengthRef = useRef(parsedLog.length);
332
+ parsedLogLengthRef.current = parsedLog.length;
333
+ useEffect(() => {
334
+ if (!logContainerRef.current) return;
335
+ const observer = new ResizeObserver(() => {
336
+ if (!logContainerRef.current) return;
337
+ if (parsedLogLengthRef.current === 0) return;
338
+ const container = logContainerRef.current;
339
+ const clientHeight = container.clientHeight;
340
+ const visibleCount = Math.ceil(clientHeight / LINE_HEIGHT) + OVERSCAN * 2;
341
+ if (followRef.current) {
342
+ const minStart = Math.max(0, parsedLogLengthRef.current - visibleCount);
343
+ setVisibleStart((prev) => Math.min(prev, minStart));
344
+ setVisibleEnd(parsedLogLengthRef.current);
345
+ requestAnimationFrame(() => {
346
+ requestAnimationFrame(() => {
347
+ smoothScrollToBottom();
348
+ });
349
+ });
350
+ } else {
351
+ const maxStart = Math.max(0, parsedLogLengthRef.current - visibleCount);
352
+ const startIndex = Math.min(maxStart, Math.max(0, Math.floor(container.scrollTop / LINE_HEIGHT) - OVERSCAN));
353
+ setVisibleStart(startIndex);
354
+ setVisibleEnd(startIndex + visibleCount);
355
+ }
356
+ });
357
+ observer.observe(logContainerRef.current);
358
+ return () => observer.disconnect();
359
+ }, [smoothScrollToBottom]);
360
+ const handleFilterChange = (event) => {
361
+ const newFilter = event.target.value;
362
+ setFilter(newFilter);
363
+ updateFollow(false);
364
+ if (logContainerRef.current) {
365
+ const clientHeight = logContainerRef.current.clientHeight;
366
+ const visibleCount = Math.ceil(clientHeight / LINE_HEIGHT) + OVERSCAN * 2;
367
+ setVisibleStart(0);
368
+ setVisibleEnd(Math.min(parsedLog.length, visibleCount));
369
+ scrollLogViewer("top", logContainerRef);
370
+ } else {
371
+ setVisibleStart(0);
372
+ setVisibleEnd(50);
373
+ }
374
+ };
375
+ const ExpandButtonIcon = fullscreen ? ShrinkIcon : ExpandIcon;
376
+ const expandButtonText = fullscreen ? "Collapse" : "Expand";
377
+ const paddingTop = visibleStart * LINE_HEIGHT;
378
+ const paddingBottom = Math.max(0, parsedLog.length - visibleEnd) * LINE_HEIGHT;
379
+ const visibleLogs = inline ? parsedLog : parsedLog.slice(visibleStart, Math.min(parsedLog.length, visibleEnd));
380
+ return /* @__PURE__ */ jsxs("div", {
381
+ className: cx("flex flex-col grow", roundedBottom && "rounded-lg"),
382
+ style: !fullscreen ? {
383
+ height,
384
+ width
385
+ } : {
386
+ height: "calc(100% - 50px)",
387
+ width: "100%",
388
+ position: "fixed",
389
+ left: 0,
390
+ top: 50,
391
+ zIndex: 9999
392
+ },
393
+ children: [
394
+ /* @__PURE__ */ jsxs("div", {
395
+ className: cx("flex select-none items-center gap-x-2 p-2", inline ? "bg-[var(--theme-panel-bg)] border-b border-[var(--theme-border)]" : "theme-panel"),
396
+ children: [/* @__PURE__ */ jsxs("div", {
397
+ className: cx("w-full flex flex-row gap-x-2 items-center", hideExpand && "justify-between"),
398
+ children: [
399
+ /* @__PURE__ */ jsxs("div", {
400
+ className: "flex gap-x-2 w-full",
401
+ children: [
402
+ !inline && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("button", {
403
+ type: "button",
404
+ className: "btn btn-neutral",
405
+ onClick: () => {
406
+ scrollLogViewer("top", logContainerRef);
407
+ updateFollow(false);
408
+ },
409
+ children: [/* @__PURE__ */ jsx(ArrowUpIcon, {}), /* @__PURE__ */ jsx("span", { children: t.top })]
410
+ }), /* @__PURE__ */ jsxs("button", {
411
+ type: "button",
412
+ className: "btn btn-neutral",
413
+ onClick: () => {
414
+ if (!follow) {
415
+ updateFollow(true);
416
+ setVisibleEnd((prev) => Math.max(prev, parsedLog.length));
417
+ requestAnimationFrame(() => {
418
+ smoothScrollToBottom();
419
+ });
420
+ } else updateFollow(false);
421
+ },
422
+ children: [/* @__PURE__ */ jsx(ArrowDownIcon, {}), /* @__PURE__ */ jsx("span", { children: !follow ? "Follow" : "Following" })]
423
+ })] }),
424
+ selectedLines.size > 0 ? /* @__PURE__ */ jsxs("div", {
425
+ className: "relative flex items-center h-full",
426
+ children: [/* @__PURE__ */ jsxs("button", {
427
+ type: "button",
428
+ className: "btn btn-neutral",
429
+ onClick: copySelectedLines,
430
+ children: [copied ? /* @__PURE__ */ jsx(CheckIcon, { className: "text-green-500" }) : /* @__PURE__ */ jsx(CopyIcon, {}), /* @__PURE__ */ jsx("span", { children: copied ? tCommon.copied : t.copyLines(selectedLines.size) })]
431
+ }), /* @__PURE__ */ jsx("button", {
432
+ type: "button",
433
+ className: "absolute -top-1.5 -right-1.5 btn-neutral rounded-full p-1 border transition-colors shadow-sm cursor-pointer z-10",
434
+ onClick: () => setSelectedLines(/* @__PURE__ */ new Set()),
435
+ title: t.clearSelection,
436
+ children: /* @__PURE__ */ jsx(XIcon, { className: "text-[10px]" })
437
+ })]
438
+ }) : /* @__PURE__ */ jsxs("button", {
439
+ type: "button",
440
+ className: "btn btn-neutral",
441
+ onClick: copyAllLogs,
442
+ children: [copied ? /* @__PURE__ */ jsx(CheckIcon, { className: "text-green-500" }) : /* @__PURE__ */ jsx(CopyIcon, {}), /* @__PURE__ */ jsx("span", { children: copied ? tCommon.copied : t.copyAll })]
443
+ }),
444
+ additionalLeftBarComponents,
445
+ enableWorkflowCommands && /* @__PURE__ */ jsx("button", {
446
+ type: "button",
447
+ className: cx("btn btn-neutral", showDebug && "btn-active"),
448
+ onClick: () => setShowDebug((prev) => !prev),
449
+ children: /* @__PURE__ */ jsx("span", { children: showDebug ? t.hideDebug : t.showDebug })
450
+ })
451
+ ]
452
+ }),
453
+ /* @__PURE__ */ jsx("input", {
454
+ type: "text",
455
+ className: "rounded-md text-sm h-8",
456
+ placeholder: t.filterLogs,
457
+ onChange: handleFilterChange
458
+ }),
459
+ /* @__PURE__ */ jsxs("a", {
460
+ href: logUrl,
461
+ className: cx(!logUrl && "disabled", "btn btn-neutral"),
462
+ download: true,
463
+ children: [/* @__PURE__ */ jsx(DownloadIcon, {}), /* @__PURE__ */ jsx("span", { children: t.download })]
464
+ }),
465
+ /* @__PURE__ */ jsxs("button", {
466
+ type: "button",
467
+ className: cx("btn btn-neutral", hideExpand && "hidden"),
468
+ onClick: () => setFullscreen((prev) => !prev),
469
+ children: [/* @__PURE__ */ jsx(ExpandButtonIcon, {}), /* @__PURE__ */ jsx("span", { children: expandButtonText })]
470
+ })
471
+ ]
472
+ }), additionalRightBarComponents]
473
+ }),
474
+ /* @__PURE__ */ jsx("div", {
475
+ "data-testid": "logviewer",
476
+ 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
+ style: inline ? void 0 : {
478
+ overflowY: "auto",
479
+ position: "relative",
480
+ overscrollBehavior: "contain"
481
+ },
482
+ ref: logContainerRef,
483
+ onScroll: inline ? void 0 : handleLogScroll,
484
+ onMouseUp: handleTextMouseUp,
485
+ children: /* @__PURE__ */ jsx("div", {
486
+ className: "min-w-max",
487
+ style: inline ? void 0 : {
488
+ paddingTop,
489
+ paddingBottom
490
+ },
491
+ children: visibleLogs.map((item) => /* @__PURE__ */ jsx(LogLine, {
492
+ index: item.index,
493
+ segments: item.segments,
494
+ lineNumbers,
495
+ isSelected: selectedLines.has(item.index + 1),
496
+ lineHeight: LINE_HEIGHT,
497
+ onLineClick: handleLineClick,
498
+ searchTerm: filter,
499
+ inline,
500
+ command: enableWorkflowCommands ? item.command : void 0,
501
+ groupDepth: enableWorkflowCommands ? item.groupDepth : 0,
502
+ isGroupCollapsed: enableWorkflowCommands && item.command?.type === "group" ? collapsedGroups.has(item.index) : void 0,
503
+ onGroupToggle: enableWorkflowCommands ? handleGroupToggle : void 0,
504
+ annotationLabels
505
+ }, item.index))
506
+ })
507
+ }),
508
+ highlightCopyPosition && highlightedText && /* @__PURE__ */ jsx("button", {
509
+ type: "button",
510
+ "data-highlight-copy-button": true,
511
+ className: cx("fixed z-[10000] flex items-center gap-1 px-2 py-1.5 text-xs font-medium", "rounded-md shadow-lg border cursor-pointer transition-colors duration-150", "bg-(--theme-panel-bg) border-(--theme-border) hover:bg-(--theme-hover) text-(--theme-app)"),
512
+ style: {
513
+ left: highlightCopyPosition.x,
514
+ top: highlightCopyPosition.y + 8,
515
+ transform: "translateX(-50%)"
516
+ },
517
+ onClick: copyHighlightedText,
518
+ children: copied ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(CheckIcon, { className: "text-green-500 w-3.5 h-3.5" }), /* @__PURE__ */ jsx("span", { children: tCommon.copied })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(CopyIcon, { className: "w-3.5 h-3.5" }), /* @__PURE__ */ jsx("span", { children: tCommon.copy })] })
519
+ }),
520
+ (additionalBottomLeftBarComponents || additionalBottomRightBarComponents) && /* @__PURE__ */ jsxs("div", {
521
+ className: cx("flex theme-panel select-none items-center gap-x-2 p-2"),
522
+ children: [/* @__PURE__ */ jsx("div", {
523
+ className: cx("w-full flex flex-row gap-x-2 items-center", hideExpand && "justify-between"),
524
+ children: /* @__PURE__ */ jsx("div", {
525
+ className: "flex gap-x-2 w-full",
526
+ children: additionalBottomLeftBarComponents
527
+ })
528
+ }), additionalBottomRightBarComponents]
529
+ })
530
+ ]
531
+ });
532
+ }
533
+ //#endregion
534
+ export { LogViewer };