@parallelworks/ui 0.37.0 → 0.39.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 +1 -1
- package/dist/ai/components/ChatInput.js +8 -23
- package/dist/ai/components/ChatSidebar.js +7 -4
- package/dist/ai/components/chatDraft.d.ts +7 -0
- package/dist/ai/components/chatDraft.js +66 -0
- package/dist/ai/index.d.ts +1 -1
- package/dist/ai/index.js +2 -2
- package/dist/ai/ui/sidebar.d.ts +6 -0
- package/dist/ai/ui/sidebar.js +15 -1
- package/dist/editing.d.ts +2 -0
- package/dist/editor/Monaco.js +1 -1
- package/dist/editor/index.js +1 -1
- package/dist/editor/lintContext.js +15 -3
- package/dist/form/FieldWrapper.js +1 -0
- package/dist/form/Form.js +64 -23
- 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/formEditing.d.ts +25 -0
- package/dist/form/formEditing.js +20 -0
- package/dist/graph/DependencyGraph.d.ts +8 -1
- package/dist/graph/DependencyGraph.js +433 -204
- package/dist/graph/GraphEditor.d.ts +27 -0
- package/dist/graph/GraphEditor.js +1543 -0
- package/dist/graph/GraphEditorDialogs.d.ts +87 -0
- package/dist/graph/GraphEditorDialogs.js +1808 -0
- package/dist/graph/InputDialog.js +1568 -54
- package/dist/graph/InputsEditor.d.ts +12 -0
- package/dist/graph/InputsEditor.js +1594 -0
- package/dist/graph/JobSummary.d.ts +3 -0
- package/dist/graph/JobSummary.js +34 -21
- package/dist/graph/MatrixGroup.d.ts +2 -1
- package/dist/graph/MatrixGroup.js +10 -3
- package/dist/graph/ShortcutsButton.js +56 -0
- package/dist/graph/SuggestionInput.js +29 -0
- package/dist/graph/WorkflowSettingsDialog.d.ts +21 -0
- package/dist/graph/WorkflowSettingsDialog.js +670 -0
- package/dist/graph/editorApi.js +8 -0
- package/dist/graph/editorChrome.js +75 -0
- package/dist/graph/editorFields.js +535 -0
- package/dist/graph/editorPrimitives.js +95 -4
- package/dist/graph/editorRows.d.ts +58 -0
- package/dist/graph/editorRows.js +460 -0
- package/dist/graph/editorStrings.d.ts +1 -0
- package/dist/graph/editorStrings.js +10 -1
- package/dist/graph/expressionRefs.js +39 -0
- package/dist/graph/fieldProblems.js +111 -0
- package/dist/graph/graphEditorState.d.ts +191 -0
- package/dist/graph/graphEditorState.js +74 -0
- package/dist/graph/index.d.ts +4 -0
- package/dist/graph/index.js +6 -2
- 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.js +388 -0
- package/dist/graph/inputRows.d.ts +43 -0
- package/dist/graph/inputRows.js +65 -0
- package/dist/graph/refusalText.js +13 -0
- package/dist/graph/settingsViews.js +62 -0
- package/dist/graph/stepWith.d.ts +38 -0
- package/dist/graph/stepWith.js +536 -0
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
import { useRepoSuggestions, useWorkflowActions, useWorkflowEditing, useWorkflowJsonResolver } from "../components/Provider.js";
|
|
2
|
+
import { IoMdClose } from "../icons/io/IoMdClose.js";
|
|
3
|
+
import { IconButton } from "../components/IconButton.js";
|
|
4
|
+
import { Input, Textarea } from "../components/Input.js";
|
|
5
|
+
import { useGraphEditorStrings, useInputsEditorStrings } from "./editorStrings.js";
|
|
6
|
+
import { asRecord } from "./records.js";
|
|
7
|
+
import { loadUsesInputs, splitAtRef } from "./usesInputs.js";
|
|
8
|
+
import { FixButtons, LintScope, useFieldLint, useFieldProblems } from "./fieldProblems.js";
|
|
9
|
+
import { SuggestionInput } from "./SuggestionInput.js";
|
|
10
|
+
import { ChoiceButtons, EXPRESSION, FieldError, FieldLabel, KeyValueEditor, LabelledField, StringListEditor, SuggestedInput, ToggleField, isScalar, labelled, parseJson, parseScalar, rowsError, rowsFrom, rowsTo, sameValue } from "./editorFields.js";
|
|
11
|
+
import { ValueOrInputField, refSuggestions } from "./inputRefs.js";
|
|
12
|
+
import { DynamicForm } from "../form/Form.js";
|
|
13
|
+
import { useEffect, useId, useState } from "react";
|
|
14
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
+
//#region src/graph/stepWith.tsx
|
|
16
|
+
var REPO_INPUTS = [{
|
|
17
|
+
key: "$yaml",
|
|
18
|
+
kind: "text",
|
|
19
|
+
label: "$yaml",
|
|
20
|
+
description: ""
|
|
21
|
+
}, {
|
|
22
|
+
key: "$thumbnail",
|
|
23
|
+
kind: "text",
|
|
24
|
+
label: "$thumbnail",
|
|
25
|
+
description: ""
|
|
26
|
+
}];
|
|
27
|
+
var GITLAB_INPUTS = [...REPO_INPUTS, {
|
|
28
|
+
key: "$host",
|
|
29
|
+
kind: "text",
|
|
30
|
+
label: "$host",
|
|
31
|
+
description: ""
|
|
32
|
+
}];
|
|
33
|
+
var allSpecs = (actions) => [...Object.values(actions).flatMap((action) => action.inputs), ...GITLAB_INPUTS];
|
|
34
|
+
/** Every `with` key the step dialog writes for `actions`, for a schema coverage test. */
|
|
35
|
+
function withFields(actions) {
|
|
36
|
+
return [...new Set(allSpecs(actions).flatMap((spec) => [spec.key, ...(spec.fields ?? []).map((field) => `${spec.key}.${field.key}`)]))];
|
|
37
|
+
}
|
|
38
|
+
var INPUT_KEY = /^[a-zA-Z0-9_-]+$/;
|
|
39
|
+
function usesKind(uses, actions) {
|
|
40
|
+
if (Object.hasOwn(actions, uses)) return "action";
|
|
41
|
+
if (uses.startsWith("github/")) return "github";
|
|
42
|
+
if (uses.startsWith("gitlab/")) return "gitlab";
|
|
43
|
+
return /^(marketplace|workflow)\//.test(uses) ? "subworkflow" : "other";
|
|
44
|
+
}
|
|
45
|
+
/** Named inputs for `uses`: an action's own, or a repository form's `$` keys. */
|
|
46
|
+
function specsFor(uses, actions) {
|
|
47
|
+
const kind = usesKind(uses, actions);
|
|
48
|
+
return kind === "action" ? actions[uses]?.inputs ?? [] : kind === "github" ? REPO_INPUTS : kind === "gitlab" ? GITLAB_INPUTS : [];
|
|
49
|
+
}
|
|
50
|
+
function textDraft(value) {
|
|
51
|
+
if (value === void 0 || value === null) return "";
|
|
52
|
+
return isScalar(value) ? String(value) : JSON.stringify(value);
|
|
53
|
+
}
|
|
54
|
+
function draftFor(spec, value) {
|
|
55
|
+
switch (spec.kind) {
|
|
56
|
+
case "list": return Array.isArray(value) ? value.map(textDraft) : [];
|
|
57
|
+
case "bool": return typeof value === "boolean" ? value : void 0;
|
|
58
|
+
case "flags": return rowsFrom(value);
|
|
59
|
+
case "target": {
|
|
60
|
+
const target = asRecord(value);
|
|
61
|
+
return Object.fromEntries((spec.fields ?? []).map((field) => [field.key, textDraft(target[field.key])]));
|
|
62
|
+
}
|
|
63
|
+
default: return textDraft(value);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function withDraftFrom(value, actions) {
|
|
67
|
+
const original = asRecord(value);
|
|
68
|
+
const values = {};
|
|
69
|
+
for (const spec of allSpecs(actions)) values[`${spec.kind}:${spec.key}`] = draftFor(spec, original[spec.key]);
|
|
70
|
+
return {
|
|
71
|
+
values,
|
|
72
|
+
rows: rowsFrom(Object.fromEntries(Object.entries(original).filter(([key]) => !key.startsWith("$")))),
|
|
73
|
+
removed: []
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
var slot = (spec) => `${spec.kind}:${spec.key}`;
|
|
77
|
+
function valueFor(spec, draft) {
|
|
78
|
+
switch (spec.kind) {
|
|
79
|
+
case "list": {
|
|
80
|
+
const items = draft.map((item) => item.trim()).filter(Boolean);
|
|
81
|
+
return items.length > 0 ? items : void 0;
|
|
82
|
+
}
|
|
83
|
+
case "bool": return draft;
|
|
84
|
+
case "flags": return rowsTo(draft, parseScalar);
|
|
85
|
+
case "target": {
|
|
86
|
+
const entries = Object.entries(draft).map(([key, item]) => [key, item.trim()]).filter(([, item]) => item);
|
|
87
|
+
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
|
|
88
|
+
}
|
|
89
|
+
case "number":
|
|
90
|
+
case "any": {
|
|
91
|
+
const raw = String(draft).trim();
|
|
92
|
+
return raw ? parseScalar(raw) : void 0;
|
|
93
|
+
}
|
|
94
|
+
case "textarea": return String(draft) ? String(draft) : void 0;
|
|
95
|
+
default: {
|
|
96
|
+
const raw = String(draft).trim();
|
|
97
|
+
return raw ? raw : void 0;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** Keys the step already has that `uses` doesn't take: any for an action, `$` ones otherwise. */
|
|
102
|
+
function extraKeys(uses, actions, original, draft) {
|
|
103
|
+
const kind = usesKind(uses, actions);
|
|
104
|
+
if (kind === "other") return [];
|
|
105
|
+
const known = new Set(specsFor(uses, actions).map((spec) => spec.key));
|
|
106
|
+
return Object.keys(asRecord(original)).filter((key) => !known.has(key) && !draft.removed.includes(key) && (kind === "action" || key.startsWith("$")));
|
|
107
|
+
}
|
|
108
|
+
/** The step's `with` for `uses`, or its original when nothing changed. */
|
|
109
|
+
function withValue(uses, actions, draft, initial, originalUses, original) {
|
|
110
|
+
if (uses === originalUses && sameValue(draft, initial)) return original;
|
|
111
|
+
const kind = usesKind(uses, actions);
|
|
112
|
+
const out = {};
|
|
113
|
+
for (const spec of specsFor(uses, actions)) {
|
|
114
|
+
const value = valueFor(spec, draft.values[slot(spec)]);
|
|
115
|
+
if (value !== void 0) out[spec.key] = value;
|
|
116
|
+
}
|
|
117
|
+
if (kind !== "action") Object.assign(out, rowsTo(draft.rows, parseScalar));
|
|
118
|
+
const kept = asRecord(original);
|
|
119
|
+
for (const key of extraKeys(uses, actions, original, draft)) out[key] = kept[key];
|
|
120
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
121
|
+
}
|
|
122
|
+
function withError(uses, actions, draft, original, t) {
|
|
123
|
+
const kind = usesKind(uses, actions);
|
|
124
|
+
if (extraKeys(uses, actions, original, draft).length > 0) return t.notActionInput;
|
|
125
|
+
for (const spec of specsFor(uses, actions)) {
|
|
126
|
+
const value = valueFor(spec, draft.values[slot(spec)]);
|
|
127
|
+
if (spec.required && value === void 0) return t.missingInput(spec.key);
|
|
128
|
+
if (spec.kind === "number" && value !== void 0 && typeof value !== "number") return t.invalidNumberInput;
|
|
129
|
+
if (spec.kind === "choice" && typeof value === "string" && !spec.choices?.includes(value) && !(spec.expression && EXPRESSION.test(value))) return t.invalidChoice(spec.key);
|
|
130
|
+
if (spec.kind === "flags") {
|
|
131
|
+
const error = rowsError(draft.values[slot(spec)], INPUT_KEY, t.invalidKey, t);
|
|
132
|
+
if (error) return error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return kind === "action" ? void 0 : rowsError(draft.rows, kind === "other" ? /^\$?[a-zA-Z0-9_-]+$/ : INPUT_KEY, t.invalidKey, t);
|
|
136
|
+
}
|
|
137
|
+
function SpecField({ spec, draft, onChange, references, source }) {
|
|
138
|
+
const t = useGraphEditorStrings();
|
|
139
|
+
const { description, label } = spec;
|
|
140
|
+
if (spec.refTypes) return /* @__PURE__ */ jsx(ValueOrInputField, {
|
|
141
|
+
label,
|
|
142
|
+
yamlKey: spec.key,
|
|
143
|
+
description,
|
|
144
|
+
value: String(draft),
|
|
145
|
+
onChange,
|
|
146
|
+
source,
|
|
147
|
+
types: spec.refTypes,
|
|
148
|
+
...spec.suffix ? { suffix: spec.suffix } : {},
|
|
149
|
+
suggestions: spec.choices ?? []
|
|
150
|
+
});
|
|
151
|
+
switch (spec.kind) {
|
|
152
|
+
case "list": return /* @__PURE__ */ jsx(LabelledField, {
|
|
153
|
+
label,
|
|
154
|
+
yamlKey: spec.key,
|
|
155
|
+
description,
|
|
156
|
+
children: /* @__PURE__ */ jsx(StringListEditor, {
|
|
157
|
+
label,
|
|
158
|
+
values: draft,
|
|
159
|
+
onChange,
|
|
160
|
+
addLabel: t.addRow
|
|
161
|
+
})
|
|
162
|
+
});
|
|
163
|
+
case "bool": return /* @__PURE__ */ jsx(ToggleField, {
|
|
164
|
+
label,
|
|
165
|
+
yamlKey: spec.key,
|
|
166
|
+
description,
|
|
167
|
+
checked: typeof draft === "boolean" ? draft : spec.fallback ?? false,
|
|
168
|
+
onChange: (on) => onChange(on === (spec.fallback ?? false) ? void 0 : on)
|
|
169
|
+
});
|
|
170
|
+
case "flags": return /* @__PURE__ */ jsx(LabelledField, {
|
|
171
|
+
label,
|
|
172
|
+
yamlKey: spec.key,
|
|
173
|
+
description,
|
|
174
|
+
children: /* @__PURE__ */ jsx(KeyValueEditor, {
|
|
175
|
+
rows: draft,
|
|
176
|
+
onChange,
|
|
177
|
+
error: void 0,
|
|
178
|
+
valueSuggestions: references
|
|
179
|
+
})
|
|
180
|
+
});
|
|
181
|
+
case "target": {
|
|
182
|
+
const target = draft;
|
|
183
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
184
|
+
className: "flex flex-col gap-2",
|
|
185
|
+
children: [/* @__PURE__ */ jsx(FieldLabel, {
|
|
186
|
+
label,
|
|
187
|
+
yamlKey: spec.key,
|
|
188
|
+
description
|
|
189
|
+
}), (spec.fields ?? []).map((field) => /* @__PURE__ */ jsx(Input, {
|
|
190
|
+
mono: true,
|
|
191
|
+
...labelled(field.label, field.key),
|
|
192
|
+
description: field.description,
|
|
193
|
+
value: target[field.key] ?? "",
|
|
194
|
+
onChange: (e) => onChange({
|
|
195
|
+
...target,
|
|
196
|
+
[field.key]: e.target.value
|
|
197
|
+
})
|
|
198
|
+
}, field.key))]
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
case "textarea": return /* @__PURE__ */ jsx(Textarea, {
|
|
202
|
+
mono: true,
|
|
203
|
+
...labelled(label, spec.key),
|
|
204
|
+
description,
|
|
205
|
+
rows: 4,
|
|
206
|
+
value: String(draft),
|
|
207
|
+
onChange: (e) => onChange(e.target.value)
|
|
208
|
+
});
|
|
209
|
+
default: return /* @__PURE__ */ jsx(SuggestedInput, {
|
|
210
|
+
label,
|
|
211
|
+
yamlKey: spec.key,
|
|
212
|
+
description,
|
|
213
|
+
value: String(draft),
|
|
214
|
+
onChange,
|
|
215
|
+
suggestions: spec.choices ?? references
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/** Where a repository `uses` reads its workflow from, for the repository fields. */
|
|
220
|
+
function repositoryOf(uses, host) {
|
|
221
|
+
if (!uses.startsWith("github/") && !uses.startsWith("gitlab/")) return {
|
|
222
|
+
repo: "",
|
|
223
|
+
branch: ""
|
|
224
|
+
};
|
|
225
|
+
const [path, ref] = splitAtRef(uses.slice(uses.indexOf("/") + 1));
|
|
226
|
+
const server = uses.startsWith("github/") ? "github.com" : host || "gitlab.com";
|
|
227
|
+
return {
|
|
228
|
+
repo: path ? `https://${server}/${path}` : "",
|
|
229
|
+
branch: ref
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/** The `uses` for a repository URL and branch, plus the $host a self-hosted server needs. */
|
|
233
|
+
function usesOfRepository(repo, branch) {
|
|
234
|
+
let url;
|
|
235
|
+
try {
|
|
236
|
+
url = new URL(repo.includes("://") ? repo : `https://${repo}`);
|
|
237
|
+
} catch {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
const path = url.pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "");
|
|
241
|
+
if (!path) return null;
|
|
242
|
+
const ref = branch.trim() ? `@${branch.trim()}` : "";
|
|
243
|
+
return url.host === "github.com" ? {
|
|
244
|
+
uses: `github/${path}${ref}`,
|
|
245
|
+
host: ""
|
|
246
|
+
} : {
|
|
247
|
+
uses: `gitlab/${path}${ref}`,
|
|
248
|
+
host: url.host === "gitlab.com" ? "" : url.host
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
/** The repository fields the workflow import form uses, so branches and files come from the repository's host. */
|
|
252
|
+
function RepositoryForm({ uses, draft, onUses, onDraft }) {
|
|
253
|
+
const { readme: _readme, ...fields } = useWorkflowEditing().WORKFLOW_FORMS.github;
|
|
254
|
+
const host = String(draft.values["text:$host"] ?? "");
|
|
255
|
+
const [initial] = useState(() => ({
|
|
256
|
+
...repositoryOf(uses, host),
|
|
257
|
+
yaml: String(draft.values["text:$yaml"] ?? "") || "workflow.yaml",
|
|
258
|
+
thumbnail: String(draft.values["text:$thumbnail"] ?? "")
|
|
259
|
+
}));
|
|
260
|
+
return /* @__PURE__ */ jsx(DynamicForm, {
|
|
261
|
+
formJSONs: fields,
|
|
262
|
+
initialValues: initial,
|
|
263
|
+
labelPosition: "top",
|
|
264
|
+
workflowForm: true,
|
|
265
|
+
setValues: (values) => {
|
|
266
|
+
if ([
|
|
267
|
+
"repo",
|
|
268
|
+
"branch",
|
|
269
|
+
"yaml",
|
|
270
|
+
"thumbnail"
|
|
271
|
+
].every((key) => String(values[key] ?? "") === initial[key])) return;
|
|
272
|
+
const next = usesOfRepository(String(values["repo"] ?? ""), String(values["branch"] ?? ""));
|
|
273
|
+
if (next) onUses(next.uses);
|
|
274
|
+
onDraft({
|
|
275
|
+
...draft,
|
|
276
|
+
values: {
|
|
277
|
+
...draft.values,
|
|
278
|
+
"text:$yaml": values["yaml"] === "workflow.yaml" ? "" : String(values["yaml"] ?? ""),
|
|
279
|
+
"text:$thumbnail": String(values["thumbnail"] ?? ""),
|
|
280
|
+
"text:$host": next?.host ?? host
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
function usesMode(uses, actions) {
|
|
287
|
+
const kind = usesKind(uses, actions);
|
|
288
|
+
if (kind === "action") return "action";
|
|
289
|
+
if (kind === "github" || kind === "gitlab") return "repository";
|
|
290
|
+
return uses.startsWith("marketplace/") ? "marketplace" : "workflow";
|
|
291
|
+
}
|
|
292
|
+
/** What a step uses: a built-in action, a workflow of yours, one from the marketplace, or one in a repository. */
|
|
293
|
+
function UsesPicker({ uses, onUses, choices, draft, onDraft, error }) {
|
|
294
|
+
const t = useGraphEditorStrings();
|
|
295
|
+
const actions = useWorkflowActions();
|
|
296
|
+
const id = useId();
|
|
297
|
+
const lint = useFieldLint("uses", id, uses, onUses);
|
|
298
|
+
const [mode, setMode] = useState(() => usesMode(uses, actions));
|
|
299
|
+
const options = (prefix) => choices.filter((choice) => choice.startsWith(prefix));
|
|
300
|
+
const select = (values, placeholder, labels) => /* @__PURE__ */ jsx(SuggestionInput, {
|
|
301
|
+
id,
|
|
302
|
+
ariaLabel: t.fields.uses,
|
|
303
|
+
value: uses,
|
|
304
|
+
placeholder,
|
|
305
|
+
suggestions: values.map((value) => ({
|
|
306
|
+
value,
|
|
307
|
+
label: labels?.(value)
|
|
308
|
+
})),
|
|
309
|
+
onChange: onUses
|
|
310
|
+
});
|
|
311
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
312
|
+
className: "flex flex-col gap-2",
|
|
313
|
+
children: [
|
|
314
|
+
/* @__PURE__ */ jsx(FieldLabel, {
|
|
315
|
+
label: t.fields.uses,
|
|
316
|
+
yamlKey: "uses",
|
|
317
|
+
description: t.help.uses
|
|
318
|
+
}),
|
|
319
|
+
/* @__PURE__ */ jsx(ChoiceButtons, {
|
|
320
|
+
options: [
|
|
321
|
+
{
|
|
322
|
+
value: "action",
|
|
323
|
+
label: t.usesAction
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
value: "workflow",
|
|
327
|
+
label: t.usesWorkflow
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
value: "marketplace",
|
|
331
|
+
label: t.usesMarketplace
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
value: "repository",
|
|
335
|
+
label: t.usesRepository
|
|
336
|
+
}
|
|
337
|
+
],
|
|
338
|
+
value: mode,
|
|
339
|
+
onChange: (next) => {
|
|
340
|
+
setMode(next);
|
|
341
|
+
if (next !== usesMode(uses, actions)) onUses("");
|
|
342
|
+
}
|
|
343
|
+
}),
|
|
344
|
+
mode === "action" ? select(Object.keys(actions), t.chooseAction, (value) => actions[value]?.description) : mode === "workflow" ? select(options("workflow/"), t.chooseWorkflow) : mode === "marketplace" ? select(options("marketplace/"), t.chooseMarketplace) : /* @__PURE__ */ jsx(RepositoryForm, {
|
|
345
|
+
uses,
|
|
346
|
+
draft,
|
|
347
|
+
onUses,
|
|
348
|
+
onDraft
|
|
349
|
+
}),
|
|
350
|
+
/* @__PURE__ */ jsx(FieldError, { message: error ?? lint.message })
|
|
351
|
+
]
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
/** The inputs of the workflow a step uses, fetched the way the YAML editor's completions are. */
|
|
355
|
+
function useSubworkflowInputs(uses, draft) {
|
|
356
|
+
const editing = useWorkflowEditing();
|
|
357
|
+
const resolve = useWorkflowJsonResolver();
|
|
358
|
+
const repos = useRepoSuggestions();
|
|
359
|
+
const [inputs, setInputs] = useState(null);
|
|
360
|
+
const yamlPath = String(draft.values["text:$yaml"] ?? "") || "workflow.yaml";
|
|
361
|
+
const host = String(draft.values["text:$host"] ?? "") || "gitlab.com";
|
|
362
|
+
useEffect(() => {
|
|
363
|
+
let live = true;
|
|
364
|
+
setInputs(null);
|
|
365
|
+
const timer = window.setTimeout(() => {
|
|
366
|
+
loadUsesInputs(editing, uses, {
|
|
367
|
+
yamlPath,
|
|
368
|
+
host
|
|
369
|
+
}, {
|
|
370
|
+
resolve,
|
|
371
|
+
repos
|
|
372
|
+
}).then((loaded) => {
|
|
373
|
+
if (live) setInputs(loaded);
|
|
374
|
+
}).catch(() => {
|
|
375
|
+
if (live) setInputs(null);
|
|
376
|
+
});
|
|
377
|
+
}, 300);
|
|
378
|
+
return () => {
|
|
379
|
+
live = false;
|
|
380
|
+
window.clearTimeout(timer);
|
|
381
|
+
};
|
|
382
|
+
}, [
|
|
383
|
+
editing,
|
|
384
|
+
uses,
|
|
385
|
+
resolve,
|
|
386
|
+
repos,
|
|
387
|
+
yamlPath,
|
|
388
|
+
host
|
|
389
|
+
]);
|
|
390
|
+
return inputs;
|
|
391
|
+
}
|
|
392
|
+
/** The step's `with`: an action's own inputs, or a subworkflow's by name. */
|
|
393
|
+
function WithEditor({ uses, draft, original, onChange, error, source }) {
|
|
394
|
+
const t = useGraphEditorStrings();
|
|
395
|
+
const inputsEditor = useInputsEditorStrings();
|
|
396
|
+
const actions = useWorkflowActions();
|
|
397
|
+
const typeNames = inputsEditor.types;
|
|
398
|
+
const kind = usesKind(uses, actions);
|
|
399
|
+
const extras = extraKeys(uses, actions, original, draft);
|
|
400
|
+
const references = refSuggestions(source);
|
|
401
|
+
const subworkflow = useSubworkflowInputs(uses, draft);
|
|
402
|
+
const known = subworkflow ? Object.entries(subworkflow).filter(([key]) => key !== "$meta") : [];
|
|
403
|
+
const knownKeys = new Set(known.map(([key]) => key));
|
|
404
|
+
const setRow = (key, value) => {
|
|
405
|
+
const nested = /^[[{]/.test(value.trim()) && parseJson(value).ok;
|
|
406
|
+
const rows = value === "" ? draft.rows.filter((row) => row.key !== key) : draft.rows.some((row) => row.key === key) ? draft.rows.map((row) => row.key === key ? {
|
|
407
|
+
...row,
|
|
408
|
+
value,
|
|
409
|
+
nested
|
|
410
|
+
} : row) : [...draft.rows, {
|
|
411
|
+
key,
|
|
412
|
+
value,
|
|
413
|
+
nested
|
|
414
|
+
}];
|
|
415
|
+
onChange({
|
|
416
|
+
...draft,
|
|
417
|
+
rows
|
|
418
|
+
});
|
|
419
|
+
};
|
|
420
|
+
return /* @__PURE__ */ jsx(LintScope, {
|
|
421
|
+
at: "with",
|
|
422
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
423
|
+
className: "flex flex-col gap-3",
|
|
424
|
+
children: [
|
|
425
|
+
/* @__PURE__ */ jsx(FieldLabel, {
|
|
426
|
+
label: t.fields.with,
|
|
427
|
+
yamlKey: "with",
|
|
428
|
+
description: t.help.with
|
|
429
|
+
}),
|
|
430
|
+
kind === "action" && specsFor(uses, actions).map((spec) => /* @__PURE__ */ jsx(SpecField, {
|
|
431
|
+
spec,
|
|
432
|
+
draft: draft.values[slot(spec)],
|
|
433
|
+
references,
|
|
434
|
+
source,
|
|
435
|
+
onChange: (value) => onChange({
|
|
436
|
+
...draft,
|
|
437
|
+
values: {
|
|
438
|
+
...draft.values,
|
|
439
|
+
[slot(spec)]: value
|
|
440
|
+
}
|
|
441
|
+
})
|
|
442
|
+
}, slot(spec))),
|
|
443
|
+
extras.map((key) => /* @__PURE__ */ jsx(ExtraKey, {
|
|
444
|
+
name: key,
|
|
445
|
+
uses,
|
|
446
|
+
draft,
|
|
447
|
+
original,
|
|
448
|
+
onChange
|
|
449
|
+
}, key)),
|
|
450
|
+
kind !== "action" && /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
451
|
+
known.map(([key, raw]) => {
|
|
452
|
+
const definition = asRecord(raw);
|
|
453
|
+
const label = String(definition["label"] ?? "");
|
|
454
|
+
const about = String(definition["description"] ?? "");
|
|
455
|
+
const type = String(definition["type"] ?? "");
|
|
456
|
+
return /* @__PURE__ */ jsx(SuggestedInput, {
|
|
457
|
+
label: label || key,
|
|
458
|
+
yamlKey: key,
|
|
459
|
+
description: about || (typeNames[type] ?? type),
|
|
460
|
+
value: draft.rows.find((row) => row.key === key)?.value ?? "",
|
|
461
|
+
suggestions: references,
|
|
462
|
+
onChange: (value) => setRow(key, value)
|
|
463
|
+
}, key);
|
|
464
|
+
}),
|
|
465
|
+
known.length > 0 && /* @__PURE__ */ jsx(FieldLabel, {
|
|
466
|
+
label: t.otherInputs,
|
|
467
|
+
description: t.help.withInputs
|
|
468
|
+
}),
|
|
469
|
+
/* @__PURE__ */ jsx(KeyValueEditor, {
|
|
470
|
+
rows: draft.rows.filter((row) => !knownKeys.has(row.key)),
|
|
471
|
+
onChange: (rows) => onChange({
|
|
472
|
+
...draft,
|
|
473
|
+
rows: [...draft.rows.filter((row) => knownKeys.has(row.key)), ...rows]
|
|
474
|
+
}),
|
|
475
|
+
error: void 0,
|
|
476
|
+
valueSuggestions: references,
|
|
477
|
+
lint: true
|
|
478
|
+
})
|
|
479
|
+
] }),
|
|
480
|
+
/* @__PURE__ */ jsx(FieldError, { message: error })
|
|
481
|
+
]
|
|
482
|
+
})
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
/** A key an action doesn't take, with the input it was likely meant as when the linter knows. */
|
|
486
|
+
function ExtraKey({ name, uses, draft, original, onChange }) {
|
|
487
|
+
const t = useGraphEditorStrings();
|
|
488
|
+
const actions = useWorkflowActions();
|
|
489
|
+
const id = useId();
|
|
490
|
+
const fixes = useFieldProblems(name, id).flatMap(({ fix }) => fix?.key && fix.find === name ? [fix] : []);
|
|
491
|
+
const remove = () => onChange({
|
|
492
|
+
...draft,
|
|
493
|
+
removed: [...draft.removed, name]
|
|
494
|
+
});
|
|
495
|
+
const rename = (fix) => {
|
|
496
|
+
const spec = specsFor(uses, actions).find((spec) => spec.key === fix.replace);
|
|
497
|
+
if (spec) onChange({
|
|
498
|
+
...draft,
|
|
499
|
+
values: {
|
|
500
|
+
...draft.values,
|
|
501
|
+
[slot(spec)]: draftFor(spec, asRecord(original)[name])
|
|
502
|
+
},
|
|
503
|
+
removed: [...draft.removed, name]
|
|
504
|
+
});
|
|
505
|
+
};
|
|
506
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
507
|
+
id,
|
|
508
|
+
tabIndex: -1,
|
|
509
|
+
className: "flex flex-col gap-1",
|
|
510
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
511
|
+
className: "flex items-center gap-2 text-sm",
|
|
512
|
+
children: [
|
|
513
|
+
/* @__PURE__ */ jsx("span", {
|
|
514
|
+
className: "font-mono",
|
|
515
|
+
children: name
|
|
516
|
+
}),
|
|
517
|
+
/* @__PURE__ */ jsx("span", {
|
|
518
|
+
className: "text-xs text-(--theme-error)",
|
|
519
|
+
children: t.notActionInput
|
|
520
|
+
}),
|
|
521
|
+
/* @__PURE__ */ jsx(IconButton, {
|
|
522
|
+
icon: /* @__PURE__ */ jsx(IoMdClose, { className: "h-4 w-4" }),
|
|
523
|
+
label: t.removeRow,
|
|
524
|
+
variant: "ghost",
|
|
525
|
+
size: "sm",
|
|
526
|
+
onClick: remove
|
|
527
|
+
})
|
|
528
|
+
]
|
|
529
|
+
}), /* @__PURE__ */ jsx(FixButtons, {
|
|
530
|
+
fixes,
|
|
531
|
+
onFix: rename
|
|
532
|
+
})]
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
//#endregion
|
|
536
|
+
export { UsesPicker, WithEditor, usesKind, withDraftFrom, withError, withFields, withValue };
|