@parallelworks/ui 0.78.0 → 0.79.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 +12 -1
- package/dist/access/AccessDrawer.d.ts +22 -1
- package/dist/access/AccessDrawer.js +33 -9
- package/dist/access/index.d.ts +1 -1
- package/dist/access/strings.d.ts +7 -1
- package/dist/access/strings.js +7 -1
- package/dist/ai/components/ChatMessageList.js +21 -6
- package/dist/components/ConfirmModal.d.ts +5 -3
- package/dist/components/ConfirmModal.js +22 -3
- package/dist/components/CreateModal.d.ts +5 -1
- package/dist/components/CreateModal.js +2 -2
- package/dist/components/MenuEntries.d.ts +3 -2
- package/dist/components/MenuEntries.js +1 -1
- package/dist/components/Popover.d.ts +4 -1
- package/dist/components/Popover.js +3 -3
- package/dist/components/Provider.d.ts +32 -2
- package/dist/components/Provider.js +16 -3
- package/dist/components/segmented.js +1 -1
- package/dist/graph/InputsEditor.js +3 -2
- package/dist/graph/ProblemPageLink.d.ts +9 -0
- package/dist/graph/ProblemPageLink.js +21 -0
- package/dist/graph/ProblemsButton.js +23 -13
- package/dist/graph/RunWarningsList.js +20 -11
- package/dist/graph/editorApi.d.ts +10 -0
- package/dist/graph/editorRows.js +3 -1
- package/dist/graph/editorStrings.d.ts +1 -0
- package/dist/graph/editorStrings.js +1 -0
- package/dist/graph/runWarnings.d.ts +12 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/styles.css +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -59,6 +59,15 @@ even for a one-word name. The host sets the tints with `--avatar-person`,
|
|
|
59
59
|
edge `--avatar-agent-ring`. An agent's avatar (`kind="agent"`) is a rounded
|
|
60
60
|
square under either coloring, so its shape and not only its color sets it apart.
|
|
61
61
|
|
|
62
|
+
A run's warnings and the workflow editor's problems may carry a problem `code`, `params` and
|
|
63
|
+
`type`, as a server's problem details do. Bind `data.useProblemMessage` to `useCodeMessage` from
|
|
64
|
+
`@parallelworks/problem/react`, and they read in the app's language: the message at
|
|
65
|
+
`apiErrors.<code>` in the app's use-intl catalog, or the shared one. It gets each one's `type` too:
|
|
66
|
+
for a type that names an application, such as `/problems/workflow/undefined_input`, the message at
|
|
67
|
+
`apiErrors.workflow.undefined_input` comes first. The app's own codes can stay at the top level of
|
|
68
|
+
`apiErrors`; another registry's go under its name, so a code both use keeps each one's message and
|
|
69
|
+
params. A code with no message, or no `useProblemMessage`, shows the `message` the server sent.
|
|
70
|
+
|
|
62
71
|
Menus, selects, pickers, popovers, tooltips and hover cards open over the page,
|
|
63
72
|
or over the fullscreen element while there is one. Opened inside a `<dialog>`
|
|
64
73
|
shown with `showModal()`, they open inside that dialog instead: the browser
|
|
@@ -806,7 +815,9 @@ each linking to its job or step: the link opens the subworkflows, matrix and job
|
|
|
806
815
|
it and what it is in into view below the graph's view bar, readable, and moves focus to it. Each job and step a warning is about, and each one it sits
|
|
807
816
|
inside, carries a mark that lists the warnings on hover, so one skipped without a log is still
|
|
808
817
|
flagged. A job name a matrix ran as several jobs marks each of them. `dag.warningCount` words the
|
|
809
|
-
count.
|
|
818
|
+
count. A warning with a `code` reads as the host's `useProblemMessage` words it, and one whose `type`
|
|
819
|
+
names a page, such as `/problems/workflow/undefined_input`, links to that page in a new tab
|
|
820
|
+
(`dag.warningAbout`); the editor's problems list does the same (`problemAbout`).
|
|
810
821
|
|
|
811
822
|
The editor checks YAML against the schemas a host registers with `configureEditorYaml` from
|
|
812
823
|
`@parallelworks/ui/editor`. The job, step, input and settings dialogs edit their YAML at their own
|
|
@@ -59,6 +59,24 @@ export interface AccessEditorProps {
|
|
|
59
59
|
/** Each person and group holds one role here. Their row picks it from a list, a change
|
|
60
60
|
* swaps the old role for the new one in a single change, and the grant form picks one. */
|
|
61
61
|
singleRole?: boolean | undefined;
|
|
62
|
+
/** Why a save failed, in the reader's words, under "Couldn’t grant…": a fixed sentence, or
|
|
63
|
+
* one made from what `onChange` rejected with, such as a localized message for its error
|
|
64
|
+
* code. Without it, a generic sentence; the error's own message never shows, as a
|
|
65
|
+
* server's words aren't the reader's. */
|
|
66
|
+
errorMessage?: string | ((error: unknown) => string | undefined) | undefined;
|
|
67
|
+
/** Access here is set somewhere else, such as on what the resource is part of: the list
|
|
68
|
+
* shows, read-only, under a note that says where, with a link there. */
|
|
69
|
+
managedElsewhere?: AccessManagedElsewhere | undefined;
|
|
70
|
+
}
|
|
71
|
+
export interface AccessManagedElsewhere {
|
|
72
|
+
/** Where access is set and why: "This team follows the workspace’s access." Without it, a
|
|
73
|
+
* generic sentence. */
|
|
74
|
+
reason?: ReactNode;
|
|
75
|
+
/** Where to change it, through the host's router. */
|
|
76
|
+
href?: string | undefined;
|
|
77
|
+
/** The link's text, which says where it goes: "Workspace access". Without it, "Manage
|
|
78
|
+
* access". */
|
|
79
|
+
linkLabel?: string | undefined;
|
|
62
80
|
}
|
|
63
81
|
export interface AccessDrawerProps extends AccessEditorProps {
|
|
64
82
|
open: boolean;
|
|
@@ -69,6 +87,9 @@ export interface AccessDrawerProps extends AccessEditorProps {
|
|
|
69
87
|
export interface AccessPanelProps extends AccessEditorProps {
|
|
70
88
|
/** On the panel's frame. */
|
|
71
89
|
className?: string | undefined;
|
|
90
|
+
/** Under the list, inside the frame and ruled off from it: a note or actions about access
|
|
91
|
+
* as a whole. The grant form takes its place while it shows. */
|
|
92
|
+
footer?: ReactNode;
|
|
72
93
|
}
|
|
73
94
|
/** Who has access to one resource, and the place to change it: roles granted to people and
|
|
74
95
|
* groups. Overlapping roles are allowed and only redundant, which the drawer points out; taking
|
|
@@ -78,7 +99,7 @@ export interface AccessPanelProps extends AccessEditorProps {
|
|
|
78
99
|
export declare function AccessDrawer({ open, onClose, width, ...props }: AccessDrawerProps): import("react").JSX.Element;
|
|
79
100
|
/** The access editor inline on a page, such as a settings section, rather than in a drawer: the
|
|
80
101
|
* list in a frame, which the grant form takes over with a back arrow in its own header. */
|
|
81
|
-
export declare function AccessPanel({ className, ...props }: AccessPanelProps): import("react").JSX.Element;
|
|
102
|
+
export declare function AccessPanel({ className, footer, ...props }: AccessPanelProps): import("react").JSX.Element;
|
|
82
103
|
type Match = (query: string, ...texts: (string | undefined)[]) => boolean;
|
|
83
104
|
export declare function filterAccess(model: AccessModel, arrangement: AccessArrangement, query: string, match: Match, t: AccessStrings): {
|
|
84
105
|
shown: Map<string, readonly string[]>;
|
|
@@ -9,7 +9,7 @@ import { LoadingRegion, Skeleton } from "../components/Skeleton.js";
|
|
|
9
9
|
import { Button } from "../components/Button.js";
|
|
10
10
|
import { IconButton } from "../components/IconButton.js";
|
|
11
11
|
import { Callout } from "../components/Callout.js";
|
|
12
|
-
import "../components/dismiss.js";
|
|
12
|
+
import { dialogFooterClassName } from "../components/dismiss.js";
|
|
13
13
|
import { Drawer } from "../components/Drawer.js";
|
|
14
14
|
import { Select } from "../components/Select.js";
|
|
15
15
|
import { SearchInput } from "../components/SearchInput.js";
|
|
@@ -28,7 +28,7 @@ var EMPTY_SELECTION = {
|
|
|
28
28
|
};
|
|
29
29
|
/** The list and the grant form, and the saving, failing and undoing they share, for the drawer
|
|
30
30
|
* and the inline panel to lay out. */
|
|
31
|
-
function useAccessEditor({ resourceName, roles, principals, grants, owner, inherited, permissions, onChange, loading = false, readOnly = false, readOnlyReason, describeGroup, searchPrincipals, groupCount, peopleCount, recentPrincipalIds, singleRole = false, defaultArrangement = singleRole ? "principal" : "role", visibility, rules }, inline) {
|
|
31
|
+
function useAccessEditor({ resourceName, roles, principals, grants, owner, inherited, permissions, onChange, loading = false, readOnly = false, readOnlyReason, describeGroup, searchPrincipals, groupCount, peopleCount, recentPrincipalIds, singleRole = false, defaultArrangement = singleRole ? "principal" : "role", visibility, rules, errorMessage, managedElsewhere }, inline) {
|
|
32
32
|
const t = useAccessStrings();
|
|
33
33
|
const uid = useId();
|
|
34
34
|
const openMenu = useOpenMenu();
|
|
@@ -93,7 +93,8 @@ function useAccessEditor({ resourceName, roles, principals, grants, owner, inher
|
|
|
93
93
|
visibilityChange.current = visibility?.onChange;
|
|
94
94
|
const match = useQueryMatch();
|
|
95
95
|
const filtered = useFiltered(model, arrangement, query.trim(), match, t);
|
|
96
|
-
const editable = !readOnly && onChange !== void 0;
|
|
96
|
+
const editable = !readOnly && !managedElsewhere && onChange !== void 0;
|
|
97
|
+
const messageOf = (error) => (typeof errorMessage === "function" ? errorMessage(error) : errorMessage) || t.failedReason;
|
|
97
98
|
const sectionId = (id) => `${uid}${arrangement}${id}`;
|
|
98
99
|
const names = (changed) => {
|
|
99
100
|
const all = [...changed.grant, ...changed.revoke];
|
|
@@ -344,7 +345,28 @@ function useAccessEditor({ resourceName, roles, principals, grants, owner, inher
|
|
|
344
345
|
const list = /* @__PURE__ */ jsxs("div", {
|
|
345
346
|
className: "@container/access flex min-h-0 flex-1 flex-col",
|
|
346
347
|
children: [
|
|
347
|
-
|
|
348
|
+
managedElsewhere ? /* @__PURE__ */ jsx("div", {
|
|
349
|
+
className: cx("px-4 pt-3", empty && !visibility && "pb-3"),
|
|
350
|
+
children: /* @__PURE__ */ jsx(Callout, {
|
|
351
|
+
tone: "info",
|
|
352
|
+
icon: /* @__PURE__ */ jsx(LockIcon, {}),
|
|
353
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
354
|
+
className: "flex flex-col items-start gap-2 @min-[30rem]/access:flex-row @min-[30rem]/access:gap-3",
|
|
355
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
356
|
+
className: "min-w-0 flex-1",
|
|
357
|
+
children: managedElsewhere.reason ?? t.managedElsewhere
|
|
358
|
+
}), managedElsewhere.href !== void 0 && /* @__PURE__ */ jsx("div", {
|
|
359
|
+
className: "flex shrink-0 items-center @min-[30rem]/access:h-5",
|
|
360
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
361
|
+
size: "sm",
|
|
362
|
+
variant: "secondary",
|
|
363
|
+
href: managedElsewhere.href,
|
|
364
|
+
children: managedElsewhere.linkLabel ?? t.manageAccess
|
|
365
|
+
})
|
|
366
|
+
})]
|
|
367
|
+
})
|
|
368
|
+
})
|
|
369
|
+
}) : readOnly && /* @__PURE__ */ jsx("div", {
|
|
348
370
|
className: "px-4 pt-3",
|
|
349
371
|
children: /* @__PURE__ */ jsxs(Callout, {
|
|
350
372
|
tone: "info",
|
|
@@ -397,7 +419,7 @@ function useAccessEditor({ resourceName, roles, principals, grants, owner, inher
|
|
|
397
419
|
children: loading ? /* @__PURE__ */ jsx(LoadingRegion, {
|
|
398
420
|
label: t.loading,
|
|
399
421
|
children: /* @__PURE__ */ jsx(RowsSkeleton, {})
|
|
400
|
-
}) : empty ? /* @__PURE__ */ jsxs(Fragment$1, { children: [ruleRows, /* @__PURE__ */ jsx(EmptyState, {
|
|
422
|
+
}) : empty && managedElsewhere ? ruleRows : empty ? /* @__PURE__ */ jsxs(Fragment$1, { children: [ruleRows, /* @__PURE__ */ jsx(EmptyState, {
|
|
401
423
|
variant: "bare",
|
|
402
424
|
icon: /* @__PURE__ */ jsx(UsersIcon, {}),
|
|
403
425
|
title: t.emptyTitle,
|
|
@@ -513,7 +535,7 @@ function AccessDrawer({ open, onClose, width = 560, ...props }) {
|
|
|
513
535
|
}
|
|
514
536
|
/** The access editor inline on a page, such as a settings section, rather than in a drawer: the
|
|
515
537
|
* list in a frame, which the grant form takes over with a back arrow in its own header. */
|
|
516
|
-
function AccessPanel({ className, ...props }) {
|
|
538
|
+
function AccessPanel({ className, footer, ...props }) {
|
|
517
539
|
const t = useAccessStrings();
|
|
518
540
|
const editor = useAccessEditor(props, true);
|
|
519
541
|
return /* @__PURE__ */ jsxs("section", {
|
|
@@ -542,14 +564,16 @@ function AccessPanel({ className, ...props }) {
|
|
|
542
564
|
})]
|
|
543
565
|
}),
|
|
544
566
|
editor.body,
|
|
545
|
-
editor.footer
|
|
546
|
-
className:
|
|
567
|
+
editor.footer ? /* @__PURE__ */ jsx("div", {
|
|
568
|
+
className: dialogFooterClassName,
|
|
547
569
|
children: editor.footer
|
|
570
|
+
}) : footer !== void 0 && footer !== null && footer !== false && /* @__PURE__ */ jsx("div", {
|
|
571
|
+
className: "border-t border-(--theme-border) px-4 py-3 text-sm text-(--theme-muted-text-color)",
|
|
572
|
+
children: footer
|
|
548
573
|
})
|
|
549
574
|
]
|
|
550
575
|
});
|
|
551
576
|
}
|
|
552
|
-
var messageOf = (error) => error instanceof Error && error.message ? error.message : void 0;
|
|
553
577
|
var anchorOf = (failure, arrangement) => arrangement === "role" ? failure.roleId ?? "" : failure.principalId;
|
|
554
578
|
/** The one of `elements` that is laid out, such as the one of two buttons a width shows, or the
|
|
555
579
|
* first where nothing is (as in tests). */
|
package/dist/access/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { type AccessArrangement, AccessDrawer, type AccessDrawerProps, type AccessEditorProps, AccessPanel, type AccessPanelProps, } from './AccessDrawer.js';
|
|
1
|
+
export { type AccessArrangement, AccessDrawer, type AccessDrawerProps, type AccessEditorProps, type AccessManagedElsewhere, AccessPanel, type AccessPanelProps, } from './AccessDrawer.js';
|
|
2
2
|
export { ACCESS_STRINGS, type AccessStrings, type AccessText, useAccessStrings, } from './strings.js';
|
|
3
3
|
export type { AccessChange, AccessGrant, AccessGroupDetails, AccessInheritedGrant, AccessMember, AccessPermission, AccessPrincipal, AccessRole, AccessRule, AccessVisibility, } from './types.js';
|
package/dist/access/strings.d.ts
CHANGED
|
@@ -66,7 +66,9 @@ export declare const ACCESS_STRINGS: {
|
|
|
66
66
|
removeFailed: (principal: string, roles: readonly string[]) => string;
|
|
67
67
|
grantFailed: (principals: readonly string[], roles: readonly string[]) => string;
|
|
68
68
|
undoFailed: string;
|
|
69
|
-
/**
|
|
69
|
+
/** Why a save failed, when the host gives no `errorMessage`. */
|
|
70
|
+
failedReason: string;
|
|
71
|
+
/** Under a failed save; `reason` is the host's `errorMessage`, or `failedReason`. */
|
|
70
72
|
nothingChanged: (reason: string | undefined) => string;
|
|
71
73
|
/** The toast after a grant. */
|
|
72
74
|
granted: (principals: readonly string[], roles: readonly string[]) => string;
|
|
@@ -74,6 +76,10 @@ export declare const ACCESS_STRINGS: {
|
|
|
74
76
|
removed: (principals: readonly string[], roles: readonly string[]) => string;
|
|
75
77
|
undone: string;
|
|
76
78
|
readOnly: string;
|
|
79
|
+
/** A note atop access set somewhere else, when the host gives no reason. */
|
|
80
|
+
managedElsewhere: string;
|
|
81
|
+
/** The note's link to where access is set, when the host doesn't name it. */
|
|
82
|
+
manageAccess: string;
|
|
77
83
|
/** After `readOnly` when the host gives no reason. */
|
|
78
84
|
readOnlyReason: string;
|
|
79
85
|
/** Names the switch between open to everyone and only those listed. */
|
package/dist/access/strings.js
CHANGED
|
@@ -91,7 +91,9 @@ var ACCESS_STRINGS = {
|
|
|
91
91
|
removeFailed: (principal, roles) => `Couldn’t remove ${principal} from ${and(roles)}`,
|
|
92
92
|
grantFailed: (principals, roles) => `Couldn’t grant ${and(roles)} to ${and(principals)}`,
|
|
93
93
|
undoFailed: "Couldn’t undo the change",
|
|
94
|
-
/**
|
|
94
|
+
/** Why a save failed, when the host gives no `errorMessage`. */
|
|
95
|
+
failedReason: "Something went wrong.",
|
|
96
|
+
/** Under a failed save; `reason` is the host's `errorMessage`, or `failedReason`. */
|
|
95
97
|
nothingChanged: (reason) => reason ? `${reason.replace(/\.?$/, ".")} Nothing changed.` : "Nothing changed.",
|
|
96
98
|
/** The toast after a grant. */
|
|
97
99
|
granted: (principals, roles) => `Granted ${and(roles)} to ${and(principals)}`,
|
|
@@ -99,6 +101,10 @@ var ACCESS_STRINGS = {
|
|
|
99
101
|
removed: (principals, roles) => `Removed ${and(principals)} from ${and(roles)}`,
|
|
100
102
|
undone: "Change undone",
|
|
101
103
|
readOnly: "View only.",
|
|
104
|
+
/** A note atop access set somewhere else, when the host gives no reason. */
|
|
105
|
+
managedElsewhere: "Access here is set somewhere else, so it can’t be changed here.",
|
|
106
|
+
/** The note's link to where access is set, when the host doesn't name it. */
|
|
107
|
+
manageAccess: "Manage access",
|
|
102
108
|
/** After `readOnly` when the host gives no reason. */
|
|
103
109
|
readOnlyReason: "Only people who manage access can make changes.",
|
|
104
110
|
/** Names the switch between open to everyone and only those listed. */
|
|
@@ -45,8 +45,8 @@ function ChatMessageList({ messages, allMessages, streamingMessage: streamingMes
|
|
|
45
45
|
const open = adapter.attachments?.onOpen;
|
|
46
46
|
return open ? open.bind(adapter.attachments) : void 0;
|
|
47
47
|
}, [adapter]);
|
|
48
|
-
const bottomRef = useRef(null);
|
|
49
48
|
const containerRef = useRef(null);
|
|
49
|
+
const contentRef = useRef(null);
|
|
50
50
|
const reasoningScrollRef = useRef(null);
|
|
51
51
|
const messageCount = messages.length;
|
|
52
52
|
const streamingText = streamingMessage?.content;
|
|
@@ -145,8 +145,25 @@ function ChatMessageList({ messages, allMessages, streamingMessage: streamingMes
|
|
|
145
145
|
]);
|
|
146
146
|
useEffect(() => {
|
|
147
147
|
if (!containerRef.current || !isNearBottomRef.current || isStreaming || !hadMessagesRef.current) return;
|
|
148
|
-
|
|
148
|
+
const el = containerRef.current;
|
|
149
|
+
el.scrollTo({
|
|
150
|
+
top: el.scrollHeight,
|
|
151
|
+
behavior: prefersReducedMotion() ? "auto" : "smooth"
|
|
152
|
+
});
|
|
149
153
|
}, [messageCount, isStreaming]);
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
const content = contentRef.current;
|
|
156
|
+
if (!content || typeof ResizeObserver === "undefined") return;
|
|
157
|
+
const observer = new ResizeObserver(() => {
|
|
158
|
+
const el = containerRef.current;
|
|
159
|
+
if (el && isNearBottomRef.current && !hasQueuedMessages) {
|
|
160
|
+
el.scrollTop = el.scrollHeight;
|
|
161
|
+
lastScrollTopRef.current = el.scrollTop;
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
observer.observe(content);
|
|
165
|
+
return () => observer.disconnect();
|
|
166
|
+
}, [hasContent, hasQueuedMessages]);
|
|
150
167
|
if (messages.length === 0 && !streamingMessage && !hasQueuedMessages) return /* @__PURE__ */ jsxs("div", {
|
|
151
168
|
className: "chat-thread flex-1 overflow-y-auto flex flex-col bg-(--theme-panel-bg)",
|
|
152
169
|
children: [
|
|
@@ -170,6 +187,7 @@ function ChatMessageList({ messages, allMessages, streamingMessage: streamingMes
|
|
|
170
187
|
className: "chat-thread flex-1 overflow-y-auto theme-scrollbar flex flex-col bg-(--theme-panel-bg)",
|
|
171
188
|
children: [
|
|
172
189
|
/* @__PURE__ */ jsxs("div", {
|
|
190
|
+
ref: contentRef,
|
|
173
191
|
className: "w-full max-w-[var(--chat-column,50rem)] mx-auto pt-4 pb-6 flex-1 px-4",
|
|
174
192
|
children: [
|
|
175
193
|
/* @__PURE__ */ jsx(MessageRows, {
|
|
@@ -283,10 +301,7 @@ function ChatMessageList({ messages, allMessages, streamingMessage: streamingMes
|
|
|
283
301
|
}, msg.id);
|
|
284
302
|
}),
|
|
285
303
|
footer,
|
|
286
|
-
/* @__PURE__ */ jsx("div", {
|
|
287
|
-
ref: bottomRef,
|
|
288
|
-
className: "h-8"
|
|
289
|
-
})
|
|
304
|
+
/* @__PURE__ */ jsx("div", { className: "h-8" })
|
|
290
305
|
]
|
|
291
306
|
}),
|
|
292
307
|
!pinned && /* @__PURE__ */ jsx("div", {
|
|
@@ -23,9 +23,11 @@ export interface ConfirmModalProps {
|
|
|
23
23
|
cancelLabel?: string | undefined;
|
|
24
24
|
/** When false, the dialog stays open after a successful confirm. */
|
|
25
25
|
closeOnConfirm?: boolean | undefined;
|
|
26
|
-
/**
|
|
27
|
-
*
|
|
28
|
-
|
|
26
|
+
/** What has focus as it opens. With a field in the body, such as a name to type, the first
|
|
27
|
+
* of them (`first-control`), so the reader types at once. Without one, Cancel for a
|
|
28
|
+
* destructive dialog, so a stray Enter destroys nothing, and the confirm button otherwise,
|
|
29
|
+
* so the keyboard answers at once. */
|
|
30
|
+
initialFocus?: 'cancel' | 'confirm' | 'first-control' | undefined;
|
|
29
31
|
/** Work the host runs for it, such as a mutation already under way: the buttons wait, and
|
|
30
32
|
* the dialog can't be dismissed, as while its own handler runs. */
|
|
31
33
|
pending?: boolean | undefined;
|
|
@@ -5,14 +5,25 @@ import { Modal } from "./Modal.js";
|
|
|
5
5
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
6
6
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
//#region src/components/ConfirmModal.tsx
|
|
8
|
+
/** What the reader types or picks in, which takes focus ahead of the buttons. A checkbox or
|
|
9
|
+
* radio doesn't: a destructive dialog with one still opens on Cancel. Inputs a picker keeps
|
|
10
|
+
* out of the tab order, for its form value, aren't fields. */
|
|
11
|
+
var FIELDS = [
|
|
12
|
+
"input:not([disabled]):not([type=\"hidden\"]):not([type=\"checkbox\"]):not([type=\"radio\"])",
|
|
13
|
+
"select:not([disabled])",
|
|
14
|
+
"textarea:not([disabled])",
|
|
15
|
+
"[role=\"combobox\"]:not([disabled]):not([aria-disabled=\"true\"])",
|
|
16
|
+
"[contenteditable=\"true\"]"
|
|
17
|
+
].map((field) => `${field}:not([tabindex="-1"]):not([aria-hidden="true"])`).join(", ");
|
|
8
18
|
/** Asks before an action: its title the question, its description what follows,
|
|
9
19
|
* and Cancel beside a confirm button named for the action. */
|
|
10
|
-
function ConfirmModal({ open, onClose, title, description, children, confirmLabel, onConfirm, confirmDisabled = false, destructive = false, align = "top", cancelLabel, closeOnConfirm = true, initialFocus
|
|
20
|
+
function ConfirmModal({ open, onClose, title, description, children, confirmLabel, onConfirm, confirmDisabled = false, destructive = false, align = "top", cancelLabel, closeOnConfirm = true, initialFocus, pending: pendingProp = false, error, size = "md" }) {
|
|
11
21
|
const { common } = useStrings();
|
|
12
22
|
const [running, setRunning] = useState(false);
|
|
13
23
|
const fired = useRef(false);
|
|
14
24
|
const cancelButton = useRef(null);
|
|
15
25
|
const confirmButton = useRef(null);
|
|
26
|
+
const bodyElement = useRef(null);
|
|
16
27
|
useEffect(() => {
|
|
17
28
|
if (open) fired.current = false;
|
|
18
29
|
}, [open]);
|
|
@@ -44,7 +55,11 @@ function ConfirmModal({ open, onClose, title, description, children, confirmLabe
|
|
|
44
55
|
size,
|
|
45
56
|
title,
|
|
46
57
|
description,
|
|
47
|
-
initialFocus:
|
|
58
|
+
initialFocus: () => {
|
|
59
|
+
const button = (initialFocus === "cancel" || initialFocus !== "confirm" && destructive ? cancelButton : confirmButton).current;
|
|
60
|
+
if (initialFocus === "cancel" || initialFocus === "confirm") return button ?? true;
|
|
61
|
+
return bodyElement.current?.querySelector(FIELDS) ?? button ?? true;
|
|
62
|
+
},
|
|
48
63
|
footer: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Button, {
|
|
49
64
|
ref: cancelButton,
|
|
50
65
|
variant: "ghost",
|
|
@@ -61,7 +76,11 @@ function ConfirmModal({ open, onClose, title, description, children, confirmLabe
|
|
|
61
76
|
loading: pending,
|
|
62
77
|
children: confirmLabel
|
|
63
78
|
})] }),
|
|
64
|
-
children: body
|
|
79
|
+
children: body !== void 0 && /* @__PURE__ */ jsx("div", {
|
|
80
|
+
ref: bodyElement,
|
|
81
|
+
className: "contents",
|
|
82
|
+
children: body
|
|
83
|
+
})
|
|
65
84
|
});
|
|
66
85
|
}
|
|
67
86
|
/** Asks from anywhere, such as a menu row, without keeping a dialog's state by hand:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import { type ComboboxItems } from './comboboxItems.js';
|
|
3
3
|
import type { MenuEntry, MenuSearch } from './MenuEntries.js';
|
|
4
|
+
import { type ModalSize } from './Modal.js';
|
|
4
5
|
interface CreateModalProps {
|
|
5
6
|
open: boolean;
|
|
6
7
|
onClose: () => void;
|
|
@@ -43,6 +44,9 @@ interface CreateModalProps {
|
|
|
43
44
|
footerHint?: ReactNode;
|
|
44
45
|
/** Why the last submit failed, at the footer's start, read out as it appears. */
|
|
45
46
|
error?: ReactNode;
|
|
47
|
+
/** Its width, as `Modal`'s: `xl` for a long body such as a rich text editor. It narrows to
|
|
48
|
+
* fit a small window. */
|
|
49
|
+
size?: ModalSize | undefined;
|
|
46
50
|
/** Resolves true to close the modal, false to keep it open (e.g. on error). `modifier` is
|
|
47
51
|
* true when submitted with ⌘/Ctrl+Enter, so a host can create and then open what it made. */
|
|
48
52
|
onSubmit: (name: string, description: string, favorite: boolean, how: {
|
|
@@ -50,7 +54,7 @@ interface CreateModalProps {
|
|
|
50
54
|
}) => Promise<boolean>;
|
|
51
55
|
}
|
|
52
56
|
/** Submits on Enter in the name field or ⌘/Ctrl+Enter from anywhere in the panel. */
|
|
53
|
-
export declare function CreateModal({ open, onClose, typeIcon, typeLabel, typeMenu, typeMenuSearch, namePlaceholder, hideName, initialName, showDescription, descriptionPlaceholder, descriptionField, submitLabel, submittingLabel, validateName, sanitizeName, nameHint, children, properties, advanced, submitDisabled, hideSubmit, nameOptional, onNameChange, dirty, showFavorite, footerHint, error, onSubmit, }: CreateModalProps): import("react").JSX.Element;
|
|
57
|
+
export declare function CreateModal({ open, onClose, typeIcon, typeLabel, typeMenu, typeMenuSearch, namePlaceholder, hideName, initialName, showDescription, descriptionPlaceholder, descriptionField, submitLabel, submittingLabel, validateName, sanitizeName, nameHint, children, properties, advanced, submitDisabled, hideSubmit, nameOptional, onNameChange, dirty, showFavorite, footerHint, error, size, onSubmit, }: CreateModalProps): import("react").JSX.Element;
|
|
54
58
|
/** Compact property pill with an on/off switch. */
|
|
55
59
|
export declare function CreateModalSwitch({ label, checked, onCheckedChange, tooltip, }: {
|
|
56
60
|
label: string;
|
|
@@ -16,7 +16,7 @@ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
|
16
16
|
import { Toggle } from "@base-ui/react/toggle";
|
|
17
17
|
//#region src/components/CreateModal.tsx
|
|
18
18
|
/** Submits on Enter in the name field or ⌘/Ctrl+Enter from anywhere in the panel. */
|
|
19
|
-
function CreateModal({ open, onClose, typeIcon, typeLabel, typeMenu, typeMenuSearch, namePlaceholder, hideName = false, initialName, showDescription = true, descriptionPlaceholder, descriptionField, submitLabel, submittingLabel, validateName, sanitizeName, nameHint, children, properties, advanced, submitDisabled = false, hideSubmit = false, nameOptional = false, onNameChange, dirty = false, showFavorite = false, footerHint, error, onSubmit }) {
|
|
19
|
+
function CreateModal({ open, onClose, typeIcon, typeLabel, typeMenu, typeMenuSearch, namePlaceholder, hideName = false, initialName, showDescription = true, descriptionPlaceholder, descriptionField, submitLabel, submittingLabel, validateName, sanitizeName, nameHint, children, properties, advanced, submitDisabled = false, hideSubmit = false, nameOptional = false, onNameChange, dirty = false, showFavorite = false, footerHint, error, size = "lg", onSubmit }) {
|
|
20
20
|
const t = useStrings().modal;
|
|
21
21
|
const { common } = useStrings();
|
|
22
22
|
const submitText = submitLabel ?? common.create;
|
|
@@ -75,7 +75,7 @@ function CreateModal({ open, onClose, typeIcon, typeLabel, typeMenu, typeMenuSea
|
|
|
75
75
|
open,
|
|
76
76
|
onClose,
|
|
77
77
|
"aria-label": `${submitText}: ${typeLabel}`,
|
|
78
|
-
size
|
|
78
|
+
size,
|
|
79
79
|
dismissible: !isDirty,
|
|
80
80
|
initialFocus: () => nameRef.current ?? true,
|
|
81
81
|
onKeyDown: (e) => {
|
|
@@ -45,11 +45,12 @@ interface MenuLinkAction extends MenuActionBase {
|
|
|
45
45
|
checked?: never;
|
|
46
46
|
copy?: never;
|
|
47
47
|
}
|
|
48
|
-
/** Copies `copy.text` to the clipboard, confirmed with a toast naming `copy.label
|
|
48
|
+
/** Copies `copy.text` to the clipboard, confirmed with a toast naming `copy.label`, or
|
|
49
|
+
* saying only that it copied without one. A failure says so too. */
|
|
49
50
|
interface MenuCopyAction extends MenuActionBase {
|
|
50
51
|
copy: {
|
|
51
52
|
text: string;
|
|
52
|
-
label
|
|
53
|
+
label?: string | undefined;
|
|
53
54
|
};
|
|
54
55
|
onClick?: never;
|
|
55
56
|
checked?: never;
|
|
@@ -188,7 +188,7 @@ var shortcutClassName = "ps-3 text-xs text-(--theme-muted-text-color) tabular-nu
|
|
|
188
188
|
function RowContent({ icon, label, description, end }) {
|
|
189
189
|
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
190
190
|
icon !== void 0 && icon !== null && /* @__PURE__ */ jsx("span", {
|
|
191
|
-
className: "flex h-5 shrink-0 items-center in-data-[size=sm]:h-4",
|
|
191
|
+
className: "flex h-5 min-w-4 shrink-0 items-center justify-center in-data-[size=sm]:h-4",
|
|
192
192
|
children: icon
|
|
193
193
|
}),
|
|
194
194
|
/* @__PURE__ */ jsxs("span", {
|
|
@@ -52,6 +52,9 @@ export interface PopoverOptions extends Omit<BasePopover.Popup.Props, 'className
|
|
|
52
52
|
sheet?: boolean | undefined;
|
|
53
53
|
/** `panel` holds controls or a list; `tip` is a tooltip-sized note in the tooltip's look. */
|
|
54
54
|
variant?: 'panel' | 'tip';
|
|
55
|
+
/** A panel's content reaches its edges, such as a list of suggestions whose rows pad
|
|
56
|
+
* themselves and highlight edge to edge. A title keeps its own padding. */
|
|
57
|
+
flush?: boolean | undefined;
|
|
55
58
|
/** On the popup: its width, padding or text size. */
|
|
56
59
|
className?: string;
|
|
57
60
|
children: ReactNode;
|
|
@@ -60,5 +63,5 @@ export interface PopoverOptions extends Omit<BasePopover.Popup.Props, 'className
|
|
|
60
63
|
* flips and shifts to stay on screen, closes on Escape or a press outside, and
|
|
61
64
|
* returns focus to the trigger. Use a Menu for a list of actions, and a
|
|
62
65
|
* HoverCard for a preview a link already leads to. */
|
|
63
|
-
export declare function Popover({ trigger, at, title, open, defaultOpen, onOpenChange, side, align, openOnHover, hoverDelay, hoverCloseDelay, initialFocus, anchor, keepSide, variant, sheet, className, children, ...popupProps }: PopoverProps): import("react").JSX.Element;
|
|
66
|
+
export declare function Popover({ trigger, at, title, open, defaultOpen, onOpenChange, side, align, openOnHover, hoverDelay, hoverCloseDelay, initialFocus, anchor, keepSide, variant, sheet, flush, className, children, ...popupProps }: PopoverProps): import("react").JSX.Element;
|
|
64
67
|
export {};
|
|
@@ -19,7 +19,7 @@ var sheetPopupClassName = "max-sm:w-auto! max-sm:max-w-none! max-sm:max-h-[min(7
|
|
|
19
19
|
* flips and shifts to stay on screen, closes on Escape or a press outside, and
|
|
20
20
|
* returns focus to the trigger. Use a Menu for a list of actions, and a
|
|
21
21
|
* HoverCard for a preview a link already leads to. */
|
|
22
|
-
function Popover$1({ trigger, at, title, open, defaultOpen, onOpenChange, side = "bottom", align = "center", openOnHover = false, hoverDelay = 300, hoverCloseDelay = 150, initialFocus = "first-control", anchor, keepSide = false, variant = "panel", sheet, className, children, ...popupProps }) {
|
|
22
|
+
function Popover$1({ trigger, at, title, open, defaultOpen, onOpenChange, side = "bottom", align = "center", openOnHover = false, hoverDelay = 300, hoverCloseDelay = 150, initialFocus = "first-control", anchor, keepSide = false, variant = "panel", sheet, flush = false, className, children, ...popupProps }) {
|
|
23
23
|
const portal = usePopupContainer();
|
|
24
24
|
const tip = variant === "tip";
|
|
25
25
|
const asSheet = sheet ?? (!tip && !at);
|
|
@@ -52,9 +52,9 @@ function Popover$1({ trigger, at, title, open, defaultOpen, onOpenChange, side =
|
|
|
52
52
|
...popupProps,
|
|
53
53
|
ref: popup,
|
|
54
54
|
initialFocus: initialFocus === "popup" ? popup : initialFocus === "first-control",
|
|
55
|
-
className: cx(tip ? cx("text-left", tipClassName) : cx(panelClassName, "max-h-(--available-height) max-w-[calc(100vw-16px)] overflow-y-auto p-3"), className, asSheet && sheetPopupClassName),
|
|
55
|
+
className: cx(tip ? cx("text-left", tipClassName) : cx(panelClassName, "max-h-(--available-height) max-w-[calc(100vw-16px)] overflow-y-auto", !flush && "p-3"), className, asSheet && sheetPopupClassName),
|
|
56
56
|
children: [title && /* @__PURE__ */ jsx(Popover.Title, {
|
|
57
|
-
className: "mb-2 text-sm font-semibold",
|
|
57
|
+
className: cx("mb-2 text-sm font-semibold", flush && !tip && "px-3 pt-3"),
|
|
58
58
|
children: title
|
|
59
59
|
}), /* @__PURE__ */ jsx(CloseContext, {
|
|
60
60
|
value: close,
|
|
@@ -288,6 +288,8 @@ export interface UIStrings {
|
|
|
288
288
|
/** How many warnings: the title of a run's list of warnings, and the name of the mark on a job
|
|
289
289
|
* or step they're about, such as "2 warnings". */
|
|
290
290
|
warningCount: (count: number) => string;
|
|
291
|
+
/** The link from a warning to the page that explains it, when it has one. */
|
|
292
|
+
warningAbout: string;
|
|
291
293
|
};
|
|
292
294
|
jobActions: {
|
|
293
295
|
openSubworkflowLogs: string;
|
|
@@ -733,6 +735,22 @@ export interface RunFileResult {
|
|
|
733
735
|
error: unknown;
|
|
734
736
|
}
|
|
735
737
|
export type PersistedStateHook = <T>(key: string, defaultValue: T) => [T, Dispatch<SetStateAction<T>>];
|
|
738
|
+
/** The values a problem's message shows, by name, as a problem's `params` carries them. */
|
|
739
|
+
export type ProblemParams = Readonly<Record<string, string | number>>;
|
|
740
|
+
/** A problem code's message in the reader's language, with its params; undefined when the host has
|
|
741
|
+
* none for the code. `type` is the problem's type URI: one that names an application, such as
|
|
742
|
+
* `/problems/workflow/undefined_input`, picks that application's message for the code over the
|
|
743
|
+
* host's own. */
|
|
744
|
+
export type ProblemMessage = (code: string, params?: ProblemParams, type?: string) => string | undefined;
|
|
745
|
+
/** A problem that may carry a code: a run's warning, or one the workflow editor marks. */
|
|
746
|
+
export interface CodedProblem {
|
|
747
|
+
/** Its text as the server wrote it, shown when the host has no message for its code. */
|
|
748
|
+
message: string;
|
|
749
|
+
code?: string | null | undefined;
|
|
750
|
+
params?: ProblemParams | null | undefined;
|
|
751
|
+
/** Its type URI, which the host's message for its code is looked up by too. */
|
|
752
|
+
type?: string | null | undefined;
|
|
753
|
+
}
|
|
736
754
|
export interface UIData {
|
|
737
755
|
/**
|
|
738
756
|
* Host-bound hook resolving a file in a run's artifact tree (logs, scripts).
|
|
@@ -763,6 +781,14 @@ export interface UIData {
|
|
|
763
781
|
error?: string;
|
|
764
782
|
}>;
|
|
765
783
|
usePersistedState: PersistedStateHook;
|
|
784
|
+
/**
|
|
785
|
+
* Host-bound hook returning a problem code's message in the reader's language, such as
|
|
786
|
+
* `useCodeMessage` from `@parallelworks/problem/react`. A run's warnings and the editor's
|
|
787
|
+
* problems that carry a `code` show it, given their `params` and `type`, and their own `message`
|
|
788
|
+
* when it gives nothing. Absent, they show their `message`. Must be a stable function reference —
|
|
789
|
+
* components call it as a hook.
|
|
790
|
+
*/
|
|
791
|
+
useProblemMessage: () => ProblemMessage;
|
|
766
792
|
}
|
|
767
793
|
import type { FieldComponent } from '../form/fieldRegistry.js';
|
|
768
794
|
import type { Breadcrumb } from './Breadcrumbs.js';
|
|
@@ -864,6 +890,9 @@ export declare function useProvisionCorsRules(): UIData['provisionCorsRules'];
|
|
|
864
890
|
export declare function useRunFile(slug: string, path: string | null, options?: {
|
|
865
891
|
refreshInterval?: number;
|
|
866
892
|
}): RunFileResult;
|
|
893
|
+
/** A coded problem's text in the reader's language: the host's message for its code and type, or
|
|
894
|
+
* else its own `message`. */
|
|
895
|
+
export declare function useProblemText(): (problem: CodedProblem) => string;
|
|
867
896
|
export declare function usePersistedState<T>(key: string, defaultValue: T): [T, Dispatch<SetStateAction<T>>];
|
|
868
897
|
export interface UIProviderProps {
|
|
869
898
|
/** Translations over the English defaults, at any depth. Define them outside a component, or
|
|
@@ -871,8 +900,9 @@ export interface UIProviderProps {
|
|
|
871
900
|
strings?: UIStringOverrides | undefined;
|
|
872
901
|
/** How the package navigates; read at the moment it navigates, so a new one never re-renders. */
|
|
873
902
|
navigation?: Partial<UINavigation> | undefined;
|
|
874
|
-
/** The host's data hooks and resolvers. `useRunFile
|
|
875
|
-
* hooks, so each must stay the same function for the life of
|
|
903
|
+
/** The host's data hooks and resolvers. `useRunFile`, `usePersistedState` and
|
|
904
|
+
* `useProblemMessage` are called as hooks, so each must stay the same function for the life of
|
|
905
|
+
* the app. */
|
|
876
906
|
data?: Partial<UIData> | undefined;
|
|
877
907
|
/** Components the host renders inside the package's; a component made each render would
|
|
878
908
|
* remount everything it renders. */
|
|
@@ -320,7 +320,8 @@ var DEFAULTS = {
|
|
|
320
320
|
].filter(([count]) => count).map(([count, one, many]) => `${count} ${count === 1 ? one : many}`).join(", ")}`,
|
|
321
321
|
annotationSource: (job, step) => `in ${job} → ${step}`,
|
|
322
322
|
stepNumber: (index) => `step ${index}`,
|
|
323
|
-
warningCount: (count) => count === 1 ? "1 warning" : `${count} warnings
|
|
323
|
+
warningCount: (count) => count === 1 ? "1 warning" : `${count} warnings`,
|
|
324
|
+
warningAbout: "About this warning"
|
|
324
325
|
},
|
|
325
326
|
jobActions: {
|
|
326
327
|
openSubworkflowLogs: "Open subworkflow logs",
|
|
@@ -692,7 +693,8 @@ var DEFAULTS = {
|
|
|
692
693
|
},
|
|
693
694
|
data: {
|
|
694
695
|
useRunFile: defaultUseRunFile,
|
|
695
|
-
usePersistedState: useLocalPersistedState
|
|
696
|
+
usePersistedState: useLocalPersistedState,
|
|
697
|
+
useProblemMessage: useNoProblemMessage
|
|
696
698
|
},
|
|
697
699
|
slots: {}
|
|
698
700
|
};
|
|
@@ -802,6 +804,17 @@ function useRunFile(slug, path, options) {
|
|
|
802
804
|
const { useRunFile: boundUseRunFile } = useContext(DataContext);
|
|
803
805
|
return boundUseRunFile(slug, path, options);
|
|
804
806
|
}
|
|
807
|
+
var noProblemMessage = () => void 0;
|
|
808
|
+
function useNoProblemMessage() {
|
|
809
|
+
return noProblemMessage;
|
|
810
|
+
}
|
|
811
|
+
/** A coded problem's text in the reader's language: the host's message for its code and type, or
|
|
812
|
+
* else its own `message`. */
|
|
813
|
+
function useProblemText() {
|
|
814
|
+
const { useProblemMessage: boundUseProblemMessage } = useContext(DataContext);
|
|
815
|
+
const message = boundUseProblemMessage();
|
|
816
|
+
return useCallback((problem) => (problem.code ? message(problem.code, problem.params ?? void 0, problem.type ?? void 0) : void 0) ?? problem.message, [message]);
|
|
817
|
+
}
|
|
805
818
|
function usePersistedState(key, defaultValue) {
|
|
806
819
|
const { usePersistedState: boundUsePersistedState } = useContext(DataContext);
|
|
807
820
|
return boundUsePersistedState(key, defaultValue);
|
|
@@ -869,4 +882,4 @@ function UIProvider({ strings, navigation, data, slots, engine: engineProp, nonc
|
|
|
869
882
|
});
|
|
870
883
|
}
|
|
871
884
|
//#endregion
|
|
872
|
-
export { UIProvider, useAvatarColoring, useLink, useLoadedWorkflowEngine, useLocale, useNavigation, useOptionalWorkflowEngine, usePersistedState, useProvisionCorsRules, useRepoSuggestions, useRunFile, useSecretVariablesResolver, useSlots, useStrings, useWorkflowActions, useWorkflowEditing, useWorkflowEngine, useWorkflowEngineLoader, useWorkflowJsonResolver };
|
|
885
|
+
export { UIProvider, useAvatarColoring, useLink, useLoadedWorkflowEngine, useLocale, useNavigation, useOptionalWorkflowEngine, usePersistedState, useProblemText, useProvisionCorsRules, useRepoSuggestions, useRunFile, useSecretVariablesResolver, useSlots, useStrings, useWorkflowActions, useWorkflowEditing, useWorkflowEngine, useWorkflowEngineLoader, useWorkflowJsonResolver };
|
|
@@ -34,7 +34,7 @@ function segmentGroupClassName({ fill, iconOnly }) {
|
|
|
34
34
|
return cx("gap-0.5", iconOnly ? "flex flex-col items-center" : cx("rounded-lg theme-muted-panel p-0.5", fillGroup[fillKey(fill)]));
|
|
35
35
|
}
|
|
36
36
|
function segmentClassName(current, { fill, iconOnly, size = "md" }, tone) {
|
|
37
|
-
return cx("relative flex min-w-0 cursor-pointer items-center justify-center rounded-md font-medium transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-(--theme-focus) data-disabled:cursor-not-allowed data-disabled:opacity-50", current ? "bg-(--theme-panel-bg) shadow-sm ring-1 ring-(--theme-border)" : "hover:not-data-disabled:bg-(--theme-hover)", tone ? toneText[tone] : current ? "theme-text" : "theme-muted-text hover:not-data-disabled:theme-text", iconOnly ? "h-8 w-8" : segmentSizes[size][fillKey(fill)]);
|
|
37
|
+
return cx("relative flex min-w-0 cursor-pointer items-center justify-center rounded-md font-medium transition-colors duration-150 focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-(--theme-focus) data-disabled:cursor-not-allowed data-disabled:opacity-50", current ? "bg-(--theme-panel-bg) shadow-sm ring-1 ring-(--theme-border)" : "hover:not-data-disabled:bg-(--theme-hover)", tone ? toneText[tone] : current ? "theme-text" : "theme-muted-text hover:not-data-disabled:theme-text", iconOnly ? "h-8 w-8" : segmentSizes[size][fillKey(fill)]);
|
|
38
38
|
}
|
|
39
39
|
/** How many things a tab or segment holds, as a pill after its label. */
|
|
40
40
|
function CountPill({ count, tone }) {
|
|
@@ -3,7 +3,7 @@ import { EmptyState } from "../components/EmptyState.js";
|
|
|
3
3
|
import { AddIcon, ArrowDownIcon, ArrowUpIcon, CollapseIcon, DragHandleIcon, DuplicateIcon, EditIcon, FilesIcon, MinusIcon, MoreIcon, TrashIcon, WarningIcon } from "../icons.js";
|
|
4
4
|
import { useContextMenu } from "../components/ContextMenu.js";
|
|
5
5
|
import { Tooltip } from "../components/Tooltip.js";
|
|
6
|
-
import { useWorkflowEditing } from "../components/Provider.js";
|
|
6
|
+
import { useProblemText, useWorkflowEditing } from "../components/Provider.js";
|
|
7
7
|
import { menuDivider } from "../components/MenuEntries.js";
|
|
8
8
|
import { IconButton } from "../components/IconButton.js";
|
|
9
9
|
import { NumberInput } from "../components/NumberInput.js";
|
|
@@ -100,7 +100,8 @@ function InputRow({ path, hidden = false, instance, children }) {
|
|
|
100
100
|
const copy = instance ?? rowKey;
|
|
101
101
|
const active = useUi(api?.store ?? EMPTY_STORE, (state) => state.open.includes(copy) && !state.drag && !state.resizing);
|
|
102
102
|
const selected = useUi(api?.store ?? EMPTY_STORE, (state) => state.selection.includes(rowKey));
|
|
103
|
-
const
|
|
103
|
+
const problemText = useProblemText();
|
|
104
|
+
const problems = useUi(api?.store ?? EMPTY_STORE, (state) => state.problems.filter((problem) => problem.input && key(problem.input) === rowKey).map((problem) => `${problem.line}\n${problemText(problem)}`).join("\n\n"));
|
|
104
105
|
if (!api) return children;
|
|
105
106
|
const found = problems ? problems.split("\n\n") : [];
|
|
106
107
|
const definition = api.definition(path);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** The page a problem's type URI resolves to, or undefined for `about:blank` and no type, which
|
|
2
|
+
* have none. */
|
|
3
|
+
export declare function problemPage(type: string | null | undefined): string | undefined;
|
|
4
|
+
/** Links a problem to the page that explains it, in a new tab, when its type has one. */
|
|
5
|
+
export declare function ProblemPageLink({ type, label, className, }: {
|
|
6
|
+
type: string | null | undefined;
|
|
7
|
+
label: string;
|
|
8
|
+
className?: string | undefined;
|
|
9
|
+
}): import("react").JSX.Element | null;
|