@parallelworks/ui 0.56.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 +36 -13
- 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/ThemeEditor.js +1 -1
- package/dist/components/category.d.ts +9 -0
- package/dist/components/category.js +30 -0
- package/dist/components/fieldBox.js +3 -3
- 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 +3 -0
- package/dist/icons.js +23 -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 +17 -0
- package/dist/styles.css +1 -1
- package/dist/theme/derive.d.ts +2 -1
- package/dist/theme/derive.js +52 -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 |
|
|
@@ -273,18 +273,24 @@ until it reaches its target against every surface it sits on.
|
|
|
273
273
|
| Placeholders | 3 / 3 / 4.5 | 45 / 50 / 65 |
|
|
274
274
|
| Focus ring, status icons, dots, borders and bars | 3 / 3 / 4.5 | |
|
|
275
275
|
| Control edges | 3 / 3 / 4.5 | |
|
|
276
|
+
| Text field edges | 1.3 / 1.5 / 3 | |
|
|
276
277
|
| Text on the accent, white text on a solid status fill | 4.5 | |
|
|
277
278
|
|
|
278
|
-
Lines come in
|
|
279
|
+
Lines come in three tokens. `--theme-border` is decorative: card outlines,
|
|
279
280
|
dividers and table lines, which stay quiet. `--theme-border-strong` is a
|
|
280
|
-
control's edge: the box of
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
281
|
+
control's edge: the box of a checkbox, the ring of a radio, and the off track of
|
|
282
|
+
a switch or slider. It is all that shows where the control is, so it is solved
|
|
283
|
+
to WCAG 1.4.11's 3:1 on the page, the panels and the input fill. A checked
|
|
284
|
+
control's edge, an on switch's track and a slider's filled part take
|
|
285
|
+
`--theme-focus`: the accent as picked when it reaches 3:1, and otherwise the
|
|
286
|
+
accent solved to secondary text's contrast, so on always reads heavier than the
|
|
287
|
+
off track. `--theme-border-field` is a text field's resting edge: inputs,
|
|
288
|
+
selects, textareas and pickers. A field is also found by its label, its size and
|
|
289
|
+
the text inside it, and a form of 3:1 boxes reads heavy, so its edge sits
|
|
290
|
+
between the other two and reaches 3:1 at contrast 100. Pointing at a field
|
|
291
|
+
raises its edge to `--theme-border-strong`. A host that sets
|
|
292
|
+
`--theme-border-strong` itself sets it to a color that keeps 3:1 against its
|
|
293
|
+
surfaces.
|
|
288
294
|
|
|
289
295
|
Status colors (success, error, warning, info, favorite) and terminal colors keep
|
|
290
296
|
fixed hues, so red means the same thing in every theme.
|
|
@@ -306,6 +312,16 @@ icon beside an error message may keep the message's text shade through
|
|
|
306
312
|
from the accent's hue when they come within 30° of it, so a keyword never looks
|
|
307
313
|
like a link.
|
|
308
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
|
+
|
|
309
325
|
Every color is mapped into sRGB with the CSS Color 4 method, which lowers
|
|
310
326
|
chroma at fixed lightness and hue. It is mapped into display-p3 too: where the
|
|
311
327
|
wider color differs from sRGB by more than one just-noticeable difference
|
|
@@ -560,13 +576,20 @@ root:
|
|
|
560
576
|
| ------------------- | ----------------------------------------------------------------------- |
|
|
561
577
|
| `--app-inset-top` | The height of a fixed top bar. Drawers and toasts start below it. |
|
|
562
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. |
|
|
563
|
-
| `--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. |
|
|
564
579
|
|
|
565
580
|
```ts
|
|
566
581
|
document.documentElement.style.setProperty('--app-inset-top', '56px')
|
|
567
582
|
document.documentElement.style.setProperty('--app-inset-start', '220px')
|
|
568
583
|
```
|
|
569
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
|
+
|
|
570
593
|
Everything the package lays over the page stacks on one scale, set on the
|
|
571
594
|
document root by `base.css`. Give the host's own fixed elements a place on it,
|
|
572
595
|
such as `z-index: 100` for a top bar, rather than a z-index chosen to win:
|
|
@@ -617,8 +640,8 @@ meaning across the apps.
|
|
|
617
640
|
`DisableUserIcon` turns off their account. `ChangesRequestedIcon` asks for
|
|
618
641
|
changes to something sent, while `FlaggedIcon` marks an item for attention.
|
|
619
642
|
`ProjectIcon` is a project, while `FolderIcon` is a folder of files.
|
|
620
|
-
`LibraryIcon` is a collection of write-ups to browse,
|
|
621
|
-
|
|
643
|
+
`LibraryIcon` is a collection of write-ups to browse, `ArticleIcon` one
|
|
644
|
+
write-up to read, such as a case study, and `NotebookIcon` one notebook.
|
|
622
645
|
`PendingIcon` is a requirement not met yet and `NotRunningIcon` a run not
|
|
623
646
|
started; they share the empty circle, so keep them out of the same list.
|
|
624
647
|
- **Adding an icon:** pick the [Lucide glyph](https://lucide.dev/icons) and add
|
|
@@ -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,
|
|
@@ -347,7 +347,7 @@ function ThemePreview({ theme, mode }) {
|
|
|
347
347
|
/* @__PURE__ */ jsxs("div", {
|
|
348
348
|
className: "flex items-center gap-2",
|
|
349
349
|
children: [/* @__PURE__ */ jsx("span", {
|
|
350
|
-
className: cx(fieldBox({ size: "sm" }), "flex flex-1 cursor-default items-center text-(--theme-placeholder)
|
|
350
|
+
className: cx(fieldBox({ size: "sm" }), "flex flex-1 cursor-default items-center text-(--theme-placeholder)"),
|
|
351
351
|
children: t.previewSearch
|
|
352
352
|
}), /* @__PURE__ */ jsx("span", {
|
|
353
353
|
className: "rounded-md bg-(--theme-accent) px-3 py-1.5 text-xs font-medium text-(--theme-accent-text)",
|
|
@@ -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 };
|
|
@@ -20,9 +20,9 @@ var sizes = {
|
|
|
20
20
|
* thumb: an outline in the theme's focus color, which is solved for 3:1 on every surface a
|
|
21
21
|
* control sits on, so it shows on a checked control as well as an empty one. */
|
|
22
22
|
var focusOutline = "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--theme-focus)";
|
|
23
|
-
var base = "w-full min-w-0 rounded-md border border-(--theme-border-
|
|
24
|
-
var own = cx("focus-visible:border-(--theme-focus) focus-visible:ring-1 focus-visible:ring-(--theme-focus)", "aria-invalid:border-(--theme-error) aria-invalid:focus-visible:ring-(--theme-error)", "disabled:cursor-not-allowed disabled:bg-(--theme-input-disabled-bg) disabled:text-(--theme-placeholder)", "data-disabled:cursor-not-allowed data-disabled:bg-(--theme-input-disabled-bg) data-disabled:text-(--theme-placeholder)");
|
|
25
|
-
var inside = cx("has-[:focus-visible]:border-(--theme-focus) has-[:focus-visible]:ring-1 has-[:focus-visible]:ring-(--theme-focus)", "has-[[aria-invalid=true]]:border-(--theme-error) has-[[aria-invalid=true]:focus-visible]:ring-(--theme-error)", "has-[input:disabled]:cursor-not-allowed has-[input:disabled]:bg-(--theme-input-disabled-bg) has-[input:disabled]:text-(--theme-placeholder)", "data-disabled:cursor-not-allowed data-disabled:bg-(--theme-input-disabled-bg) data-disabled:text-(--theme-placeholder)");
|
|
23
|
+
var base = "w-full min-w-0 rounded-md border border-(--theme-border-field) bg-(--theme-input-bg) text-(--theme-app) outline-none transition-colors placeholder:text-(--theme-placeholder) hover:border-(--theme-border-strong)";
|
|
24
|
+
var own = cx("focus-visible:border-(--theme-focus) focus-visible:ring-1 focus-visible:ring-(--theme-focus)", "aria-invalid:border-(--theme-error) aria-invalid:focus-visible:ring-(--theme-error)", "disabled:cursor-not-allowed disabled:hover:border-(--theme-border-field) disabled:bg-(--theme-input-disabled-bg) disabled:text-(--theme-placeholder)", "data-disabled:cursor-not-allowed data-disabled:hover:border-(--theme-border-field) data-disabled:bg-(--theme-input-disabled-bg) data-disabled:text-(--theme-placeholder)");
|
|
25
|
+
var inside = cx("has-[:focus-visible]:border-(--theme-focus) has-[:focus-visible]:ring-1 has-[:focus-visible]:ring-(--theme-focus)", "has-[[aria-invalid=true]]:border-(--theme-error) has-[[aria-invalid=true]:focus-visible]:ring-(--theme-error)", "has-[input:disabled]:cursor-not-allowed has-[input:disabled]:hover:border-(--theme-border-field) has-[input:disabled]:bg-(--theme-input-disabled-bg) has-[input:disabled]:text-(--theme-placeholder)", "data-disabled:cursor-not-allowed data-disabled:hover:border-(--theme-border-field) data-disabled:bg-(--theme-input-disabled-bg) data-disabled:text-(--theme-placeholder)");
|
|
26
26
|
/** The box every input-like control draws: text inputs, textareas, and the triggers of
|
|
27
27
|
* pickers. Invalid follows `aria-invalid`, disabled follows `disabled` or `data-disabled`. */
|
|
28
28
|
function fieldBox({ size = "md", multiline = false, padded = true, within = false } = {}) {
|
|
@@ -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) => {
|