@backstage/plugin-scaffolder 1.17.1 → 1.18.0-next.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.
@@ -1,363 +1,183 @@
1
- import React, { useRef, useEffect, memo, useState, useMemo, Fragment, createContext, useContext, useCallback, Children } from 'react';
1
+ import React, { useState, Fragment, useCallback, createContext, useRef, useMemo, useContext, useEffect, Component, Children, memo } from 'react';
2
+ import { Link as Link$1, useNavigate, Navigate, useOutlet, Routes, Route } from 'react-router-dom';
3
+ import { useTemplateSecrets, scaffolderApiRef, useCustomFieldExtensions, useCustomLayouts, SecretsContextProvider } from '@backstage/plugin-scaffolder-react';
4
+ import { E as EntityPicker, a as EntityPickerSchema, b as EntityNamePicker, e as entityNamePickerValidation, c as EntityNamePickerSchema, l as EntityTagsPicker, m as EntityTagsPickerSchema, R as RepoUrlPicker, r as repoPickerValidation, f as RepoUrlPickerSchema, O as OwnerPicker, g as OwnerPickerSchema, j as OwnedEntityPicker, k as OwnedEntityPickerSchema, h as MyGroupsPicker, i as MyGroupsPickerSchema, M as MultiEntityPicker, d as MultiEntityPickerSchema, v as validateMultiEntityPickerValidation, n as OngoingTask } from './OngoingTask-caf1a6e8.esm.js';
5
+ import { ScaffolderField, ScaffolderPageContextMenu, TemplateCategoryPicker, TemplateGroups, Workflow, createAsyncValidators, Stepper, Form } from '@backstage/plugin-scaffolder-react/alpha';
6
+ import { InputLabel, Input, makeStyles, Box, Typography, Accordion, AccordionSummary, AccordionDetails, Grid, TableContainer, Paper, Table, TableHead, TableRow, TableCell, TableBody, Collapse, Card, List, MenuItem, ListItemIcon, ListItemText, FormControl, Select, IconButton as IconButton$1, CardHeader, CardContent, Button as Button$1, Tooltip, Divider, createStyles, StepButton, CircularProgress, LinearProgress } from '@material-ui/core';
7
+ import { r as rootRouteRef, d as registerComponentRouteRef, e as editRouteRef, b as actionsRouteRef, c as scaffolderListTaskRouteRef, v as viewTechDocRouteRef, s as selectedTemplateRouteRef, a as scaffolderTaskRouteRef } from './routes-1428737e.esm.js';
8
+ import { Progress, ErrorPage, MarkdownContent, Page, Header, Content, CodeSnippet, StatusError, StatusOK, StatusPending, ErrorPanel, EmptyState, Table as Table$1, Link, DocsIcon, ContentHeader, SupportButton, LogViewer } from '@backstage/core-components';
2
9
  import useAsync from 'react-use/lib/useAsync';
3
- import { scaffolderApiRef, useTaskEventStream, useTemplateSecrets } from '@backstage/plugin-scaffolder-react';
4
- import { Box, makeStyles, Grid, Typography, StepButton, CircularProgress, Paper, Button, Accordion, AccordionSummary, AccordionDetails, TableContainer, Table, TableHead, TableRow, TableCell, TableBody, Collapse, InputLabel, Input, Card, List, MenuItem, ListItemIcon, ListItemText, Tooltip as Tooltip$1, IconButton, Divider } from '@material-ui/core';
5
10
  import classNames from 'classnames';
6
11
  import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
7
12
  import ExpandLessIcon from '@material-ui/icons/ExpandLess';
8
- import { useApp, useRouteRef, useApi, useRouteRefParams } from '@backstage/core-plugin-api';
9
- import { DismissableBanner, Link, Page, Header, Content, ErrorPage, Progress, LogViewer, MarkdownContent, CodeSnippet, StatusError, StatusOK, StatusPending, Lifecycle, ErrorPanel, EmptyState, Table as Table$1 } from '@backstage/core-components';
13
+ import { useApi, useRouteRef, useApp, useRouteRefParams, AnalyticsContext, useApiHolder, alertApiRef } from '@backstage/core-plugin-api';
10
14
  import Chip from '@material-ui/core/Chip';
11
- import { r as rootRouteRef, o as selectedTemplateRouteRef, w as scaffolderTaskRouteRef, y as EntityPicker, z as EntityPickerSchema, A as EntityNamePicker, B as entityNamePickerValidation, C as EntityNamePickerSchema, D as EntityTagsPicker, F as EntityTagsPickerSchema, G as RepoUrlPicker, i as repoPickerValidation, H as RepoUrlPickerSchema, I as OwnerPicker, J as OwnerPickerSchema, K as OwnedEntityPicker, N as OwnedEntityPickerSchema, P as MyGroupsPicker, l as MyGroupsPickerSchema } from './OngoingTask-87c6761c.esm.js';
12
- import { ScaffolderField } from '@backstage/plugin-scaffolder-react/alpha';
13
- import { entityRouteRef, catalogApiRef, EntityRefLink, CatalogFilterLayout } from '@backstage/plugin-catalog-react';
15
+ import { catalogApiRef, EntityRefLink, CatalogFilterLayout, EntityListProvider, EntitySearchBar, EntityKindPicker, UserListPicker, EntityTagPicker, entityRouteRef, humanizeEntityRef } from '@backstage/plugin-catalog-react';
14
16
  import SettingsIcon from '@material-ui/icons/Settings';
15
17
  import AllIcon from '@material-ui/icons/FontDownload';
16
18
  import { DateTime, Interval } from 'luxon';
17
19
  import humanizeDuration from 'humanize-duration';
18
20
  import Typography$1 from '@material-ui/core/Typography';
19
- import { parseEntityRef } from '@backstage/catalog-model';
20
- import Card$1 from '@material-ui/core/Card';
21
- import CardActionArea from '@material-ui/core/CardActionArea';
22
- import CardContent from '@material-ui/core/CardContent';
23
- import Tooltip from '@material-ui/core/Tooltip';
24
- import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined';
25
- import { makeStyles as makeStyles$1, createStyles } from '@material-ui/core/styles';
21
+ import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model';
22
+ import Button from '@material-ui/core/Button';
23
+ import IconButton from '@material-ui/core/IconButton';
24
+ import useMediaQuery from '@material-ui/core/useMediaQuery';
25
+ import CreateComponentIcon from '@material-ui/icons/AddCircleOutline';
26
+ import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha';
27
+ import { usePermission } from '@backstage/plugin-permission-react';
28
+ import { StreamLanguage } from '@codemirror/language';
29
+ import { yaml as yaml$1 } from '@codemirror/legacy-modes/mode/yaml';
26
30
  import CloseIcon from '@material-ui/icons/Close';
31
+ import CodeMirror from '@uiw/react-codemirror';
32
+ import yaml from 'yaml';
33
+ import { makeStyles as makeStyles$1 } from '@material-ui/core/styles';
34
+ import useDebounce from 'react-use/lib/useDebounce';
35
+ import { useAsync as useAsync$1, useRerender, useKeyboardEvent, usePrevious } from '@react-hookz/web';
36
+ import validator from '@rjsf/validator-ajv8';
27
37
  import RefreshIcon from '@material-ui/icons/Refresh';
28
38
  import SaveIcon from '@material-ui/icons/Save';
29
- import { StreamLanguage } from '@codemirror/language';
30
- import { yaml as yaml$1 } from '@codemirror/legacy-modes/mode/yaml';
39
+ import TreeView from '@material-ui/lab/TreeView';
40
+ import ChevronRightIcon from '@material-ui/icons/ChevronRight';
41
+ import TreeItem from '@material-ui/lab/TreeItem';
31
42
  import { showPanel } from '@codemirror/view';
32
- import { useAsync as useAsync$1, useRerender, useKeyboardEvent, usePrevious } from '@react-hookz/web';
33
- import CodeMirror from '@uiw/react-codemirror';
34
43
  import Accordion$1 from '@material-ui/core/Accordion';
35
44
  import AccordionDetails$1 from '@material-ui/core/AccordionDetails';
36
45
  import AccordionSummary$1 from '@material-ui/core/AccordionSummary';
37
46
  import Divider$1 from '@material-ui/core/Divider';
38
- import yaml from 'yaml';
39
- import IconButton$1 from '@material-ui/core/IconButton';
40
47
  import List$1 from '@material-ui/core/List';
41
48
  import ListItem from '@material-ui/core/ListItem';
42
49
  import ListItemIcon$1 from '@material-ui/core/ListItemIcon';
43
50
  import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
44
51
  import ListItemText$1 from '@material-ui/core/ListItemText';
45
52
  import Cancel from '@material-ui/icons/Cancel';
46
- import Check from '@material-ui/icons/Check';
53
+ import CheckIcon from '@material-ui/icons/Check';
47
54
  import DeleteIcon from '@material-ui/icons/Delete';
48
55
  import DownloadIcon from '@material-ui/icons/GetApp';
49
56
  import Box$1 from '@material-ui/core/Box';
50
57
  import Tab from '@material-ui/core/Tab';
51
58
  import Tabs from '@material-ui/core/Tabs';
52
- import TreeView from '@material-ui/lab/TreeView';
53
- import ChevronRightIcon from '@material-ui/icons/ChevronRight';
54
- import TreeItem from '@material-ui/lab/TreeItem';
55
59
  import LanguageIcon from '@material-ui/icons/Language';
56
- import Grid$1 from '@material-ui/core/Grid';
57
60
  import Step from '@material-ui/core/Step';
58
61
  import StepLabel from '@material-ui/core/StepLabel';
59
- import Stepper from '@material-ui/core/Stepper';
62
+ import Stepper$1 from '@material-ui/core/Stepper';
60
63
  import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord';
61
- import qs from 'qs';
62
- import { useNavigate } from 'react-router-dom';
63
64
  import useInterval from 'react-use/lib/useInterval';
65
+ import Card$1 from '@material-ui/core/Card';
66
+ import CardActionArea from '@material-ui/core/CardActionArea';
67
+ import CardContent$1 from '@material-ui/core/CardContent';
68
+ import Tooltip$1 from '@material-ui/core/Tooltip';
69
+ import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined';
70
+ import 'zod';
71
+ import '@backstage/catalog-client';
72
+ import '@material-ui/core/FormControl';
73
+ import '@material-ui/lab/Autocomplete';
74
+ import '@backstage/integration-react';
75
+ import '@material-ui/core/FormHelperText';
76
+ import '@material-ui/core/Input';
77
+ import '@material-ui/core/InputLabel';
78
+ import 'react-use/lib/useEffectOnce';
79
+ import '@material-ui/lab';
80
+ import 'zod-to-json-schema';
81
+ import '@backstage/errors';
82
+ import 'qs';
83
+ import '@material-ui/icons/Repeat';
84
+ import '@material-ui/icons/Toc';
85
+ import '@material-ui/icons/ControlPoint';
86
+ import '@material-ui/icons/MoreVert';
87
+ import 'zen-observable';
88
+ import 'event-source-polyfill';
64
89
 
65
- const TaskErrors = ({ error }) => {
66
- const id = useRef("");
67
- useEffect(() => {
68
- id.current = String(Math.random());
69
- }, [error]);
70
- return error ? /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(
71
- DismissableBanner,
72
- {
73
- id: id.current,
74
- variant: "warning",
75
- message: error.message
76
- }
77
- )) : null;
78
- };
79
-
80
- const useStyles$b = makeStyles({
81
- svgIcon: {
82
- display: "inline-block",
83
- "& svg": {
84
- display: "inline-block",
85
- fontSize: "inherit",
86
- verticalAlign: "baseline"
87
- }
88
- }
89
- });
90
- const IconLink = (props) => {
91
- const { href, text, Icon, ...linkProps } = props;
92
- const classes = useStyles$b();
93
- return /* @__PURE__ */ React.createElement(Grid, { container: true, direction: "row", spacing: 1 }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Typography, { component: "div", className: classes.svgIcon }, Icon ? /* @__PURE__ */ React.createElement(Icon, null) : /* @__PURE__ */ React.createElement(LanguageIcon, null))), /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Link, { to: href, ...linkProps }, text || href)));
94
- };
95
-
96
- const TaskPageLinks = ({ output }) => {
97
- const { links = [] } = output;
98
- const app = useApp();
99
- const entityRoute = useRouteRef(entityRouteRef);
100
- const iconResolver = (key) => {
101
- var _a;
102
- return key ? (_a = app.getSystemIcon(key)) != null ? _a : LanguageIcon : LanguageIcon;
103
- };
104
- return /* @__PURE__ */ React.createElement(Box, { px: 3, pb: 3 }, links.filter(({ url, entityRef }) => url || entityRef).map(({ url, entityRef, title, icon }) => {
105
- if (entityRef) {
106
- const entityName = parseEntityRef(entityRef, {
107
- defaultKind: "<unknown>",
108
- defaultNamespace: "<unknown>"
109
- });
110
- const target = entityRoute(entityName);
111
- return { title, icon, url: target };
112
- }
113
- return { title, icon, url };
114
- }).map(({ url, title, icon }, i) => /* @__PURE__ */ React.createElement(
115
- IconLink,
116
- {
117
- key: `output-link-${i}`,
118
- href: url,
119
- text: title != null ? title : url,
120
- Icon: iconResolver(icon),
121
- target: "_blank"
122
- }
123
- )));
124
- };
125
-
126
- const useStyles$a = makeStyles$1(
127
- (theme) => createStyles({
128
- root: {
129
- width: "100%"
130
- },
131
- button: {
132
- marginBottom: theme.spacing(2),
133
- marginLeft: theme.spacing(2)
134
- },
135
- actionsContainer: {
136
- marginBottom: theme.spacing(2)
137
- },
138
- resetContainer: {
139
- padding: theme.spacing(3)
140
- },
141
- labelWrapper: {
142
- display: "flex",
143
- flex: 1,
144
- flexDirection: "row",
145
- justifyContent: "space-between"
146
- },
147
- stepWrapper: {
148
- width: "100%"
149
- }
150
- })
151
- );
152
- const StepTimeTicker = ({ step }) => {
153
- const [time, setTime] = useState("");
154
- useInterval(() => {
155
- if (!step.startedAt) {
156
- setTime("");
157
- return;
158
- }
159
- const end = step.endedAt ? DateTime.fromISO(step.endedAt) : DateTime.local();
160
- const startedAt = DateTime.fromISO(step.startedAt);
161
- const formatted = Interval.fromDateTimes(startedAt, end).toDuration().valueOf();
162
- setTime(humanizeDuration(formatted, { round: true }));
163
- }, 1e3);
164
- return /* @__PURE__ */ React.createElement(Typography$1, { variant: "caption" }, time);
165
- };
166
- const useStepIconStyles = makeStyles$1(
167
- (theme) => createStyles({
168
- root: {
169
- color: theme.palette.text.disabled,
170
- display: "flex",
171
- height: 22,
172
- alignItems: "center"
173
- },
174
- completed: {
175
- color: theme.palette.status.ok
176
- },
177
- error: {
178
- color: theme.palette.status.error
179
- }
180
- })
181
- );
182
- function TaskStepIconComponent(props) {
183
- const classes = useStepIconStyles();
184
- const { active, completed, error } = props;
185
- const getMiddle = () => {
186
- if (active) {
187
- return /* @__PURE__ */ React.createElement(CircularProgress, { size: "24px" });
188
- }
189
- if (completed) {
190
- return /* @__PURE__ */ React.createElement(Check, null);
191
- }
192
- if (error) {
193
- return /* @__PURE__ */ React.createElement(Cancel, null);
194
- }
195
- return /* @__PURE__ */ React.createElement(FiberManualRecordIcon, null);
196
- };
90
+ const SecretInput = (props) => {
91
+ var _a;
92
+ const { setSecrets, secrets } = useTemplateSecrets();
93
+ const {
94
+ name,
95
+ onChange,
96
+ schema: { title, description },
97
+ rawErrors,
98
+ disabled,
99
+ errors,
100
+ required
101
+ } = props;
197
102
  return /* @__PURE__ */ React.createElement(
198
- "div",
103
+ ScaffolderField,
199
104
  {
200
- className: classNames(classes.root, {
201
- [classes.completed]: completed,
202
- [classes.error]: error
203
- })
105
+ rawErrors,
106
+ rawDescription: description,
107
+ disabled,
108
+ errors,
109
+ required
204
110
  },
205
- getMiddle()
206
- );
207
- }
208
- const TaskStatusStepper = memo(
209
- (props) => {
210
- const { steps, currentStepId, onUserStepChange } = props;
211
- const classes = useStyles$a(props);
212
- return /* @__PURE__ */ React.createElement("div", { className: classes.root }, /* @__PURE__ */ React.createElement(
213
- Stepper,
111
+ /* @__PURE__ */ React.createElement(InputLabel, { htmlFor: title }, title),
112
+ /* @__PURE__ */ React.createElement(
113
+ Input,
214
114
  {
215
- activeStep: steps.findIndex((s) => s.id === currentStepId),
216
- orientation: "vertical",
217
- nonLinear: true
218
- },
219
- steps.map((step, index) => {
220
- const isCancelled = step.status === "cancelled";
221
- const isActive = step.status === "processing";
222
- const isCompleted = step.status === "completed";
223
- const isFailed = step.status === "failed";
224
- const isSkipped = step.status === "skipped";
225
- return /* @__PURE__ */ React.createElement(Step, { key: String(index), expanded: true }, /* @__PURE__ */ React.createElement(StepButton, { onClick: () => onUserStepChange(step.id) }, /* @__PURE__ */ React.createElement(
226
- StepLabel,
227
- {
228
- StepIconProps: {
229
- completed: isCompleted,
230
- error: isFailed || isCancelled,
231
- active: isActive
232
- },
233
- StepIconComponent: TaskStepIconComponent,
234
- className: classes.stepWrapper
235
- },
236
- /* @__PURE__ */ React.createElement("div", { className: classes.labelWrapper }, /* @__PURE__ */ React.createElement(Typography$1, { variant: "subtitle2" }, step.name), isSkipped ? /* @__PURE__ */ React.createElement(Typography$1, { variant: "caption" }, "Skipped") : /* @__PURE__ */ React.createElement(StepTimeTicker, { step }))
237
- )));
238
- })
239
- ));
240
- }
241
- );
242
- const hasLinks = ({ links = [] }) => links.length > 0;
243
- const TaskPage = (props) => {
244
- const { loadingText } = props;
245
- const classes = useStyles$a();
246
- const navigate = useNavigate();
247
- const rootPath = useRouteRef(rootRouteRef);
248
- const scaffolderApi = useApi(scaffolderApiRef);
249
- const templateRoute = useRouteRef(selectedTemplateRouteRef);
250
- const [userSelectedStepId, setUserSelectedStepId] = useState(void 0);
251
- const [clickedToCancel, setClickedToCancel] = useState(false);
252
- const [lastActiveStepId, setLastActiveStepId] = useState(
253
- void 0
254
- );
255
- const { taskId } = useRouteRefParams(scaffolderTaskRouteRef);
256
- const taskStream = useTaskEventStream(taskId);
257
- const completed = taskStream.completed;
258
- const taskCancelled = taskStream.cancelled;
259
- const steps = useMemo(
260
- () => {
261
- var _a, _b;
262
- return (_b = (_a = taskStream.task) == null ? void 0 : _a.spec.steps.map((step) => {
263
- var _a2;
264
- return {
265
- ...step,
266
- ...(_a2 = taskStream == null ? void 0 : taskStream.steps) == null ? void 0 : _a2[step.id]
267
- };
268
- })) != null ? _b : [];
269
- },
270
- [taskStream]
115
+ id: title,
116
+ "aria-describedby": title,
117
+ onChange: (e) => {
118
+ var _a2, _b;
119
+ onChange(Array((_a2 = e.target) == null ? void 0 : _a2.value.length).fill("*").join(""));
120
+ setSecrets({ [name]: (_b = e.target) == null ? void 0 : _b.value });
121
+ },
122
+ value: (_a = secrets[name]) != null ? _a : "",
123
+ type: "password",
124
+ autoComplete: "off"
125
+ }
126
+ )
271
127
  );
272
- useEffect(() => {
273
- var _a;
274
- const mostRecentFailedOrActiveStep = steps.find(
275
- (step) => ["failed", "processing"].includes(step.status)
276
- );
277
- if (completed && !mostRecentFailedOrActiveStep) {
278
- setLastActiveStepId((_a = steps[steps.length - 1]) == null ? void 0 : _a.id);
279
- return;
280
- }
281
- setLastActiveStepId(mostRecentFailedOrActiveStep == null ? void 0 : mostRecentFailedOrActiveStep.id);
282
- }, [steps, completed]);
283
- const currentStepId = userSelectedStepId != null ? userSelectedStepId : lastActiveStepId;
284
- const logAsString = useMemo(() => {
285
- if (!currentStepId) {
286
- return loadingText ? loadingText : "Loading...";
287
- }
288
- const log = taskStream.stepLogs[currentStepId];
289
- if (!(log == null ? void 0 : log.length)) {
290
- return "Waiting for logs...";
291
- }
292
- return log.join("\n");
293
- }, [taskStream.stepLogs, currentStepId, loadingText]);
294
- const taskNotFound = taskStream.completed && !taskStream.loading && !taskStream.task;
295
- const { output } = taskStream;
296
- const handleStartOver = () => {
297
- var _a, _b, _c;
298
- if (!taskStream.task || !((_b = (_a = taskStream.task) == null ? void 0 : _a.spec.templateInfo) == null ? void 0 : _b.entityRef)) {
299
- navigate(rootPath());
300
- return;
301
- }
302
- const formData = taskStream.task.spec.parameters;
303
- const { name, namespace } = parseEntityRef(
304
- (_c = taskStream.task.spec.templateInfo) == null ? void 0 : _c.entityRef
305
- );
306
- navigate(
307
- `${templateRoute({ templateName: name, namespace })}?${qs.stringify({
308
- formData: JSON.stringify(formData)
309
- })}`
310
- );
311
- };
312
- const handleCancel = async () => {
313
- setClickedToCancel(true);
314
- await scaffolderApi.cancelTask(taskId);
315
- };
316
- return /* @__PURE__ */ React.createElement(Page, { themeId: "home" }, /* @__PURE__ */ React.createElement(
317
- Header,
318
- {
319
- pageTitleOverride: `Task ${taskId}`,
320
- title: "Task Activity",
321
- subtitle: `Activity for task: ${taskId}`
322
- }
323
- ), /* @__PURE__ */ React.createElement(Content, null, taskNotFound ? /* @__PURE__ */ React.createElement(
324
- ErrorPage,
325
- {
326
- status: "404",
327
- statusMessage: "Task not found",
328
- additionalInfo: "No task found with this ID"
329
- }
330
- ) : /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(Grid$1, { container: true }, /* @__PURE__ */ React.createElement(Grid$1, { item: true, xs: 3 }, /* @__PURE__ */ React.createElement(Paper, null, /* @__PURE__ */ React.createElement(
331
- TaskStatusStepper,
332
- {
333
- steps,
334
- currentStepId,
335
- onUserStepChange: setUserSelectedStepId
336
- }
337
- ), output && hasLinks(output) && /* @__PURE__ */ React.createElement(TaskPageLinks, { output }), /* @__PURE__ */ React.createElement(
338
- Button,
339
- {
340
- className: classes.button,
341
- onClick: handleStartOver,
342
- disabled: !completed,
343
- variant: "contained",
344
- color: "primary"
345
- },
346
- "Start Over"
347
- ), /* @__PURE__ */ React.createElement(
348
- Button,
349
- {
350
- className: classes.button,
351
- onClick: handleCancel,
352
- disabled: completed || taskCancelled || clickedToCancel,
353
- variant: "outlined",
354
- color: "secondary"
355
- },
356
- (taskCancelled || clickedToCancel) && !completed ? "Cancelling..." : "Cancel"
357
- ))), /* @__PURE__ */ React.createElement(Grid$1, { item: true, xs: 9 }, !currentStepId && /* @__PURE__ */ React.createElement(Progress, null), /* @__PURE__ */ React.createElement("div", { style: { height: "80vh" } }, /* @__PURE__ */ React.createElement(TaskErrors, { error: taskStream.error }), /* @__PURE__ */ React.createElement(LogViewer, { text: logAsString })))))));
358
128
  };
359
129
 
360
- const useStyles$9 = makeStyles((theme) => ({
130
+ const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS = [
131
+ {
132
+ component: EntityPicker,
133
+ name: "EntityPicker",
134
+ schema: EntityPickerSchema
135
+ },
136
+ {
137
+ component: EntityNamePicker,
138
+ name: "EntityNamePicker",
139
+ validation: entityNamePickerValidation,
140
+ schema: EntityNamePickerSchema
141
+ },
142
+ {
143
+ component: EntityTagsPicker,
144
+ name: "EntityTagsPicker",
145
+ schema: EntityTagsPickerSchema
146
+ },
147
+ {
148
+ component: RepoUrlPicker,
149
+ name: "RepoUrlPicker",
150
+ validation: repoPickerValidation,
151
+ schema: RepoUrlPickerSchema
152
+ },
153
+ {
154
+ component: OwnerPicker,
155
+ name: "OwnerPicker",
156
+ schema: OwnerPickerSchema
157
+ },
158
+ {
159
+ component: OwnedEntityPicker,
160
+ name: "OwnedEntityPicker",
161
+ schema: OwnedEntityPickerSchema
162
+ },
163
+ {
164
+ component: MyGroupsPicker,
165
+ name: "MyGroupsPicker",
166
+ schema: MyGroupsPickerSchema
167
+ },
168
+ {
169
+ component: SecretInput,
170
+ name: "Secret"
171
+ },
172
+ {
173
+ component: MultiEntityPicker,
174
+ name: "MultiEntityPicker",
175
+ schema: MultiEntityPickerSchema,
176
+ validation: validateMultiEntityPickerValidation
177
+ }
178
+ ];
179
+
180
+ const useStyles$f = makeStyles((theme) => ({
361
181
  code: {
362
182
  fontFamily: "Menlo, monospace",
363
183
  padding: theme.spacing(1),
@@ -393,7 +213,7 @@ const ExamplesTable = (props) => {
393
213
  };
394
214
  const ActionsPage = () => {
395
215
  const api = useApi(scaffolderApiRef);
396
- const classes = useStyles$9();
216
+ const classes = useStyles$f();
397
217
  const { loading, value, error } = useAsync(async () => {
398
218
  return api.listActions();
399
219
  });
@@ -501,91 +321,7 @@ const ActionsPage = () => {
501
321
  ), /* @__PURE__ */ React.createElement(Content, null, items));
502
322
  };
503
323
 
504
- const SecretInput = (props) => {
505
- var _a;
506
- const { setSecrets, secrets } = useTemplateSecrets();
507
- const {
508
- name,
509
- onChange,
510
- schema: { title, description },
511
- rawErrors,
512
- disabled,
513
- errors,
514
- required
515
- } = props;
516
- return /* @__PURE__ */ React.createElement(
517
- ScaffolderField,
518
- {
519
- rawErrors,
520
- rawDescription: description,
521
- disabled,
522
- errors,
523
- required
524
- },
525
- /* @__PURE__ */ React.createElement(InputLabel, { htmlFor: title }, title),
526
- /* @__PURE__ */ React.createElement(
527
- Input,
528
- {
529
- id: title,
530
- "aria-describedby": title,
531
- onChange: (e) => {
532
- var _a2, _b;
533
- onChange(Array((_a2 = e.target) == null ? void 0 : _a2.value.length).fill("*").join(""));
534
- setSecrets({ [name]: (_b = e.target) == null ? void 0 : _b.value });
535
- },
536
- value: (_a = secrets[name]) != null ? _a : "",
537
- type: "password",
538
- autoComplete: "off"
539
- }
540
- )
541
- );
542
- };
543
-
544
- const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS = [
545
- {
546
- component: EntityPicker,
547
- name: "EntityPicker",
548
- schema: EntityPickerSchema
549
- },
550
- {
551
- component: EntityNamePicker,
552
- name: "EntityNamePicker",
553
- validation: entityNamePickerValidation,
554
- schema: EntityNamePickerSchema
555
- },
556
- {
557
- component: EntityTagsPicker,
558
- name: "EntityTagsPicker",
559
- schema: EntityTagsPickerSchema
560
- },
561
- {
562
- component: RepoUrlPicker,
563
- name: "RepoUrlPicker",
564
- validation: repoPickerValidation,
565
- schema: RepoUrlPickerSchema
566
- },
567
- {
568
- component: OwnerPicker,
569
- name: "OwnerPicker",
570
- schema: OwnerPickerSchema
571
- },
572
- {
573
- component: OwnedEntityPicker,
574
- name: "OwnedEntityPicker",
575
- schema: OwnedEntityPickerSchema
576
- },
577
- {
578
- component: MyGroupsPicker,
579
- name: "MyGroupsPicker",
580
- schema: MyGroupsPickerSchema
581
- },
582
- {
583
- component: SecretInput,
584
- name: "Secret"
585
- }
586
- ];
587
-
588
- const useStyles$8 = makeStyles(
324
+ const useStyles$e = makeStyles(
589
325
  (theme) => ({
590
326
  root: {
591
327
  backgroundColor: "rgba(0, 0, 0, .11)",
@@ -634,7 +370,7 @@ function getFilterGroups() {
634
370
  }
635
371
  const OwnerListPicker = (props) => {
636
372
  const { filter, onSelectOwner } = props;
637
- const classes = useStyles$8();
373
+ const classes = useStyles$e();
638
374
  const filterGroups = getFilterGroups();
639
375
  return /* @__PURE__ */ React.createElement(Card, { className: classes.root }, filterGroups.map((group) => /* @__PURE__ */ React.createElement(Fragment, { key: group.name }, /* @__PURE__ */ React.createElement(
640
376
  Typography,
@@ -796,11 +532,170 @@ const ListTasksPage = (props) => {
796
532
  return /* @__PURE__ */ React.createElement(Page, { themeId: "home" }, /* @__PURE__ */ React.createElement(
797
533
  Header,
798
534
  {
799
- pageTitleOverride: "Templates Tasks",
800
- title: /* @__PURE__ */ React.createElement(React.Fragment, null, "List template tasks ", /* @__PURE__ */ React.createElement(Lifecycle, { shorthand: true, alpha: true })),
801
- subtitle: "All tasks that have been started"
535
+ pageTitleOverride: "Templates Tasks",
536
+ title: "List template tasks",
537
+ subtitle: "All tasks that have been started"
538
+ }
539
+ ), /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ListTaskPageContent, { ...props })));
540
+ };
541
+
542
+ const RegisterExistingButton = (props) => {
543
+ const { title, to } = props;
544
+ const { allowed } = usePermission({
545
+ permission: catalogEntityCreatePermission
546
+ });
547
+ const isXSScreen = useMediaQuery(
548
+ (theme) => theme.breakpoints.down("xs")
549
+ );
550
+ if (!to || !allowed) {
551
+ return null;
552
+ }
553
+ return isXSScreen ? /* @__PURE__ */ React.createElement(
554
+ IconButton,
555
+ {
556
+ component: Link$1,
557
+ color: "primary",
558
+ title,
559
+ size: "small",
560
+ to
561
+ },
562
+ /* @__PURE__ */ React.createElement(CreateComponentIcon, null)
563
+ ) : /* @__PURE__ */ React.createElement(Button, { component: Link$1, variant: "contained", color: "primary", to }, title);
564
+ };
565
+
566
+ const defaultGroup = {
567
+ title: "Templates",
568
+ filter: () => true
569
+ };
570
+ const createGroupsWithOther = (groups) => [
571
+ ...groups,
572
+ {
573
+ title: "Other Templates",
574
+ filter: (e) => ![...groups].some(({ filter }) => filter(e))
575
+ }
576
+ ];
577
+ const TemplateListPage = (props) => {
578
+ var _a, _b, _c;
579
+ const registerComponentLink = useRouteRef(registerComponentRouteRef);
580
+ const {
581
+ TemplateCardComponent,
582
+ groups: givenGroups = [],
583
+ templateFilter,
584
+ headerOptions
585
+ } = props;
586
+ const navigate = useNavigate();
587
+ const editorLink = useRouteRef(editRouteRef);
588
+ const actionsLink = useRouteRef(actionsRouteRef);
589
+ const tasksLink = useRouteRef(scaffolderListTaskRouteRef);
590
+ const viewTechDocsLink = useRouteRef(viewTechDocRouteRef);
591
+ const templateRoute = useRouteRef(selectedTemplateRouteRef);
592
+ const app = useApp();
593
+ const groups = givenGroups.length ? createGroupsWithOther(givenGroups) : [defaultGroup];
594
+ const scaffolderPageContextMenuProps = {
595
+ onEditorClicked: ((_a = props == null ? void 0 : props.contextMenu) == null ? void 0 : _a.editor) !== false ? () => navigate(editorLink()) : void 0,
596
+ onActionsClicked: ((_b = props == null ? void 0 : props.contextMenu) == null ? void 0 : _b.actions) !== false ? () => navigate(actionsLink()) : void 0,
597
+ onTasksClicked: ((_c = props == null ? void 0 : props.contextMenu) == null ? void 0 : _c.tasks) !== false ? () => navigate(tasksLink()) : void 0
598
+ };
599
+ const additionalLinksForEntity = useCallback(
600
+ (template) => {
601
+ var _a2, _b2;
602
+ const { kind, namespace, name } = parseEntityRef(
603
+ stringifyEntityRef(template)
604
+ );
605
+ return ((_a2 = template.metadata.annotations) == null ? void 0 : _a2["backstage.io/techdocs-ref"]) && viewTechDocsLink ? [
606
+ {
607
+ icon: (_b2 = app.getSystemIcon("docs")) != null ? _b2 : DocsIcon,
608
+ text: "View TechDocs",
609
+ url: viewTechDocsLink({ kind, namespace, name })
610
+ }
611
+ ] : [];
612
+ },
613
+ [app, viewTechDocsLink]
614
+ );
615
+ const onTemplateSelected = useCallback(
616
+ (template) => {
617
+ const { namespace, name } = parseEntityRef(stringifyEntityRef(template));
618
+ navigate(templateRoute({ namespace, templateName: name }));
619
+ },
620
+ [navigate, templateRoute]
621
+ );
622
+ return /* @__PURE__ */ React.createElement(EntityListProvider, null, /* @__PURE__ */ React.createElement(Page, { themeId: "home" }, /* @__PURE__ */ React.createElement(
623
+ Header,
624
+ {
625
+ pageTitleOverride: "Create a new component",
626
+ title: "Create a new component",
627
+ subtitle: "Create new software components using standard templates in your organization",
628
+ ...headerOptions
629
+ },
630
+ /* @__PURE__ */ React.createElement(ScaffolderPageContextMenu, { ...scaffolderPageContextMenuProps })
631
+ ), /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ContentHeader, { title: "Available Templates" }, /* @__PURE__ */ React.createElement(
632
+ RegisterExistingButton,
633
+ {
634
+ title: "Register Existing Component",
635
+ to: registerComponentLink && registerComponentLink()
636
+ }
637
+ ), /* @__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(
638
+ UserListPicker,
639
+ {
640
+ initialFilter: "all",
641
+ availableFilters: ["all", "starred"]
642
+ }
643
+ ), /* @__PURE__ */ React.createElement(TemplateCategoryPicker, null), /* @__PURE__ */ React.createElement(EntityTagPicker, null)), /* @__PURE__ */ React.createElement(CatalogFilterLayout.Content, null, /* @__PURE__ */ React.createElement(
644
+ TemplateGroups,
645
+ {
646
+ groups,
647
+ templateFilter,
648
+ TemplateCardComponent,
649
+ onTemplateSelected,
650
+ additionalLinksForEntity
651
+ }
652
+ ))))));
653
+ };
654
+
655
+ const TemplateWizardPage = (props) => {
656
+ const rootRef = useRouteRef(rootRouteRef);
657
+ const taskRoute = useRouteRef(scaffolderTaskRouteRef);
658
+ const { secrets } = useTemplateSecrets();
659
+ const scaffolderApi = useApi(scaffolderApiRef);
660
+ const navigate = useNavigate();
661
+ const { templateName, namespace } = useRouteRefParams(
662
+ selectedTemplateRouteRef
663
+ );
664
+ const templateRef = stringifyEntityRef({
665
+ kind: "Template",
666
+ namespace,
667
+ name: templateName
668
+ });
669
+ const onCreate = async (values) => {
670
+ const { taskId } = await scaffolderApi.scaffold({
671
+ templateRef,
672
+ values,
673
+ secrets
674
+ });
675
+ navigate(taskRoute({ taskId }));
676
+ };
677
+ const onError = () => /* @__PURE__ */ React.createElement(Navigate, { to: rootRef() });
678
+ return /* @__PURE__ */ React.createElement(AnalyticsContext, { attributes: { entityRef: templateRef } }, /* @__PURE__ */ React.createElement(Page, { themeId: "website" }, /* @__PURE__ */ React.createElement(
679
+ Header,
680
+ {
681
+ pageTitleOverride: "Create a new component",
682
+ title: "Create a new component",
683
+ subtitle: "Create new software components using standard templates in your organization",
684
+ ...props.headerOptions
802
685
  }
803
- ), /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ListTaskPageContent, { ...props })));
686
+ ), /* @__PURE__ */ React.createElement(
687
+ Workflow,
688
+ {
689
+ namespace,
690
+ templateName,
691
+ onCreate,
692
+ components: props.components,
693
+ onError,
694
+ extensions: props.customFieldExtensions,
695
+ formProps: props.formProps,
696
+ layouts: props.layouts
697
+ }
698
+ )));
804
699
  };
805
700
 
806
701
  const showDirectoryPicker = window.showDirectoryPicker;
@@ -857,91 +752,113 @@ class WebFileSystemAccess {
857
752
  }
858
753
  }
859
754
 
860
- const useStyles$7 = makeStyles$1((theme) => ({
861
- introText: {
862
- textAlign: "center",
863
- marginTop: theme.spacing(2)
864
- },
865
- card: {
866
- position: "relative",
867
- maxWidth: 340,
868
- marginTop: theme.spacing(4),
869
- margin: theme.spacing(0, 2)
870
- },
871
- infoIcon: {
872
- position: "absolute",
873
- top: theme.spacing(1),
874
- right: theme.spacing(1)
755
+ const MAX_CONTENT_SIZE = 64 * 1024;
756
+ const CHUNK_SIZE = 32 * 1024;
757
+ const DryRunContext = createContext(void 0);
758
+ function base64EncodeContent(content) {
759
+ if (content.length > MAX_CONTENT_SIZE) {
760
+ return window.btoa("<file too large>");
875
761
  }
876
- }));
877
- function TemplateEditorIntro(props) {
878
- const classes = useStyles$7();
879
- const supportsLoad = WebFileSystemAccess.isSupported();
880
- const cardLoadLocal = /* @__PURE__ */ React.createElement(Card$1, { className: classes.card, elevation: 4 }, /* @__PURE__ */ React.createElement(
881
- CardActionArea,
882
- {
883
- disabled: !supportsLoad,
884
- onClick: () => {
885
- var _a;
886
- return (_a = props.onSelect) == null ? void 0 : _a.call(props, "local");
762
+ try {
763
+ return window.btoa(content);
764
+ } catch {
765
+ const decoder = new TextEncoder();
766
+ const buffer = decoder.encode(content);
767
+ const chunks = new Array();
768
+ for (let offset = 0; offset < buffer.length; offset += CHUNK_SIZE) {
769
+ chunks.push(
770
+ String.fromCharCode(...buffer.slice(offset, offset + CHUNK_SIZE))
771
+ );
772
+ }
773
+ return window.btoa(chunks.join(""));
774
+ }
775
+ }
776
+ function DryRunProvider(props) {
777
+ const scaffolderApi = useApi(scaffolderApiRef);
778
+ const [state, setState] = useState({
779
+ results: [],
780
+ selectedResult: void 0
781
+ });
782
+ const idRef = useRef(1);
783
+ const selectResult = useCallback((id) => {
784
+ setState((prevState) => {
785
+ const result = prevState.results.find((r) => r.id === id);
786
+ if (result === prevState.selectedResult) {
787
+ return prevState;
887
788
  }
888
- },
889
- /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(
890
- Typography$1,
891
- {
892
- variant: "h4",
893
- component: "h3",
894
- gutterBottom: true,
895
- color: supportsLoad ? void 0 : "textSecondary",
896
- style: { display: "flex", flexFlow: "row nowrap" }
897
- },
898
- "Load Template Directory"
899
- ), /* @__PURE__ */ React.createElement(
900
- Typography$1,
901
- {
902
- variant: "body1",
903
- color: supportsLoad ? void 0 : "textSecondary"
904
- },
905
- "Load a local template directory, allowing you to both edit and try executing your own template."
906
- ))
907
- ), !supportsLoad && /* @__PURE__ */ React.createElement("div", { className: classes.infoIcon }, /* @__PURE__ */ React.createElement(
908
- Tooltip,
909
- {
910
- placement: "top",
911
- title: "Only supported in some Chromium-based browsers"
912
- },
913
- /* @__PURE__ */ React.createElement(InfoOutlinedIcon, null)
914
- )));
915
- const cardFormEditor = /* @__PURE__ */ React.createElement(Card$1, { className: classes.card, elevation: 4 }, /* @__PURE__ */ React.createElement(CardActionArea, { onClick: () => {
916
- var _a;
917
- return (_a = props.onSelect) == null ? void 0 : _a.call(props, "form");
918
- } }, /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(Typography$1, { variant: "h4", component: "h3", gutterBottom: true }, "Edit Template Form"), /* @__PURE__ */ React.createElement(Typography$1, { variant: "body1" }, "Preview and edit a template form, either using a sample template or by loading a template from the catalog."))));
919
- const cardFieldExplorer = /* @__PURE__ */ React.createElement(Card$1, { className: classes.card, elevation: 4 }, /* @__PURE__ */ React.createElement(CardActionArea, { onClick: () => {
920
- var _a;
921
- return (_a = props.onSelect) == null ? void 0 : _a.call(props, "field-explorer");
922
- } }, /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(Typography$1, { variant: "h4", component: "h3", gutterBottom: true }, "Custom Field Explorer"), /* @__PURE__ */ React.createElement(Typography$1, { variant: "body1" }, "View and play around with available installed custom field extensions."))));
923
- return /* @__PURE__ */ React.createElement("div", { style: props.style }, /* @__PURE__ */ React.createElement(Typography$1, { variant: "h4", component: "h2", className: classes.introText }, "Get started by choosing one of the options below"), /* @__PURE__ */ React.createElement(
924
- "div",
925
- {
926
- style: {
927
- display: "flex",
928
- flexFlow: "row wrap",
929
- alignItems: "flex-start",
930
- justifyContent: "center",
931
- alignContent: "flex-start"
789
+ return {
790
+ results: prevState.results,
791
+ selectedResult: result
792
+ };
793
+ });
794
+ }, []);
795
+ const deleteResult = useCallback((id) => {
796
+ setState((prevState) => {
797
+ var _a;
798
+ const index = prevState.results.findIndex((r) => r.id === id);
799
+ if (index === -1) {
800
+ return prevState;
801
+ }
802
+ const newResults = prevState.results.slice();
803
+ const [deleted] = newResults.splice(index, 1);
804
+ return {
805
+ results: newResults,
806
+ selectedResult: ((_a = prevState.selectedResult) == null ? void 0 : _a.id) === deleted.id ? newResults[0] : prevState.selectedResult
807
+ };
808
+ });
809
+ }, []);
810
+ const execute = useCallback(
811
+ async (options) => {
812
+ if (!scaffolderApi.dryRun) {
813
+ throw new Error("Scaffolder API does not support dry-run");
932
814
  }
815
+ const parsed = yaml.parse(options.templateContent);
816
+ const response = await scaffolderApi.dryRun({
817
+ template: parsed,
818
+ values: options.values,
819
+ secrets: {},
820
+ directoryContents: options.files.map((file) => ({
821
+ path: file.path,
822
+ base64Content: base64EncodeContent(file.content)
823
+ }))
824
+ });
825
+ const result = {
826
+ ...response,
827
+ id: idRef.current++
828
+ };
829
+ setState((prevState) => {
830
+ var _a;
831
+ return {
832
+ results: [...prevState.results, result],
833
+ selectedResult: (_a = prevState.selectedResult) != null ? _a : result
834
+ };
835
+ });
933
836
  },
934
- supportsLoad && cardLoadLocal,
935
- cardFormEditor,
936
- !supportsLoad && cardLoadLocal,
937
- cardFieldExplorer
938
- ));
837
+ [scaffolderApi]
838
+ );
839
+ const dryRun = useMemo(
840
+ () => ({
841
+ ...state,
842
+ selectResult,
843
+ deleteResult,
844
+ execute
845
+ }),
846
+ [state, selectResult, deleteResult, execute]
847
+ );
848
+ return /* @__PURE__ */ React.createElement(DryRunContext.Provider, { value: dryRun }, props.children);
849
+ }
850
+ function useDryRun() {
851
+ const value = useContext(DryRunContext);
852
+ if (!value) {
853
+ throw new Error("must be used within a DryRunProvider");
854
+ }
855
+ return value;
939
856
  }
940
857
 
941
- var __defProp = Object.defineProperty;
942
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
943
- var __publicField = (obj, key, value) => {
944
- __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
858
+ var __defProp$1 = Object.defineProperty;
859
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
860
+ var __publicField$1 = (obj, key, value) => {
861
+ __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
945
862
  return value;
946
863
  };
947
864
  var __accessCheck = (obj, member, msg) => {
@@ -1026,7 +943,7 @@ class DirectoryEditorManager {
1026
943
  __privateAdd(this, _listeners, /* @__PURE__ */ new Set());
1027
944
  __privateAdd(this, _files, []);
1028
945
  __privateAdd(this, _selectedFile, void 0);
1029
- __publicField(this, "setSelectedFile", (path) => {
946
+ __publicField$1(this, "setSelectedFile", (path) => {
1030
947
  const prev = __privateGet(this, _selectedFile);
1031
948
  const next = __privateGet(this, _files).find((file) => file.path === path);
1032
949
  if (prev !== next) {
@@ -1118,110 +1035,299 @@ function DirectoryEditorProvider(props) {
1118
1035
  return /* @__PURE__ */ React.createElement(DirectoryEditorContext.Provider, { value: result }, props.children);
1119
1036
  }
1120
1037
 
1121
- const MAX_CONTENT_SIZE = 64 * 1024;
1122
- const CHUNK_SIZE = 32 * 1024;
1123
- const DryRunContext = createContext(void 0);
1124
- function base64EncodeContent(content) {
1125
- if (content.length > MAX_CONTENT_SIZE) {
1126
- return window.btoa("<file too large>");
1127
- }
1128
- try {
1129
- return window.btoa(content);
1130
- } catch {
1131
- const decoder = new TextEncoder();
1132
- const buffer = decoder.encode(content);
1133
- const chunks = new Array();
1134
- for (let offset = 0; offset < buffer.length; offset += CHUNK_SIZE) {
1135
- chunks.push(
1136
- String.fromCharCode(...buffer.slice(offset, offset + CHUNK_SIZE))
1137
- );
1138
- }
1139
- return window.btoa(chunks.join(""));
1038
+ var __defProp = Object.defineProperty;
1039
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1040
+ var __publicField = (obj, key, value) => {
1041
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
1042
+ return value;
1043
+ };
1044
+ const useStyles$d = makeStyles$1({
1045
+ containerWrapper: {
1046
+ position: "relative",
1047
+ width: "100%",
1048
+ height: "100%"
1049
+ },
1050
+ container: {
1051
+ position: "absolute",
1052
+ top: 0,
1053
+ bottom: 0,
1054
+ left: 0,
1055
+ right: 0,
1056
+ overflow: "auto"
1140
1057
  }
1141
- }
1142
- function DryRunProvider(props) {
1143
- const scaffolderApi = useApi(scaffolderApiRef);
1144
- const [state, setState] = useState({
1145
- results: [],
1146
- selectedResult: void 0
1147
- });
1148
- const idRef = useRef(1);
1149
- const selectResult = useCallback((id) => {
1150
- setState((prevState) => {
1151
- const result = prevState.results.find((r) => r.id === id);
1152
- if (result === prevState.selectedResult) {
1153
- return prevState;
1154
- }
1155
- return {
1156
- results: prevState.results,
1157
- selectedResult: result
1158
- };
1159
- });
1160
- }, []);
1161
- const deleteResult = useCallback((id) => {
1162
- setState((prevState) => {
1163
- var _a;
1164
- const index = prevState.results.findIndex((r) => r.id === id);
1165
- if (index === -1) {
1166
- return prevState;
1167
- }
1168
- const newResults = prevState.results.slice();
1169
- const [deleted] = newResults.splice(index, 1);
1170
- return {
1171
- results: newResults,
1172
- selectedResult: ((_a = prevState.selectedResult) == null ? void 0 : _a.id) === deleted.id ? newResults[0] : prevState.selectedResult
1173
- };
1058
+ });
1059
+ class ErrorBoundary extends Component {
1060
+ constructor() {
1061
+ super(...arguments);
1062
+ __publicField(this, "state", {
1063
+ shouldRender: true
1174
1064
  });
1175
- }, []);
1176
- const execute = useCallback(
1177
- async (options) => {
1178
- if (!scaffolderApi.dryRun) {
1179
- throw new Error("Scaffolder API does not support dry-run");
1065
+ }
1066
+ componentDidUpdate(prevProps) {
1067
+ if (prevProps.invalidator !== this.props.invalidator) {
1068
+ this.setState({ shouldRender: true });
1069
+ }
1070
+ }
1071
+ componentDidCatch(error) {
1072
+ this.props.setErrorText(error.message);
1073
+ this.setState({ shouldRender: false });
1074
+ }
1075
+ render() {
1076
+ return this.state.shouldRender ? this.props.children : null;
1077
+ }
1078
+ }
1079
+ function isJsonObject(value) {
1080
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1081
+ }
1082
+ function TemplateEditorForm(props) {
1083
+ const {
1084
+ content,
1085
+ contentIsSpec,
1086
+ onDryRun,
1087
+ setErrorText,
1088
+ fieldExtensions = [],
1089
+ layouts = []
1090
+ } = props;
1091
+ const classes = useStyles$d();
1092
+ const apiHolder = useApiHolder();
1093
+ const [steps, setSteps] = useState();
1094
+ const fields = useMemo(() => {
1095
+ return Object.fromEntries(
1096
+ fieldExtensions.map(({ name, component }) => [name, component])
1097
+ );
1098
+ }, [fieldExtensions]);
1099
+ useDebounce(
1100
+ () => {
1101
+ try {
1102
+ if (!content) {
1103
+ setSteps(void 0);
1104
+ return;
1105
+ }
1106
+ const parsed = yaml.parseAllDocuments(content).filter((c) => c).map((c) => c.toJSON())[0];
1107
+ if (!isJsonObject(parsed)) {
1108
+ setSteps(void 0);
1109
+ return;
1110
+ }
1111
+ let rootObj = parsed;
1112
+ if (!contentIsSpec) {
1113
+ const isTemplate = String(parsed.kind).toLocaleLowerCase("en-US") === "template";
1114
+ if (!isTemplate) {
1115
+ setSteps(void 0);
1116
+ return;
1117
+ }
1118
+ rootObj = isJsonObject(parsed.spec) ? parsed.spec : {};
1119
+ }
1120
+ const { parameters } = rootObj;
1121
+ if (!Array.isArray(parameters)) {
1122
+ setErrorText("Template parameters must be an array");
1123
+ setSteps(void 0);
1124
+ return;
1125
+ }
1126
+ const fieldValidators = Object.fromEntries(
1127
+ fieldExtensions.map(({ name, validation }) => [name, validation])
1128
+ );
1129
+ setErrorText();
1130
+ setSteps(
1131
+ parameters.flatMap(
1132
+ (param) => isJsonObject(param) ? [
1133
+ {
1134
+ title: String(param.title),
1135
+ schema: param,
1136
+ validate: createAsyncValidators(param, fieldValidators, {
1137
+ apiHolder
1138
+ })
1139
+ }
1140
+ ] : []
1141
+ )
1142
+ );
1143
+ } catch (e) {
1144
+ setErrorText(e.message);
1180
1145
  }
1181
- const parsed = yaml.parse(options.templateContent);
1182
- const response = await scaffolderApi.dryRun({
1183
- template: parsed,
1184
- values: options.values,
1185
- secrets: {},
1186
- directoryContents: options.files.map((file) => ({
1187
- path: file.path,
1188
- base64Content: base64EncodeContent(file.content)
1189
- }))
1190
- });
1191
- const result = {
1192
- ...response,
1193
- id: idRef.current++
1194
- };
1195
- setState((prevState) => {
1196
- var _a;
1197
- return {
1198
- results: [...prevState.results, result],
1199
- selectedResult: (_a = prevState.selectedResult) != null ? _a : result
1200
- };
1201
- });
1202
1146
  },
1203
- [scaffolderApi]
1147
+ 250,
1148
+ [contentIsSpec, content, apiHolder]
1204
1149
  );
1205
- const dryRun = useMemo(
1206
- () => ({
1207
- ...state,
1208
- selectResult,
1209
- deleteResult,
1210
- execute
1211
- }),
1212
- [state, selectResult, deleteResult, execute]
1150
+ if (!steps) {
1151
+ return null;
1152
+ }
1153
+ 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(
1154
+ Stepper,
1155
+ {
1156
+ manifest: { steps, title: "Template Editor" },
1157
+ extensions: fieldExtensions,
1158
+ components: fields,
1159
+ onCreate: async (options) => {
1160
+ await (onDryRun == null ? void 0 : onDryRun(options));
1161
+ },
1162
+ layouts
1163
+ }
1164
+ ))));
1165
+ }
1166
+ function TemplateEditorFormDirectoryEditorDryRun(props) {
1167
+ const { setErrorText, fieldExtensions = [], layouts } = props;
1168
+ const dryRun = useDryRun();
1169
+ const directoryEditor = useDirectoryEditor();
1170
+ const { selectedFile } = directoryEditor;
1171
+ const handleDryRun = async (data) => {
1172
+ if (!selectedFile) {
1173
+ return;
1174
+ }
1175
+ try {
1176
+ await dryRun.execute({
1177
+ templateContent: selectedFile.content,
1178
+ values: data,
1179
+ files: directoryEditor.files
1180
+ });
1181
+ setErrorText();
1182
+ } catch (e) {
1183
+ setErrorText(String(e.cause || e));
1184
+ throw e;
1185
+ }
1186
+ };
1187
+ const content = selectedFile && selectedFile.path.match(/\.ya?ml$/) ? selectedFile.content : void 0;
1188
+ return /* @__PURE__ */ React.createElement(
1189
+ TemplateEditorForm,
1190
+ {
1191
+ onDryRun: handleDryRun,
1192
+ fieldExtensions,
1193
+ setErrorText,
1194
+ content,
1195
+ layouts
1196
+ }
1213
1197
  );
1214
- return /* @__PURE__ */ React.createElement(DryRunContext.Provider, { value: dryRun }, props.children);
1215
1198
  }
1216
- function useDryRun() {
1217
- const value = useContext(DryRunContext);
1218
- if (!value) {
1219
- throw new Error("must be used within a DryRunProvider");
1199
+ TemplateEditorForm.DirectoryEditorDryRun = TemplateEditorFormDirectoryEditorDryRun;
1200
+
1201
+ const useStyles$c = makeStyles((theme) => ({
1202
+ root: {
1203
+ gridArea: "pageContent",
1204
+ display: "grid",
1205
+ gridTemplateAreas: `
1206
+ "controls controls"
1207
+ "fieldForm preview"
1208
+ `,
1209
+ gridTemplateRows: "auto 1fr",
1210
+ gridTemplateColumns: "1fr 1fr"
1211
+ },
1212
+ controls: {
1213
+ gridArea: "controls",
1214
+ display: "flex",
1215
+ flexFlow: "row nowrap",
1216
+ alignItems: "center",
1217
+ margin: theme.spacing(1)
1218
+ },
1219
+ fieldForm: {
1220
+ gridArea: "fieldForm"
1221
+ },
1222
+ preview: {
1223
+ gridArea: "preview"
1220
1224
  }
1221
- return value;
1222
- }
1225
+ }));
1226
+ const CustomFieldExplorer = ({
1227
+ customFieldExtensions = [],
1228
+ onClose
1229
+ }) => {
1230
+ var _a, _b;
1231
+ const classes = useStyles$c();
1232
+ const fieldOptions = customFieldExtensions.filter((field) => !!field.schema);
1233
+ const [selectedField, setSelectedField] = useState(fieldOptions[0]);
1234
+ const [fieldFormState, setFieldFormState] = useState({});
1235
+ const [refreshKey, setRefreshKey] = useState(Date.now());
1236
+ const sampleFieldTemplate = useMemo(
1237
+ () => {
1238
+ var _a2, _b2;
1239
+ return yaml.stringify({
1240
+ parameters: [
1241
+ {
1242
+ title: `${selectedField.name} Example`,
1243
+ properties: {
1244
+ [selectedField.name]: {
1245
+ type: (_b2 = (_a2 = selectedField.schema) == null ? void 0 : _a2.returnValue) == null ? void 0 : _b2.type,
1246
+ "ui:field": selectedField.name,
1247
+ "ui:options": fieldFormState
1248
+ }
1249
+ }
1250
+ }
1251
+ ]
1252
+ });
1253
+ },
1254
+ [fieldFormState, selectedField]
1255
+ );
1256
+ const fieldComponents = useMemo(() => {
1257
+ return Object.fromEntries(
1258
+ customFieldExtensions.map(({ name, component }) => [name, component])
1259
+ );
1260
+ }, [customFieldExtensions]);
1261
+ const handleSelectionChange = useCallback(
1262
+ (selection) => {
1263
+ setSelectedField(selection);
1264
+ setFieldFormState({});
1265
+ },
1266
+ [setFieldFormState, setSelectedField]
1267
+ );
1268
+ const handleFieldConfigChange = useCallback(
1269
+ (state) => {
1270
+ setFieldFormState(state);
1271
+ setRefreshKey(Date.now());
1272
+ },
1273
+ [setFieldFormState, setRefreshKey]
1274
+ );
1275
+ 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(
1276
+ Select,
1277
+ {
1278
+ value: selectedField,
1279
+ label: "Choose Custom Field Extension",
1280
+ labelId: "select-field-label",
1281
+ onChange: (e) => handleSelectionChange(e.target.value)
1282
+ },
1283
+ fieldOptions.map((option, idx) => /* @__PURE__ */ React.createElement(MenuItem, { key: idx, value: option }, option.name))
1284
+ )), /* @__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(
1285
+ Form,
1286
+ {
1287
+ showErrorList: false,
1288
+ fields: { ...fieldComponents },
1289
+ noHtml5Validate: true,
1290
+ formData: fieldFormState,
1291
+ formContext: { fieldFormState },
1292
+ onSubmit: (e) => handleFieldConfigChange(e.formData),
1293
+ validator,
1294
+ schema: ((_a = selectedField.schema) == null ? void 0 : _a.uiOptions) || {},
1295
+ experimental_defaultFormStateBehavior: {
1296
+ allOf: "populateDefaults"
1297
+ }
1298
+ },
1299
+ /* @__PURE__ */ React.createElement(
1300
+ Button$1,
1301
+ {
1302
+ variant: "contained",
1303
+ color: "primary",
1304
+ type: "submit",
1305
+ disabled: !((_b = selectedField.schema) == null ? void 0 : _b.uiOptions)
1306
+ },
1307
+ "Apply"
1308
+ )
1309
+ )))), /* @__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(
1310
+ CodeMirror,
1311
+ {
1312
+ readOnly: true,
1313
+ theme: "dark",
1314
+ height: "100%",
1315
+ extensions: [StreamLanguage.define(yaml$1)],
1316
+ value: sampleFieldTemplate
1317
+ }
1318
+ ))), /* @__PURE__ */ React.createElement(
1319
+ TemplateEditorForm,
1320
+ {
1321
+ key: refreshKey,
1322
+ content: sampleFieldTemplate,
1323
+ contentIsSpec: true,
1324
+ fieldExtensions: customFieldExtensions,
1325
+ setErrorText: () => null
1326
+ }
1327
+ )));
1328
+ };
1223
1329
 
1224
- const useStyles$6 = makeStyles$1({
1330
+ const useStyles$b = makeStyles$1({
1225
1331
  root: {
1226
1332
  whiteSpace: "nowrap",
1227
1333
  overflowY: "auto"
@@ -1280,7 +1386,7 @@ function FileTreeItem({ entry }) {
1280
1386
  return /* @__PURE__ */ React.createElement(TreeItem, { nodeId: entry.path, label: entry.name }, entry.children.map((child) => /* @__PURE__ */ React.createElement(FileTreeItem, { key: child.path, entry: child })));
1281
1387
  }
1282
1388
  function FileBrowser(props) {
1283
- const classes = useStyles$6();
1389
+ const classes = useStyles$b();
1284
1390
  const fileTree = useMemo(
1285
1391
  () => parseFileEntires(props.filePaths),
1286
1392
  [props.filePaths]
@@ -1302,7 +1408,7 @@ function FileBrowser(props) {
1302
1408
  );
1303
1409
  }
1304
1410
 
1305
- const useStyles$5 = makeStyles((theme) => ({
1411
+ const useStyles$a = makeStyles((theme) => ({
1306
1412
  button: {
1307
1413
  padding: theme.spacing(1)
1308
1414
  },
@@ -1321,7 +1427,7 @@ const useStyles$5 = makeStyles((theme) => ({
1321
1427
  }));
1322
1428
  function TemplateEditorBrowser(props) {
1323
1429
  var _a, _b;
1324
- const classes = useStyles$5();
1430
+ const classes = useStyles$a();
1325
1431
  const directoryEditor = useDirectoryEditor();
1326
1432
  const changedFiles = directoryEditor.files.filter((file) => file.dirty);
1327
1433
  const handleClose = () => {
@@ -1338,22 +1444,22 @@ function TemplateEditorBrowser(props) {
1338
1444
  }
1339
1445
  props.onClose();
1340
1446
  };
1341
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: classes.buttons }, /* @__PURE__ */ React.createElement(Tooltip$1, { title: "Save all files" }, /* @__PURE__ */ React.createElement(
1342
- IconButton,
1447
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: classes.buttons }, /* @__PURE__ */ React.createElement(Tooltip, { title: "Save all files" }, /* @__PURE__ */ React.createElement(
1448
+ IconButton$1,
1343
1449
  {
1344
1450
  className: classes.button,
1345
1451
  disabled: directoryEditor.files.every((file) => !file.dirty),
1346
1452
  onClick: () => directoryEditor.save()
1347
1453
  },
1348
1454
  /* @__PURE__ */ React.createElement(SaveIcon, null)
1349
- )), /* @__PURE__ */ React.createElement(Tooltip$1, { title: "Reload directory" }, /* @__PURE__ */ React.createElement(
1350
- IconButton,
1455
+ )), /* @__PURE__ */ React.createElement(Tooltip, { title: "Reload directory" }, /* @__PURE__ */ React.createElement(
1456
+ IconButton$1,
1351
1457
  {
1352
1458
  className: classes.button,
1353
1459
  onClick: () => directoryEditor.reload()
1354
1460
  },
1355
1461
  /* @__PURE__ */ React.createElement(RefreshIcon, null)
1356
- )), /* @__PURE__ */ React.createElement("div", { className: classes.buttonsGap }), /* @__PURE__ */ React.createElement(Tooltip$1, { title: "Close directory" }, /* @__PURE__ */ React.createElement(IconButton, { className: classes.button, onClick: handleClose }, /* @__PURE__ */ React.createElement(CloseIcon, null)))), /* @__PURE__ */ React.createElement(Divider, { className: classes.buttonsDivider }), /* @__PURE__ */ React.createElement(
1462
+ )), /* @__PURE__ */ React.createElement("div", { className: classes.buttonsGap }), /* @__PURE__ */ React.createElement(Tooltip, { title: "Close directory" }, /* @__PURE__ */ React.createElement(IconButton$1, { className: classes.button, onClick: handleClose }, /* @__PURE__ */ React.createElement(CloseIcon, null)))), /* @__PURE__ */ React.createElement(Divider, { className: classes.buttonsDivider }), /* @__PURE__ */ React.createElement(
1357
1463
  FileBrowser,
1358
1464
  {
1359
1465
  selected: (_b = (_a = directoryEditor.selectedFile) == null ? void 0 : _a.path) != null ? _b : "",
@@ -1363,7 +1469,7 @@ function TemplateEditorBrowser(props) {
1363
1469
  ));
1364
1470
  }
1365
1471
 
1366
- const useStyles$4 = makeStyles((theme) => ({
1472
+ const useStyles$9 = makeStyles((theme) => ({
1367
1473
  container: {
1368
1474
  position: "relative",
1369
1475
  width: "100%",
@@ -1392,7 +1498,7 @@ const useStyles$4 = makeStyles((theme) => ({
1392
1498
  }));
1393
1499
  function TemplateEditorTextArea(props) {
1394
1500
  const { errorText } = props;
1395
- const classes = useStyles$4();
1501
+ const classes = useStyles$9();
1396
1502
  const panelExtension = useMemo(() => {
1397
1503
  if (!errorText) {
1398
1504
  return showPanel.of(null);
@@ -1421,8 +1527,8 @@ function TemplateEditorTextArea(props) {
1421
1527
  value: props.content,
1422
1528
  onChange: props.onUpdate
1423
1529
  }
1424
- ), (props.onSave || props.onReload) && /* @__PURE__ */ React.createElement("div", { className: classes.floatingButtons }, /* @__PURE__ */ React.createElement(Paper, null, props.onSave && /* @__PURE__ */ React.createElement(Tooltip$1, { title: "Save file" }, /* @__PURE__ */ React.createElement(
1425
- IconButton,
1530
+ ), (props.onSave || props.onReload) && /* @__PURE__ */ React.createElement("div", { className: classes.floatingButtons }, /* @__PURE__ */ React.createElement(Paper, null, props.onSave && /* @__PURE__ */ React.createElement(Tooltip, { title: "Save file" }, /* @__PURE__ */ React.createElement(
1531
+ IconButton$1,
1426
1532
  {
1427
1533
  className: classes.floatingButton,
1428
1534
  onClick: () => {
@@ -1431,8 +1537,8 @@ function TemplateEditorTextArea(props) {
1431
1537
  }
1432
1538
  },
1433
1539
  /* @__PURE__ */ React.createElement(SaveIcon, null)
1434
- )), props.onReload && /* @__PURE__ */ React.createElement(Tooltip$1, { title: "Reload file" }, /* @__PURE__ */ React.createElement(
1435
- IconButton,
1540
+ )), props.onReload && /* @__PURE__ */ React.createElement(Tooltip, { title: "Reload file" }, /* @__PURE__ */ React.createElement(
1541
+ IconButton$1,
1436
1542
  {
1437
1543
  className: classes.floatingButton,
1438
1544
  onClick: () => {
@@ -1476,7 +1582,7 @@ function downloadBlob(blob, name) {
1476
1582
  a.remove();
1477
1583
  }
1478
1584
 
1479
- const useStyles$3 = makeStyles$1((theme) => ({
1585
+ const useStyles$8 = makeStyles$1((theme) => ({
1480
1586
  root: {
1481
1587
  overflowY: "auto",
1482
1588
  background: theme.palette.background.default
@@ -1493,7 +1599,7 @@ const useStyles$3 = makeStyles$1((theme) => ({
1493
1599
  }
1494
1600
  }));
1495
1601
  function DryRunResultsList() {
1496
- const classes = useStyles$3();
1602
+ const classes = useStyles$8();
1497
1603
  const dryRun = useDryRun();
1498
1604
  return /* @__PURE__ */ React.createElement(List$1, { className: classes.root, dense: true }, dryRun.results.map((result) => {
1499
1605
  var _a;
@@ -1520,11 +1626,11 @@ function DryRunResultsList() {
1520
1626
  {
1521
1627
  className: failed ? classes.iconFailure : classes.iconSuccess
1522
1628
  },
1523
- failed ? /* @__PURE__ */ React.createElement(Cancel, null) : /* @__PURE__ */ React.createElement(Check, null)
1629
+ failed ? /* @__PURE__ */ React.createElement(Cancel, null) : /* @__PURE__ */ React.createElement(CheckIcon, null)
1524
1630
  ),
1525
1631
  /* @__PURE__ */ React.createElement(ListItemText$1, { primary: `Result ${result.id}` }),
1526
1632
  /* @__PURE__ */ React.createElement(ListItemSecondaryAction, null, /* @__PURE__ */ React.createElement(
1527
- IconButton$1,
1633
+ IconButton,
1528
1634
  {
1529
1635
  edge: "end",
1530
1636
  "aria-label": "download",
@@ -1534,7 +1640,7 @@ function DryRunResultsList() {
1534
1640
  },
1535
1641
  /* @__PURE__ */ React.createElement(DownloadIcon, null)
1536
1642
  ), /* @__PURE__ */ React.createElement(
1537
- IconButton$1,
1643
+ IconButton,
1538
1644
  {
1539
1645
  edge: "end",
1540
1646
  "aria-label": "delete",
@@ -1556,32 +1662,195 @@ async function downloadDirectoryContents(directoryContents, name) {
1556
1662
  const blob = await zip.generateAsync({ type: "blob" });
1557
1663
  downloadBlob(blob, name);
1558
1664
  }
1559
-
1560
- const useStyles$2 = makeStyles$1((theme) => ({
1561
- root: {
1562
- display: "grid",
1563
- gridTemplateColumns: "280px auto 3fr",
1564
- gridTemplateRows: "1fr"
1565
- },
1566
- child: {
1567
- overflowY: "auto",
1568
- height: "100%",
1569
- minHeight: 0
1570
- },
1571
- firstChild: {
1572
- background: theme.palette.background.paper
1573
- }
1574
- }));
1575
- function DryRunResultsSplitView(props) {
1576
- const classes = useStyles$2();
1577
- const childArray = Children.toArray(props.children);
1578
- if (childArray.length !== 2) {
1579
- throw new Error("must have exactly 2 children");
1665
+
1666
+ const useStyles$7 = makeStyles$1((theme) => ({
1667
+ root: {
1668
+ display: "grid",
1669
+ gridTemplateColumns: "280px auto 3fr",
1670
+ gridTemplateRows: "1fr"
1671
+ },
1672
+ child: {
1673
+ overflowY: "auto",
1674
+ height: "100%",
1675
+ minHeight: 0
1676
+ },
1677
+ firstChild: {
1678
+ background: theme.palette.background.paper
1679
+ }
1680
+ }));
1681
+ function DryRunResultsSplitView(props) {
1682
+ const classes = useStyles$7();
1683
+ const childArray = Children.toArray(props.children);
1684
+ if (childArray.length !== 2) {
1685
+ throw new Error("must have exactly 2 children");
1686
+ }
1687
+ return /* @__PURE__ */ React.createElement("div", { className: classes.root }, /* @__PURE__ */ React.createElement("div", { className: classNames(classes.child, classes.firstChild) }, childArray[0]), /* @__PURE__ */ React.createElement(Divider$1, { orientation: "horizontal" }), /* @__PURE__ */ React.createElement("div", { className: classes.child }, childArray[1]));
1688
+ }
1689
+
1690
+ const useStyles$6 = makeStyles({
1691
+ svgIcon: {
1692
+ display: "inline-block",
1693
+ "& svg": {
1694
+ display: "inline-block",
1695
+ fontSize: "inherit",
1696
+ verticalAlign: "baseline"
1697
+ }
1698
+ }
1699
+ });
1700
+ const IconLink = (props) => {
1701
+ const { href, text, Icon, ...linkProps } = props;
1702
+ const classes = useStyles$6();
1703
+ return /* @__PURE__ */ React.createElement(Grid, { container: true, direction: "row", spacing: 1 }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Typography, { component: "div", className: classes.svgIcon }, Icon ? /* @__PURE__ */ React.createElement(Icon, null) : /* @__PURE__ */ React.createElement(LanguageIcon, null))), /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Link, { to: href, ...linkProps }, text || href)));
1704
+ };
1705
+
1706
+ const TaskPageLinks = ({ output }) => {
1707
+ const { links = [] } = output;
1708
+ const app = useApp();
1709
+ const entityRoute = useRouteRef(entityRouteRef);
1710
+ const iconResolver = (key) => {
1711
+ var _a;
1712
+ return key ? (_a = app.getSystemIcon(key)) != null ? _a : LanguageIcon : LanguageIcon;
1713
+ };
1714
+ return /* @__PURE__ */ React.createElement(Box, { px: 3, pb: 3 }, links.filter(({ url, entityRef }) => url || entityRef).map(({ url, entityRef, title, icon }) => {
1715
+ if (entityRef) {
1716
+ const entityName = parseEntityRef(entityRef, {
1717
+ defaultKind: "<unknown>",
1718
+ defaultNamespace: "<unknown>"
1719
+ });
1720
+ const target = entityRoute(entityName);
1721
+ return { title, icon, url: target };
1722
+ }
1723
+ return { title, icon, url };
1724
+ }).map(({ url, title, icon }, i) => /* @__PURE__ */ React.createElement(
1725
+ IconLink,
1726
+ {
1727
+ key: `output-link-${i}`,
1728
+ href: url,
1729
+ text: title != null ? title : url,
1730
+ Icon: iconResolver(icon),
1731
+ target: "_blank"
1732
+ }
1733
+ )));
1734
+ };
1735
+
1736
+ const useStyles$5 = makeStyles(
1737
+ (theme) => createStyles({
1738
+ root: {
1739
+ width: "100%"
1740
+ },
1741
+ button: {
1742
+ marginBottom: theme.spacing(2),
1743
+ marginLeft: theme.spacing(2)
1744
+ },
1745
+ actionsContainer: {
1746
+ marginBottom: theme.spacing(2)
1747
+ },
1748
+ resetContainer: {
1749
+ padding: theme.spacing(3)
1750
+ },
1751
+ labelWrapper: {
1752
+ display: "flex",
1753
+ flex: 1,
1754
+ flexDirection: "row",
1755
+ justifyContent: "space-between"
1756
+ },
1757
+ stepWrapper: {
1758
+ width: "100%"
1759
+ }
1760
+ })
1761
+ );
1762
+ const useStepIconStyles = makeStyles(
1763
+ (theme) => createStyles({
1764
+ root: {
1765
+ color: theme.palette.text.disabled,
1766
+ display: "flex",
1767
+ height: 22,
1768
+ alignItems: "center"
1769
+ },
1770
+ completed: {
1771
+ color: theme.palette.status.ok
1772
+ },
1773
+ error: {
1774
+ color: theme.palette.status.error
1775
+ }
1776
+ })
1777
+ );
1778
+ const StepTimeTicker = ({ step }) => {
1779
+ const [time, setTime] = useState("");
1780
+ useInterval(() => {
1781
+ if (!step.startedAt) {
1782
+ setTime("");
1783
+ return;
1784
+ }
1785
+ const end = step.endedAt ? DateTime.fromISO(step.endedAt) : DateTime.local();
1786
+ const startedAt = DateTime.fromISO(step.startedAt);
1787
+ const formatted = Interval.fromDateTimes(startedAt, end).toDuration().valueOf();
1788
+ setTime(humanizeDuration(formatted, { round: true }));
1789
+ }, 1e3);
1790
+ return /* @__PURE__ */ React.createElement(Typography$1, { variant: "caption" }, time);
1791
+ };
1792
+ function TaskStepIconComponent(props) {
1793
+ const classes = useStepIconStyles();
1794
+ const { active, completed, error } = props;
1795
+ const getMiddle = () => {
1796
+ if (active) {
1797
+ return /* @__PURE__ */ React.createElement(CircularProgress, { size: "24px" });
1798
+ }
1799
+ if (completed) {
1800
+ return /* @__PURE__ */ React.createElement(CheckIcon, null);
1801
+ }
1802
+ if (error) {
1803
+ return /* @__PURE__ */ React.createElement(Cancel, null);
1804
+ }
1805
+ return /* @__PURE__ */ React.createElement(FiberManualRecordIcon, null);
1806
+ };
1807
+ return /* @__PURE__ */ React.createElement(
1808
+ "div",
1809
+ {
1810
+ className: classNames(classes.root, {
1811
+ [classes.completed]: completed,
1812
+ [classes.error]: error
1813
+ })
1814
+ },
1815
+ getMiddle()
1816
+ );
1817
+ }
1818
+ const TaskStatusStepper = memo(
1819
+ (props) => {
1820
+ const { steps, currentStepId, onUserStepChange } = props;
1821
+ const classes = useStyles$5(props);
1822
+ return /* @__PURE__ */ React.createElement("div", { className: classes.root }, /* @__PURE__ */ React.createElement(
1823
+ Stepper$1,
1824
+ {
1825
+ activeStep: steps.findIndex((s) => s.id === currentStepId),
1826
+ orientation: "vertical",
1827
+ nonLinear: true
1828
+ },
1829
+ steps.map((step, index) => {
1830
+ const isCancelled = step.status === "cancelled";
1831
+ const isActive = step.status === "processing";
1832
+ const isCompleted = step.status === "completed";
1833
+ const isFailed = step.status === "failed";
1834
+ const isSkipped = step.status === "skipped";
1835
+ return /* @__PURE__ */ React.createElement(Step, { key: String(index), expanded: true }, /* @__PURE__ */ React.createElement(StepButton, { onClick: () => onUserStepChange(step.id) }, /* @__PURE__ */ React.createElement(
1836
+ StepLabel,
1837
+ {
1838
+ StepIconProps: {
1839
+ completed: isCompleted,
1840
+ error: isFailed || isCancelled,
1841
+ active: isActive
1842
+ },
1843
+ StepIconComponent: TaskStepIconComponent,
1844
+ className: classes.stepWrapper
1845
+ },
1846
+ /* @__PURE__ */ React.createElement("div", { className: classes.labelWrapper }, /* @__PURE__ */ React.createElement(Typography$1, { variant: "subtitle2" }, step.name), isSkipped ? /* @__PURE__ */ React.createElement(Typography$1, { variant: "caption" }, "Skipped") : /* @__PURE__ */ React.createElement(StepTimeTicker, { step }))
1847
+ )));
1848
+ })
1849
+ ));
1580
1850
  }
1581
- return /* @__PURE__ */ React.createElement("div", { className: classes.root }, /* @__PURE__ */ React.createElement("div", { className: classNames(classes.child, classes.firstChild) }, childArray[0]), /* @__PURE__ */ React.createElement(Divider$1, { orientation: "horizontal" }), /* @__PURE__ */ React.createElement("div", { className: classes.child }, childArray[1]));
1582
- }
1851
+ );
1583
1852
 
1584
- const useStyles$1 = makeStyles$1({
1853
+ const useStyles$4 = makeStyles$1({
1585
1854
  root: {
1586
1855
  display: "flex",
1587
1856
  flexFlow: "column nowrap"
@@ -1607,7 +1876,7 @@ const useStyles$1 = makeStyles$1({
1607
1876
  }
1608
1877
  });
1609
1878
  function FilesContent() {
1610
- const classes = useStyles$1();
1879
+ const classes = useStyles$4();
1611
1880
  const { selectedResult } = useDryRun();
1612
1881
  const [selectedPath, setSelectedPath] = useState("");
1613
1882
  const selectedFile = selectedResult == null ? void 0 : selectedResult.directoryContents.find(
@@ -1683,7 +1952,7 @@ function LogContent() {
1683
1952
  }
1684
1953
  function OutputContent() {
1685
1954
  var _a, _b;
1686
- const classes = useStyles$1();
1955
+ const classes = useStyles$4();
1687
1956
  const { selectedResult } = useDryRun();
1688
1957
  if (!selectedResult) {
1689
1958
  return null;
@@ -1701,14 +1970,14 @@ function OutputContent() {
1701
1970
  ));
1702
1971
  }
1703
1972
  function DryRunResultsView() {
1704
- const classes = useStyles$1();
1973
+ const classes = useStyles$4();
1705
1974
  const [selectedTab, setSelectedTab] = useState(
1706
1975
  "files"
1707
1976
  );
1708
1977
  return /* @__PURE__ */ React.createElement("div", { className: classes.root }, /* @__PURE__ */ React.createElement(Tabs, { value: selectedTab, onChange: (_, v) => setSelectedTab(v) }, /* @__PURE__ */ React.createElement(Tab, { value: "files", label: "Files" }), /* @__PURE__ */ React.createElement(Tab, { value: "log", label: "Log" }), /* @__PURE__ */ React.createElement(Tab, { value: "output", label: "Output" })), /* @__PURE__ */ React.createElement(Divider$1, null), /* @__PURE__ */ React.createElement("div", { className: classes.contentWrapper }, /* @__PURE__ */ React.createElement("div", { className: classes.content }, selectedTab === "files" && /* @__PURE__ */ React.createElement(FilesContent, null), selectedTab === "log" && /* @__PURE__ */ React.createElement(LogContent, null), selectedTab === "output" && /* @__PURE__ */ React.createElement(OutputContent, null))));
1709
1978
  }
1710
1979
 
1711
- const useStyles = makeStyles$1((theme) => ({
1980
+ const useStyles$3 = makeStyles$1((theme) => ({
1712
1981
  header: {
1713
1982
  height: 48,
1714
1983
  minHeight: 0,
@@ -1727,7 +1996,7 @@ const useStyles = makeStyles$1((theme) => ({
1727
1996
  }
1728
1997
  }));
1729
1998
  function DryRunResults() {
1730
- const classes = useStyles();
1999
+ const classes = useStyles$3();
1731
2000
  const dryRun = useDryRun();
1732
2001
  const [expanded, setExpanded] = useState(false);
1733
2002
  const [hidden, setHidden] = useState(true);
@@ -1763,5 +2032,416 @@ function DryRunResults() {
1763
2032
  ));
1764
2033
  }
1765
2034
 
1766
- export { ActionsPage as A, DirectoryEditorProvider as D, ListTasksPage as L, TemplateEditorBrowser as T, WebFileSystemAccess as W, useDirectoryEditor as a, DryRunProvider as b, TemplateEditorTextArea as c, DryRunResults as d, TemplateEditorIntro as e, DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS as f, TaskPage as g, useDryRun as u };
1767
- //# sourceMappingURL=DryRunResults-c0cd5eb5.esm.js.map
2035
+ const useStyles$2 = makeStyles({
2036
+ // Reset and fix sizing to make sure scrolling behaves correctly
2037
+ root: {
2038
+ gridArea: "pageContent",
2039
+ display: "grid",
2040
+ gridTemplateAreas: `
2041
+ "browser editor preview"
2042
+ "results results results"
2043
+ `,
2044
+ gridTemplateColumns: "1fr 3fr 2fr",
2045
+ gridTemplateRows: "1fr auto"
2046
+ },
2047
+ browser: {
2048
+ gridArea: "browser",
2049
+ overflow: "auto"
2050
+ },
2051
+ editor: {
2052
+ gridArea: "editor",
2053
+ overflow: "auto"
2054
+ },
2055
+ preview: {
2056
+ gridArea: "preview",
2057
+ overflow: "auto"
2058
+ },
2059
+ results: {
2060
+ gridArea: "results"
2061
+ }
2062
+ });
2063
+ const TemplateEditor = (props) => {
2064
+ const classes = useStyles$2();
2065
+ const [errorText, setErrorText] = useState();
2066
+ 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(
2067
+ TemplateEditorForm.DirectoryEditorDryRun,
2068
+ {
2069
+ setErrorText,
2070
+ fieldExtensions: props.fieldExtensions,
2071
+ layouts: props.layouts
2072
+ }
2073
+ )), /* @__PURE__ */ React.createElement("section", { className: classes.results }, /* @__PURE__ */ React.createElement(DryRunResults, null)))));
2074
+ };
2075
+
2076
+ const EXAMPLE_TEMPLATE_PARAMS_YAML = `# Edit the template parameters below to see how they will render in the scaffolder form UI
2077
+ parameters:
2078
+ - title: Fill in some steps
2079
+ required:
2080
+ - name
2081
+ properties:
2082
+ name:
2083
+ title: Name
2084
+ type: string
2085
+ description: Unique name of the component
2086
+ owner:
2087
+ title: Owner
2088
+ type: string
2089
+ description: Owner of the component
2090
+ ui:field: OwnerPicker
2091
+ ui:options:
2092
+ catalogFilter:
2093
+ kind: Group
2094
+ - title: Choose a location
2095
+ required:
2096
+ - repoUrl
2097
+ properties:
2098
+ repoUrl:
2099
+ title: Repository Location
2100
+ type: string
2101
+ ui:field: RepoUrlPicker
2102
+ ui:options:
2103
+ allowedHosts:
2104
+ - github.com
2105
+ steps:
2106
+ - id: fetch-base
2107
+ name: Fetch Base
2108
+ action: fetch:template
2109
+ input:
2110
+ url: ./template
2111
+ values:
2112
+ name: \${{parameters.name}}
2113
+ `;
2114
+ const useStyles$1 = makeStyles((theme) => ({
2115
+ root: {
2116
+ gridArea: "pageContent",
2117
+ display: "grid",
2118
+ gridTemplateAreas: `
2119
+ "controls controls"
2120
+ "textArea preview"
2121
+ `,
2122
+ gridTemplateRows: "auto 1fr",
2123
+ gridTemplateColumns: "1fr 1fr"
2124
+ },
2125
+ controls: {
2126
+ gridArea: "controls",
2127
+ display: "flex",
2128
+ flexFlow: "row nowrap",
2129
+ alignItems: "center",
2130
+ margin: theme.spacing(1)
2131
+ },
2132
+ textArea: {
2133
+ gridArea: "textArea"
2134
+ },
2135
+ preview: {
2136
+ gridArea: "preview"
2137
+ }
2138
+ }));
2139
+ const TemplateFormPreviewer = ({
2140
+ defaultPreviewTemplate = EXAMPLE_TEMPLATE_PARAMS_YAML,
2141
+ customFieldExtensions = [],
2142
+ onClose,
2143
+ layouts = []
2144
+ }) => {
2145
+ const classes = useStyles$1();
2146
+ const alertApi = useApi(alertApiRef);
2147
+ const catalogApi = useApi(catalogApiRef);
2148
+ const [selectedTemplate, setSelectedTemplate] = useState("");
2149
+ const [errorText, setErrorText] = useState();
2150
+ const [templateOptions, setTemplateOptions] = useState([]);
2151
+ const [templateYaml, setTemplateYaml] = useState(defaultPreviewTemplate);
2152
+ const { loading } = useAsync(
2153
+ () => catalogApi.getEntities({
2154
+ filter: { kind: "template" },
2155
+ fields: [
2156
+ "kind",
2157
+ "metadata.namespace",
2158
+ "metadata.name",
2159
+ "metadata.title",
2160
+ "spec.parameters",
2161
+ "spec.steps",
2162
+ "spec.output"
2163
+ ]
2164
+ }).then(
2165
+ ({ items }) => setTemplateOptions(
2166
+ items.map((template) => {
2167
+ var _a;
2168
+ return {
2169
+ label: (_a = template.metadata.title) != null ? _a : humanizeEntityRef(template, { defaultKind: "template" }),
2170
+ value: template
2171
+ };
2172
+ })
2173
+ )
2174
+ ).catch(
2175
+ (e) => alertApi.post({
2176
+ message: `Error loading exisiting templates: ${e.message}`,
2177
+ severity: "error"
2178
+ })
2179
+ ),
2180
+ [catalogApi]
2181
+ );
2182
+ const handleSelectChange = useCallback(
2183
+ // TODO(Rugvip): Afaik this should be Entity, but didn't want to make runtime changes while fixing types
2184
+ (selected) => {
2185
+ setSelectedTemplate(selected);
2186
+ setTemplateYaml(yaml.stringify(selected.spec));
2187
+ },
2188
+ [setTemplateYaml]
2189
+ );
2190
+ 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(
2191
+ Select,
2192
+ {
2193
+ value: selectedTemplate,
2194
+ label: "Load Existing Template",
2195
+ labelId: "select-template-label",
2196
+ onChange: (e) => handleSelectChange(e.target.value)
2197
+ },
2198
+ templateOptions.map((option, idx) => /* @__PURE__ */ React.createElement(MenuItem, { key: idx, value: option.value }, option.label))
2199
+ )), /* @__PURE__ */ React.createElement(IconButton$1, { size: "medium", onClick: onClose }, /* @__PURE__ */ React.createElement(CloseIcon, null))), /* @__PURE__ */ React.createElement("div", { className: classes.textArea }, /* @__PURE__ */ React.createElement(
2200
+ TemplateEditorTextArea,
2201
+ {
2202
+ content: templateYaml,
2203
+ onUpdate: setTemplateYaml,
2204
+ errorText
2205
+ }
2206
+ )), /* @__PURE__ */ React.createElement("div", { className: classes.preview }, /* @__PURE__ */ React.createElement(
2207
+ TemplateEditorForm,
2208
+ {
2209
+ content: templateYaml,
2210
+ contentIsSpec: true,
2211
+ fieldExtensions: customFieldExtensions,
2212
+ setErrorText,
2213
+ layouts
2214
+ }
2215
+ ))));
2216
+ };
2217
+
2218
+ const useStyles = makeStyles$1((theme) => ({
2219
+ introText: {
2220
+ textAlign: "center",
2221
+ marginTop: theme.spacing(2)
2222
+ },
2223
+ card: {
2224
+ position: "relative",
2225
+ maxWidth: 340,
2226
+ marginTop: theme.spacing(4),
2227
+ margin: theme.spacing(0, 2)
2228
+ },
2229
+ infoIcon: {
2230
+ position: "absolute",
2231
+ top: theme.spacing(1),
2232
+ right: theme.spacing(1)
2233
+ }
2234
+ }));
2235
+ function TemplateEditorIntro(props) {
2236
+ const classes = useStyles();
2237
+ const supportsLoad = WebFileSystemAccess.isSupported();
2238
+ const cardLoadLocal = /* @__PURE__ */ React.createElement(Card$1, { className: classes.card, elevation: 4 }, /* @__PURE__ */ React.createElement(
2239
+ CardActionArea,
2240
+ {
2241
+ disabled: !supportsLoad,
2242
+ onClick: () => {
2243
+ var _a;
2244
+ return (_a = props.onSelect) == null ? void 0 : _a.call(props, "local");
2245
+ }
2246
+ },
2247
+ /* @__PURE__ */ React.createElement(CardContent$1, null, /* @__PURE__ */ React.createElement(
2248
+ Typography$1,
2249
+ {
2250
+ variant: "h4",
2251
+ component: "h3",
2252
+ gutterBottom: true,
2253
+ color: supportsLoad ? void 0 : "textSecondary",
2254
+ style: { display: "flex", flexFlow: "row nowrap" }
2255
+ },
2256
+ "Load Template Directory"
2257
+ ), /* @__PURE__ */ React.createElement(
2258
+ Typography$1,
2259
+ {
2260
+ variant: "body1",
2261
+ color: supportsLoad ? void 0 : "textSecondary"
2262
+ },
2263
+ "Load a local template directory, allowing you to both edit and try executing your own template."
2264
+ ))
2265
+ ), !supportsLoad && /* @__PURE__ */ React.createElement("div", { className: classes.infoIcon }, /* @__PURE__ */ React.createElement(
2266
+ Tooltip$1,
2267
+ {
2268
+ placement: "top",
2269
+ title: "Only supported in some Chromium-based browsers"
2270
+ },
2271
+ /* @__PURE__ */ React.createElement(InfoOutlinedIcon, null)
2272
+ )));
2273
+ const cardFormEditor = /* @__PURE__ */ React.createElement(Card$1, { className: classes.card, elevation: 4 }, /* @__PURE__ */ React.createElement(CardActionArea, { onClick: () => {
2274
+ var _a;
2275
+ return (_a = props.onSelect) == null ? void 0 : _a.call(props, "form");
2276
+ } }, /* @__PURE__ */ React.createElement(CardContent$1, null, /* @__PURE__ */ React.createElement(Typography$1, { variant: "h4", component: "h3", gutterBottom: true }, "Edit Template Form"), /* @__PURE__ */ React.createElement(Typography$1, { variant: "body1" }, "Preview and edit a template form, either using a sample template or by loading a template from the catalog."))));
2277
+ const cardFieldExplorer = /* @__PURE__ */ React.createElement(Card$1, { className: classes.card, elevation: 4 }, /* @__PURE__ */ React.createElement(CardActionArea, { onClick: () => {
2278
+ var _a;
2279
+ return (_a = props.onSelect) == null ? void 0 : _a.call(props, "field-explorer");
2280
+ } }, /* @__PURE__ */ React.createElement(CardContent$1, null, /* @__PURE__ */ React.createElement(Typography$1, { variant: "h4", component: "h3", gutterBottom: true }, "Custom Field Explorer"), /* @__PURE__ */ React.createElement(Typography$1, { variant: "body1" }, "View and play around with available installed custom field extensions."))));
2281
+ return /* @__PURE__ */ React.createElement("div", { style: props.style }, /* @__PURE__ */ React.createElement(Typography$1, { variant: "h4", component: "h2", className: classes.introText }, "Get started by choosing one of the options below"), /* @__PURE__ */ React.createElement(
2282
+ "div",
2283
+ {
2284
+ style: {
2285
+ display: "flex",
2286
+ flexFlow: "row wrap",
2287
+ alignItems: "flex-start",
2288
+ justifyContent: "center",
2289
+ alignContent: "flex-start"
2290
+ }
2291
+ },
2292
+ supportsLoad && cardLoadLocal,
2293
+ cardFormEditor,
2294
+ !supportsLoad && cardLoadLocal,
2295
+ cardFieldExplorer
2296
+ ));
2297
+ }
2298
+
2299
+ function TemplateEditorPage(props) {
2300
+ const [selection, setSelection] = useState();
2301
+ let content = null;
2302
+ if ((selection == null ? void 0 : selection.type) === "local") {
2303
+ content = /* @__PURE__ */ React.createElement(
2304
+ TemplateEditor,
2305
+ {
2306
+ directory: selection.directory,
2307
+ fieldExtensions: props.customFieldExtensions,
2308
+ onClose: () => setSelection(void 0),
2309
+ layouts: props.layouts
2310
+ }
2311
+ );
2312
+ } else if ((selection == null ? void 0 : selection.type) === "form") {
2313
+ content = /* @__PURE__ */ React.createElement(
2314
+ TemplateFormPreviewer,
2315
+ {
2316
+ defaultPreviewTemplate: props.defaultPreviewTemplate,
2317
+ customFieldExtensions: props.customFieldExtensions,
2318
+ onClose: () => setSelection(void 0),
2319
+ layouts: props.layouts
2320
+ }
2321
+ );
2322
+ } else if ((selection == null ? void 0 : selection.type) === "field-explorer") {
2323
+ content = /* @__PURE__ */ React.createElement(
2324
+ CustomFieldExplorer,
2325
+ {
2326
+ customFieldExtensions: props.customFieldExtensions,
2327
+ onClose: () => setSelection(void 0)
2328
+ }
2329
+ );
2330
+ } else {
2331
+ content = /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(
2332
+ TemplateEditorIntro,
2333
+ {
2334
+ onSelect: (option) => {
2335
+ if (option === "local") {
2336
+ WebFileSystemAccess.requestDirectoryAccess().then((directory) => setSelection({ type: "local", directory })).catch(() => {
2337
+ });
2338
+ } else if (option === "form") {
2339
+ setSelection({ type: "form" });
2340
+ } else if (option === "field-explorer") {
2341
+ setSelection({ type: "field-explorer" });
2342
+ }
2343
+ }
2344
+ }
2345
+ ));
2346
+ }
2347
+ return /* @__PURE__ */ React.createElement(Page, { themeId: "home" }, /* @__PURE__ */ React.createElement(
2348
+ Header,
2349
+ {
2350
+ title: "Template Editor",
2351
+ subtitle: "Edit, preview, and try out templates and template forms"
2352
+ }
2353
+ ), content);
2354
+ }
2355
+
2356
+ const Router = (props) => {
2357
+ const {
2358
+ components: {
2359
+ TemplateCardComponent,
2360
+ TaskPageComponent = OngoingTask,
2361
+ ReviewStepComponent,
2362
+ EXPERIMENTAL_TemplateOutputsComponent: TemplateOutputsComponent,
2363
+ EXPERIMENTAL_TemplateListPageComponent: TemplateListPageComponent = TemplateListPage,
2364
+ EXPERIMENTAL_TemplateWizardPageComponent: TemplateWizardPageComponent = TemplateWizardPage
2365
+ } = {}
2366
+ } = props;
2367
+ const outlet = useOutlet() || props.children;
2368
+ const customFieldExtensions = useCustomFieldExtensions(outlet);
2369
+ const fieldExtensions = [
2370
+ ...customFieldExtensions,
2371
+ ...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter(
2372
+ ({ name }) => !customFieldExtensions.some(
2373
+ (customFieldExtension) => customFieldExtension.name === name
2374
+ )
2375
+ )
2376
+ ];
2377
+ const customLayouts = useCustomLayouts(outlet);
2378
+ return /* @__PURE__ */ React.createElement(Routes, null, /* @__PURE__ */ React.createElement(
2379
+ Route,
2380
+ {
2381
+ path: "/",
2382
+ element: /* @__PURE__ */ React.createElement(
2383
+ TemplateListPageComponent,
2384
+ {
2385
+ TemplateCardComponent,
2386
+ contextMenu: props.contextMenu,
2387
+ groups: props.groups,
2388
+ templateFilter: props.templateFilter,
2389
+ headerOptions: props.headerOptions
2390
+ }
2391
+ )
2392
+ }
2393
+ ), /* @__PURE__ */ React.createElement(
2394
+ Route,
2395
+ {
2396
+ path: selectedTemplateRouteRef.path,
2397
+ element: /* @__PURE__ */ React.createElement(SecretsContextProvider, null, /* @__PURE__ */ React.createElement(
2398
+ TemplateWizardPageComponent,
2399
+ {
2400
+ headerOptions: props.headerOptions,
2401
+ customFieldExtensions: fieldExtensions,
2402
+ layouts: customLayouts,
2403
+ components: { ReviewStepComponent },
2404
+ formProps: props.formProps
2405
+ }
2406
+ ))
2407
+ }
2408
+ ), /* @__PURE__ */ React.createElement(
2409
+ Route,
2410
+ {
2411
+ path: scaffolderTaskRouteRef.path,
2412
+ element: /* @__PURE__ */ React.createElement(
2413
+ TaskPageComponent,
2414
+ {
2415
+ TemplateOutputsComponent
2416
+ }
2417
+ )
2418
+ }
2419
+ ), /* @__PURE__ */ React.createElement(
2420
+ Route,
2421
+ {
2422
+ path: editRouteRef.path,
2423
+ element: /* @__PURE__ */ React.createElement(SecretsContextProvider, null, /* @__PURE__ */ React.createElement(
2424
+ TemplateEditorPage,
2425
+ {
2426
+ customFieldExtensions: fieldExtensions,
2427
+ layouts: customLayouts
2428
+ }
2429
+ ))
2430
+ }
2431
+ ), /* @__PURE__ */ React.createElement(Route, { path: actionsRouteRef.path, element: /* @__PURE__ */ React.createElement(ActionsPage, null) }), /* @__PURE__ */ React.createElement(
2432
+ Route,
2433
+ {
2434
+ path: scaffolderListTaskRouteRef.path,
2435
+ element: /* @__PURE__ */ React.createElement(ListTasksPage, null)
2436
+ }
2437
+ ), /* @__PURE__ */ React.createElement(
2438
+ Route,
2439
+ {
2440
+ path: "*",
2441
+ element: /* @__PURE__ */ React.createElement(ErrorPage, { status: "404", statusMessage: "Page not found" })
2442
+ }
2443
+ ));
2444
+ };
2445
+
2446
+ export { Router };
2447
+ //# sourceMappingURL=index-445b4718.esm.js.map