@parallelworks/ui 0.38.0 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +1 -1
  2. package/dist/ai/agent/permissions.d.ts +5 -4
  3. package/dist/ai/agent/permissions.js +15 -6
  4. package/dist/ai/components/ChatInput.js +8 -23
  5. package/dist/ai/components/agent/PermissionPicker.d.ts +3 -1
  6. package/dist/ai/components/agent/PermissionPicker.js +2 -2
  7. package/dist/ai/components/chatDraft.d.ts +7 -0
  8. package/dist/ai/components/chatDraft.js +66 -0
  9. package/dist/editing.d.ts +2 -0
  10. package/dist/editor/Monaco.js +1 -1
  11. package/dist/editor/index.js +1 -1
  12. package/dist/editor/lintContext.js +15 -3
  13. package/dist/graph/DependencyGraph.d.ts +8 -1
  14. package/dist/graph/DependencyGraph.js +433 -204
  15. package/dist/graph/GraphEditor.d.ts +27 -0
  16. package/dist/graph/GraphEditor.js +1543 -0
  17. package/dist/graph/GraphEditorDialogs.d.ts +87 -0
  18. package/dist/graph/GraphEditorDialogs.js +1808 -0
  19. package/dist/graph/InputDialog.js +4 -4
  20. package/dist/graph/InputsEditor.js +2 -2
  21. package/dist/graph/JobSummary.d.ts +3 -0
  22. package/dist/graph/JobSummary.js +34 -21
  23. package/dist/graph/MatrixGroup.d.ts +2 -1
  24. package/dist/graph/MatrixGroup.js +10 -3
  25. package/dist/graph/WorkflowSettingsDialog.d.ts +21 -0
  26. package/dist/graph/WorkflowSettingsDialog.js +670 -0
  27. package/dist/graph/editorFields.js +144 -3
  28. package/dist/graph/editorRows.d.ts +58 -0
  29. package/dist/graph/editorRows.js +460 -0
  30. package/dist/graph/editorStrings.d.ts +1 -0
  31. package/dist/graph/editorStrings.js +1 -0
  32. package/dist/graph/expressionRefs.js +39 -0
  33. package/dist/graph/fieldProblems.js +36 -2
  34. package/dist/graph/graphEditorState.d.ts +191 -0
  35. package/dist/graph/graphEditorState.js +74 -0
  36. package/dist/graph/index.d.ts +3 -0
  37. package/dist/graph/index.js +5 -2
  38. package/dist/graph/inputLayout.js +1 -1
  39. package/dist/graph/inputRefs.js +196 -4
  40. package/dist/graph/refusalText.js +13 -0
  41. package/dist/graph/stepWith.d.ts +38 -0
  42. package/dist/graph/stepWith.js +536 -0
  43. package/dist/styles.css +1 -1
  44. package/package.json +1 -1
@@ -0,0 +1,191 @@
1
+ import { type PointerEvent as ReactPointerEvent } from 'react';
2
+ import type { GraphEdit, GraphSlot, NeedRef, NudgeDirection, StepRef } from '../editing.js';
3
+ import type { EditorProblem, RevealTarget } from './editorApi.js';
4
+ import type { Box, Point, Store } from './editorPrimitives.js';
5
+ import type { GraphEditorStrings } from './editorStrings.js';
6
+ /** Pointer-downs on this class drag editor items instead of panning the graph. */
7
+ export declare const EDITOR_HANDLE_CLASS = "dag-editor-handle";
8
+ /** A node's left circle takes dependencies; its right circle gives them. */
9
+ export type PortSide = 'in' | 'out';
10
+ export type ConnectPayload = {
11
+ kind: 'connect';
12
+ from: string[];
13
+ side: PortSide;
14
+ label: string;
15
+ /** The circle dragged from: `node:<head job>`, or `selection`. */
16
+ port: string;
17
+ /** An end of a connector being moved: its needs go on the drop, and the line starts at its other end. */
18
+ replaces?: NeedRef[];
19
+ origin?: Point;
20
+ };
21
+ export type DragPayload =
22
+ /** `anchor` lands where the drag drops; the other jobs keep their offsets from it. */
23
+ {
24
+ kind: 'jobs';
25
+ jobs: string[];
26
+ label: string;
27
+ anchor?: string;
28
+ } | {
29
+ kind: 'new';
30
+ matrix: boolean;
31
+ label: string;
32
+ } | ConnectPayload | {
33
+ kind: 'steps';
34
+ steps: StepRef[];
35
+ label: string;
36
+ };
37
+ export type DropTarget =
38
+ /** `blocked` when the move would cut a need an expression of a moved job reads. */
39
+ {
40
+ kind: 'slot';
41
+ slot: GraphSlot;
42
+ indicator: Box;
43
+ blocked?: boolean;
44
+ } | {
45
+ kind: 'trash';
46
+ }
47
+ /** `edits` is null when the drop can't connect. */
48
+ | {
49
+ kind: 'connect';
50
+ edits: GraphEdit[] | null;
51
+ box: Box;
52
+ }
53
+ /** Dragged jobs join `into`'s node, taking its needs; `valid` is false when that would loop. */
54
+ | {
55
+ kind: 'merge';
56
+ into: string;
57
+ jobs: string[];
58
+ valid: boolean;
59
+ box: Box;
60
+ }
61
+ /** Steps land at `index` of `job`; `around` outlines a job whose steps aren't shown, `problem` refuses the drop. */
62
+ | {
63
+ kind: 'steps';
64
+ job: string;
65
+ index: number;
66
+ indicator: Box;
67
+ around?: boolean;
68
+ problem?: string;
69
+ };
70
+ export interface DragView {
71
+ payload: DragPayload;
72
+ client: Point;
73
+ content: Point | null;
74
+ origin: Point | null;
75
+ target: DropTarget | null;
76
+ }
77
+ /** A job or step being added: `yml` is the workflow as if `edit` were applied, which the dialog's first save does. */
78
+ export interface Addition {
79
+ edit: GraphEdit;
80
+ yml: string;
81
+ }
82
+ export type Dialog = {
83
+ kind: 'job';
84
+ job: string;
85
+ addition?: Addition;
86
+ } | {
87
+ kind: 'step';
88
+ job: string;
89
+ index: number;
90
+ addition?: Addition;
91
+ };
92
+ export declare const labelsOf: (jobs: string[]) => string;
93
+ /** A drawn connector, named by the head jobs of the boxes it joins. */
94
+ export interface EdgeSelection {
95
+ from: string;
96
+ to: string;
97
+ }
98
+ export interface UiState {
99
+ drag: DragView | null;
100
+ /** Selected connectors. Jobs, steps and connectors are never selected together. */
101
+ edges: EdgeSelection[];
102
+ dialog: Dialog | null;
103
+ selection: string[];
104
+ steps: StepRef[];
105
+ problems: EditorProblem[];
106
+ /** The box being dragged out on empty space, in content coordinates. */
107
+ marquee: Box | null;
108
+ }
109
+ export declare const NO_SELECTION: string[];
110
+ export declare const NO_STEPS: StepRef[];
111
+ export declare const NO_EDGES: EdgeSelection[];
112
+ export declare const NO_PROBLEMS: EditorProblem[];
113
+ export declare const problemsIn: (problems: EditorProblem[], job: string, step?: number) => EditorProblem[];
114
+ export declare const sameStep: (a: StepRef, b: StepRef) => boolean;
115
+ export type UiStore = Store<UiState>;
116
+ /** The drawn grid of the level being edited; `rows` holds each box's row, `columns` each column's number. */
117
+ export interface GraphGrid {
118
+ wrapper: HTMLDivElement;
119
+ cols: string[][][];
120
+ rows: number[][];
121
+ columns: number[];
122
+ }
123
+ /** What the editor asks of the graph's view. */
124
+ export interface GraphView {
125
+ /** Keeps `job` where it is on screen through the next layout change, rather than refitting. */
126
+ hold: (job: string) => void;
127
+ }
128
+ export interface GraphEditorApi {
129
+ store: UiStore;
130
+ /** The editor's strings, so what the graph draws for it needn't load them itself. */
131
+ t: GraphEditorStrings;
132
+ isMatrixJob: (job: string) => boolean;
133
+ startDrag: (e: ReactPointerEvent, payload: DragPayload) => void;
134
+ openJobMenu: (x: number, y: number, job: string) => void;
135
+ openStepMenu: (x: number, y: number, job: string, index: number) => void;
136
+ openEdgeMenu: (x: number, y: number, edge: EdgeSelection) => void;
137
+ /**
138
+ * Selects a connector alone, or with `add` adds it to the selected connectors or takes it out;
139
+ * selected jobs or steps give way to it.
140
+ */
141
+ selectEdge: (edge: EdgeSelection, add: boolean) => void;
142
+ /** Whether an expression reads one of a connector's needs, so it can't be removed or moved. */
143
+ edgeInUse: (edge: EdgeSelection) => boolean;
144
+ /** Starts moving one end of a connector; `fixed` is its other end, in the graph's own pixels. */
145
+ startEdgeEnd: (e: ReactPointerEvent, edge: EdgeSelection, end: 'from' | 'to', fixed: Point) => void;
146
+ /** Removes the selected jobs, steps and connectors; false when nothing is selected. */
147
+ deleteSelection: () => boolean;
148
+ /** Opens the dialog of the one selected job or step, reporting whether there was one. */
149
+ editSelection: () => boolean;
150
+ addJob: (matrix: boolean) => void;
151
+ addStep: (job: string) => void;
152
+ editJob: (job: string) => void;
153
+ editStep: (job: string, index: number) => void;
154
+ registerGrid: (grid: GraphGrid | null) => void;
155
+ grid: () => GraphGrid | null;
156
+ registerView: (view: GraphView | null) => void;
157
+ /** The selected jobs that still exist. */
158
+ selected: () => string[];
159
+ /** Adds a job to the selected jobs or takes it out; selected steps or connectors give way to it. */
160
+ toggleSelected: (job: string) => void;
161
+ /** The selected steps that still exist. */
162
+ selectedSteps: () => StepRef[];
163
+ /** Adds a step to the selected steps or takes it out; selected jobs or connectors give way to it. */
164
+ toggleStep: (step: StepRef) => void;
165
+ /** Puts the selected jobs or steps on the clipboard, as YAML; null when nothing is selected. */
166
+ copySelection: () => string | null;
167
+ /** Adds the jobs or steps in `text` near `pointer`; false when `text` holds neither. */
168
+ paste: (text: string, pointer: Point | null) => boolean;
169
+ /** Moves the selected jobs one slot; false when no jobs are selected. */
170
+ nudge: (direction: NudgeDirection) => boolean;
171
+ reveal: (target: RevealTarget) => void;
172
+ /** Selects a job, or one of its steps, alone. */
173
+ focus: (target: {
174
+ job: string;
175
+ step?: number;
176
+ }) => void;
177
+ }
178
+ export declare const GraphEditorProvider: import("react").Provider<GraphEditorApi | null>;
179
+ export declare function useGraphEditor(): GraphEditorApi | null;
180
+ export declare function useUi<T>(api: GraphEditorApi | null, select: (state: UiState) => T, fallback: T): T;
181
+ export declare function useSelectedEdges(api: GraphEditorApi | null): EdgeSelection[];
182
+ export declare const sameEdge: (a: EdgeSelection, b: EdgeSelection) => boolean;
183
+ export declare function toContent(wrapper: HTMLElement, client: Point): Point;
184
+ export declare function contentBox(wrapper: HTMLElement, el: Element): Box;
185
+ /**
186
+ * The outline around the selected jobs, in content coordinates: as tall as their rows and
187
+ * as wide as their nodes, so its circles sit clear of the nodes' own.
188
+ */
189
+ export declare function selectionBounds(wrapper: HTMLElement, jobs: string[]): (Box & {
190
+ connectorY: number;
191
+ }) | null;
@@ -0,0 +1,74 @@
1
+ import { jobLabel } from "./util.js";
2
+ import { createContext, useContext, useSyncExternalStore } from "react";
3
+ //#region src/graph/graphEditorState.ts
4
+ /** Pointer-downs on this class drag editor items instead of panning the graph. */
5
+ var EDITOR_HANDLE_CLASS = "dag-editor-handle";
6
+ var labelsOf = (jobs) => jobs.map((job) => jobLabel(job)).join(", ");
7
+ var NO_SELECTION = [];
8
+ var NO_STEPS = [];
9
+ var NO_EDGES = [];
10
+ var NO_PROBLEMS = [];
11
+ var problemsIn = (problems, job, step) => problems.filter((problem) => problem.job === job && problem.step === step);
12
+ var sameStep = (a, b) => a.job === b.job && a.index === b.index;
13
+ var EditorContext = createContext(null);
14
+ var GraphEditorProvider = EditorContext.Provider;
15
+ function useGraphEditor() {
16
+ return useContext(EditorContext);
17
+ }
18
+ var noopSubscribe = () => () => {};
19
+ function useUi(api, select, fallback) {
20
+ const snapshot = () => api ? select(api.store.get()) : fallback;
21
+ return useSyncExternalStore(api ? api.store.subscribe : noopSubscribe, snapshot, snapshot);
22
+ }
23
+ function useSelectedEdges(api) {
24
+ return useUi(api, (state) => state.edges, NO_EDGES);
25
+ }
26
+ var sameEdge = (a, b) => a.from === b.from && a.to === b.to;
27
+ function toContent(wrapper, client) {
28
+ const rect = wrapper.getBoundingClientRect();
29
+ const scale = wrapper.offsetWidth > 0 ? rect.width / wrapper.offsetWidth : 1;
30
+ return {
31
+ x: (client.x - rect.left) / scale,
32
+ y: (client.y - rect.top) / scale
33
+ };
34
+ }
35
+ function contentBox(wrapper, el) {
36
+ const rect = wrapper.getBoundingClientRect();
37
+ const scale = wrapper.offsetWidth > 0 ? rect.width / wrapper.offsetWidth : 1;
38
+ const r = el.getBoundingClientRect();
39
+ return {
40
+ left: (r.left - rect.left) / scale,
41
+ top: (r.top - rect.top) / scale,
42
+ right: (r.right - rect.left) / scale,
43
+ bottom: (r.bottom - rect.top) / scale
44
+ };
45
+ }
46
+ var SELECTION_PAD = 12;
47
+ var SELECTION_SIDE_PAD = 36;
48
+ /**
49
+ * The outline around the selected jobs, in content coordinates: as tall as their rows and
50
+ * as wide as their nodes, so its circles sit clear of the nodes' own.
51
+ */
52
+ function selectionBounds(wrapper, jobs) {
53
+ const found = jobs.flatMap((job) => {
54
+ const row = wrapper.querySelector(`[data-dag-job="${CSS.escape(job)}"]`);
55
+ const node = row?.closest("[id^=\"node_\"]");
56
+ const first = node?.querySelector("[data-dag-job]");
57
+ return row && node && first ? [{
58
+ row: contentBox(wrapper, row),
59
+ node: contentBox(wrapper, node),
60
+ first: contentBox(wrapper, first)
61
+ }] : [];
62
+ });
63
+ if (found.length === 0) return null;
64
+ const connectors = found.map((f) => f.node.top + 37 + f.row.top - f.first.top);
65
+ return {
66
+ left: Math.min(...found.map((f) => f.node.left)) - SELECTION_SIDE_PAD,
67
+ top: Math.min(...found.map((f) => f.row.top)) - SELECTION_PAD,
68
+ right: Math.max(...found.map((f) => f.node.right)) + SELECTION_SIDE_PAD,
69
+ bottom: Math.max(...found.map((f) => f.row.bottom)) + SELECTION_PAD,
70
+ connectorY: (Math.min(...connectors) + Math.max(...connectors)) / 2
71
+ };
72
+ }
73
+ //#endregion
74
+ export { EDITOR_HANDLE_CLASS, GraphEditorProvider, NO_EDGES, NO_PROBLEMS, NO_SELECTION, NO_STEPS, contentBox, labelsOf, problemsIn, sameEdge, sameStep, selectionBounds, toContent, useGraphEditor, useSelectedEdges, useUi };
@@ -2,8 +2,11 @@ export { DependencyGraphPreview, default as DependencyGraph, type ViewMode, } fr
2
2
  export type { DependencyGraphEditor, EditorProblem, RevealTarget, } from './editorApi.js';
3
3
  export { trackDrag } from './editorPrimitives.js';
4
4
  export { GRAPH_EDITOR_STRINGS, INPUTS_EDITOR_STRINGS } from './editorStrings.js';
5
+ export { JOB_FIELDS, RETRY_FIELDS, RUNS_ON_FIELDS, SSH_KEYS, STEP_FIELDS, STRATEGY_FIELDS, } from './GraphEditorDialogs.js';
5
6
  export { inputTypes, offeredInputKeys } from './InputDialog.js';
6
7
  export { InputsFormEditor } from './InputsEditor.js';
7
8
  export { type ListedProblem, ProblemsButton } from './ProblemsButton.js';
8
9
  export type { SettingsView } from './settingsViews.js';
10
+ export { type WorkflowActions, withFields } from './stepWith.js';
9
11
  export type { RunLink } from './types.js';
12
+ export { INPUT_FORM_FIELDS, LINK_FIELDS, NEEDS_FIELDS, SESSION_FIELDS, WORKFLOW_FIELDS, WorkflowSettingsDialog, } from './WorkflowSettingsDialog.js';
@@ -1,7 +1,10 @@
1
- import DependencyGraph, { DependencyGraphPreview } from "./DependencyGraph.js";
2
1
  import { trackDrag } from "./editorPrimitives.js";
2
+ import DependencyGraph, { DependencyGraphPreview } from "./DependencyGraph.js";
3
3
  import { GRAPH_EDITOR_STRINGS, INPUTS_EDITOR_STRINGS } from "./editorStrings.js";
4
4
  import { inputTypes, offeredInputKeys } from "./InputDialog.js";
5
+ import { withFields } from "./stepWith.js";
6
+ import { JOB_FIELDS, RETRY_FIELDS, RUNS_ON_FIELDS, SSH_KEYS, STEP_FIELDS, STRATEGY_FIELDS } from "./GraphEditorDialogs.js";
5
7
  import { ProblemsButton } from "./ProblemsButton.js";
6
8
  import { InputsFormEditor } from "./InputsEditor.js";
7
- export { DependencyGraph, DependencyGraphPreview, GRAPH_EDITOR_STRINGS, INPUTS_EDITOR_STRINGS, InputsFormEditor, ProblemsButton, inputTypes, offeredInputKeys, trackDrag };
9
+ import { INPUT_FORM_FIELDS, LINK_FIELDS, NEEDS_FIELDS, SESSION_FIELDS, WORKFLOW_FIELDS, WorkflowSettingsDialog } from "./WorkflowSettingsDialog.js";
10
+ export { DependencyGraph, DependencyGraphPreview, GRAPH_EDITOR_STRINGS, INPUTS_EDITOR_STRINGS, INPUT_FORM_FIELDS, InputsFormEditor, JOB_FIELDS, LINK_FIELDS, NEEDS_FIELDS, ProblemsButton, RETRY_FIELDS, RUNS_ON_FIELDS, SESSION_FIELDS, SSH_KEYS, STEP_FIELDS, STRATEGY_FIELDS, WORKFLOW_FIELDS, WorkflowSettingsDialog, inputTypes, offeredInputKeys, trackDrag, withFields };
@@ -1,6 +1,6 @@
1
1
  import { inputWidth } from "../form/lib.js";
2
- import { resolveFormLayout, responsiveLayoutValues } from "../form/layout.js";
3
2
  import { asRecord } from "./records.js";
3
+ import { resolveFormLayout, responsiveLayoutValues } from "../form/layout.js";
4
4
  //#region src/graph/inputLayout.ts
5
5
  var namesIn = (list) => Object.keys(list).filter((name) => !name.startsWith("$"));
6
6
  var field = (name) => ({
@@ -1,12 +1,13 @@
1
1
  import { FaPlus } from "../icons/fa/FaPlus.js";
2
2
  import { IconButton } from "../components/IconButton.js";
3
+ import { Textarea, fieldBoxClasses } from "../components/Input.js";
3
4
  import { Toggle } from "../components/Toggle.js";
4
5
  import { useGraphEditorStrings, useInputsEditorStrings } from "./editorStrings.js";
5
- import { useFieldLint } from "./fieldProblems.js";
6
6
  import { asRecord } from "./records.js";
7
+ import { useFieldLint } from "./fieldProblems.js";
7
8
  import { SuggestionInput } from "./SuggestionInput.js";
8
- import { ExpressionToggle, FieldError, FieldLabel, LabelledField, flagError, flagValue, text } from "./editorFields.js";
9
- import { useId, useState } from "react";
9
+ import { ExpressionToggle, FieldError, FieldLabel, GROWING_AREA, LabelledField, flagError, flagValue, text, useGrowingArea } from "./editorFields.js";
10
+ import { useId, useRef, useState } from "react";
10
11
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
11
12
  //#region src/graph/inputRefs.tsx
12
13
  /** Every input with a value, where a run reads it: groups nest unless flattened, lists hide their rows. */
@@ -37,9 +38,18 @@ function inputRefs(editing, inputs) {
37
38
  walk(root, []);
38
39
  return out;
39
40
  }
41
+ var REF = /^\$\{\{\s*(!?)\s*inputs\.([A-Za-z0-9_.-]+?)\s*\}\}$/;
40
42
  function refExpression(path, suffix) {
41
43
  return `\${{ inputs.${[...path, ...suffix ? [suffix] : []].join(".")} }}`;
42
44
  }
45
+ /** The input `value` reads when it is exactly `${{ inputs.PATH[.suffix] }}`. */
46
+ function readRef(value, suffix) {
47
+ const match = typeof value === "string" ? REF.exec(value.trim()) : null;
48
+ if (!match || match[1]) return null;
49
+ const parts = (match[2] ?? "").split(".");
50
+ if (suffix) return parts.at(-1) === suffix ? parts.slice(0, -1) : null;
51
+ return parts;
52
+ }
43
53
  function flagExpression(path, negated) {
44
54
  return `\${{ ${negated ? "!" : ""}inputs.${path.join(".")} }}`;
45
55
  }
@@ -47,11 +57,50 @@ function equalsExpression(path, value, negated = false) {
47
57
  const operator = negated ? "!=" : "==";
48
58
  return `\${{ inputs.${path.join(".")} ${operator} '${value.replace(/'/g, "''")}' }}`;
49
59
  }
60
+ var pathKey = (path) => path.join(".");
50
61
  /** The option values a dropdown input offers, for conditions on it. */
51
62
  function optionValues(definition) {
52
63
  const options = definition["options"];
53
64
  return Array.isArray(options) ? options.map((option) => typeof option === "object" && option !== null ? text(option["value"]) : text(option)) : [];
54
65
  }
66
+ /** Picks an existing input of the given types, or asks for a new one. */
67
+ function InputRefSelect({ refs, types, value, onChange, onNew, newLabel, label }) {
68
+ const t = useGraphEditorStrings();
69
+ const id = useId();
70
+ const matching = refs.filter((ref) => types.includes(ref.type));
71
+ const current = value ? pathKey(value) : "";
72
+ const known = matching.some((ref) => pathKey(ref.path) === current);
73
+ return /* @__PURE__ */ jsxs("select", {
74
+ id,
75
+ "aria-label": label,
76
+ value: known || !current ? current : `?${current}`,
77
+ onChange: (e) => {
78
+ const next = e.target.value;
79
+ if (next === "+") onNew?.();
80
+ else if (next && !next.startsWith("?")) onChange(next.split("."));
81
+ },
82
+ className: fieldBoxClasses,
83
+ children: [
84
+ /* @__PURE__ */ jsx("option", {
85
+ value: "",
86
+ disabled: true,
87
+ children: matching.length > 0 ? t.chooseInput : t.noMatchingInputs
88
+ }),
89
+ !known && current && /* @__PURE__ */ jsxs("option", {
90
+ value: `?${current}`,
91
+ children: ["inputs.", current]
92
+ }),
93
+ matching.map((ref) => /* @__PURE__ */ jsx("option", {
94
+ value: pathKey(ref.path),
95
+ children: ref.label === ref.path.at(-1) ? `inputs.${pathKey(ref.path)}` : `${ref.label} (inputs.${pathKey(ref.path)})`
96
+ }, pathKey(ref.path))),
97
+ onNew && /* @__PURE__ */ jsx("option", {
98
+ value: "+",
99
+ children: newLabel ?? t.newInput
100
+ })
101
+ ]
102
+ });
103
+ }
55
104
  /** Makes a new input of `type` for the field, when the host can; `onCreated` gets where it went. */
56
105
  function NewInputButton({ type, source, onCreated }) {
57
106
  const t = useGraphEditorStrings();
@@ -67,6 +116,11 @@ function NewInputButton({ type, source, onCreated }) {
67
116
  onClick: () => create(type, onCreated)
68
117
  });
69
118
  }
119
+ function insertAt(element, value, reference) {
120
+ const start = element?.selectionStart ?? value.length;
121
+ const end = element?.selectionEnd ?? value.length;
122
+ return value.slice(0, start) + reference + value.slice(end);
123
+ }
70
124
  var ONE_OF = ["dropdown", "radio"];
71
125
  /** A text field that takes any value, expressions included, with suggestions to pick from. */
72
126
  function ExpressionInput({ id, label, value, onChange, suggestions = [], placeholder, error, describedBy, required }) {
@@ -151,6 +205,59 @@ function FlagField({ label, yamlKey, description, value, original, fallback = fa
151
205
  ]
152
206
  });
153
207
  }
208
+ var CONDITIONS = [
209
+ ["always", "always"],
210
+ ["failure()", "failure"],
211
+ ["cancelled()", "cancelled"],
212
+ ["success()", "success"]
213
+ ];
214
+ /** When a job or step runs: any condition, typed, with the executor's status checks and the form's switches offered. */
215
+ function ConditionField({ label, yamlKey, description, value, onChange, source, scope }) {
216
+ const t = useGraphEditorStrings();
217
+ const id = useId();
218
+ const texts = scope === "job" ? t.jobConditions : t.stepConditions;
219
+ const lint = useFieldLint(yamlKey, id, value, onChange);
220
+ const insert = (reference) => {
221
+ const element = document.getElementById(id);
222
+ onChange(insertAt(element instanceof HTMLInputElement && element.value === value ? element : null, value, reference));
223
+ };
224
+ return /* @__PURE__ */ jsxs(LabelledField, {
225
+ label,
226
+ yamlKey,
227
+ description,
228
+ htmlFor: id,
229
+ descriptionId: `${id}-help`,
230
+ actions: /* @__PURE__ */ jsxs(Fragment$1, { children: [allRefs(source).length > 0 && /* @__PURE__ */ jsx(InsertRefSelect, {
231
+ source,
232
+ label: t.insertValue,
233
+ onInsert: insert
234
+ }), /* @__PURE__ */ jsx(NewInputButton, {
235
+ type: "boolean",
236
+ source,
237
+ onCreated: (path) => onChange(flagExpression(path, false))
238
+ })] }),
239
+ children: [/* @__PURE__ */ jsx(ExpressionInput, {
240
+ id,
241
+ label,
242
+ describedBy: `${id}-help`,
243
+ value,
244
+ error: lint.message,
245
+ placeholder: texts.default,
246
+ suggestions: [
247
+ ...CONDITIONS.map(([condition, key]) => ({
248
+ value: condition,
249
+ label: texts[key]
250
+ })),
251
+ {
252
+ value: "false",
253
+ label: t.conditionNever
254
+ },
255
+ ...conditionSuggestions(source, t)
256
+ ],
257
+ onChange
258
+ }), lint.fixes]
259
+ });
260
+ }
154
261
  /** A value typed in, with the form's inputs of the given types offered, or a new one made for it. */
155
262
  function ValueOrInputField({ label, yamlKey, description, value, onChange, source, types, suffix, literal = true, required = false, error, suggestions = [], placeholder }) {
156
263
  const t = useGraphEditorStrings();
@@ -192,5 +299,90 @@ function ValueOrInputField({ label, yamlKey, description, value, onChange, sourc
192
299
  ]
193
300
  });
194
301
  }
302
+ /** A script field with a menu that drops an input's reference at the cursor. */
303
+ function ScriptField({ label, yamlKey, description, value, onChange, source, rows, error, placeholder }) {
304
+ const t = useGraphEditorStrings();
305
+ const id = useId();
306
+ const growing = useGrowingArea(value);
307
+ const area = rows === void 0 ? growing : null;
308
+ const fixed = useRef(null);
309
+ const lint = useFieldLint(yamlKey, id, value, onChange);
310
+ const shownError = error ?? lint.message;
311
+ const insert = (reference) => onChange(insertAt((area ?? fixed).current, value, reference));
312
+ return /* @__PURE__ */ jsxs(LabelledField, {
313
+ label,
314
+ yamlKey,
315
+ description,
316
+ htmlFor: id,
317
+ descriptionId: `${id}-help`,
318
+ actions: allRefs(source).length > 0 && /* @__PURE__ */ jsx(InsertRefSelect, {
319
+ source,
320
+ label: t.insertValue,
321
+ onInsert: insert
322
+ }),
323
+ children: [/* @__PURE__ */ jsx(Textarea, {
324
+ ref: area ?? fixed,
325
+ id,
326
+ mono: true,
327
+ "aria-describedby": `${id}-help`,
328
+ rows: rows ?? 1,
329
+ value,
330
+ className: rows === void 0 ? GROWING_AREA : "",
331
+ ...shownError ? { error: shownError } : {},
332
+ ...placeholder ? { placeholder } : {},
333
+ onChange: (e) => onChange(e.target.value)
334
+ }), lint.fixes]
335
+ });
336
+ }
337
+ var GROUP_ORDER = [
338
+ "inputs",
339
+ "outputs",
340
+ "matrix",
341
+ "variables",
342
+ "sessions",
343
+ "env"
344
+ ];
345
+ function InsertRefSelect({ source, label, onInsert }) {
346
+ const t = useGraphEditorStrings();
347
+ const all = allRefs(source);
348
+ return /* @__PURE__ */ jsxs("span", {
349
+ className: "relative inline-flex shrink-0 items-center",
350
+ children: [/* @__PURE__ */ jsx(FaPlus, {
351
+ "aria-hidden": "true",
352
+ className: "pointer-events-none absolute left-2 h-3 w-3 theme-muted-text"
353
+ }), /* @__PURE__ */ jsxs("select", {
354
+ "aria-label": label,
355
+ value: "",
356
+ onChange: (e) => {
357
+ if (e.target.value) onInsert(e.target.value);
358
+ },
359
+ className: "cursor-pointer appearance-none rounded-md border border-(--theme-border) bg-(--theme-panel-bg) py-1 pr-2 pl-6 text-xs text-(--theme-app) hover:bg-(--theme-muted-panel-bg)",
360
+ children: [/* @__PURE__ */ jsx("option", {
361
+ value: "",
362
+ children: label
363
+ }), GROUP_ORDER.map((group) => {
364
+ const refs = all.filter((item) => item.group === group);
365
+ return refs.length > 0 ? /* @__PURE__ */ jsx("optgroup", {
366
+ label: t.refGroups[group],
367
+ children: refs.map((item) => /* @__PURE__ */ jsx("option", {
368
+ value: item.expression,
369
+ children: item.label
370
+ }, item.label))
371
+ }, group) : null;
372
+ })]
373
+ })]
374
+ });
375
+ }
376
+ function allRefs(source) {
377
+ return [...source.refs.map((input) => ({
378
+ group: "inputs",
379
+ label: `inputs.${pathKey(input.path)}`,
380
+ expression: refExpression(input.path)
381
+ })), ...(source.extras ?? []).filter((extra) => extra.group !== "inputs")];
382
+ }
383
+ /** References offered as suggestions on free-text values. */
384
+ function refSuggestions(source) {
385
+ return allRefs(source).map((item) => item.expression);
386
+ }
195
387
  //#endregion
196
- export { ExpressionInput, FlagField, ValueOrInputField, equalsExpression, flagExpression, inputRefs, refExpression };
388
+ export { ConditionField, ExpressionInput, FlagField, InputRefSelect, ScriptField, ValueOrInputField, equalsExpression, flagExpression, inputRefs, readRef, refExpression, refSuggestions };
@@ -0,0 +1,13 @@
1
+ import { GRAPH_EDITOR_STRINGS } from "./editorStrings.js";
2
+ //#region src/graph/refusalText.ts
3
+ /** A refusal in the host's words, or the engine's English when the host gives none. */
4
+ function refusalText(t, editing, reason) {
5
+ return t.refusal === GRAPH_EDITOR_STRINGS.refusal ? editing.refusalMessage(reason) : t.refusal(reason);
6
+ }
7
+ /** What to tell the person when an edit throws: why it was refused, or the error itself. */
8
+ function editErrorText(t, editing, error) {
9
+ if (error instanceof editing.EditRefusal) return refusalText(t, editing, error.reason);
10
+ return error instanceof Error ? error.message : String(error);
11
+ }
12
+ //#endregion
13
+ export { editErrorText, refusalText };
@@ -0,0 +1,38 @@
1
+ import type { WorkflowAction } from '../editing.js';
2
+ import { type Row, type Strings } from './editorFields.js';
3
+ import { type InputSource } from './inputRefs.js';
4
+ /** The built-in actions a step can use, by `uses` value, as the host lists them. */
5
+ export type WorkflowActions = Record<string, WorkflowAction>;
6
+ /** Every `with` key the step dialog writes for `actions`, for a schema coverage test. */
7
+ export declare function withFields(actions: WorkflowActions): string[];
8
+ type UsesKind = 'action' | 'github' | 'gitlab' | 'subworkflow' | 'other';
9
+ export declare function usesKind(uses: string, actions: WorkflowActions): UsesKind;
10
+ export interface WithDraft {
11
+ values: Record<string, unknown>;
12
+ rows: Row[];
13
+ removed: string[];
14
+ }
15
+ export declare function withDraftFrom(value: unknown, actions: WorkflowActions): WithDraft;
16
+ /** The step's `with` for `uses`, or its original when nothing changed. */
17
+ export declare function withValue(uses: string, actions: WorkflowActions, draft: WithDraft, initial: WithDraft, originalUses: string, original: unknown): unknown;
18
+ export declare function withError(uses: string, actions: WorkflowActions, draft: WithDraft, original: unknown, t: Strings): string | undefined;
19
+ /** What a step uses: a built-in action, a workflow of yours, one from the marketplace, or one in a repository. */
20
+ export declare function UsesPicker({ uses, onUses, choices, draft, onDraft, error, }: {
21
+ uses: string;
22
+ onUses: (uses: string) => void;
23
+ /** `workflow/NAME` and `marketplace/SLUG` values to offer. */
24
+ choices: string[];
25
+ draft: WithDraft;
26
+ onDraft: (draft: WithDraft) => void;
27
+ error: string | undefined;
28
+ }): import("react").JSX.Element;
29
+ /** The step's `with`: an action's own inputs, or a subworkflow's by name. */
30
+ export declare function WithEditor({ uses, draft, original, onChange, error, source, }: {
31
+ uses: string;
32
+ draft: WithDraft;
33
+ original: unknown;
34
+ onChange: (draft: WithDraft) => void;
35
+ error: string | undefined;
36
+ source: InputSource;
37
+ }): import("react").JSX.Element;
38
+ export {};