@parallelworks/ui 0.57.0 → 0.58.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.
- package/README.md +19 -2
- package/dist/access/PrincipalPicker.js +2 -1
- package/dist/ai/ai.css +38 -0
- package/dist/components/Avatar.d.ts +5 -1
- package/dist/components/Avatar.js +4 -2
- package/dist/components/Markdown.d.ts +3 -0
- package/dist/components/Markdown.js +2 -1
- package/dist/components/MarkdownImpl.d.ts +1 -1
- package/dist/components/MarkdownImpl.js +5 -3
- package/dist/components/Provider.d.ts +10 -0
- package/dist/components/Provider.js +9 -0
- package/dist/components/Table.d.ts +7 -5
- package/dist/components/Table.js +6 -2
- package/dist/components/category.d.ts +9 -0
- package/dist/components/category.js +30 -0
- package/dist/components/httpsImages.d.ts +12 -0
- package/dist/components/httpsImages.js +27 -0
- package/dist/components/stickyTop.d.ts +10 -0
- package/dist/components/stickyTop.js +88 -0
- package/dist/file-explorer/FileExplorer.js +21 -15
- package/dist/graph/DependencyGraph.d.ts +14 -1
- package/dist/graph/DependencyGraph.js +169 -77
- package/dist/graph/JobSummary.js +13 -3
- package/dist/graph/StepSummary.d.ts +4 -0
- package/dist/graph/StepSummary.js +31 -0
- package/dist/graph/index.d.ts +1 -0
- package/dist/graph/index.js +2 -1
- package/dist/graph/types.d.ts +3 -0
- package/dist/graph/util.d.ts +1 -1
- package/dist/icons.d.ts +1 -0
- package/dist/icons.js +3 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -1
- package/dist/logviewer/LogViewer.js +3 -3
- package/dist/styles/theme.css +16 -0
- package/dist/styles.css +1 -1
- package/dist/theme/derive.d.ts +2 -1
- package/dist/theme/derive.js +45 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ Each need has one component. Reach for it before styling markup by hand.
|
|
|
84
84
|
| How full something is, how far a task has come | `Meter`, `ProgressBar` |
|
|
85
85
|
| Pages of a long list | `Pagination` |
|
|
86
86
|
| A grouped page of settings | `SettingsCard` with `SettingRow`s |
|
|
87
|
-
| A person | `Avatar
|
|
87
|
+
| A person | `Avatar`, colored by name without a picture |
|
|
88
88
|
| A status in words | `StatusBadge`, or `StatusDot` beside text that says it |
|
|
89
89
|
| A run's, job's or step's status beside its name | `RunStatusIcon`; `StatusBadge` with `runStatusTone` to say it in words |
|
|
90
90
|
| A copy action | `CopyButton`; `CopyCodeBlock` for a command or snippet; `useCopy` for a menu row or a control of your own |
|
|
@@ -312,6 +312,16 @@ icon beside an error message may keep the message's text shade through
|
|
|
312
312
|
from the accent's hue when they come within 30° of it, so a keyword never looks
|
|
313
313
|
like a link.
|
|
314
314
|
|
|
315
|
+
Eight category colors tell things apart without ranking them:
|
|
316
|
+
`--theme-category-1` to `--theme-category-8` are soft fills at hues 45° apart,
|
|
317
|
+
all at one lightness and chroma so none reads louder, and each
|
|
318
|
+
`--theme-category-N-text` is solved to secondary text's targets on its fill. An
|
|
319
|
+
`Avatar` without a picture takes one, picked by an FNV-1a hash of its name (or
|
|
320
|
+
its `colorKey`), so the same name has the same color on every page; a nameless
|
|
321
|
+
one keeps the accent. `categoryOf(key)` gives the same pick, 1–8, and
|
|
322
|
+
`categoryClasses(key)` its fill and text classes, for anything else drawn
|
|
323
|
+
beside a name, such as a group's icon.
|
|
324
|
+
|
|
315
325
|
Every color is mapped into sRGB with the CSS Color 4 method, which lowers
|
|
316
326
|
chroma at fixed lightness and hue. It is mapped into display-p3 too: where the
|
|
317
327
|
wider color differs from sRGB by more than one just-noticeable difference
|
|
@@ -566,13 +576,20 @@ root:
|
|
|
566
576
|
| ------------------- | ----------------------------------------------------------------------- |
|
|
567
577
|
| `--app-inset-top` | The height of a fixed top bar. Drawers and toasts start below it. |
|
|
568
578
|
| `--app-inset-start` | The width of a fixed sidebar on the start side. Toasts center over the page beside it, and a drawer at its widest leaves that strip, and never less than 2.5rem. Update it when the sidebar collapses. |
|
|
569
|
-
| `--table-header-top` | How far below the top of its scrolling box a table's sticky header row stops (`Table stickyHeader`, which list tables use). Set it on the scrolling box, or an ancestor, when something else sticks at that box's top, such as a page header in a page that scrolls as a whole. Inside `ListPage` the rows scroll in their own box below the header and toolbar, so it stays 0. |
|
|
570
579
|
|
|
571
580
|
```ts
|
|
572
581
|
document.documentElement.style.setProperty('--app-inset-top', '56px')
|
|
573
582
|
document.documentElement.style.setProperty('--app-inset-start', '220px')
|
|
574
583
|
```
|
|
575
584
|
|
|
585
|
+
A table's sticky header row (`Table stickyHeader`, which list tables use) needs
|
|
586
|
+
no variable. It stops just below whatever its scrolling box already pins at the
|
|
587
|
+
top, such as a page header, a toolbar and tabs: it finds each element there with
|
|
588
|
+
`position: sticky` and a `top` whose parent also holds the table, so it stays
|
|
589
|
+
pinned while the table passes, and follows it as it wraps to a new height or
|
|
590
|
+
comes and goes. In a box that scrolls on its own, such as a fixed-height panel
|
|
591
|
+
or `ListPage`'s rows, it pins at the box's top.
|
|
592
|
+
|
|
576
593
|
Everything the package lays over the page stacks on one scale, set on the
|
|
577
594
|
document root by `base.css`. Give the host's own fixed elements a place on it,
|
|
578
595
|
such as `z-index: 100` for a top bar, rather than a z-index chosen to win:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { cx } from "../cx.js";
|
|
2
2
|
import { OrganizationIcon, RefreshIcon, UsersIcon } from "../icons.js";
|
|
3
3
|
import { useQueryMatch } from "../components/search.js";
|
|
4
|
+
import { categoryClasses } from "../components/category.js";
|
|
4
5
|
import { Avatar } from "../components/Avatar.js";
|
|
5
6
|
import { Callout } from "../components/Callout.js";
|
|
6
7
|
import { Combobox } from "../components/Combobox.js";
|
|
@@ -35,7 +36,7 @@ function PrincipalTile({ principal, compact = false, className }) {
|
|
|
35
36
|
const Icon = everyone ? OrganizationIcon : UsersIcon;
|
|
36
37
|
return /* @__PURE__ */ jsx("span", {
|
|
37
38
|
"aria-hidden": "true",
|
|
38
|
-
className: cx("flex size-8 shrink-0 items-center justify-center rounded-md [&_svg]:size-4", compact && "size-6 [&_svg]:size-3.5", everyone ? "bg-(--theme-info-muted) text-(--theme-info-text)" :
|
|
39
|
+
className: cx("flex size-8 shrink-0 items-center justify-center rounded-md [&_svg]:size-4", compact && "size-6 [&_svg]:size-3.5", everyone ? "bg-(--theme-info-muted) text-(--theme-info-text)" : categoryClasses(principal.name), className),
|
|
39
40
|
children: /* @__PURE__ */ jsx(Icon, {})
|
|
40
41
|
});
|
|
41
42
|
}
|
package/dist/ai/ai.css
CHANGED
|
@@ -182,6 +182,44 @@
|
|
|
182
182
|
.markdown [data-streamdown="table"] tbody tr:last-child {
|
|
183
183
|
border-bottom: 0;
|
|
184
184
|
}
|
|
185
|
+
/* Markdown set inside other UI, such as a step's summary, reads at the page's
|
|
186
|
+
* text size, its headings a step above it rather than at a document's sizes. */
|
|
187
|
+
.markdown-compact {
|
|
188
|
+
font-size: 0.875rem;
|
|
189
|
+
line-height: 1.5;
|
|
190
|
+
}
|
|
191
|
+
.markdown-compact :is(h1, h2, h3, h4, h5, h6) {
|
|
192
|
+
font-size: 0.875rem;
|
|
193
|
+
line-height: 1.25rem;
|
|
194
|
+
font-weight: 600;
|
|
195
|
+
}
|
|
196
|
+
.markdown-compact :is(h1, h2) {
|
|
197
|
+
font-size: 1rem;
|
|
198
|
+
line-height: 1.5rem;
|
|
199
|
+
}
|
|
200
|
+
.markdown-compact [data-streamdown="table-wrapper"] table,
|
|
201
|
+
.markdown-compact [data-streamdown="table"] :is(th, td) {
|
|
202
|
+
font-size: 0.875rem;
|
|
203
|
+
line-height: 1.25rem;
|
|
204
|
+
}
|
|
205
|
+
/* A summary's tables are mostly a few short columns of counts: they hug their
|
|
206
|
+
* content so each number sits by its label, and a phone fits them unscrolled. */
|
|
207
|
+
.markdown-compact table[data-streamdown="table"] {
|
|
208
|
+
width: auto;
|
|
209
|
+
min-width: min(100%, 20rem);
|
|
210
|
+
}
|
|
211
|
+
.markdown-compact [data-streamdown="table"] :is(th, td) {
|
|
212
|
+
min-width: 0;
|
|
213
|
+
padding-inline-end: 2rem;
|
|
214
|
+
font-variant-numeric: tabular-nums;
|
|
215
|
+
}
|
|
216
|
+
.markdown-compact [data-streamdown="table"] th {
|
|
217
|
+
font-weight: 500;
|
|
218
|
+
color: var(--theme-muted-text-color);
|
|
219
|
+
}
|
|
220
|
+
.markdown-compact :not(pre) > code {
|
|
221
|
+
font-size: 0.8125rem;
|
|
222
|
+
}
|
|
185
223
|
@media (prefers-reduced-motion: reduce) {
|
|
186
224
|
.markdown [data-streamdown="table-wrapper"] > div:first-child {
|
|
187
225
|
transition: none;
|
|
@@ -4,6 +4,10 @@ export interface AvatarProps {
|
|
|
4
4
|
/** Image URL. Until it loads, or when it fails, the initials of `name` show. */
|
|
5
5
|
src?: string | null | undefined;
|
|
6
6
|
name?: string | undefined;
|
|
7
|
+
/** Picks the fallback's color from the theme's categorical palette, so the same key gets the
|
|
8
|
+
* same color everywhere. Defaults to `name`; pass a stable id when names can repeat or
|
|
9
|
+
* change, or `null` for the accent fill whatever the name. Without either, the accent fills it. */
|
|
10
|
+
colorKey?: string | null | undefined;
|
|
7
11
|
size?: AvatarSize | undefined;
|
|
8
12
|
status?: AvatarStatus | undefined;
|
|
9
13
|
className?: string | undefined;
|
|
@@ -13,4 +17,4 @@ export declare function avatarInitials(name: string | undefined): string;
|
|
|
13
17
|
/** A person's picture, or their initials while it loads or when it fails. It is
|
|
14
18
|
* one image to assistive technology, named for the person, whichever shows,
|
|
15
19
|
* and their presence when it has a status dot. */
|
|
16
|
-
export declare function Avatar({ src, name, size, status, className }: AvatarProps): import("react").JSX.Element;
|
|
20
|
+
export declare function Avatar({ src, name, colorKey, size, status, className }: AvatarProps): import("react").JSX.Element;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cx } from "../cx.js";
|
|
2
2
|
import { useStrings } from "./Provider.js";
|
|
3
|
+
import { categoryClasses } from "./category.js";
|
|
3
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
5
|
import { Avatar } from "@base-ui/react/avatar";
|
|
5
6
|
//#region src/components/Avatar.tsx
|
|
@@ -28,9 +29,10 @@ function avatarInitials(name) {
|
|
|
28
29
|
/** A person's picture, or their initials while it loads or when it fails. It is
|
|
29
30
|
* one image to assistive technology, named for the person, whichever shows,
|
|
30
31
|
* and their presence when it has a status dot. */
|
|
31
|
-
function Avatar$1({ src, name, size = "md", status, className }) {
|
|
32
|
+
function Avatar$1({ src, name, colorKey, size = "md", status, className }) {
|
|
32
33
|
const { common } = useStrings();
|
|
33
34
|
const who = name || common.userAvatar;
|
|
35
|
+
const colorBy = colorKey === void 0 ? name?.trim() : colorKey;
|
|
34
36
|
return /* @__PURE__ */ jsxs("span", {
|
|
35
37
|
role: "img",
|
|
36
38
|
"aria-label": status ? common.withPresence(who, common.presence[status]) : who,
|
|
@@ -45,7 +47,7 @@ function Avatar$1({ src, name, size = "md", status, className }) {
|
|
|
45
47
|
className: "absolute inset-0 size-full object-cover data-error:hidden data-loading:invisible"
|
|
46
48
|
}, src), /* @__PURE__ */ jsx(Avatar.Fallback, {
|
|
47
49
|
"aria-hidden": "true",
|
|
48
|
-
className: "flex size-full items-center justify-center bg-gradient-to-br from-[var(--gradient-start,var(--theme-accent))] to-[var(--gradient-mid,var(--theme-link))] text-(--theme-accent-text)",
|
|
50
|
+
className: cx("flex size-full items-center justify-center", colorBy ? categoryClasses(colorBy) : "bg-gradient-to-br from-[var(--gradient-start,var(--theme-accent))] to-[var(--gradient-mid,var(--theme-link))] text-(--theme-accent-text)"),
|
|
49
51
|
children: size === "xs" ? avatarInitials(name).charAt(0) : avatarInitials(name)
|
|
50
52
|
})]
|
|
51
53
|
}), status && /* @__PURE__ */ jsx("span", {
|
|
@@ -7,6 +7,9 @@ export interface MarkdownProps {
|
|
|
7
7
|
controls?: ControlsConfig;
|
|
8
8
|
/** Per-tag overrides of the rendered elements. */
|
|
9
9
|
components?: Components;
|
|
10
|
+
httpsImagesOnly?: boolean;
|
|
11
|
+
/** Sets it at the page's text size, for Markdown inside other UI rather than a document of its own. */
|
|
12
|
+
compact?: boolean;
|
|
10
13
|
}
|
|
11
14
|
/** Markdown, with highlighted code, math and mermaid diagrams. Links leave in a
|
|
12
15
|
* new tab, and inline code breaks after path separators. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { cx } from "../cx.js";
|
|
1
2
|
import { jsx } from "react/jsx-runtime";
|
|
2
3
|
import { Suspense, lazy } from "react";
|
|
3
4
|
//#region src/components/Markdown.tsx
|
|
@@ -7,7 +8,7 @@ var MarkdownImpl = lazy(() => import("./MarkdownImpl.js"));
|
|
|
7
8
|
function Markdown(props) {
|
|
8
9
|
return /* @__PURE__ */ jsx(Suspense, {
|
|
9
10
|
fallback: /* @__PURE__ */ jsx("div", {
|
|
10
|
-
className: "markdown whitespace-pre-wrap text-start w-full",
|
|
11
|
+
className: cx("markdown whitespace-pre-wrap text-start w-full", props.compact && "markdown-compact"),
|
|
11
12
|
children: props.children
|
|
12
13
|
}),
|
|
13
14
|
children: /* @__PURE__ */ jsx(MarkdownImpl, { ...props })
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import 'katex/dist/katex.min.css';
|
|
2
2
|
import type { MarkdownProps } from './Markdown.js';
|
|
3
|
-
export default function MarkdownImpl({ children, isStreaming, controls, components, }: MarkdownProps): import("react").JSX.Element;
|
|
3
|
+
export default function MarkdownImpl({ children, isStreaming, controls, components, httpsImagesOnly, compact, }: MarkdownProps): import("react").JSX.Element;
|
|
@@ -2,6 +2,7 @@ import { cx } from "../cx.js";
|
|
|
2
2
|
import { ExternalLink } from "./ExternalLink.js";
|
|
3
3
|
import { readIsDark } from "./useCssIsDark.js";
|
|
4
4
|
import { CODE_THEMES } from "./codeTheme.js";
|
|
5
|
+
import { rehypeHttpsImagesOnly } from "./httpsImages.js";
|
|
5
6
|
import { rehypeCodeBreaks } from "./markdownCodeBreaks.js";
|
|
6
7
|
import { jsx } from "react/jsx-runtime";
|
|
7
8
|
import { useEffect, useState } from "react";
|
|
@@ -33,7 +34,8 @@ function loadMermaid() {
|
|
|
33
34
|
return mermaidLoad;
|
|
34
35
|
}
|
|
35
36
|
var rehypePlugins = [...Object.values(defaultRehypePlugins), rehypeCodeBreaks];
|
|
36
|
-
|
|
37
|
+
var httpsImagesRehypePlugins = [...Object.entries(defaultRehypePlugins).flatMap(([name, plugin]) => name === "harden" ? [rehypeHttpsImagesOnly, plugin] : [plugin]), rehypeCodeBreaks];
|
|
38
|
+
function MarkdownImpl({ children, isStreaming, controls, components, httpsImagesOnly, compact }) {
|
|
37
39
|
const needsMermaid = !!children && children.includes("```mermaid");
|
|
38
40
|
const [, setMermaidReady] = useState(() => !!pluginsWithMermaid);
|
|
39
41
|
useEffect(() => {
|
|
@@ -48,7 +50,7 @@ function MarkdownImpl({ children, isStreaming, controls, components }) {
|
|
|
48
50
|
}, [needsMermaid]);
|
|
49
51
|
const plugins = needsMermaid && pluginsWithMermaid ? pluginsWithMermaid : basePlugins;
|
|
50
52
|
return /* @__PURE__ */ jsx("div", {
|
|
51
|
-
className: "markdown whitespace-normal text-start w-full",
|
|
53
|
+
className: cx("markdown whitespace-normal text-start w-full", compact && "markdown-compact"),
|
|
52
54
|
children: /* @__PURE__ */ jsx(Streamdown, {
|
|
53
55
|
mode: isStreaming ? "streaming" : "static",
|
|
54
56
|
isAnimating: isStreaming ?? false,
|
|
@@ -58,7 +60,7 @@ function MarkdownImpl({ children, isStreaming, controls, components }) {
|
|
|
58
60
|
} : {},
|
|
59
61
|
...controls ? { controls } : {},
|
|
60
62
|
plugins,
|
|
61
|
-
rehypePlugins,
|
|
63
|
+
rehypePlugins: httpsImagesOnly ? httpsImagesRehypePlugins : rehypePlugins,
|
|
62
64
|
shikiTheme: CODE_THEMES,
|
|
63
65
|
components: {
|
|
64
66
|
a: ({ node, ...props }) => /* @__PURE__ */ jsx(ExternalLink, {
|
|
@@ -227,6 +227,14 @@ export interface UIStrings {
|
|
|
227
227
|
loadingLogs: string;
|
|
228
228
|
noLogFound: string;
|
|
229
229
|
logLoadFailed: string;
|
|
230
|
+
loadingSummary: string;
|
|
231
|
+
hasSummary: string;
|
|
232
|
+
stepOf: (position: number, count: number) => string;
|
|
233
|
+
summaryLoadFailed: string;
|
|
234
|
+
summaryTab: string;
|
|
235
|
+
logsTab: string;
|
|
236
|
+
scriptTab: string;
|
|
237
|
+
openSubworkflow: string;
|
|
230
238
|
statusLabel: (status: string) => string;
|
|
231
239
|
statusReason: (reason: string | undefined | null) => string | undefined;
|
|
232
240
|
/** A run's general log, beside its summary. */
|
|
@@ -277,6 +285,8 @@ export interface UIStrings {
|
|
|
277
285
|
openFolder: string;
|
|
278
286
|
/** A path's page in the app, as in "Copy link". */
|
|
279
287
|
link: string;
|
|
288
|
+
/** An object's key within its storage, as in "Copy path". */
|
|
289
|
+
path: string;
|
|
280
290
|
detailsTab: string;
|
|
281
291
|
previewTab: string;
|
|
282
292
|
preview: string;
|
|
@@ -236,6 +236,14 @@ var DEFAULTS = {
|
|
|
236
236
|
loadingLogs: "Loading logs",
|
|
237
237
|
noLogFound: "No log found",
|
|
238
238
|
logLoadFailed: "Log could not be loaded",
|
|
239
|
+
loadingSummary: "Loading summary",
|
|
240
|
+
hasSummary: "Has a summary",
|
|
241
|
+
stepOf: (position, count) => `Step ${position} of ${count}`,
|
|
242
|
+
summaryLoadFailed: "Summary could not be loaded",
|
|
243
|
+
summaryTab: "Summary",
|
|
244
|
+
logsTab: "Logs",
|
|
245
|
+
scriptTab: "Script",
|
|
246
|
+
openSubworkflow: "Open Subworkflow",
|
|
239
247
|
statusLabel: (status) => status,
|
|
240
248
|
statusReason: () => void 0,
|
|
241
249
|
generalLog: "General log",
|
|
@@ -289,6 +297,7 @@ var DEFAULTS = {
|
|
|
289
297
|
viewDetails: "View details",
|
|
290
298
|
openFolder: "Open folder",
|
|
291
299
|
link: "link",
|
|
300
|
+
path: "path",
|
|
292
301
|
detailsTab: "Details",
|
|
293
302
|
previewTab: "Preview",
|
|
294
303
|
preview: "Preview",
|
|
@@ -37,11 +37,13 @@ interface TableProps {
|
|
|
37
37
|
roundedBottom?: boolean;
|
|
38
38
|
isLoading?: boolean;
|
|
39
39
|
/** The header row stays in view while the rows scroll under it. It sticks
|
|
40
|
-
* to the nearest scrolling ancestor,
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
40
|
+
* to the nearest scrolling ancestor, just below whatever that ancestor
|
|
41
|
+
* already pins at its top, such as a page header, a toolbar or tabs, which
|
|
42
|
+
* it finds and follows by itself; in a box that scrolls on its own it pins
|
|
43
|
+
* at the box's top. So the table no longer scrolls or clips on its own: a
|
|
44
|
+
* box that scrolls itself is all a sticky header can stick to. Its ancestor
|
|
45
|
+
* scrolls it sideways as well as down. Rows pass under the header, so a
|
|
46
|
+
* `theadClassName` of your own needs a background. */
|
|
45
47
|
stickyHeader?: boolean | undefined;
|
|
46
48
|
/** Forwarded to the `<table>`, for attributes that must sit on the element owning
|
|
47
49
|
* the rows — `role='grid'`, aria-activedescendant, aria-rowcount. `className` is
|
package/dist/components/Table.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { cx } from "../cx.js";
|
|
2
2
|
import { SortDownIcon, SortUnsortedIcon, SortUpIcon } from "../icons.js";
|
|
3
3
|
import { Loader } from "./Loader.js";
|
|
4
|
+
import { useStickyTop } from "./stickyTop.js";
|
|
4
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
-
import React, { createContext, useContext } from "react";
|
|
6
|
+
import React, { createContext, useContext, useRef } from "react";
|
|
6
7
|
//#region src/components/Table.tsx
|
|
7
8
|
var DensityContext = createContext("comfortable");
|
|
8
9
|
var cellX = "px-3";
|
|
@@ -47,6 +48,8 @@ var isHeaderChild = (child) => {
|
|
|
47
48
|
* the children, then `<tr>`s of `Table.Item`s. */
|
|
48
49
|
function Table({ density = "comfortable", borderless = false, panel = true, className, wrapperClassName = "", tbodyClassName = "divide-y", theadClassName = "theme-muted-panel", children, roundedTop = false, rounded = false, roundedBottom = false, isLoading = false, stickyHeader = false, tableProps }) {
|
|
49
50
|
const childList = React.Children.toArray(children);
|
|
51
|
+
const thead = useRef(null);
|
|
52
|
+
useStickyTop(thead, stickyHeader && !isLoading);
|
|
50
53
|
const hasOverflow = wrapperClassName.split(" ").some((cls) => cls.startsWith("overflow-"));
|
|
51
54
|
return /* @__PURE__ */ jsx(DensityContext, {
|
|
52
55
|
value: density,
|
|
@@ -62,7 +65,8 @@ function Table({ density = "comfortable", borderless = false, panel = true, clas
|
|
|
62
65
|
className: cx("min-w-full divide-y theme-border", className),
|
|
63
66
|
...tableProps,
|
|
64
67
|
children: [/* @__PURE__ */ jsx("thead", {
|
|
65
|
-
|
|
68
|
+
ref: thead,
|
|
69
|
+
className: cx(stickyHeader && "sticky top-0 z-(--layer-sticky) border-b-0 shadow-[inset_0_-1px_0_var(--theme-border)]", theadClassName),
|
|
66
70
|
children: /* @__PURE__ */ jsx("tr", { children: childList.filter(isHeaderChild) })
|
|
67
71
|
}), /* @__PURE__ */ jsx("tbody", {
|
|
68
72
|
className: tbodyClassName,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** The fill and text of each palette entry, written out so the stylesheet build sees them. */
|
|
2
|
+
export declare const CATEGORY_CLASSES: readonly ['bg-(--theme-category-1) text-(--theme-category-1-text)', 'bg-(--theme-category-2) text-(--theme-category-2-text)', 'bg-(--theme-category-3) text-(--theme-category-3-text)', 'bg-(--theme-category-4) text-(--theme-category-4-text)', 'bg-(--theme-category-5) text-(--theme-category-5-text)', 'bg-(--theme-category-6) text-(--theme-category-6-text)', 'bg-(--theme-category-7) text-(--theme-category-7-text)', 'bg-(--theme-category-8) text-(--theme-category-8-text)'];
|
|
3
|
+
/** 32-bit FNV-1a over a string's UTF-8 bytes: cheap, well spread, and the same in any language. */
|
|
4
|
+
export declare function fnv1a(text: string): number;
|
|
5
|
+
/** The palette entry, 1–8, for a key. Case, surrounding space and Unicode composition don't
|
|
6
|
+
* change it, so "Ada Lovelace" and " ada lovelace" match. */
|
|
7
|
+
export declare function categoryOf(key: string): number;
|
|
8
|
+
/** The fill and text classes for a key. */
|
|
9
|
+
export declare function categoryClasses(key: string): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/components/category.ts
|
|
2
|
+
/** The fill and text of each palette entry, written out so the stylesheet build sees them. */
|
|
3
|
+
var CATEGORY_CLASSES = [
|
|
4
|
+
"bg-(--theme-category-1) text-(--theme-category-1-text)",
|
|
5
|
+
"bg-(--theme-category-2) text-(--theme-category-2-text)",
|
|
6
|
+
"bg-(--theme-category-3) text-(--theme-category-3-text)",
|
|
7
|
+
"bg-(--theme-category-4) text-(--theme-category-4-text)",
|
|
8
|
+
"bg-(--theme-category-5) text-(--theme-category-5-text)",
|
|
9
|
+
"bg-(--theme-category-6) text-(--theme-category-6-text)",
|
|
10
|
+
"bg-(--theme-category-7) text-(--theme-category-7-text)",
|
|
11
|
+
"bg-(--theme-category-8) text-(--theme-category-8-text)"
|
|
12
|
+
];
|
|
13
|
+
var encoder = new TextEncoder();
|
|
14
|
+
/** 32-bit FNV-1a over a string's UTF-8 bytes: cheap, well spread, and the same in any language. */
|
|
15
|
+
function fnv1a(text) {
|
|
16
|
+
let hash = 2166136261;
|
|
17
|
+
for (const byte of encoder.encode(text)) hash = Math.imul(hash ^ byte, 16777619);
|
|
18
|
+
return hash >>> 0;
|
|
19
|
+
}
|
|
20
|
+
/** The palette entry, 1–8, for a key. Case, surrounding space and Unicode composition don't
|
|
21
|
+
* change it, so "Ada Lovelace" and " ada lovelace" match. */
|
|
22
|
+
function categoryOf(key) {
|
|
23
|
+
return fnv1a(key.trim().normalize("NFC").toLowerCase()) % CATEGORY_CLASSES.length + 1;
|
|
24
|
+
}
|
|
25
|
+
/** The fill and text classes for a key. */
|
|
26
|
+
function categoryClasses(key) {
|
|
27
|
+
return CATEGORY_CLASSES[categoryOf(key) - 1];
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { CATEGORY_CLASSES, categoryClasses, categoryOf, fnv1a };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface HastElement {
|
|
2
|
+
type: 'element';
|
|
3
|
+
tagName: string;
|
|
4
|
+
properties?: Record<string, unknown>;
|
|
5
|
+
children: HastNode[];
|
|
6
|
+
}
|
|
7
|
+
type HastNode = HastElement | {
|
|
8
|
+
type: string;
|
|
9
|
+
children?: HastNode[];
|
|
10
|
+
};
|
|
11
|
+
export declare function rehypeHttpsImagesOnly(): (tree: HastNode) => void;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/components/httpsImages.ts
|
|
2
|
+
function isHttpsUrl(url) {
|
|
3
|
+
if (typeof url !== "string") return false;
|
|
4
|
+
try {
|
|
5
|
+
return new URL(url).protocol === "https:";
|
|
6
|
+
} catch {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function isHttpsSrcSet(srcSet) {
|
|
11
|
+
return (Array.isArray(srcSet) ? srcSet : [srcSet]).every((entry) => typeof entry === "string" && entry.split(",").every((candidate) => isHttpsUrl(candidate.trim().split(/\s+/)[0])));
|
|
12
|
+
}
|
|
13
|
+
function walk(node) {
|
|
14
|
+
if (node.type === "element") {
|
|
15
|
+
const { tagName, properties } = node;
|
|
16
|
+
if (properties && tagName === "img" && !isHttpsUrl(properties["src"])) delete properties["src"];
|
|
17
|
+
if (properties && (tagName === "img" || tagName === "source") && !isHttpsSrcSet(properties["srcSet"])) delete properties["srcSet"];
|
|
18
|
+
}
|
|
19
|
+
for (const child of node.children ?? []) walk(child);
|
|
20
|
+
}
|
|
21
|
+
function rehypeHttpsImagesOnly() {
|
|
22
|
+
return (tree) => {
|
|
23
|
+
walk(tree);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { rehypeHttpsImagesOnly };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Pins a `sticky` element just below whatever its scroller already pins at
|
|
4
|
+
* the top, such as a page header, a toolbar and a tab row, rather than at
|
|
5
|
+
* `top: 0` behind them. It finds them in the page, so neither they nor the
|
|
6
|
+
* host declare anything, and it follows them as they change height (a toolbar
|
|
7
|
+
* wrapping on a narrow screen) or come and go, before the browser paints.
|
|
8
|
+
* In a box that scrolls on its own, nothing sits above, so it pins at 0.
|
|
9
|
+
*/
|
|
10
|
+
export declare function useStickyTop(ref: RefObject<HTMLElement | null>, enabled: boolean): void;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { useLayoutEffect } from "react";
|
|
2
|
+
//#region src/components/stickyTop.ts
|
|
3
|
+
var scrolls = (style) => /auto|scroll|hidden|overlay/.test(`${style.overflow} ${style.overflowX} ${style.overflowY}`);
|
|
4
|
+
/** The box a sticky element sticks to: its nearest ancestor that scrolls, or
|
|
5
|
+
* the document's when none does. */
|
|
6
|
+
function scrollContainer(el) {
|
|
7
|
+
const root = el.ownerDocument.documentElement;
|
|
8
|
+
for (let node = el.parentElement; node && node !== root; node = node.parentElement) if (scrolls(getComputedStyle(node))) return node;
|
|
9
|
+
return root;
|
|
10
|
+
}
|
|
11
|
+
/** Elements laid out as children of `parent`'s box: its children, with those
|
|
12
|
+
* of a `display: contents` child (such as a tab row's root) in its place. */
|
|
13
|
+
function boxChildren(nodes, into) {
|
|
14
|
+
for (const node of nodes) {
|
|
15
|
+
into.push(node);
|
|
16
|
+
if (getComputedStyle(node).display === "contents") boxChildren(node.children, into);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function chromeAbove(el, scroller) {
|
|
20
|
+
const above = {
|
|
21
|
+
chrome: [],
|
|
22
|
+
parents: [],
|
|
23
|
+
siblings: []
|
|
24
|
+
};
|
|
25
|
+
for (let node = el; node !== scroller && node.parentElement;) {
|
|
26
|
+
const parent = node.parentElement;
|
|
27
|
+
const earlier = [];
|
|
28
|
+
for (let sib = node.previousElementSibling; sib; sib = sib.previousElementSibling) earlier.push(sib);
|
|
29
|
+
const laidOut = [];
|
|
30
|
+
boxChildren(earlier, laidOut);
|
|
31
|
+
for (const sib of laidOut) {
|
|
32
|
+
above.siblings.push(sib);
|
|
33
|
+
const style = getComputedStyle(sib);
|
|
34
|
+
if (style.display === "contents") above.parents.push(sib);
|
|
35
|
+
if (style.position === "sticky" && style.top !== "auto" && sib instanceof HTMLElement) above.chrome.push(sib);
|
|
36
|
+
}
|
|
37
|
+
above.parents.push(parent);
|
|
38
|
+
node = parent;
|
|
39
|
+
}
|
|
40
|
+
return above;
|
|
41
|
+
}
|
|
42
|
+
/** Where `chrome`'s bottom edge sits, below the scroller's top, while it's pinned. */
|
|
43
|
+
function pinnedBottom(chrome) {
|
|
44
|
+
const height = chrome.getBoundingClientRect().height;
|
|
45
|
+
return height > 0 ? (Number.parseFloat(getComputedStyle(chrome).top) || 0) + height : 0;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Pins a `sticky` element just below whatever its scroller already pins at
|
|
49
|
+
* the top, such as a page header, a toolbar and a tab row, rather than at
|
|
50
|
+
* `top: 0` behind them. It finds them in the page, so neither they nor the
|
|
51
|
+
* host declare anything, and it follows them as they change height (a toolbar
|
|
52
|
+
* wrapping on a narrow screen) or come and go, before the browser paints.
|
|
53
|
+
* In a box that scrolls on its own, nothing sits above, so it pins at 0.
|
|
54
|
+
*/
|
|
55
|
+
function useStickyTop(ref, enabled) {
|
|
56
|
+
useLayoutEffect(() => {
|
|
57
|
+
const el = ref.current;
|
|
58
|
+
if (!enabled || !el) return;
|
|
59
|
+
let chrome = [];
|
|
60
|
+
const place = () => {
|
|
61
|
+
el.style.top = `${Math.max(0, ...chrome.map(pinnedBottom))}px`;
|
|
62
|
+
};
|
|
63
|
+
const resizes = new ResizeObserver(place);
|
|
64
|
+
const changes = new MutationObserver(gather);
|
|
65
|
+
function gather() {
|
|
66
|
+
if (!el) return;
|
|
67
|
+
const above = chromeAbove(el, scrollContainer(el));
|
|
68
|
+
chrome = above.chrome;
|
|
69
|
+
resizes.disconnect();
|
|
70
|
+
changes.disconnect();
|
|
71
|
+
for (const node of chrome) resizes.observe(node);
|
|
72
|
+
for (const parent of above.parents) changes.observe(parent, { childList: true });
|
|
73
|
+
for (const sib of above.siblings) changes.observe(sib, {
|
|
74
|
+
attributes: true,
|
|
75
|
+
attributeFilter: ["class", "style"]
|
|
76
|
+
});
|
|
77
|
+
place();
|
|
78
|
+
}
|
|
79
|
+
gather();
|
|
80
|
+
return () => {
|
|
81
|
+
resizes.disconnect();
|
|
82
|
+
changes.disconnect();
|
|
83
|
+
el.style.top = "";
|
|
84
|
+
};
|
|
85
|
+
}, [ref, enabled]);
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
export { useStickyTop };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cx } from "../cx.js";
|
|
2
2
|
import { buttonClasses } from "../components/buttonClasses.js";
|
|
3
|
-
import { AccessIcon, ChevronDownIcon, ChevronRightIcon, CollapseIcon, DownloadIcon, FileIcon, FileTextIcon, FolderIcon, LinkIcon, PreviewIcon, ShareIcon, TrashIcon, UploadIcon } from "../icons.js";
|
|
3
|
+
import { AccessIcon, ChevronDownIcon, ChevronRightIcon, CollapseIcon, DownloadIcon, FileIcon, FileTextIcon, FolderIcon, FolderTreeIcon, LinkIcon, PreviewIcon, ShareIcon, TrashIcon, UploadIcon } from "../icons.js";
|
|
4
4
|
import { MenuItem, MenuPopup, MenuSeparator } from "../components/Menu.js";
|
|
5
5
|
import { safeUrl } from "../safeUrl.js";
|
|
6
6
|
import { Loader } from "../components/Loader.js";
|
|
@@ -676,24 +676,30 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
676
676
|
...open,
|
|
677
677
|
onClick: () => handleSelectTreeItem(node.path, node)
|
|
678
678
|
}];
|
|
679
|
-
const
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
679
|
+
const copy = copySubmenu({
|
|
680
|
+
name: isFile ? node.name : void 0,
|
|
681
|
+
extra: [
|
|
682
|
+
{
|
|
683
|
+
label: t.preview.path,
|
|
684
|
+
value: node.root ? void 0 : getObjectKeyFromNode(node),
|
|
685
|
+
icon: /* @__PURE__ */ jsx(FolderTreeIcon, {})
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
label: t.preview.link,
|
|
689
|
+
value: absoluteUrl(href),
|
|
690
|
+
icon: /* @__PURE__ */ jsx(LinkIcon, {})
|
|
691
|
+
},
|
|
692
|
+
...uris.map((value) => ({
|
|
693
|
+
label: `${getUriScheme(value).toUpperCase()} URI`,
|
|
694
|
+
value,
|
|
695
|
+
icon: /* @__PURE__ */ jsx(LinkIcon, {})
|
|
696
|
+
}))
|
|
697
|
+
]
|
|
689
698
|
});
|
|
690
699
|
return assembleRowMenu({
|
|
691
700
|
lead,
|
|
692
701
|
actions,
|
|
693
|
-
copy:
|
|
694
|
-
name: isFile ? node.name : void 0,
|
|
695
|
-
extra: uriExtras
|
|
696
|
-
}) : void 0
|
|
702
|
+
copy: copy.items.length > 0 ? copy : void 0
|
|
697
703
|
});
|
|
698
704
|
};
|
|
699
705
|
const openNodeContextMenu = (e, node) => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
1
2
|
import type { GraphLayout } from '../editing.js';
|
|
2
3
|
import type { DependencyGraphEditor } from './editorApi.js';
|
|
3
4
|
import { type RunLink, type WorkflowJob } from './types.js';
|
|
@@ -17,7 +18,7 @@ interface GraphRun {
|
|
|
17
18
|
executedJobs?: Record<string, unknown> | undefined;
|
|
18
19
|
links?: Record<string, RunLink> | undefined;
|
|
19
20
|
}
|
|
20
|
-
export default function DependencyGraph({ run, preview, removeBorder, viewMode, setViewMode, layout, editor, yamlJobs, inputs, workflow, fixedHeight, }: {
|
|
21
|
+
export default function DependencyGraph({ run, preview, removeBorder, viewMode, setViewMode, layout, editor, yamlJobs, inputs, workflow, fixedHeight, openStep, onCloseLog, sidebarStickyTop, children, }: {
|
|
21
22
|
run: GraphRun;
|
|
22
23
|
preview?: boolean;
|
|
23
24
|
removeBorder?: boolean | undefined;
|
|
@@ -30,6 +31,18 @@ export default function DependencyGraph({ run, preview, removeBorder, viewMode,
|
|
|
30
31
|
/** The workflow as written, for what the editor's expressions can read. */
|
|
31
32
|
workflow?: Record<string, unknown> | undefined;
|
|
32
33
|
fixedHeight?: string | undefined;
|
|
34
|
+
/** A step to open; `prefix` is its `subworkflows/<job>/step_<n>/` chain when it runs inside a subworkflow. */
|
|
35
|
+
openStep?: {
|
|
36
|
+
job: string;
|
|
37
|
+
step: number;
|
|
38
|
+
prefix?: string | undefined;
|
|
39
|
+
} | undefined;
|
|
40
|
+
/** Called when the log dialog closes, so a host can forget the step it asked to open. */
|
|
41
|
+
onCloseLog?: (() => void) | undefined;
|
|
42
|
+
/** Pins the job list this many pixels below the top of the page's scroll area, under the host's own pinned bars. */
|
|
43
|
+
sidebarStickyTop?: number | undefined;
|
|
44
|
+
/** Shown under the annotations, above the graph. */
|
|
45
|
+
children?: ReactNode;
|
|
33
46
|
}): import("react").JSX.Element | undefined;
|
|
34
47
|
export declare function DependencyGraphPreview(inputs: {
|
|
35
48
|
yml?: Record<string, unknown>;
|