@parallelworks/ui 0.36.0 → 0.38.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 +16 -0
- package/dist/ai/agent/permissions.d.ts +2 -2
- package/dist/ai/agent/permissions.js +3 -1
- package/dist/ai/components/ChatSidebar.js +7 -4
- package/dist/ai/components/agent/PermissionPicker.js +1 -0
- package/dist/ai/index.d.ts +1 -1
- package/dist/ai/index.js +2 -2
- package/dist/ai/strings.d.ts +2 -0
- package/dist/ai/strings.js +2 -0
- package/dist/ai/ui/sidebar.d.ts +6 -0
- package/dist/ai/ui/sidebar.js +15 -1
- package/dist/components/Dropdown.d.ts +2 -0
- package/dist/components/Dropdown.js +7 -2
- package/dist/components/Input.d.ts +11 -6
- package/dist/components/Input.js +93 -74
- package/dist/components/Provider.d.ts +31 -0
- package/dist/components/Provider.js +50 -2
- package/dist/editing.d.ts +404 -0
- package/dist/editor/EditorField.js +1 -1
- package/dist/editor/Monaco.d.ts +15 -1
- package/dist/editor/Monaco.js +117 -13
- package/dist/editor/editorModel.d.ts +5 -0
- package/dist/editor/editorModel.js +23 -0
- package/dist/editor/index.d.ts +4 -0
- package/dist/editor/index.js +5 -1
- package/dist/editor/lintContext.d.ts +21 -0
- package/dist/editor/lintContext.js +136 -0
- package/dist/editor/lintOwner.d.ts +2 -0
- package/dist/editor/lintOwner.js +5 -0
- package/dist/editor/nestedText.d.ts +13 -0
- package/dist/editor/nestedText.js +15 -0
- package/dist/editor/reveal.d.ts +4 -0
- package/dist/editor/reveal.js +15 -0
- package/dist/editor/settingsYaml.d.ts +12 -0
- package/dist/editor/settingsYaml.js +41 -0
- package/dist/form/FieldWrapper.js +1 -0
- package/dist/form/Fields/DropdownField.js +1 -1
- package/dist/form/Fields/DurationField.js +1 -1
- package/dist/form/Form.js +65 -24
- package/dist/form/Wizard/WizardContainer.js +66 -6
- package/dist/form/Wizard/WizardStepContent.js +23 -10
- package/dist/form/Wizard/wizardPages.d.ts +12 -0
- package/dist/form/Wizard/wizardPages.js +5 -0
- package/dist/form/fieldRegistry.js +1 -1
- package/dist/form/formEditing.d.ts +25 -0
- package/dist/form/formEditing.js +20 -0
- package/dist/form/index.js +5 -5
- package/dist/graph/InputDialog.d.ts +80 -0
- package/dist/graph/InputDialog.js +2110 -0
- package/dist/graph/InputsEditor.d.ts +12 -0
- package/dist/graph/InputsEditor.js +1594 -0
- package/dist/graph/ProblemsButton.d.ts +9 -0
- package/dist/graph/ProblemsButton.js +61 -0
- package/dist/graph/ShortcutsButton.d.ts +15 -0
- package/dist/graph/ShortcutsButton.js +56 -0
- package/dist/graph/SuggestionInput.d.ts +18 -0
- package/dist/graph/SuggestionInput.js +29 -0
- package/dist/graph/ToolbarPopover.d.ts +8 -0
- package/dist/graph/ToolbarPopover.js +71 -0
- package/dist/graph/editorApi.d.ts +51 -0
- package/dist/graph/editorApi.js +8 -0
- package/dist/graph/editorChrome.d.ts +27 -0
- package/dist/graph/editorChrome.js +75 -0
- package/dist/graph/editorFields.d.ts +180 -0
- package/dist/graph/editorFields.js +394 -0
- package/dist/graph/editorPrimitives.d.ts +60 -0
- package/dist/graph/editorPrimitives.js +150 -0
- package/dist/graph/editorStrings.d.ts +695 -0
- package/dist/graph/editorStrings.js +707 -0
- package/dist/graph/expressionRefs.d.ts +12 -0
- package/dist/graph/fieldProblems.d.ts +32 -0
- package/dist/graph/fieldProblems.js +77 -0
- package/dist/graph/index.d.ts +7 -0
- package/dist/graph/index.js +6 -1
- package/dist/graph/inputLayout.d.ts +25 -0
- package/dist/graph/inputLayout.js +337 -0
- package/dist/graph/inputPages.d.ts +33 -0
- package/dist/graph/inputPages.js +186 -0
- package/dist/graph/inputRefs.d.ts +103 -0
- package/dist/graph/inputRefs.js +196 -0
- package/dist/graph/inputRows.d.ts +43 -0
- package/dist/graph/inputRows.js +65 -0
- package/dist/graph/records.d.ts +3 -0
- package/dist/graph/records.js +7 -0
- package/dist/graph/refusalText.d.ts +6 -0
- package/dist/graph/settingsViews.d.ts +26 -0
- package/dist/graph/settingsViews.js +62 -0
- package/dist/graph/usesInputs.d.ts +17 -0
- package/dist/graph/usesInputs.js +40 -0
- package/dist/icons/bi/BiRedo.js +16 -0
- package/dist/icons/bi/BiUndo.js +16 -0
- package/dist/icons/lu/LuBraces.js +27 -0
- package/dist/icons/lu/LuCode.js +27 -0
- package/dist/icons/lu/LuKeyboard.js +71 -0
- package/dist/icons/lu/LuNetwork.js +63 -0
- package/dist/icons/lu/LuTextCursorInput.js +45 -0
- package/dist/icons.d.ts +2 -2
- package/dist/icons.js +8 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -2
- package/dist/list/ActionToolbar.d.ts +2 -0
- package/dist/list/ActionToolbar.js +18 -5
- package/dist/list/RowContextMenu.d.ts +10 -1
- package/dist/list/RowContextMenu.js +185 -30
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/dist/editing.d.ts
CHANGED
|
@@ -5,20 +5,386 @@ export interface GraphPosition {
|
|
|
5
5
|
}
|
|
6
6
|
/** Job name to grid slot, as stored on a workflow. */
|
|
7
7
|
export type GraphLayout = Record<string, GraphPosition>;
|
|
8
|
+
/** Where a dragged job lands; `insertColumn` opens a new column at `column`. */
|
|
9
|
+
export interface GraphSlot {
|
|
10
|
+
column: number;
|
|
11
|
+
row: number;
|
|
12
|
+
insertColumn?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** One entry of a job's `needs` list. */
|
|
15
|
+
export interface NeedRef {
|
|
16
|
+
job: string;
|
|
17
|
+
need: string;
|
|
18
|
+
}
|
|
19
|
+
export type NudgeDirection = 'up' | 'down' | 'left' | 'right';
|
|
8
20
|
/** One column's boxes, top to bottom, the row each box sits in, and the column's number. */
|
|
9
21
|
export interface LaidOutColumn {
|
|
10
22
|
boxes: string[][];
|
|
11
23
|
rows: number[];
|
|
12
24
|
column: number;
|
|
13
25
|
}
|
|
26
|
+
/** A step by its job and its index in that job's `steps`. */
|
|
27
|
+
export interface StepRef {
|
|
28
|
+
job: string;
|
|
29
|
+
index: number;
|
|
30
|
+
}
|
|
31
|
+
/** A matrix variable: a list of values, or a `${{ }}` expression string. */
|
|
32
|
+
export type MatrixVariable = [name: string, values: unknown[] | string];
|
|
33
|
+
/** Keys to write (a null value writes YAML null) and keys to remove. */
|
|
34
|
+
export interface FieldPatch {
|
|
35
|
+
set?: Record<string, unknown>;
|
|
36
|
+
unset?: string[];
|
|
37
|
+
}
|
|
38
|
+
/** Keys from the workflow's inputs down to an input, through groups, lists and steps. */
|
|
39
|
+
export type InputPath = string[];
|
|
40
|
+
export type InputEdit = {
|
|
41
|
+
type: 'addInput';
|
|
42
|
+
parent: InputPath;
|
|
43
|
+
/** Position among the container's inputs; appends when omitted. */
|
|
44
|
+
index?: number;
|
|
45
|
+
/** Generated when omitted. */
|
|
46
|
+
name?: string;
|
|
47
|
+
definition: Record<string, unknown>;
|
|
48
|
+
} | {
|
|
49
|
+
type: 'deleteInput';
|
|
50
|
+
path: InputPath;
|
|
51
|
+
}
|
|
52
|
+
/** Inputs, kept in form order, moved together to one place. */
|
|
53
|
+
| {
|
|
54
|
+
type: 'moveInputs';
|
|
55
|
+
paths: InputPath[];
|
|
56
|
+
parent: InputPath;
|
|
57
|
+
index: number;
|
|
58
|
+
} | {
|
|
59
|
+
type: 'duplicateInput';
|
|
60
|
+
path: InputPath;
|
|
61
|
+
} | ({
|
|
62
|
+
type: 'updateInput';
|
|
63
|
+
path: InputPath;
|
|
64
|
+
name?: string;
|
|
65
|
+
} & FieldPatch)
|
|
66
|
+
/** Replaces an input's settings with YAML text, as its YAML editor writes them. */
|
|
67
|
+
| {
|
|
68
|
+
type: 'setInputYaml';
|
|
69
|
+
path: InputPath;
|
|
70
|
+
yaml: string;
|
|
71
|
+
} | ({
|
|
72
|
+
type: 'updateWorkflow';
|
|
73
|
+
/** Changes to the input form's `$meta`, in the same undo step. */
|
|
74
|
+
inputsMeta?: FieldPatch;
|
|
75
|
+
} & FieldPatch);
|
|
76
|
+
export type GraphEdit =
|
|
77
|
+
/** `anchor` lands in `to`; the other jobs keep their offsets from it. */
|
|
78
|
+
{
|
|
79
|
+
type: 'move';
|
|
80
|
+
jobs: string[];
|
|
81
|
+
to: GraphSlot;
|
|
82
|
+
layout: GraphLayout;
|
|
83
|
+
anchor?: string;
|
|
84
|
+
} | {
|
|
85
|
+
type: 'addJob';
|
|
86
|
+
matrix?: boolean;
|
|
87
|
+
to?: GraphSlot;
|
|
88
|
+
layout?: GraphLayout;
|
|
89
|
+
} | {
|
|
90
|
+
type: 'deleteJob';
|
|
91
|
+
jobs: string[];
|
|
92
|
+
} | {
|
|
93
|
+
type: 'renameJob';
|
|
94
|
+
from: string;
|
|
95
|
+
to: string;
|
|
96
|
+
} | {
|
|
97
|
+
type: 'duplicateJob';
|
|
98
|
+
job: string;
|
|
99
|
+
layout?: GraphLayout;
|
|
100
|
+
} | {
|
|
101
|
+
type: 'connect';
|
|
102
|
+
from: string;
|
|
103
|
+
to: string;
|
|
104
|
+
}
|
|
105
|
+
/** `jobs` and what depends on them move right of their needs; `layout` is the grid as drawn. */
|
|
106
|
+
| {
|
|
107
|
+
type: 'placeAfterNeeds';
|
|
108
|
+
jobs: string[];
|
|
109
|
+
layout?: GraphLayout;
|
|
110
|
+
}
|
|
111
|
+
/** Moves `jobs` left of what needs them; `dependents` gained a need and may leave their node. */
|
|
112
|
+
| {
|
|
113
|
+
type: 'placeBeforeDependents';
|
|
114
|
+
jobs: string[];
|
|
115
|
+
dependents: string[];
|
|
116
|
+
layout?: GraphLayout;
|
|
117
|
+
}
|
|
118
|
+
/** Jobs join `into`'s node: they take its slot and a copy of its needs. */
|
|
119
|
+
| {
|
|
120
|
+
type: 'groupJobs';
|
|
121
|
+
jobs: string[];
|
|
122
|
+
into: string;
|
|
123
|
+
layout: GraphLayout;
|
|
124
|
+
} | {
|
|
125
|
+
type: 'disconnect';
|
|
126
|
+
needs: NeedRef[];
|
|
127
|
+
} | {
|
|
128
|
+
type: 'setNeedAny';
|
|
129
|
+
jobs: string[];
|
|
130
|
+
dep: string;
|
|
131
|
+
any: boolean;
|
|
132
|
+
} | {
|
|
133
|
+
type: 'setMatrix';
|
|
134
|
+
job: string;
|
|
135
|
+
variables: MatrixVariable[] | null;
|
|
136
|
+
} | {
|
|
137
|
+
type: 'addStep';
|
|
138
|
+
job: string;
|
|
139
|
+
} | {
|
|
140
|
+
type: 'deleteStep';
|
|
141
|
+
job: string;
|
|
142
|
+
index: number;
|
|
143
|
+
} | {
|
|
144
|
+
type: 'renameStep';
|
|
145
|
+
job: string;
|
|
146
|
+
index: number;
|
|
147
|
+
name: string;
|
|
148
|
+
} | {
|
|
149
|
+
type: 'moveStep';
|
|
150
|
+
job: string;
|
|
151
|
+
from: number;
|
|
152
|
+
to: number;
|
|
153
|
+
}
|
|
154
|
+
/** Steps, in order, to insertion point `index` of `job` (an index into its steps before the move). */
|
|
155
|
+
| {
|
|
156
|
+
type: 'moveSteps';
|
|
157
|
+
steps: StepRef[];
|
|
158
|
+
job: string;
|
|
159
|
+
index: number;
|
|
160
|
+
}
|
|
161
|
+
/** The steps in `yaml`, a list as stepsYaml writes it, inserted at `index` of `job`. */
|
|
162
|
+
| {
|
|
163
|
+
type: 'pasteSteps';
|
|
164
|
+
yaml: string;
|
|
165
|
+
job: string;
|
|
166
|
+
index: number;
|
|
167
|
+
}
|
|
168
|
+
/** The jobs in `yaml`, as jobsYaml writes it, added as new jobs; each lands at `to` plus its offset. */
|
|
169
|
+
| {
|
|
170
|
+
type: 'pasteJobs';
|
|
171
|
+
yaml: string;
|
|
172
|
+
to?: GraphSlot;
|
|
173
|
+
offsets?: Record<string, GraphPosition>;
|
|
174
|
+
layout?: GraphLayout;
|
|
175
|
+
}
|
|
176
|
+
/** Jobs one slot over, as the arrow keys move them. */
|
|
177
|
+
| {
|
|
178
|
+
type: 'nudge';
|
|
179
|
+
jobs: string[];
|
|
180
|
+
direction: NudgeDirection;
|
|
181
|
+
layout: GraphLayout;
|
|
182
|
+
} | ({
|
|
183
|
+
type: 'updateJob';
|
|
184
|
+
job: string;
|
|
185
|
+
name?: string;
|
|
186
|
+
} & FieldPatch) | ({
|
|
187
|
+
type: 'updateStep';
|
|
188
|
+
job: string;
|
|
189
|
+
index: number;
|
|
190
|
+
} & FieldPatch)
|
|
191
|
+
/** Replaces a job's or step's settings with YAML text, as its YAML editor writes them. */
|
|
192
|
+
| {
|
|
193
|
+
type: 'setJobYaml';
|
|
194
|
+
job: string;
|
|
195
|
+
yaml: string;
|
|
196
|
+
} | {
|
|
197
|
+
type: 'setStepYaml';
|
|
198
|
+
job: string;
|
|
199
|
+
index: number;
|
|
200
|
+
yaml: string;
|
|
201
|
+
}
|
|
202
|
+
/** Replaces the workflow's own settings, everything but its jobs and trigger, with YAML text. */
|
|
203
|
+
| {
|
|
204
|
+
type: 'setSettingsYaml';
|
|
205
|
+
yaml: string;
|
|
206
|
+
} | {
|
|
207
|
+
type: 'resetLayout';
|
|
208
|
+
} | InputEdit
|
|
209
|
+
/** Several edits as one undo step, such as a new input and the field that reads it. */
|
|
210
|
+
| {
|
|
211
|
+
type: 'batch';
|
|
212
|
+
edits: GraphEdit[];
|
|
213
|
+
};
|
|
214
|
+
export interface GraphEditState {
|
|
215
|
+
yml: string;
|
|
216
|
+
layout: GraphLayout | undefined;
|
|
217
|
+
}
|
|
218
|
+
export interface GraphEditResult extends GraphEditState {
|
|
219
|
+
/** Name of the job or input the edit created, if any. */
|
|
220
|
+
created?: string;
|
|
221
|
+
/** The jobs a paste added, in order. */
|
|
222
|
+
createdJobs?: string[];
|
|
223
|
+
/** Where moved or pasted steps now sit. */
|
|
224
|
+
steps?: StepRef[];
|
|
225
|
+
}
|
|
226
|
+
/** A step as a refusal names it: by its name, or by where it sits when it has none. */
|
|
227
|
+
export type RefusedStep = {
|
|
228
|
+
name: string;
|
|
229
|
+
} | {
|
|
230
|
+
job: string;
|
|
231
|
+
index: number;
|
|
232
|
+
};
|
|
233
|
+
/** Why the workflow can't take an edit, by code, so the editor can say it in the reader's language. */
|
|
234
|
+
export type EditRefusalReason = {
|
|
235
|
+
code: 'cycle';
|
|
236
|
+
job: string;
|
|
237
|
+
need: string;
|
|
238
|
+
} | {
|
|
239
|
+
code: 'needRead';
|
|
240
|
+
job: string;
|
|
241
|
+
need: string;
|
|
242
|
+
} | {
|
|
243
|
+
code: 'columnRead';
|
|
244
|
+
job: string;
|
|
245
|
+
need: string;
|
|
246
|
+
} | {
|
|
247
|
+
code: 'stepReadsNeed';
|
|
248
|
+
step: RefusedStep;
|
|
249
|
+
need: string;
|
|
250
|
+
job: string;
|
|
251
|
+
} | {
|
|
252
|
+
code: 'stepReadsStep';
|
|
253
|
+
step: RefusedStep;
|
|
254
|
+
id: string;
|
|
255
|
+
job: string;
|
|
256
|
+
} | {
|
|
257
|
+
code: 'stepReadsMatrix';
|
|
258
|
+
step: RefusedStep;
|
|
259
|
+
variable: string;
|
|
260
|
+
job: string;
|
|
261
|
+
} | {
|
|
262
|
+
code: 'stepLeftBehind';
|
|
263
|
+
job: string;
|
|
264
|
+
id: string;
|
|
265
|
+
target: string;
|
|
266
|
+
} | {
|
|
267
|
+
code: 'jobNameInvalid';
|
|
268
|
+
} | {
|
|
269
|
+
code: 'jobNameTaken';
|
|
270
|
+
name: string;
|
|
271
|
+
} | {
|
|
272
|
+
code: 'inputNameInvalid';
|
|
273
|
+
} | {
|
|
274
|
+
code: 'inputNameTaken';
|
|
275
|
+
name: string;
|
|
276
|
+
} | {
|
|
277
|
+
code: 'inputIntoItself';
|
|
278
|
+
} | {
|
|
279
|
+
code: 'needsExpression';
|
|
280
|
+
} | {
|
|
281
|
+
code: 'matrixExpression';
|
|
282
|
+
} | {
|
|
283
|
+
code: 'inputsExpression';
|
|
284
|
+
name: string;
|
|
285
|
+
} | {
|
|
286
|
+
code: 'notAGroup';
|
|
287
|
+
name: string;
|
|
288
|
+
} | {
|
|
289
|
+
code: 'noStepsToPaste';
|
|
290
|
+
} | {
|
|
291
|
+
code: 'noJobsToPaste';
|
|
292
|
+
} | {
|
|
293
|
+
code: 'notASetting';
|
|
294
|
+
key: string;
|
|
295
|
+
};
|
|
296
|
+
/** A "did you mean": `replace` for each reference to `find` in the value, or, with `key`, the key's new name. */
|
|
297
|
+
export interface LintFix {
|
|
298
|
+
find: string;
|
|
299
|
+
replace: string;
|
|
300
|
+
key: boolean;
|
|
301
|
+
}
|
|
302
|
+
/** One problem a workflow's schema can't catch, such as a need read but never listed. */
|
|
303
|
+
export interface WorkflowLintProblem {
|
|
304
|
+
path: string;
|
|
305
|
+
message: string;
|
|
306
|
+
line: number;
|
|
307
|
+
fix?: LintFix;
|
|
308
|
+
}
|
|
309
|
+
/** What the checks can't work out from the document: `uses` targets' inputs, and secret variable names. */
|
|
310
|
+
export interface WorkflowLintContext {
|
|
311
|
+
usesInputs?: Record<string, unknown>;
|
|
312
|
+
secretVars?: string[];
|
|
313
|
+
}
|
|
314
|
+
export type SettingsYamlProblem = {
|
|
315
|
+
kind: 'syntax';
|
|
316
|
+
message: string;
|
|
317
|
+
} | {
|
|
318
|
+
kind: 'notMap';
|
|
319
|
+
};
|
|
320
|
+
/** A key or list index on the way down to a YAML node. */
|
|
321
|
+
export type YamlSegment = string | number;
|
|
322
|
+
type UnknownRecord = Record<string, unknown>;
|
|
323
|
+
/** One input a built-in action takes in its step's `with`, labelled in the reader's language. */
|
|
324
|
+
export interface WorkflowActionInput {
|
|
325
|
+
key: string;
|
|
326
|
+
kind: 'text' | 'textarea' | 'number' | 'any' | 'list' | 'bool' | 'choice' | 'flags' | 'target';
|
|
327
|
+
label: string;
|
|
328
|
+
description: string;
|
|
329
|
+
required?: boolean | undefined;
|
|
330
|
+
choices?: string[] | undefined;
|
|
331
|
+
/** A `${{ }}` expression also stands in for a choice. */
|
|
332
|
+
expression?: boolean | undefined;
|
|
333
|
+
/** Inputs of these types, or their `suffix` property, can fill this one in. */
|
|
334
|
+
refTypes?: string[] | undefined;
|
|
335
|
+
suffix?: string | undefined;
|
|
336
|
+
/** What a `bool` left unset means. */
|
|
337
|
+
fallback?: boolean | undefined;
|
|
338
|
+
/** A `target` input's own keys. */
|
|
339
|
+
fields?: {
|
|
340
|
+
key: string;
|
|
341
|
+
label: string;
|
|
342
|
+
description: string;
|
|
343
|
+
}[] | undefined;
|
|
344
|
+
}
|
|
345
|
+
/** A built-in action a step can use, keyed by its `uses` value in `UIData['workflowActions']`. */
|
|
346
|
+
export interface WorkflowAction {
|
|
347
|
+
/** What it does, shown beside it where it's picked. */
|
|
348
|
+
description: string;
|
|
349
|
+
inputs: WorkflowActionInput[];
|
|
350
|
+
}
|
|
14
351
|
/**
|
|
15
352
|
* The workflow editing the visual editor defers to: reading and rewriting the workflow's YAML,
|
|
16
353
|
* its graph layout and its checks. A host built on a workflow package can pass that package's
|
|
17
354
|
* functions of the same names.
|
|
18
355
|
*/
|
|
19
356
|
export interface WorkflowEditing {
|
|
357
|
+
loadYaml: (text: string) => unknown;
|
|
358
|
+
dumpYaml: (data: unknown) => string;
|
|
359
|
+
/** Applies one edit to the workflow's text and layout; throws `EditRefusal` when it can't. */
|
|
360
|
+
applyGraphEdit: (state: GraphEditState, edit: GraphEdit) => GraphEditResult;
|
|
361
|
+
/** `edits` as one undo step: the edit itself when there is one, nothing when there are none. */
|
|
362
|
+
batchOf: (edits: GraphEdit[]) => GraphEdit | null;
|
|
363
|
+
/** What an edit throws when the workflow can't take it, so the reason can be told. */
|
|
364
|
+
EditRefusal: abstract new (reason: EditRefusalReason) => Error & {
|
|
365
|
+
readonly reason: EditRefusalReason;
|
|
366
|
+
};
|
|
367
|
+
/** The English wording of a refusal. */
|
|
368
|
+
refusalMessage: (reason: EditRefusalReason) => string;
|
|
369
|
+
/** Each job's `needs` entries; expression-valued needs count as none. */
|
|
370
|
+
jobNeeds: (jobs: unknown) => Record<string, string[]>;
|
|
371
|
+
/** Whether `from` already depends on `to`, directly or through other jobs. */
|
|
372
|
+
dependsOn: (needs: Record<string, string[]>, from: string, to: string) => boolean;
|
|
373
|
+
/** The first of `refs` an expression in its job reads, so cutting it would break the read. */
|
|
374
|
+
needInUse: (jobs: Record<string, unknown>, refs: NeedRef[]) => NeedRef | undefined;
|
|
20
375
|
/** The job a `needs` entry points at, without a matrix's wait-for-any suffix. */
|
|
21
376
|
needTarget: (need: string) => string;
|
|
377
|
+
isValidJobName: (name: string) => boolean;
|
|
378
|
+
jobYaml: (text: string, job: string) => string;
|
|
379
|
+
jobsYaml: (text: string, jobs: string[]) => string;
|
|
380
|
+
stepYaml: (text: string, job: string, index: number) => string;
|
|
381
|
+
stepsYaml: (text: string, refs: StepRef[]) => string;
|
|
382
|
+
/** What a pasted snippet holds: jobs, steps, or neither. */
|
|
383
|
+
snippetKind: (snippet: string) => 'jobs' | 'steps' | undefined;
|
|
384
|
+
/** Why moving `refs` into `job` would leave an expression reading something that isn't there. */
|
|
385
|
+
stepMoveProblem: (text: string, refs: StepRef[], job: string) => EditRefusalReason | undefined;
|
|
386
|
+
/** A job's matrix variables; null when the matrix is an expression, which may hold any. */
|
|
387
|
+
matrixNames: (job: unknown) => Set<string> | null;
|
|
22
388
|
/** Jobs whose matrix runs more than once as `<job>-<n>` members, the way a run lists them. */
|
|
23
389
|
expandMatrixJobs: <T>(jobs: Record<string, T>) => Record<string, T>;
|
|
24
390
|
hasLayout: (layout: GraphLayout | undefined) => boolean;
|
|
@@ -29,4 +395,42 @@ export interface WorkflowEditing {
|
|
|
29
395
|
together: (a: string, b: string) => boolean;
|
|
30
396
|
alone: (job: string) => boolean;
|
|
31
397
|
}) => LaidOutColumn[];
|
|
398
|
+
/** The grid as drawn, as a layout to store. */
|
|
399
|
+
layoutFromCols: (dependencyCols: string[][][], previous?: GraphLayout, rows?: number[][], columns?: number[]) => GraphLayout;
|
|
400
|
+
/** Where moved jobs land, and the needs the move would cut. */
|
|
401
|
+
moveJobs: (layout: GraphLayout, needs: Record<string, string[]>, jobs: string[], slot: GraphSlot, anchor?: string | undefined) => {
|
|
402
|
+
layout: GraphLayout;
|
|
403
|
+
cuts: NeedRef[];
|
|
404
|
+
};
|
|
405
|
+
/** Where an input of this type keeps the inputs it holds, if it holds any. */
|
|
406
|
+
inputChildrenKey: (type: unknown) => string | undefined;
|
|
407
|
+
isValidInputName: (name: string) => boolean;
|
|
408
|
+
newInputName: (existing: Iterable<string>) => string;
|
|
409
|
+
/** `base`, or `base_2`, `base_3`… when it's taken. */
|
|
410
|
+
nextName: (taken: Iterable<string>, base: string) => string;
|
|
411
|
+
/** The first of `candidate(1)`, `candidate(2)`… that `taken` doesn't hold. */
|
|
412
|
+
freeName: (taken: Iterable<string>, candidate: (n: number) => string) => string;
|
|
413
|
+
inputYaml: (text: string, path: InputPath) => string;
|
|
414
|
+
/** Whether a wizard's steps put their fields at the top level of the inputs. */
|
|
415
|
+
wizardFlattens: (schema: UnknownRecord) => boolean;
|
|
416
|
+
/** The input definitions of a parsed workflow. */
|
|
417
|
+
workflowInputsSchema: (workflow: UnknownRecord | undefined | null) => UnknownRecord | undefined;
|
|
418
|
+
/** The workflow's own settings as YAML: everything but its jobs and trigger. */
|
|
419
|
+
settingsYaml: (text: string) => string;
|
|
420
|
+
/** What's wrong with YAML text written as a job's, step's or input's settings, if anything. */
|
|
421
|
+
settingsYamlProblem: (snippet: string) => SettingsYamlProblem | undefined;
|
|
422
|
+
/** The first and last 1-based line of the node at `path`, its key included. */
|
|
423
|
+
linesAt: (text: string, path: YamlSegment[]) => {
|
|
424
|
+
start: number;
|
|
425
|
+
end: number;
|
|
426
|
+
} | undefined;
|
|
427
|
+
/** `value` with each whole reference to `find` replaced by `replace`. */
|
|
428
|
+
replaceReference: (value: string, find: string, replace: string) => string;
|
|
429
|
+
/** The workflow's problems its schema can't catch; none until the checks have loaded. */
|
|
430
|
+
lintWorkflow: (yaml: string, context?: WorkflowLintContext) => WorkflowLintProblem[];
|
|
431
|
+
/** Form definitions for picking a workflow file from a repository. */
|
|
432
|
+
WORKFLOW_FORMS: {
|
|
433
|
+
github: UnknownRecord;
|
|
434
|
+
};
|
|
32
435
|
}
|
|
436
|
+
export {};
|
package/dist/editor/Monaco.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import * as monaco from 'monaco-editor';
|
|
2
|
+
import { type NestedWorkflowText } from './nestedText.js';
|
|
2
3
|
export type ISchemaError = monaco.editor.IMarker;
|
|
4
|
+
/** A problem to mark on one line of the editor. */
|
|
5
|
+
export interface EditorMarker {
|
|
6
|
+
line: number;
|
|
7
|
+
message: string;
|
|
8
|
+
}
|
|
3
9
|
export interface IEditorProps extends monaco.editor.IStandaloneEditorConstructionOptions {
|
|
4
10
|
className?: string;
|
|
5
11
|
disabled?: boolean | undefined;
|
|
@@ -8,5 +14,13 @@ export interface IEditorProps extends monaco.editor.IStandaloneEditorConstructio
|
|
|
8
14
|
width?: string | number;
|
|
9
15
|
height?: string | number;
|
|
10
16
|
path?: 'file:///workflow.yaml' | string;
|
|
17
|
+
/** Apply outside `value` changes as undoable edits instead of resetting the undo history. */
|
|
18
|
+
preserveUndo?: boolean;
|
|
19
|
+
/** Checks a whole workflow for what the schema can't, such as needs read but never listed. */
|
|
20
|
+
lint?: boolean;
|
|
21
|
+
/** Problems found elsewhere to mark in the text, such as a job's in the workflow around it. */
|
|
22
|
+
markers?: EditorMarker[];
|
|
23
|
+
/** Set when the text is one job or step, for completions that touch the rest of the workflow. */
|
|
24
|
+
nested?: NestedWorkflowText;
|
|
11
25
|
}
|
|
12
|
-
export default function MonacoEditor({ className, disabled, onChange, onValidate, width, height, path, ...options }: IEditorProps): import("react").JSX.Element;
|
|
26
|
+
export default function MonacoEditor({ className, disabled, onChange, onValidate, width, height, path, preserveUndo, lint, markers, nested, ...options }: IEditorProps): import("react").JSX.Element;
|
package/dist/editor/Monaco.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { useCssIsDark } from "../components/useCssIsDark.js";
|
|
2
|
+
import { HOST_MARKER_OWNER, LINT_OWNER } from "./lintOwner.js";
|
|
3
|
+
import { publishNestedText } from "./nestedText.js";
|
|
4
|
+
import { registerRevealer } from "./reveal.js";
|
|
2
5
|
import { defineEditorThemes, getThemeName } from "./themes.js";
|
|
3
6
|
import { setupMonacoWorkers } from "./workers.js";
|
|
7
|
+
import { holdListeners, modelFor } from "./editorModel.js";
|
|
8
|
+
import { lintContext, useLintEditing, useLintSources } from "./lintContext.js";
|
|
4
9
|
import { ensureYamlLanguage } from "./yamlLanguage.js";
|
|
5
10
|
import cx from "classnames";
|
|
6
11
|
import { useEffect, useLayoutEffect, useRef } from "react";
|
|
@@ -8,20 +13,68 @@ import { jsx } from "react/jsx-runtime";
|
|
|
8
13
|
import * as monaco from "monaco-editor";
|
|
9
14
|
//#region src/editor/Monaco.tsx
|
|
10
15
|
setupMonacoWorkers();
|
|
16
|
+
var SCHEMA_UNREADABLE = "768";
|
|
17
|
+
function lintMarker(model, { line, message }) {
|
|
18
|
+
const at = Math.min(Math.max(line || 1, 1), model.getLineCount());
|
|
19
|
+
return {
|
|
20
|
+
severity: monaco.MarkerSeverity.Error,
|
|
21
|
+
message,
|
|
22
|
+
startLineNumber: at,
|
|
23
|
+
startColumn: model.getLineFirstNonWhitespaceColumn(at) || 1,
|
|
24
|
+
endLineNumber: at,
|
|
25
|
+
endColumn: model.getLineMaxColumn(at)
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function sameMarkers(a, b) {
|
|
29
|
+
return a.length === b.length && a.every((marker, i) => marker.message === b[i]?.message && marker.startLineNumber === b[i]?.startLineNumber);
|
|
30
|
+
}
|
|
31
|
+
function setMarkers(model, owner, wanted) {
|
|
32
|
+
if (!sameMarkers(monaco.editor.getModelMarkers({
|
|
33
|
+
resource: model.uri,
|
|
34
|
+
owner
|
|
35
|
+
}), wanted)) monaco.editor.setModelMarkers(model, owner, wanted);
|
|
36
|
+
}
|
|
11
37
|
function editorText(value) {
|
|
12
38
|
return value.replace(/\r/g, "");
|
|
13
39
|
}
|
|
14
40
|
var noop = () => {};
|
|
15
|
-
function MonacoEditor({ className, disabled, onChange = noop, onValidate = noop, width = "100%", height = "100%", path = "file:///default", ...options }) {
|
|
41
|
+
function MonacoEditor({ className, disabled, onChange = noop, onValidate = noop, width = "100%", height = "100%", path = "file:///default", preserveUndo = false, lint = false, markers, nested, ...options }) {
|
|
16
42
|
const isDark = useCssIsDark();
|
|
17
|
-
const readOnly = disabled || options.readOnly;
|
|
43
|
+
const readOnly = Boolean(disabled || options.readOnly);
|
|
18
44
|
const monacoRef = useRef(null);
|
|
19
45
|
const isSyncingValueRef = useRef(false);
|
|
46
|
+
const lintSources = useLintSources();
|
|
47
|
+
const lintSourcesRef = useRef(lintSources);
|
|
48
|
+
lintSourcesRef.current = lintSources;
|
|
49
|
+
const runLintRef = useRef(null);
|
|
50
|
+
const unregisterRevealRef = useRef(null);
|
|
51
|
+
const listenersRef = useRef([]);
|
|
52
|
+
const nestedRef = useRef(nested);
|
|
53
|
+
nestedRef.current = nested;
|
|
54
|
+
const isNested = nested !== void 0;
|
|
55
|
+
const lintEditing = useLintEditing(lint);
|
|
56
|
+
const lintEditingRef = useRef(lintEditing);
|
|
57
|
+
lintEditingRef.current = lintEditing;
|
|
58
|
+
const lintRef = useRef(lint);
|
|
59
|
+
lintRef.current = lint;
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
const model = monacoRef.current?.getModel();
|
|
62
|
+
if (!lint && model) setMarkers(model, LINT_OWNER, []);
|
|
63
|
+
else if (lint && lintEditing) runLintRef.current?.();
|
|
64
|
+
}, [lint, lintEditing]);
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const model = monacoRef.current?.getModel();
|
|
67
|
+
if (model) setMarkers(model, HOST_MARKER_OWNER, (markers ?? []).map((marker) => lintMarker(model, marker)));
|
|
68
|
+
}, [markers]);
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (!isNested) return;
|
|
71
|
+
return publishNestedText(monaco.Uri.parse(path).toString(), nestedRef);
|
|
72
|
+
}, [path, isNested]);
|
|
20
73
|
useEffect(() => {
|
|
21
74
|
if (!monacoRef.current) return;
|
|
22
75
|
defineEditorThemes(monaco.editor);
|
|
23
76
|
monaco.editor.setTheme(getThemeName(isDark, disabled));
|
|
24
|
-
monacoRef.current.updateOptions(
|
|
77
|
+
monacoRef.current.updateOptions({ readOnly });
|
|
25
78
|
}, [
|
|
26
79
|
disabled,
|
|
27
80
|
isDark,
|
|
@@ -32,13 +85,23 @@ function MonacoEditor({ className, disabled, onChange = noop, onValidate = noop,
|
|
|
32
85
|
if (!model || options.value === void 0 || editorText(model.getValue()) === editorText(options.value)) return;
|
|
33
86
|
isSyncingValueRef.current = true;
|
|
34
87
|
try {
|
|
35
|
-
|
|
88
|
+
if (preserveUndo) {
|
|
89
|
+
const editor = monacoRef.current;
|
|
90
|
+
editor?.pushUndoStop();
|
|
91
|
+
model.pushEditOperations([], [{
|
|
92
|
+
range: model.getFullModelRange(),
|
|
93
|
+
text: options.value
|
|
94
|
+
}], () => null);
|
|
95
|
+
editor?.pushUndoStop();
|
|
96
|
+
} else model.setValue(options.value);
|
|
36
97
|
} finally {
|
|
37
98
|
isSyncingValueRef.current = false;
|
|
38
99
|
}
|
|
39
|
-
}, [options.value]);
|
|
100
|
+
}, [options.value, preserveUndo]);
|
|
40
101
|
useEffect(() => {
|
|
41
102
|
return () => {
|
|
103
|
+
unregisterRevealRef.current?.();
|
|
104
|
+
for (const listener of listenersRef.current) listener.dispose();
|
|
42
105
|
monacoRef.current?.dispose();
|
|
43
106
|
monacoRef.current = null;
|
|
44
107
|
};
|
|
@@ -56,15 +119,14 @@ function MonacoEditor({ className, disabled, onChange = noop, onValidate = noop,
|
|
|
56
119
|
className: cx("relative border rounded", className),
|
|
57
120
|
ref: (ref) => {
|
|
58
121
|
if (ref && !monacoRef.current) {
|
|
59
|
-
monaco.editor.getModel(monaco.Uri.parse(path))?.dispose();
|
|
60
122
|
defineEditorThemes(monaco.editor);
|
|
61
123
|
monaco.editor.setTheme(getThemeName(isDark, disabled));
|
|
62
|
-
const model = monaco.
|
|
124
|
+
const model = modelFor(monaco.Uri.parse(path), options.value ?? "", options.language);
|
|
63
125
|
const editor = monaco.editor.create(ref, {
|
|
64
126
|
automaticLayout: true,
|
|
65
127
|
model,
|
|
66
128
|
...options,
|
|
67
|
-
|
|
129
|
+
readOnly,
|
|
68
130
|
fixedOverflowWidgets: true,
|
|
69
131
|
suggest: {
|
|
70
132
|
preview: true,
|
|
@@ -72,12 +134,54 @@ function MonacoEditor({ className, disabled, onChange = noop, onValidate = noop,
|
|
|
72
134
|
showInlineDetails: true
|
|
73
135
|
}
|
|
74
136
|
});
|
|
75
|
-
model.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
137
|
+
setMarkers(model, HOST_MARKER_OWNER, (markers ?? []).map((marker) => lintMarker(model, marker)));
|
|
138
|
+
setMarkers(model, LINT_OWNER, []);
|
|
139
|
+
const runLint = () => {
|
|
140
|
+
const editing = lintEditingRef.current;
|
|
141
|
+
if (!lintRef.current || !editing || model.isDisposed()) return;
|
|
142
|
+
const schema = monaco.editor.getModelMarkers({ resource: model.uri }).filter((marker) => marker.owner !== "workflowlint" && marker.owner !== "workflowmarkers" && String(marker.code ?? "") !== SCHEMA_UNREADABLE);
|
|
143
|
+
const source = model.getValue();
|
|
144
|
+
setMarkers(model, LINT_OWNER, schema.length > 0 ? [] : editing.lintWorkflow(source, lintContext(editing, source, lintSourcesRef.current, runLint)).map((problem) => lintMarker(model, problem)));
|
|
145
|
+
};
|
|
146
|
+
runLintRef.current = runLint;
|
|
147
|
+
let lintTimer;
|
|
148
|
+
const validate = () => {
|
|
149
|
+
runLint();
|
|
80
150
|
onValidate(monaco.editor.getModelMarkers({ resource: model.uri }));
|
|
151
|
+
};
|
|
152
|
+
const modelListeners = [
|
|
153
|
+
model.onDidChangeContent(() => {
|
|
154
|
+
if (lintRef.current) {
|
|
155
|
+
window.clearTimeout(lintTimer);
|
|
156
|
+
lintTimer = window.setTimeout(runLint, 300);
|
|
157
|
+
}
|
|
158
|
+
if (isSyncingValueRef.current) return;
|
|
159
|
+
onChangeWrapper(editor.getValue());
|
|
160
|
+
}),
|
|
161
|
+
monaco.editor.onDidChangeMarkers((resources) => {
|
|
162
|
+
const uri = model.uri.toString();
|
|
163
|
+
if (resources.some((resource) => resource.toString() === uri)) validate();
|
|
164
|
+
}),
|
|
165
|
+
{ dispose: () => window.clearTimeout(lintTimer) }
|
|
166
|
+
];
|
|
167
|
+
holdListeners(model, modelListeners);
|
|
168
|
+
listenersRef.current = modelListeners;
|
|
169
|
+
validate();
|
|
170
|
+
unregisterRevealRef.current = registerRevealer(path, (start, end) => {
|
|
171
|
+
const last = Math.min(Math.max(start, end), model.getLineCount());
|
|
172
|
+
editor.revealLinesInCenterIfOutsideViewport(start, last);
|
|
173
|
+
editor.setPosition({
|
|
174
|
+
lineNumber: start,
|
|
175
|
+
column: model.getLineFirstNonWhitespaceColumn(start) || 1
|
|
176
|
+
});
|
|
177
|
+
const flash = editor.createDecorationsCollection([{
|
|
178
|
+
range: new monaco.Range(start, 1, last, 1),
|
|
179
|
+
options: {
|
|
180
|
+
isWholeLine: true,
|
|
181
|
+
className: "bg-(--theme-element)/20"
|
|
182
|
+
}
|
|
183
|
+
}]);
|
|
184
|
+
window.setTimeout(() => flash.clear(), 1500);
|
|
81
185
|
});
|
|
82
186
|
ensureYamlLanguage();
|
|
83
187
|
monacoRef.current = editor;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import * as monaco from 'monaco-editor';
|
|
2
|
+
/** The model a new editor on `uri` edits, holding `value`. */
|
|
3
|
+
export declare function modelFor(uri: monaco.Uri, value: string, language: string | undefined): monaco.editor.ITextModel;
|
|
4
|
+
/** An editor's listeners on its model, dropped when the next editor takes the model over. */
|
|
5
|
+
export declare function holdListeners(model: monaco.editor.ITextModel, disposables: monaco.IDisposable[]): void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as monaco from "monaco-editor";
|
|
2
|
+
//#region src/editor/editorModel.ts
|
|
3
|
+
var listeners = /* @__PURE__ */ new WeakMap();
|
|
4
|
+
/** The model a new editor on `uri` edits, holding `value`. */
|
|
5
|
+
function modelFor(uri, value, language) {
|
|
6
|
+
const existing = monaco.editor.getModel(uri);
|
|
7
|
+
if (existing && language === "yaml" && existing.getLanguageId() === "yaml") {
|
|
8
|
+
for (const listener of listeners.get(existing) ?? []) listener.dispose();
|
|
9
|
+
const owners = new Set(monaco.editor.getModelMarkers({ resource: uri }).map((marker) => marker.owner));
|
|
10
|
+
for (const owner of owners) monaco.editor.setModelMarkers(existing, owner, []);
|
|
11
|
+
existing.setValue(value);
|
|
12
|
+
existing.detectIndentation(true, 4);
|
|
13
|
+
return existing;
|
|
14
|
+
}
|
|
15
|
+
existing?.dispose();
|
|
16
|
+
return monaco.editor.createModel(value, language, uri);
|
|
17
|
+
}
|
|
18
|
+
/** An editor's listeners on its model, dropped when the next editor takes the model over. */
|
|
19
|
+
function holdListeners(model, disposables) {
|
|
20
|
+
listeners.set(model, disposables);
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { holdListeners, modelFor };
|
package/dist/editor/index.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
export { default as Editor } from './Editor.js';
|
|
2
2
|
export { default as EditorField, type IEditorField } from './EditorField.js';
|
|
3
3
|
export { default as FormCodePanel } from './FormCodePanel.js';
|
|
4
|
+
export { HOST_MARKER_OWNER, LINT_OWNER } from './lintOwner.js';
|
|
4
5
|
export type { IEditorProps, ISchemaError } from './Monaco.js';
|
|
6
|
+
export { type NestedWorkflowText, nestedWorkflowText } from './nestedText.js';
|
|
7
|
+
export { revealLines } from './reveal.js';
|
|
8
|
+
export { INPUT_YAML_PATH, JOB_YAML_PATH, type JsonSchema, SETTINGS_YAML_PATH, STEP_YAML_PATH, settingsSchemas, } from './settingsYaml.js';
|
|
5
9
|
export { defineEditorThemes, getThemeName } from './themes.js';
|
|
6
10
|
export { setupMonacoWorkers } from './workers.js';
|
|
7
11
|
export { configureEditorYaml } from './yaml.js';
|