@backstage/plugin-scaffolder 1.17.1 → 1.18.0-next.1

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.
@@ -1,876 +0,0 @@
1
- import React, { useCallback, useState, useMemo, Component } from 'react';
2
- import { Link, useNavigate, Navigate, useOutlet, Routes, Route } from 'react-router-dom';
3
- import { useTemplateSecrets, scaffolderApiRef, useCustomFieldExtensions, useCustomLayouts, SecretsContextProvider } from '@backstage/plugin-scaffolder-react';
4
- import { u as useDryRun, a as useDirectoryEditor, D as DirectoryEditorProvider, b as DryRunProvider, T as TemplateEditorBrowser, c as TemplateEditorTextArea, d as DryRunResults, e as TemplateEditorIntro, W as WebFileSystemAccess, f as DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS, A as ActionsPage, L as ListTasksPage } from './DryRunResults-c0cd5eb5.esm.js';
5
- import { u as registerComponentRouteRef, p as editRouteRef, q as actionsRouteRef, t as scaffolderListTaskRouteRef, v as viewTechDocRouteRef, o as selectedTemplateRouteRef, r as rootRouteRef, w as scaffolderTaskRouteRef, e as OngoingTask } from './OngoingTask-87c6761c.esm.js';
6
- import { DocsIcon, Page, Header, Content, ContentHeader, SupportButton, ErrorPage } from '@backstage/core-components';
7
- import { useRouteRef, useApp, useApi, useRouteRefParams, AnalyticsContext, useApiHolder, alertApiRef } from '@backstage/core-plugin-api';
8
- import { EntityListProvider, CatalogFilterLayout, EntitySearchBar, EntityKindPicker, UserListPicker, EntityTagPicker, catalogApiRef, humanizeEntityRef } from '@backstage/plugin-catalog-react';
9
- import { ScaffolderPageContextMenu, TemplateCategoryPicker, TemplateGroups, Workflow, createAsyncValidators, Stepper, Form } from '@backstage/plugin-scaffolder-react/alpha';
10
- import Button from '@material-ui/core/Button';
11
- import IconButton from '@material-ui/core/IconButton';
12
- import useMediaQuery from '@material-ui/core/useMediaQuery';
13
- import AddCircleOutline from '@material-ui/icons/AddCircleOutline';
14
- import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha';
15
- import { usePermission } from '@backstage/plugin-permission-react';
16
- import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model';
17
- import { StreamLanguage } from '@codemirror/language';
18
- import { yaml as yaml$1 } from '@codemirror/legacy-modes/mode/yaml';
19
- import { makeStyles as makeStyles$1, FormControl, InputLabel, Select, MenuItem, IconButton as IconButton$1, Card, CardHeader, CardContent, Button as Button$1, LinearProgress } from '@material-ui/core';
20
- import CloseIcon from '@material-ui/icons/Close';
21
- import CodeMirror from '@uiw/react-codemirror';
22
- import yaml from 'yaml';
23
- import { makeStyles } from '@material-ui/core/styles';
24
- import useDebounce from 'react-use/lib/useDebounce';
25
- import validator from '@rjsf/validator-ajv8';
26
- import useAsync from 'react-use/lib/useAsync';
27
- import 'classnames';
28
- import '@material-ui/icons/ExpandMore';
29
- import '@material-ui/icons/ExpandLess';
30
- import '@material-ui/core/Chip';
31
- import '@material-ui/icons/Settings';
32
- import '@material-ui/icons/FontDownload';
33
- import 'luxon';
34
- import 'humanize-duration';
35
- import '@material-ui/core/Typography';
36
- import '@material-ui/core/Card';
37
- import '@material-ui/core/CardActionArea';
38
- import '@material-ui/core/CardContent';
39
- import '@material-ui/core/Tooltip';
40
- import '@material-ui/icons/InfoOutlined';
41
- import '@material-ui/icons/Refresh';
42
- import '@material-ui/icons/Save';
43
- import '@codemirror/view';
44
- import '@react-hookz/web';
45
- import '@material-ui/core/Accordion';
46
- import '@material-ui/core/AccordionDetails';
47
- import '@material-ui/core/AccordionSummary';
48
- import '@material-ui/core/Divider';
49
- import '@material-ui/core/List';
50
- import '@material-ui/core/ListItem';
51
- import '@material-ui/core/ListItemIcon';
52
- import '@material-ui/core/ListItemSecondaryAction';
53
- import '@material-ui/core/ListItemText';
54
- import '@material-ui/icons/Cancel';
55
- import '@material-ui/icons/Check';
56
- import '@material-ui/icons/Delete';
57
- import '@material-ui/icons/GetApp';
58
- import '@material-ui/core/Box';
59
- import '@material-ui/core/Tab';
60
- import '@material-ui/core/Tabs';
61
- import '@material-ui/lab/TreeView';
62
- import '@material-ui/icons/ChevronRight';
63
- import '@material-ui/lab/TreeItem';
64
- import '@material-ui/icons/Language';
65
- import '@material-ui/core/Grid';
66
- import '@material-ui/core/Step';
67
- import '@material-ui/core/StepLabel';
68
- import '@material-ui/core/Stepper';
69
- import '@material-ui/icons/FiberManualRecord';
70
- import 'qs';
71
- import 'react-use/lib/useInterval';
72
- import '@backstage/integration-react';
73
- import '@backstage/errors';
74
- import 'zen-observable';
75
- import 'event-source-polyfill';
76
- import '@backstage/catalog-client';
77
- import '@material-ui/core/FormControl';
78
- import '@material-ui/lab/Autocomplete';
79
- import 'zod';
80
- import 'zod-to-json-schema';
81
- import '@material-ui/core/FormHelperText';
82
- import '@material-ui/core/Input';
83
- import '@material-ui/core/InputLabel';
84
- import 'react-use/lib/useEffectOnce';
85
- import '@material-ui/lab';
86
- import 'lodash/capitalize';
87
- import '@material-ui/icons/CheckBox';
88
- import '@material-ui/icons/CheckBoxOutlineBlank';
89
- import '@material-ui/icons/Repeat';
90
- import '@material-ui/icons/Toc';
91
- import '@material-ui/icons/ControlPoint';
92
- import '@material-ui/icons/MoreVert';
93
-
94
- const RegisterExistingButton = (props) => {
95
- const { title, to } = props;
96
- const { allowed } = usePermission({
97
- permission: catalogEntityCreatePermission
98
- });
99
- const isXSScreen = useMediaQuery(
100
- (theme) => theme.breakpoints.down("xs")
101
- );
102
- if (!to || !allowed) {
103
- return null;
104
- }
105
- return isXSScreen ? /* @__PURE__ */ React.createElement(
106
- IconButton,
107
- {
108
- component: Link,
109
- color: "primary",
110
- title,
111
- size: "small",
112
- to
113
- },
114
- /* @__PURE__ */ React.createElement(AddCircleOutline, null)
115
- ) : /* @__PURE__ */ React.createElement(Button, { component: Link, variant: "contained", color: "primary", to }, title);
116
- };
117
-
118
- const defaultGroup = {
119
- title: "Templates",
120
- filter: () => true
121
- };
122
- const createGroupsWithOther = (groups) => [
123
- ...groups,
124
- {
125
- title: "Other Templates",
126
- filter: (e) => ![...groups].some(({ filter }) => filter(e))
127
- }
128
- ];
129
- const TemplateListPage = (props) => {
130
- var _a, _b, _c;
131
- const registerComponentLink = useRouteRef(registerComponentRouteRef);
132
- const {
133
- TemplateCardComponent,
134
- groups: givenGroups = [],
135
- templateFilter,
136
- headerOptions
137
- } = props;
138
- const navigate = useNavigate();
139
- const editorLink = useRouteRef(editRouteRef);
140
- const actionsLink = useRouteRef(actionsRouteRef);
141
- const tasksLink = useRouteRef(scaffolderListTaskRouteRef);
142
- const viewTechDocsLink = useRouteRef(viewTechDocRouteRef);
143
- const templateRoute = useRouteRef(selectedTemplateRouteRef);
144
- const app = useApp();
145
- const groups = givenGroups.length ? createGroupsWithOther(givenGroups) : [defaultGroup];
146
- const scaffolderPageContextMenuProps = {
147
- onEditorClicked: ((_a = props == null ? void 0 : props.contextMenu) == null ? void 0 : _a.editor) !== false ? () => navigate(editorLink()) : void 0,
148
- onActionsClicked: ((_b = props == null ? void 0 : props.contextMenu) == null ? void 0 : _b.actions) !== false ? () => navigate(actionsLink()) : void 0,
149
- onTasksClicked: ((_c = props == null ? void 0 : props.contextMenu) == null ? void 0 : _c.tasks) !== false ? () => navigate(tasksLink()) : void 0
150
- };
151
- const additionalLinksForEntity = useCallback(
152
- (template) => {
153
- var _a2, _b2;
154
- const { kind, namespace, name } = parseEntityRef(
155
- stringifyEntityRef(template)
156
- );
157
- return ((_a2 = template.metadata.annotations) == null ? void 0 : _a2["backstage.io/techdocs-ref"]) && viewTechDocsLink ? [
158
- {
159
- icon: (_b2 = app.getSystemIcon("docs")) != null ? _b2 : DocsIcon,
160
- text: "View TechDocs",
161
- url: viewTechDocsLink({ kind, namespace, name })
162
- }
163
- ] : [];
164
- },
165
- [app, viewTechDocsLink]
166
- );
167
- const onTemplateSelected = useCallback(
168
- (template) => {
169
- const { namespace, name } = parseEntityRef(stringifyEntityRef(template));
170
- navigate(templateRoute({ namespace, templateName: name }));
171
- },
172
- [navigate, templateRoute]
173
- );
174
- return /* @__PURE__ */ React.createElement(EntityListProvider, null, /* @__PURE__ */ React.createElement(Page, { themeId: "home" }, /* @__PURE__ */ React.createElement(
175
- Header,
176
- {
177
- pageTitleOverride: "Create a new component",
178
- title: "Create a new component",
179
- subtitle: "Create new software components using standard templates in your organization",
180
- ...headerOptions
181
- },
182
- /* @__PURE__ */ React.createElement(ScaffolderPageContextMenu, { ...scaffolderPageContextMenuProps })
183
- ), /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ContentHeader, { title: "Available Templates" }, /* @__PURE__ */ React.createElement(
184
- RegisterExistingButton,
185
- {
186
- title: "Register Existing Component",
187
- to: registerComponentLink && registerComponentLink()
188
- }
189
- ), /* @__PURE__ */ React.createElement(SupportButton, null, "Create new software components using standard templates. Different templates create different kinds of components (services, websites, documentation, ...).")), /* @__PURE__ */ React.createElement(CatalogFilterLayout, null, /* @__PURE__ */ React.createElement(CatalogFilterLayout.Filters, null, /* @__PURE__ */ React.createElement(EntitySearchBar, null), /* @__PURE__ */ React.createElement(EntityKindPicker, { initialFilter: "template", hidden: true }), /* @__PURE__ */ React.createElement(
190
- UserListPicker,
191
- {
192
- initialFilter: "all",
193
- availableFilters: ["all", "starred"]
194
- }
195
- ), /* @__PURE__ */ React.createElement(TemplateCategoryPicker, null), /* @__PURE__ */ React.createElement(EntityTagPicker, null)), /* @__PURE__ */ React.createElement(CatalogFilterLayout.Content, null, /* @__PURE__ */ React.createElement(
196
- TemplateGroups,
197
- {
198
- groups,
199
- templateFilter,
200
- TemplateCardComponent,
201
- onTemplateSelected,
202
- additionalLinksForEntity
203
- }
204
- ))))));
205
- };
206
-
207
- const TemplateWizardPage = (props) => {
208
- const rootRef = useRouteRef(rootRouteRef);
209
- const taskRoute = useRouteRef(scaffolderTaskRouteRef);
210
- const { secrets } = useTemplateSecrets();
211
- const scaffolderApi = useApi(scaffolderApiRef);
212
- const navigate = useNavigate();
213
- const { templateName, namespace } = useRouteRefParams(
214
- selectedTemplateRouteRef
215
- );
216
- const templateRef = stringifyEntityRef({
217
- kind: "Template",
218
- namespace,
219
- name: templateName
220
- });
221
- const onCreate = async (values) => {
222
- const { taskId } = await scaffolderApi.scaffold({
223
- templateRef,
224
- values,
225
- secrets
226
- });
227
- navigate(taskRoute({ taskId }));
228
- };
229
- const onError = () => /* @__PURE__ */ React.createElement(Navigate, { to: rootRef() });
230
- return /* @__PURE__ */ React.createElement(AnalyticsContext, { attributes: { entityRef: templateRef } }, /* @__PURE__ */ React.createElement(Page, { themeId: "website" }, /* @__PURE__ */ React.createElement(
231
- Header,
232
- {
233
- pageTitleOverride: "Create a new component",
234
- title: "Create a new component",
235
- subtitle: "Create new software components using standard templates in your organization",
236
- ...props.headerOptions
237
- }
238
- ), /* @__PURE__ */ React.createElement(
239
- Workflow,
240
- {
241
- namespace,
242
- templateName,
243
- onCreate,
244
- components: props.components,
245
- onError,
246
- extensions: props.customFieldExtensions,
247
- formProps: props.formProps,
248
- layouts: props.layouts
249
- }
250
- )));
251
- };
252
-
253
- var __defProp = Object.defineProperty;
254
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
255
- var __publicField = (obj, key, value) => {
256
- __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
257
- return value;
258
- };
259
- const useStyles$3 = makeStyles({
260
- containerWrapper: {
261
- position: "relative",
262
- width: "100%",
263
- height: "100%"
264
- },
265
- container: {
266
- position: "absolute",
267
- top: 0,
268
- bottom: 0,
269
- left: 0,
270
- right: 0,
271
- overflow: "auto"
272
- }
273
- });
274
- class ErrorBoundary extends Component {
275
- constructor() {
276
- super(...arguments);
277
- __publicField(this, "state", {
278
- shouldRender: true
279
- });
280
- }
281
- componentDidUpdate(prevProps) {
282
- if (prevProps.invalidator !== this.props.invalidator) {
283
- this.setState({ shouldRender: true });
284
- }
285
- }
286
- componentDidCatch(error) {
287
- this.props.setErrorText(error.message);
288
- this.setState({ shouldRender: false });
289
- }
290
- render() {
291
- return this.state.shouldRender ? this.props.children : null;
292
- }
293
- }
294
- function isJsonObject(value) {
295
- return typeof value === "object" && value !== null && !Array.isArray(value);
296
- }
297
- function TemplateEditorForm(props) {
298
- const {
299
- content,
300
- contentIsSpec,
301
- onDryRun,
302
- setErrorText,
303
- fieldExtensions = [],
304
- layouts = []
305
- } = props;
306
- const classes = useStyles$3();
307
- const apiHolder = useApiHolder();
308
- const [steps, setSteps] = useState();
309
- const fields = useMemo(() => {
310
- return Object.fromEntries(
311
- fieldExtensions.map(({ name, component }) => [name, component])
312
- );
313
- }, [fieldExtensions]);
314
- useDebounce(
315
- () => {
316
- try {
317
- if (!content) {
318
- setSteps(void 0);
319
- return;
320
- }
321
- const parsed = yaml.parseAllDocuments(content).filter((c) => c).map((c) => c.toJSON())[0];
322
- if (!isJsonObject(parsed)) {
323
- setSteps(void 0);
324
- return;
325
- }
326
- let rootObj = parsed;
327
- if (!contentIsSpec) {
328
- const isTemplate = String(parsed.kind).toLocaleLowerCase("en-US") === "template";
329
- if (!isTemplate) {
330
- setSteps(void 0);
331
- return;
332
- }
333
- rootObj = isJsonObject(parsed.spec) ? parsed.spec : {};
334
- }
335
- const { parameters } = rootObj;
336
- if (!Array.isArray(parameters)) {
337
- setErrorText("Template parameters must be an array");
338
- setSteps(void 0);
339
- return;
340
- }
341
- const fieldValidators = Object.fromEntries(
342
- fieldExtensions.map(({ name, validation }) => [name, validation])
343
- );
344
- setErrorText();
345
- setSteps(
346
- parameters.flatMap(
347
- (param) => isJsonObject(param) ? [
348
- {
349
- title: String(param.title),
350
- schema: param,
351
- validate: createAsyncValidators(param, fieldValidators, {
352
- apiHolder
353
- })
354
- }
355
- ] : []
356
- )
357
- );
358
- } catch (e) {
359
- setErrorText(e.message);
360
- }
361
- },
362
- 250,
363
- [contentIsSpec, content, apiHolder]
364
- );
365
- if (!steps) {
366
- return null;
367
- }
368
- return /* @__PURE__ */ React.createElement("div", { className: classes.containerWrapper }, /* @__PURE__ */ React.createElement("div", { className: classes.container }, /* @__PURE__ */ React.createElement(ErrorBoundary, { invalidator: steps, setErrorText }, /* @__PURE__ */ React.createElement(
369
- Stepper,
370
- {
371
- manifest: { steps, title: "Template Editor" },
372
- extensions: fieldExtensions,
373
- components: fields,
374
- onCreate: async (options) => {
375
- await (onDryRun == null ? void 0 : onDryRun(options));
376
- },
377
- layouts
378
- }
379
- ))));
380
- }
381
- function TemplateEditorFormDirectoryEditorDryRun(props) {
382
- const { setErrorText, fieldExtensions = [], layouts } = props;
383
- const dryRun = useDryRun();
384
- const directoryEditor = useDirectoryEditor();
385
- const { selectedFile } = directoryEditor;
386
- const handleDryRun = async (data) => {
387
- if (!selectedFile) {
388
- return;
389
- }
390
- try {
391
- await dryRun.execute({
392
- templateContent: selectedFile.content,
393
- values: data,
394
- files: directoryEditor.files
395
- });
396
- setErrorText();
397
- } catch (e) {
398
- setErrorText(String(e.cause || e));
399
- throw e;
400
- }
401
- };
402
- const content = selectedFile && selectedFile.path.match(/\.ya?ml$/) ? selectedFile.content : void 0;
403
- return /* @__PURE__ */ React.createElement(
404
- TemplateEditorForm,
405
- {
406
- onDryRun: handleDryRun,
407
- fieldExtensions,
408
- setErrorText,
409
- content,
410
- layouts
411
- }
412
- );
413
- }
414
- TemplateEditorForm.DirectoryEditorDryRun = TemplateEditorFormDirectoryEditorDryRun;
415
-
416
- const useStyles$2 = makeStyles$1((theme) => ({
417
- root: {
418
- gridArea: "pageContent",
419
- display: "grid",
420
- gridTemplateAreas: `
421
- "controls controls"
422
- "fieldForm preview"
423
- `,
424
- gridTemplateRows: "auto 1fr",
425
- gridTemplateColumns: "1fr 1fr"
426
- },
427
- controls: {
428
- gridArea: "controls",
429
- display: "flex",
430
- flexFlow: "row nowrap",
431
- alignItems: "center",
432
- margin: theme.spacing(1)
433
- },
434
- fieldForm: {
435
- gridArea: "fieldForm"
436
- },
437
- preview: {
438
- gridArea: "preview"
439
- }
440
- }));
441
- const CustomFieldExplorer = ({
442
- customFieldExtensions = [],
443
- onClose
444
- }) => {
445
- var _a, _b;
446
- const classes = useStyles$2();
447
- const fieldOptions = customFieldExtensions.filter((field) => !!field.schema);
448
- const [selectedField, setSelectedField] = useState(fieldOptions[0]);
449
- const [fieldFormState, setFieldFormState] = useState({});
450
- const [refreshKey, setRefreshKey] = useState(Date.now());
451
- const sampleFieldTemplate = useMemo(
452
- () => {
453
- var _a2, _b2;
454
- return yaml.stringify({
455
- parameters: [
456
- {
457
- title: `${selectedField.name} Example`,
458
- properties: {
459
- [selectedField.name]: {
460
- type: (_b2 = (_a2 = selectedField.schema) == null ? void 0 : _a2.returnValue) == null ? void 0 : _b2.type,
461
- "ui:field": selectedField.name,
462
- "ui:options": fieldFormState
463
- }
464
- }
465
- }
466
- ]
467
- });
468
- },
469
- [fieldFormState, selectedField]
470
- );
471
- const fieldComponents = useMemo(() => {
472
- return Object.fromEntries(
473
- customFieldExtensions.map(({ name, component }) => [name, component])
474
- );
475
- }, [customFieldExtensions]);
476
- const handleSelectionChange = useCallback(
477
- (selection) => {
478
- setSelectedField(selection);
479
- setFieldFormState({});
480
- },
481
- [setFieldFormState, setSelectedField]
482
- );
483
- const handleFieldConfigChange = useCallback(
484
- (state) => {
485
- setFieldFormState(state);
486
- setRefreshKey(Date.now());
487
- },
488
- [setFieldFormState, setRefreshKey]
489
- );
490
- return /* @__PURE__ */ React.createElement("main", { className: classes.root }, /* @__PURE__ */ React.createElement("div", { className: classes.controls }, /* @__PURE__ */ React.createElement(FormControl, { variant: "outlined", size: "small", fullWidth: true }, /* @__PURE__ */ React.createElement(InputLabel, { id: "select-field-label" }, "Choose Custom Field Extension"), /* @__PURE__ */ React.createElement(
491
- Select,
492
- {
493
- value: selectedField,
494
- label: "Choose Custom Field Extension",
495
- labelId: "select-field-label",
496
- onChange: (e) => handleSelectionChange(e.target.value)
497
- },
498
- fieldOptions.map((option, idx) => /* @__PURE__ */ React.createElement(MenuItem, { key: idx, value: option }, option.name))
499
- )), /* @__PURE__ */ React.createElement(IconButton$1, { size: "medium", onClick: onClose, "aria-label": "Close" }, /* @__PURE__ */ React.createElement(CloseIcon, null))), /* @__PURE__ */ React.createElement("div", { className: classes.fieldForm }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardHeader, { title: "Field Options" }), /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(
500
- Form,
501
- {
502
- showErrorList: false,
503
- fields: { ...fieldComponents },
504
- noHtml5Validate: true,
505
- formData: fieldFormState,
506
- formContext: { fieldFormState },
507
- onSubmit: (e) => handleFieldConfigChange(e.formData),
508
- validator,
509
- schema: ((_a = selectedField.schema) == null ? void 0 : _a.uiOptions) || {},
510
- experimental_defaultFormStateBehavior: {
511
- allOf: "populateDefaults"
512
- }
513
- },
514
- /* @__PURE__ */ React.createElement(
515
- Button$1,
516
- {
517
- variant: "contained",
518
- color: "primary",
519
- type: "submit",
520
- disabled: !((_b = selectedField.schema) == null ? void 0 : _b.uiOptions)
521
- },
522
- "Apply"
523
- )
524
- )))), /* @__PURE__ */ React.createElement("div", { className: classes.preview }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardHeader, { title: "Example Template Spec" }), /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(
525
- CodeMirror,
526
- {
527
- readOnly: true,
528
- theme: "dark",
529
- height: "100%",
530
- extensions: [StreamLanguage.define(yaml$1)],
531
- value: sampleFieldTemplate
532
- }
533
- ))), /* @__PURE__ */ React.createElement(
534
- TemplateEditorForm,
535
- {
536
- key: refreshKey,
537
- content: sampleFieldTemplate,
538
- contentIsSpec: true,
539
- fieldExtensions: customFieldExtensions,
540
- setErrorText: () => null
541
- }
542
- )));
543
- };
544
-
545
- const useStyles$1 = makeStyles$1({
546
- // Reset and fix sizing to make sure scrolling behaves correctly
547
- root: {
548
- gridArea: "pageContent",
549
- display: "grid",
550
- gridTemplateAreas: `
551
- "browser editor preview"
552
- "results results results"
553
- `,
554
- gridTemplateColumns: "1fr 3fr 2fr",
555
- gridTemplateRows: "1fr auto"
556
- },
557
- browser: {
558
- gridArea: "browser",
559
- overflow: "auto"
560
- },
561
- editor: {
562
- gridArea: "editor",
563
- overflow: "auto"
564
- },
565
- preview: {
566
- gridArea: "preview",
567
- overflow: "auto"
568
- },
569
- results: {
570
- gridArea: "results"
571
- }
572
- });
573
- const TemplateEditor = (props) => {
574
- const classes = useStyles$1();
575
- const [errorText, setErrorText] = useState();
576
- return /* @__PURE__ */ React.createElement(DirectoryEditorProvider, { directory: props.directory }, /* @__PURE__ */ React.createElement(DryRunProvider, null, /* @__PURE__ */ React.createElement("main", { className: classes.root }, /* @__PURE__ */ React.createElement("section", { className: classes.browser }, /* @__PURE__ */ React.createElement(TemplateEditorBrowser, { onClose: props.onClose })), /* @__PURE__ */ React.createElement("section", { className: classes.editor }, /* @__PURE__ */ React.createElement(TemplateEditorTextArea.DirectoryEditor, { errorText })), /* @__PURE__ */ React.createElement("section", { className: classes.preview }, /* @__PURE__ */ React.createElement(
577
- TemplateEditorForm.DirectoryEditorDryRun,
578
- {
579
- setErrorText,
580
- fieldExtensions: props.fieldExtensions,
581
- layouts: props.layouts
582
- }
583
- )), /* @__PURE__ */ React.createElement("section", { className: classes.results }, /* @__PURE__ */ React.createElement(DryRunResults, null)))));
584
- };
585
-
586
- const EXAMPLE_TEMPLATE_PARAMS_YAML = `# Edit the template parameters below to see how they will render in the scaffolder form UI
587
- parameters:
588
- - title: Fill in some steps
589
- required:
590
- - name
591
- properties:
592
- name:
593
- title: Name
594
- type: string
595
- description: Unique name of the component
596
- owner:
597
- title: Owner
598
- type: string
599
- description: Owner of the component
600
- ui:field: OwnerPicker
601
- ui:options:
602
- catalogFilter:
603
- kind: Group
604
- - title: Choose a location
605
- required:
606
- - repoUrl
607
- properties:
608
- repoUrl:
609
- title: Repository Location
610
- type: string
611
- ui:field: RepoUrlPicker
612
- ui:options:
613
- allowedHosts:
614
- - github.com
615
- steps:
616
- - id: fetch-base
617
- name: Fetch Base
618
- action: fetch:template
619
- input:
620
- url: ./template
621
- values:
622
- name: \${{parameters.name}}
623
- `;
624
- const useStyles = makeStyles$1((theme) => ({
625
- root: {
626
- gridArea: "pageContent",
627
- display: "grid",
628
- gridTemplateAreas: `
629
- "controls controls"
630
- "textArea preview"
631
- `,
632
- gridTemplateRows: "auto 1fr",
633
- gridTemplateColumns: "1fr 1fr"
634
- },
635
- controls: {
636
- gridArea: "controls",
637
- display: "flex",
638
- flexFlow: "row nowrap",
639
- alignItems: "center",
640
- margin: theme.spacing(1)
641
- },
642
- textArea: {
643
- gridArea: "textArea"
644
- },
645
- preview: {
646
- gridArea: "preview"
647
- }
648
- }));
649
- const TemplateFormPreviewer = ({
650
- defaultPreviewTemplate = EXAMPLE_TEMPLATE_PARAMS_YAML,
651
- customFieldExtensions = [],
652
- onClose,
653
- layouts = []
654
- }) => {
655
- const classes = useStyles();
656
- const alertApi = useApi(alertApiRef);
657
- const catalogApi = useApi(catalogApiRef);
658
- const [selectedTemplate, setSelectedTemplate] = useState("");
659
- const [errorText, setErrorText] = useState();
660
- const [templateOptions, setTemplateOptions] = useState([]);
661
- const [templateYaml, setTemplateYaml] = useState(defaultPreviewTemplate);
662
- const { loading } = useAsync(
663
- () => catalogApi.getEntities({
664
- filter: { kind: "template" },
665
- fields: [
666
- "kind",
667
- "metadata.namespace",
668
- "metadata.name",
669
- "metadata.title",
670
- "spec.parameters",
671
- "spec.steps",
672
- "spec.output"
673
- ]
674
- }).then(
675
- ({ items }) => setTemplateOptions(
676
- items.map((template) => {
677
- var _a;
678
- return {
679
- label: (_a = template.metadata.title) != null ? _a : humanizeEntityRef(template, { defaultKind: "template" }),
680
- value: template
681
- };
682
- })
683
- )
684
- ).catch(
685
- (e) => alertApi.post({
686
- message: `Error loading exisiting templates: ${e.message}`,
687
- severity: "error"
688
- })
689
- ),
690
- [catalogApi]
691
- );
692
- const handleSelectChange = useCallback(
693
- // TODO(Rugvip): Afaik this should be Entity, but didn't want to make runtime changes while fixing types
694
- (selected) => {
695
- setSelectedTemplate(selected);
696
- setTemplateYaml(yaml.stringify(selected.spec));
697
- },
698
- [setTemplateYaml]
699
- );
700
- return /* @__PURE__ */ React.createElement(React.Fragment, null, loading && /* @__PURE__ */ React.createElement(LinearProgress, null), /* @__PURE__ */ React.createElement("main", { className: classes.root }, /* @__PURE__ */ React.createElement("div", { className: classes.controls }, /* @__PURE__ */ React.createElement(FormControl, { variant: "outlined", size: "small", fullWidth: true }, /* @__PURE__ */ React.createElement(InputLabel, { id: "select-template-label" }, "Load Existing Template"), /* @__PURE__ */ React.createElement(
701
- Select,
702
- {
703
- value: selectedTemplate,
704
- label: "Load Existing Template",
705
- labelId: "select-template-label",
706
- onChange: (e) => handleSelectChange(e.target.value)
707
- },
708
- templateOptions.map((option, idx) => /* @__PURE__ */ React.createElement(MenuItem, { key: idx, value: option.value }, option.label))
709
- )), /* @__PURE__ */ React.createElement(IconButton$1, { size: "medium", onClick: onClose }, /* @__PURE__ */ React.createElement(CloseIcon, null))), /* @__PURE__ */ React.createElement("div", { className: classes.textArea }, /* @__PURE__ */ React.createElement(
710
- TemplateEditorTextArea,
711
- {
712
- content: templateYaml,
713
- onUpdate: setTemplateYaml,
714
- errorText
715
- }
716
- )), /* @__PURE__ */ React.createElement("div", { className: classes.preview }, /* @__PURE__ */ React.createElement(
717
- TemplateEditorForm,
718
- {
719
- content: templateYaml,
720
- contentIsSpec: true,
721
- fieldExtensions: customFieldExtensions,
722
- setErrorText,
723
- layouts
724
- }
725
- ))));
726
- };
727
-
728
- function TemplateEditorPage(props) {
729
- const [selection, setSelection] = useState();
730
- let content = null;
731
- if ((selection == null ? void 0 : selection.type) === "local") {
732
- content = /* @__PURE__ */ React.createElement(
733
- TemplateEditor,
734
- {
735
- directory: selection.directory,
736
- fieldExtensions: props.customFieldExtensions,
737
- onClose: () => setSelection(void 0),
738
- layouts: props.layouts
739
- }
740
- );
741
- } else if ((selection == null ? void 0 : selection.type) === "form") {
742
- content = /* @__PURE__ */ React.createElement(
743
- TemplateFormPreviewer,
744
- {
745
- defaultPreviewTemplate: props.defaultPreviewTemplate,
746
- customFieldExtensions: props.customFieldExtensions,
747
- onClose: () => setSelection(void 0),
748
- layouts: props.layouts
749
- }
750
- );
751
- } else if ((selection == null ? void 0 : selection.type) === "field-explorer") {
752
- content = /* @__PURE__ */ React.createElement(
753
- CustomFieldExplorer,
754
- {
755
- customFieldExtensions: props.customFieldExtensions,
756
- onClose: () => setSelection(void 0)
757
- }
758
- );
759
- } else {
760
- content = /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(
761
- TemplateEditorIntro,
762
- {
763
- onSelect: (option) => {
764
- if (option === "local") {
765
- WebFileSystemAccess.requestDirectoryAccess().then((directory) => setSelection({ type: "local", directory })).catch(() => {
766
- });
767
- } else if (option === "form") {
768
- setSelection({ type: "form" });
769
- } else if (option === "field-explorer") {
770
- setSelection({ type: "field-explorer" });
771
- }
772
- }
773
- }
774
- ));
775
- }
776
- return /* @__PURE__ */ React.createElement(Page, { themeId: "home" }, /* @__PURE__ */ React.createElement(
777
- Header,
778
- {
779
- title: "Template Editor",
780
- subtitle: "Edit, preview, and try out templates and template forms"
781
- }
782
- ), content);
783
- }
784
-
785
- const Router = (props) => {
786
- const {
787
- components: {
788
- TemplateCardComponent,
789
- TaskPageComponent = OngoingTask,
790
- ReviewStepComponent,
791
- EXPERIMENTAL_TemplateOutputsComponent: TemplateOutputsComponent,
792
- EXPERIMENTAL_TemplateListPageComponent: TemplateListPageComponent = TemplateListPage,
793
- EXPERIMENTAL_TemplateWizardPageComponent: TemplateWizardPageComponent = TemplateWizardPage
794
- } = {}
795
- } = props;
796
- const outlet = useOutlet() || props.children;
797
- const customFieldExtensions = useCustomFieldExtensions(outlet);
798
- const fieldExtensions = [
799
- ...customFieldExtensions,
800
- ...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter(
801
- ({ name }) => !customFieldExtensions.some(
802
- (customFieldExtension) => customFieldExtension.name === name
803
- )
804
- )
805
- ];
806
- const customLayouts = useCustomLayouts(outlet);
807
- return /* @__PURE__ */ React.createElement(Routes, null, /* @__PURE__ */ React.createElement(
808
- Route,
809
- {
810
- path: "/",
811
- element: /* @__PURE__ */ React.createElement(
812
- TemplateListPageComponent,
813
- {
814
- TemplateCardComponent,
815
- contextMenu: props.contextMenu,
816
- groups: props.groups,
817
- templateFilter: props.templateFilter,
818
- headerOptions: props.headerOptions
819
- }
820
- )
821
- }
822
- ), /* @__PURE__ */ React.createElement(
823
- Route,
824
- {
825
- path: selectedTemplateRouteRef.path,
826
- element: /* @__PURE__ */ React.createElement(SecretsContextProvider, null, /* @__PURE__ */ React.createElement(
827
- TemplateWizardPageComponent,
828
- {
829
- headerOptions: props.headerOptions,
830
- customFieldExtensions: fieldExtensions,
831
- layouts: customLayouts,
832
- components: { ReviewStepComponent },
833
- formProps: props.formProps
834
- }
835
- ))
836
- }
837
- ), /* @__PURE__ */ React.createElement(
838
- Route,
839
- {
840
- path: scaffolderTaskRouteRef.path,
841
- element: /* @__PURE__ */ React.createElement(
842
- TaskPageComponent,
843
- {
844
- TemplateOutputsComponent
845
- }
846
- )
847
- }
848
- ), /* @__PURE__ */ React.createElement(
849
- Route,
850
- {
851
- path: editRouteRef.path,
852
- element: /* @__PURE__ */ React.createElement(SecretsContextProvider, null, /* @__PURE__ */ React.createElement(
853
- TemplateEditorPage,
854
- {
855
- customFieldExtensions: fieldExtensions,
856
- layouts: customLayouts
857
- }
858
- ))
859
- }
860
- ), /* @__PURE__ */ React.createElement(Route, { path: actionsRouteRef.path, element: /* @__PURE__ */ React.createElement(ActionsPage, null) }), /* @__PURE__ */ React.createElement(
861
- Route,
862
- {
863
- path: scaffolderListTaskRouteRef.path,
864
- element: /* @__PURE__ */ React.createElement(ListTasksPage, null)
865
- }
866
- ), /* @__PURE__ */ React.createElement(
867
- Route,
868
- {
869
- path: "*",
870
- element: /* @__PURE__ */ React.createElement(ErrorPage, { status: "404", statusMessage: "Page not found" })
871
- }
872
- ));
873
- };
874
-
875
- export { Router };
876
- //# sourceMappingURL=index-38156f16.esm.js.map