@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,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 { useNavigate, Link as Link$1, 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-5a95edc0.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 { e as editRouteRef, c as scaffolderListTaskRouteRef, r as rootRouteRef, b as actionsRouteRef, d as registerComponentRouteRef, 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,17 @@ const ExamplesTable = (props) => {
393
213
  };
394
214
  const ActionsPage = () => {
395
215
  const api = useApi(scaffolderApiRef);
396
- const classes = useStyles$9();
216
+ const navigate = useNavigate();
217
+ const editorLink = useRouteRef(editRouteRef);
218
+ const tasksLink = useRouteRef(scaffolderListTaskRouteRef);
219
+ const createLink = useRouteRef(rootRouteRef);
220
+ const scaffolderPageContextMenuProps = {
221
+ onEditorClicked: () => navigate(editorLink()),
222
+ onActionsClicked: void 0,
223
+ onTasksClicked: () => navigate(tasksLink()),
224
+ onCreateClicked: () => navigate(createLink())
225
+ };
226
+ const classes = useStyles$f();
397
227
  const { loading, value, error } = useAsync(async () => {
398
228
  return api.listActions();
399
229
  });
@@ -497,95 +327,12 @@ const ActionsPage = () => {
497
327
  pageTitleOverride: "Create a New Component",
498
328
  title: "Installed actions",
499
329
  subtitle: "This is the collection of all installed actions"
500
- }
501
- ), /* @__PURE__ */ React.createElement(Content, null, items));
502
- };
503
-
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
330
  },
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
- );
331
+ /* @__PURE__ */ React.createElement(ScaffolderPageContextMenu, { ...scaffolderPageContextMenuProps })
332
+ ), /* @__PURE__ */ React.createElement(Content, null, items));
542
333
  };
543
334
 
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(
335
+ const useStyles$e = makeStyles(
589
336
  (theme) => ({
590
337
  root: {
591
338
  backgroundColor: "rgba(0, 0, 0, .11)",
@@ -634,7 +381,7 @@ function getFilterGroups() {
634
381
  }
635
382
  const OwnerListPicker = (props) => {
636
383
  const { filter, onSelectOwner } = props;
637
- const classes = useStyles$8();
384
+ const classes = useStyles$e();
638
385
  const filterGroups = getFilterGroups();
639
386
  return /* @__PURE__ */ React.createElement(Card, { className: classes.root }, filterGroups.map((group) => /* @__PURE__ */ React.createElement(Fragment, { key: group.name }, /* @__PURE__ */ React.createElement(
640
387
  Typography,
@@ -793,14 +540,184 @@ const ListTaskPageContent = (props) => {
793
540
  )));
794
541
  };
795
542
  const ListTasksPage = (props) => {
543
+ const navigate = useNavigate();
544
+ const editorLink = useRouteRef(editRouteRef);
545
+ const actionsLink = useRouteRef(actionsRouteRef);
546
+ const createLink = useRouteRef(rootRouteRef);
547
+ const scaffolderPageContextMenuProps = {
548
+ onEditorClicked: () => navigate(editorLink()),
549
+ onActionsClicked: () => navigate(actionsLink()),
550
+ onTasksClicked: void 0,
551
+ onCreateClicked: () => navigate(createLink())
552
+ };
796
553
  return /* @__PURE__ */ React.createElement(Page, { themeId: "home" }, /* @__PURE__ */ React.createElement(
797
554
  Header,
798
555
  {
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"
556
+ pageTitleOverride: "Templates Tasks",
557
+ title: "List template tasks",
558
+ subtitle: "All tasks that have been started"
559
+ },
560
+ /* @__PURE__ */ React.createElement(ScaffolderPageContextMenu, { ...scaffolderPageContextMenuProps })
561
+ ), /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ListTaskPageContent, { ...props })));
562
+ };
563
+
564
+ const RegisterExistingButton = (props) => {
565
+ const { title, to } = props;
566
+ const { allowed } = usePermission({
567
+ permission: catalogEntityCreatePermission
568
+ });
569
+ const isXSScreen = useMediaQuery(
570
+ (theme) => theme.breakpoints.down("xs")
571
+ );
572
+ if (!to || !allowed) {
573
+ return null;
574
+ }
575
+ return isXSScreen ? /* @__PURE__ */ React.createElement(
576
+ IconButton,
577
+ {
578
+ component: Link$1,
579
+ color: "primary",
580
+ title,
581
+ size: "small",
582
+ to
583
+ },
584
+ /* @__PURE__ */ React.createElement(CreateComponentIcon, null)
585
+ ) : /* @__PURE__ */ React.createElement(Button, { component: Link$1, variant: "contained", color: "primary", to }, title);
586
+ };
587
+
588
+ const defaultGroup = {
589
+ title: "Templates",
590
+ filter: () => true
591
+ };
592
+ const createGroupsWithOther = (groups) => [
593
+ ...groups,
594
+ {
595
+ title: "Other Templates",
596
+ filter: (e) => ![...groups].some(({ filter }) => filter(e))
597
+ }
598
+ ];
599
+ const TemplateListPage = (props) => {
600
+ var _a, _b, _c;
601
+ const registerComponentLink = useRouteRef(registerComponentRouteRef);
602
+ const {
603
+ TemplateCardComponent,
604
+ groups: givenGroups = [],
605
+ templateFilter,
606
+ headerOptions
607
+ } = props;
608
+ const navigate = useNavigate();
609
+ const editorLink = useRouteRef(editRouteRef);
610
+ const actionsLink = useRouteRef(actionsRouteRef);
611
+ const tasksLink = useRouteRef(scaffolderListTaskRouteRef);
612
+ const viewTechDocsLink = useRouteRef(viewTechDocRouteRef);
613
+ const templateRoute = useRouteRef(selectedTemplateRouteRef);
614
+ const app = useApp();
615
+ const groups = givenGroups.length ? createGroupsWithOther(givenGroups) : [defaultGroup];
616
+ const scaffolderPageContextMenuProps = {
617
+ onEditorClicked: ((_a = props == null ? void 0 : props.contextMenu) == null ? void 0 : _a.editor) !== false ? () => navigate(editorLink()) : void 0,
618
+ onActionsClicked: ((_b = props == null ? void 0 : props.contextMenu) == null ? void 0 : _b.actions) !== false ? () => navigate(actionsLink()) : void 0,
619
+ onTasksClicked: ((_c = props == null ? void 0 : props.contextMenu) == null ? void 0 : _c.tasks) !== false ? () => navigate(tasksLink()) : void 0
620
+ };
621
+ const additionalLinksForEntity = useCallback(
622
+ (template) => {
623
+ var _a2, _b2;
624
+ const { kind, namespace, name } = parseEntityRef(
625
+ stringifyEntityRef(template)
626
+ );
627
+ return ((_a2 = template.metadata.annotations) == null ? void 0 : _a2["backstage.io/techdocs-ref"]) && viewTechDocsLink ? [
628
+ {
629
+ icon: (_b2 = app.getSystemIcon("docs")) != null ? _b2 : DocsIcon,
630
+ text: "View TechDocs",
631
+ url: viewTechDocsLink({ kind, namespace, name })
632
+ }
633
+ ] : [];
634
+ },
635
+ [app, viewTechDocsLink]
636
+ );
637
+ const onTemplateSelected = useCallback(
638
+ (template) => {
639
+ const { namespace, name } = parseEntityRef(stringifyEntityRef(template));
640
+ navigate(templateRoute({ namespace, templateName: name }));
641
+ },
642
+ [navigate, templateRoute]
643
+ );
644
+ return /* @__PURE__ */ React.createElement(EntityListProvider, null, /* @__PURE__ */ React.createElement(Page, { themeId: "home" }, /* @__PURE__ */ React.createElement(
645
+ Header,
646
+ {
647
+ pageTitleOverride: "Create a new component",
648
+ title: "Create a new component",
649
+ subtitle: "Create new software components using standard templates in your organization",
650
+ ...headerOptions
651
+ },
652
+ /* @__PURE__ */ React.createElement(ScaffolderPageContextMenu, { ...scaffolderPageContextMenuProps })
653
+ ), /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ContentHeader, { title: "Available Templates" }, /* @__PURE__ */ React.createElement(
654
+ RegisterExistingButton,
655
+ {
656
+ title: "Register Existing Component",
657
+ to: registerComponentLink && registerComponentLink()
658
+ }
659
+ ), /* @__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(
660
+ UserListPicker,
661
+ {
662
+ initialFilter: "all",
663
+ availableFilters: ["all", "starred"]
664
+ }
665
+ ), /* @__PURE__ */ React.createElement(TemplateCategoryPicker, null), /* @__PURE__ */ React.createElement(EntityTagPicker, null)), /* @__PURE__ */ React.createElement(CatalogFilterLayout.Content, null, /* @__PURE__ */ React.createElement(
666
+ TemplateGroups,
667
+ {
668
+ groups,
669
+ templateFilter,
670
+ TemplateCardComponent,
671
+ onTemplateSelected,
672
+ additionalLinksForEntity
673
+ }
674
+ ))))));
675
+ };
676
+
677
+ const TemplateWizardPage = (props) => {
678
+ const rootRef = useRouteRef(rootRouteRef);
679
+ const taskRoute = useRouteRef(scaffolderTaskRouteRef);
680
+ const { secrets } = useTemplateSecrets();
681
+ const scaffolderApi = useApi(scaffolderApiRef);
682
+ const navigate = useNavigate();
683
+ const { templateName, namespace } = useRouteRefParams(
684
+ selectedTemplateRouteRef
685
+ );
686
+ const templateRef = stringifyEntityRef({
687
+ kind: "Template",
688
+ namespace,
689
+ name: templateName
690
+ });
691
+ const onCreate = async (values) => {
692
+ const { taskId } = await scaffolderApi.scaffold({
693
+ templateRef,
694
+ values,
695
+ secrets
696
+ });
697
+ navigate(taskRoute({ taskId }));
698
+ };
699
+ const onError = () => /* @__PURE__ */ React.createElement(Navigate, { to: rootRef() });
700
+ return /* @__PURE__ */ React.createElement(AnalyticsContext, { attributes: { entityRef: templateRef } }, /* @__PURE__ */ React.createElement(Page, { themeId: "website" }, /* @__PURE__ */ React.createElement(
701
+ Header,
702
+ {
703
+ pageTitleOverride: "Create a new component",
704
+ title: "Create a new component",
705
+ subtitle: "Create new software components using standard templates in your organization",
706
+ ...props.headerOptions
802
707
  }
803
- ), /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ListTaskPageContent, { ...props })));
708
+ ), /* @__PURE__ */ React.createElement(
709
+ Workflow,
710
+ {
711
+ namespace,
712
+ templateName,
713
+ onCreate,
714
+ components: props.components,
715
+ onError,
716
+ extensions: props.customFieldExtensions,
717
+ formProps: props.formProps,
718
+ layouts: props.layouts
719
+ }
720
+ )));
804
721
  };
805
722
 
806
723
  const showDirectoryPicker = window.showDirectoryPicker;
@@ -857,91 +774,113 @@ class WebFileSystemAccess {
857
774
  }
858
775
  }
859
776
 
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)
777
+ const MAX_CONTENT_SIZE = 64 * 1024;
778
+ const CHUNK_SIZE = 32 * 1024;
779
+ const DryRunContext = createContext(void 0);
780
+ function base64EncodeContent(content) {
781
+ if (content.length > MAX_CONTENT_SIZE) {
782
+ return window.btoa("<file too large>");
875
783
  }
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");
784
+ try {
785
+ return window.btoa(content);
786
+ } catch {
787
+ const decoder = new TextEncoder();
788
+ const buffer = decoder.encode(content);
789
+ const chunks = new Array();
790
+ for (let offset = 0; offset < buffer.length; offset += CHUNK_SIZE) {
791
+ chunks.push(
792
+ String.fromCharCode(...buffer.slice(offset, offset + CHUNK_SIZE))
793
+ );
794
+ }
795
+ return window.btoa(chunks.join(""));
796
+ }
797
+ }
798
+ function DryRunProvider(props) {
799
+ const scaffolderApi = useApi(scaffolderApiRef);
800
+ const [state, setState] = useState({
801
+ results: [],
802
+ selectedResult: void 0
803
+ });
804
+ const idRef = useRef(1);
805
+ const selectResult = useCallback((id) => {
806
+ setState((prevState) => {
807
+ const result = prevState.results.find((r) => r.id === id);
808
+ if (result === prevState.selectedResult) {
809
+ return prevState;
887
810
  }
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"
811
+ return {
812
+ results: prevState.results,
813
+ selectedResult: result
814
+ };
815
+ });
816
+ }, []);
817
+ const deleteResult = useCallback((id) => {
818
+ setState((prevState) => {
819
+ var _a;
820
+ const index = prevState.results.findIndex((r) => r.id === id);
821
+ if (index === -1) {
822
+ return prevState;
823
+ }
824
+ const newResults = prevState.results.slice();
825
+ const [deleted] = newResults.splice(index, 1);
826
+ return {
827
+ results: newResults,
828
+ selectedResult: ((_a = prevState.selectedResult) == null ? void 0 : _a.id) === deleted.id ? newResults[0] : prevState.selectedResult
829
+ };
830
+ });
831
+ }, []);
832
+ const execute = useCallback(
833
+ async (options) => {
834
+ if (!scaffolderApi.dryRun) {
835
+ throw new Error("Scaffolder API does not support dry-run");
932
836
  }
837
+ const parsed = yaml.parse(options.templateContent);
838
+ const response = await scaffolderApi.dryRun({
839
+ template: parsed,
840
+ values: options.values,
841
+ secrets: {},
842
+ directoryContents: options.files.map((file) => ({
843
+ path: file.path,
844
+ base64Content: base64EncodeContent(file.content)
845
+ }))
846
+ });
847
+ const result = {
848
+ ...response,
849
+ id: idRef.current++
850
+ };
851
+ setState((prevState) => {
852
+ var _a;
853
+ return {
854
+ results: [...prevState.results, result],
855
+ selectedResult: (_a = prevState.selectedResult) != null ? _a : result
856
+ };
857
+ });
933
858
  },
934
- supportsLoad && cardLoadLocal,
935
- cardFormEditor,
936
- !supportsLoad && cardLoadLocal,
937
- cardFieldExplorer
938
- ));
859
+ [scaffolderApi]
860
+ );
861
+ const dryRun = useMemo(
862
+ () => ({
863
+ ...state,
864
+ selectResult,
865
+ deleteResult,
866
+ execute
867
+ }),
868
+ [state, selectResult, deleteResult, execute]
869
+ );
870
+ return /* @__PURE__ */ React.createElement(DryRunContext.Provider, { value: dryRun }, props.children);
871
+ }
872
+ function useDryRun() {
873
+ const value = useContext(DryRunContext);
874
+ if (!value) {
875
+ throw new Error("must be used within a DryRunProvider");
876
+ }
877
+ return value;
939
878
  }
940
879
 
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);
880
+ var __defProp$1 = Object.defineProperty;
881
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
882
+ var __publicField$1 = (obj, key, value) => {
883
+ __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
945
884
  return value;
946
885
  };
947
886
  var __accessCheck = (obj, member, msg) => {
@@ -1026,7 +965,7 @@ class DirectoryEditorManager {
1026
965
  __privateAdd(this, _listeners, /* @__PURE__ */ new Set());
1027
966
  __privateAdd(this, _files, []);
1028
967
  __privateAdd(this, _selectedFile, void 0);
1029
- __publicField(this, "setSelectedFile", (path) => {
968
+ __publicField$1(this, "setSelectedFile", (path) => {
1030
969
  const prev = __privateGet(this, _selectedFile);
1031
970
  const next = __privateGet(this, _files).find((file) => file.path === path);
1032
971
  if (prev !== next) {
@@ -1118,110 +1057,299 @@ function DirectoryEditorProvider(props) {
1118
1057
  return /* @__PURE__ */ React.createElement(DirectoryEditorContext.Provider, { value: result }, props.children);
1119
1058
  }
1120
1059
 
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>");
1060
+ var __defProp = Object.defineProperty;
1061
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1062
+ var __publicField = (obj, key, value) => {
1063
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
1064
+ return value;
1065
+ };
1066
+ const useStyles$d = makeStyles$1({
1067
+ containerWrapper: {
1068
+ position: "relative",
1069
+ width: "100%",
1070
+ height: "100%"
1071
+ },
1072
+ container: {
1073
+ position: "absolute",
1074
+ top: 0,
1075
+ bottom: 0,
1076
+ left: 0,
1077
+ right: 0,
1078
+ overflow: "auto"
1127
1079
  }
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
- );
1080
+ });
1081
+ class ErrorBoundary extends Component {
1082
+ constructor() {
1083
+ super(...arguments);
1084
+ __publicField(this, "state", {
1085
+ shouldRender: true
1086
+ });
1087
+ }
1088
+ componentDidUpdate(prevProps) {
1089
+ if (prevProps.invalidator !== this.props.invalidator) {
1090
+ this.setState({ shouldRender: true });
1138
1091
  }
1139
- return window.btoa(chunks.join(""));
1140
1092
  }
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
- };
1174
- });
1175
- }, []);
1176
- const execute = useCallback(
1177
- async (options) => {
1178
- if (!scaffolderApi.dryRun) {
1179
- throw new Error("Scaffolder API does not support dry-run");
1093
+ componentDidCatch(error) {
1094
+ this.props.setErrorText(error.message);
1095
+ this.setState({ shouldRender: false });
1096
+ }
1097
+ render() {
1098
+ return this.state.shouldRender ? this.props.children : null;
1099
+ }
1100
+ }
1101
+ function isJsonObject(value) {
1102
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1103
+ }
1104
+ function TemplateEditorForm(props) {
1105
+ const {
1106
+ content,
1107
+ contentIsSpec,
1108
+ onDryRun,
1109
+ setErrorText,
1110
+ fieldExtensions = [],
1111
+ layouts = []
1112
+ } = props;
1113
+ const classes = useStyles$d();
1114
+ const apiHolder = useApiHolder();
1115
+ const [steps, setSteps] = useState();
1116
+ const fields = useMemo(() => {
1117
+ return Object.fromEntries(
1118
+ fieldExtensions.map(({ name, component }) => [name, component])
1119
+ );
1120
+ }, [fieldExtensions]);
1121
+ useDebounce(
1122
+ () => {
1123
+ try {
1124
+ if (!content) {
1125
+ setSteps(void 0);
1126
+ return;
1127
+ }
1128
+ const parsed = yaml.parseAllDocuments(content).filter((c) => c).map((c) => c.toJSON())[0];
1129
+ if (!isJsonObject(parsed)) {
1130
+ setSteps(void 0);
1131
+ return;
1132
+ }
1133
+ let rootObj = parsed;
1134
+ if (!contentIsSpec) {
1135
+ const isTemplate = String(parsed.kind).toLocaleLowerCase("en-US") === "template";
1136
+ if (!isTemplate) {
1137
+ setSteps(void 0);
1138
+ return;
1139
+ }
1140
+ rootObj = isJsonObject(parsed.spec) ? parsed.spec : {};
1141
+ }
1142
+ const { parameters } = rootObj;
1143
+ if (!Array.isArray(parameters)) {
1144
+ setErrorText("Template parameters must be an array");
1145
+ setSteps(void 0);
1146
+ return;
1147
+ }
1148
+ const fieldValidators = Object.fromEntries(
1149
+ fieldExtensions.map(({ name, validation }) => [name, validation])
1150
+ );
1151
+ setErrorText();
1152
+ setSteps(
1153
+ parameters.flatMap(
1154
+ (param) => isJsonObject(param) ? [
1155
+ {
1156
+ title: String(param.title),
1157
+ schema: param,
1158
+ validate: createAsyncValidators(param, fieldValidators, {
1159
+ apiHolder
1160
+ })
1161
+ }
1162
+ ] : []
1163
+ )
1164
+ );
1165
+ } catch (e) {
1166
+ setErrorText(e.message);
1180
1167
  }
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
1168
  },
1203
- [scaffolderApi]
1169
+ 250,
1170
+ [contentIsSpec, content, apiHolder]
1204
1171
  );
1205
- const dryRun = useMemo(
1206
- () => ({
1207
- ...state,
1208
- selectResult,
1209
- deleteResult,
1210
- execute
1211
- }),
1212
- [state, selectResult, deleteResult, execute]
1172
+ if (!steps) {
1173
+ return null;
1174
+ }
1175
+ 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(
1176
+ Stepper,
1177
+ {
1178
+ manifest: { steps, title: "Template Editor" },
1179
+ extensions: fieldExtensions,
1180
+ components: fields,
1181
+ onCreate: async (options) => {
1182
+ await (onDryRun == null ? void 0 : onDryRun(options));
1183
+ },
1184
+ layouts
1185
+ }
1186
+ ))));
1187
+ }
1188
+ function TemplateEditorFormDirectoryEditorDryRun(props) {
1189
+ const { setErrorText, fieldExtensions = [], layouts } = props;
1190
+ const dryRun = useDryRun();
1191
+ const directoryEditor = useDirectoryEditor();
1192
+ const { selectedFile } = directoryEditor;
1193
+ const handleDryRun = async (data) => {
1194
+ if (!selectedFile) {
1195
+ return;
1196
+ }
1197
+ try {
1198
+ await dryRun.execute({
1199
+ templateContent: selectedFile.content,
1200
+ values: data,
1201
+ files: directoryEditor.files
1202
+ });
1203
+ setErrorText();
1204
+ } catch (e) {
1205
+ setErrorText(String(e.cause || e));
1206
+ throw e;
1207
+ }
1208
+ };
1209
+ const content = selectedFile && selectedFile.path.match(/\.ya?ml$/) ? selectedFile.content : void 0;
1210
+ return /* @__PURE__ */ React.createElement(
1211
+ TemplateEditorForm,
1212
+ {
1213
+ onDryRun: handleDryRun,
1214
+ fieldExtensions,
1215
+ setErrorText,
1216
+ content,
1217
+ layouts
1218
+ }
1213
1219
  );
1214
- return /* @__PURE__ */ React.createElement(DryRunContext.Provider, { value: dryRun }, props.children);
1215
1220
  }
1216
- function useDryRun() {
1217
- const value = useContext(DryRunContext);
1218
- if (!value) {
1219
- throw new Error("must be used within a DryRunProvider");
1221
+ TemplateEditorForm.DirectoryEditorDryRun = TemplateEditorFormDirectoryEditorDryRun;
1222
+
1223
+ const useStyles$c = makeStyles((theme) => ({
1224
+ root: {
1225
+ gridArea: "pageContent",
1226
+ display: "grid",
1227
+ gridTemplateAreas: `
1228
+ "controls controls"
1229
+ "fieldForm preview"
1230
+ `,
1231
+ gridTemplateRows: "auto 1fr",
1232
+ gridTemplateColumns: "1fr 1fr"
1233
+ },
1234
+ controls: {
1235
+ gridArea: "controls",
1236
+ display: "flex",
1237
+ flexFlow: "row nowrap",
1238
+ alignItems: "center",
1239
+ margin: theme.spacing(1)
1240
+ },
1241
+ fieldForm: {
1242
+ gridArea: "fieldForm"
1243
+ },
1244
+ preview: {
1245
+ gridArea: "preview"
1220
1246
  }
1221
- return value;
1222
- }
1247
+ }));
1248
+ const CustomFieldExplorer = ({
1249
+ customFieldExtensions = [],
1250
+ onClose
1251
+ }) => {
1252
+ var _a, _b;
1253
+ const classes = useStyles$c();
1254
+ const fieldOptions = customFieldExtensions.filter((field) => !!field.schema);
1255
+ const [selectedField, setSelectedField] = useState(fieldOptions[0]);
1256
+ const [fieldFormState, setFieldFormState] = useState({});
1257
+ const [refreshKey, setRefreshKey] = useState(Date.now());
1258
+ const sampleFieldTemplate = useMemo(
1259
+ () => {
1260
+ var _a2, _b2;
1261
+ return yaml.stringify({
1262
+ parameters: [
1263
+ {
1264
+ title: `${selectedField.name} Example`,
1265
+ properties: {
1266
+ [selectedField.name]: {
1267
+ type: (_b2 = (_a2 = selectedField.schema) == null ? void 0 : _a2.returnValue) == null ? void 0 : _b2.type,
1268
+ "ui:field": selectedField.name,
1269
+ "ui:options": fieldFormState
1270
+ }
1271
+ }
1272
+ }
1273
+ ]
1274
+ });
1275
+ },
1276
+ [fieldFormState, selectedField]
1277
+ );
1278
+ const fieldComponents = useMemo(() => {
1279
+ return Object.fromEntries(
1280
+ customFieldExtensions.map(({ name, component }) => [name, component])
1281
+ );
1282
+ }, [customFieldExtensions]);
1283
+ const handleSelectionChange = useCallback(
1284
+ (selection) => {
1285
+ setSelectedField(selection);
1286
+ setFieldFormState({});
1287
+ },
1288
+ [setFieldFormState, setSelectedField]
1289
+ );
1290
+ const handleFieldConfigChange = useCallback(
1291
+ (state) => {
1292
+ setFieldFormState(state);
1293
+ setRefreshKey(Date.now());
1294
+ },
1295
+ [setFieldFormState, setRefreshKey]
1296
+ );
1297
+ 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(
1298
+ Select,
1299
+ {
1300
+ value: selectedField,
1301
+ label: "Choose Custom Field Extension",
1302
+ labelId: "select-field-label",
1303
+ onChange: (e) => handleSelectionChange(e.target.value)
1304
+ },
1305
+ fieldOptions.map((option, idx) => /* @__PURE__ */ React.createElement(MenuItem, { key: idx, value: option }, option.name))
1306
+ )), /* @__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(
1307
+ Form,
1308
+ {
1309
+ showErrorList: false,
1310
+ fields: { ...fieldComponents },
1311
+ noHtml5Validate: true,
1312
+ formData: fieldFormState,
1313
+ formContext: { fieldFormState },
1314
+ onSubmit: (e) => handleFieldConfigChange(e.formData),
1315
+ validator,
1316
+ schema: ((_a = selectedField.schema) == null ? void 0 : _a.uiOptions) || {},
1317
+ experimental_defaultFormStateBehavior: {
1318
+ allOf: "populateDefaults"
1319
+ }
1320
+ },
1321
+ /* @__PURE__ */ React.createElement(
1322
+ Button$1,
1323
+ {
1324
+ variant: "contained",
1325
+ color: "primary",
1326
+ type: "submit",
1327
+ disabled: !((_b = selectedField.schema) == null ? void 0 : _b.uiOptions)
1328
+ },
1329
+ "Apply"
1330
+ )
1331
+ )))), /* @__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(
1332
+ CodeMirror,
1333
+ {
1334
+ readOnly: true,
1335
+ theme: "dark",
1336
+ height: "100%",
1337
+ extensions: [StreamLanguage.define(yaml$1)],
1338
+ value: sampleFieldTemplate
1339
+ }
1340
+ ))), /* @__PURE__ */ React.createElement(
1341
+ TemplateEditorForm,
1342
+ {
1343
+ key: refreshKey,
1344
+ content: sampleFieldTemplate,
1345
+ contentIsSpec: true,
1346
+ fieldExtensions: customFieldExtensions,
1347
+ setErrorText: () => null
1348
+ }
1349
+ )));
1350
+ };
1223
1351
 
1224
- const useStyles$6 = makeStyles$1({
1352
+ const useStyles$b = makeStyles$1({
1225
1353
  root: {
1226
1354
  whiteSpace: "nowrap",
1227
1355
  overflowY: "auto"
@@ -1280,7 +1408,7 @@ function FileTreeItem({ entry }) {
1280
1408
  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
1409
  }
1282
1410
  function FileBrowser(props) {
1283
- const classes = useStyles$6();
1411
+ const classes = useStyles$b();
1284
1412
  const fileTree = useMemo(
1285
1413
  () => parseFileEntires(props.filePaths),
1286
1414
  [props.filePaths]
@@ -1302,7 +1430,7 @@ function FileBrowser(props) {
1302
1430
  );
1303
1431
  }
1304
1432
 
1305
- const useStyles$5 = makeStyles((theme) => ({
1433
+ const useStyles$a = makeStyles((theme) => ({
1306
1434
  button: {
1307
1435
  padding: theme.spacing(1)
1308
1436
  },
@@ -1321,7 +1449,7 @@ const useStyles$5 = makeStyles((theme) => ({
1321
1449
  }));
1322
1450
  function TemplateEditorBrowser(props) {
1323
1451
  var _a, _b;
1324
- const classes = useStyles$5();
1452
+ const classes = useStyles$a();
1325
1453
  const directoryEditor = useDirectoryEditor();
1326
1454
  const changedFiles = directoryEditor.files.filter((file) => file.dirty);
1327
1455
  const handleClose = () => {
@@ -1338,22 +1466,22 @@ function TemplateEditorBrowser(props) {
1338
1466
  }
1339
1467
  props.onClose();
1340
1468
  };
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,
1469
+ 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(
1470
+ IconButton$1,
1343
1471
  {
1344
1472
  className: classes.button,
1345
1473
  disabled: directoryEditor.files.every((file) => !file.dirty),
1346
1474
  onClick: () => directoryEditor.save()
1347
1475
  },
1348
1476
  /* @__PURE__ */ React.createElement(SaveIcon, null)
1349
- )), /* @__PURE__ */ React.createElement(Tooltip$1, { title: "Reload directory" }, /* @__PURE__ */ React.createElement(
1350
- IconButton,
1477
+ )), /* @__PURE__ */ React.createElement(Tooltip, { title: "Reload directory" }, /* @__PURE__ */ React.createElement(
1478
+ IconButton$1,
1351
1479
  {
1352
1480
  className: classes.button,
1353
1481
  onClick: () => directoryEditor.reload()
1354
1482
  },
1355
1483
  /* @__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(
1484
+ )), /* @__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
1485
  FileBrowser,
1358
1486
  {
1359
1487
  selected: (_b = (_a = directoryEditor.selectedFile) == null ? void 0 : _a.path) != null ? _b : "",
@@ -1363,7 +1491,7 @@ function TemplateEditorBrowser(props) {
1363
1491
  ));
1364
1492
  }
1365
1493
 
1366
- const useStyles$4 = makeStyles((theme) => ({
1494
+ const useStyles$9 = makeStyles((theme) => ({
1367
1495
  container: {
1368
1496
  position: "relative",
1369
1497
  width: "100%",
@@ -1392,7 +1520,7 @@ const useStyles$4 = makeStyles((theme) => ({
1392
1520
  }));
1393
1521
  function TemplateEditorTextArea(props) {
1394
1522
  const { errorText } = props;
1395
- const classes = useStyles$4();
1523
+ const classes = useStyles$9();
1396
1524
  const panelExtension = useMemo(() => {
1397
1525
  if (!errorText) {
1398
1526
  return showPanel.of(null);
@@ -1421,8 +1549,8 @@ function TemplateEditorTextArea(props) {
1421
1549
  value: props.content,
1422
1550
  onChange: props.onUpdate
1423
1551
  }
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,
1552
+ ), (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(
1553
+ IconButton$1,
1426
1554
  {
1427
1555
  className: classes.floatingButton,
1428
1556
  onClick: () => {
@@ -1431,8 +1559,8 @@ function TemplateEditorTextArea(props) {
1431
1559
  }
1432
1560
  },
1433
1561
  /* @__PURE__ */ React.createElement(SaveIcon, null)
1434
- )), props.onReload && /* @__PURE__ */ React.createElement(Tooltip$1, { title: "Reload file" }, /* @__PURE__ */ React.createElement(
1435
- IconButton,
1562
+ )), props.onReload && /* @__PURE__ */ React.createElement(Tooltip, { title: "Reload file" }, /* @__PURE__ */ React.createElement(
1563
+ IconButton$1,
1436
1564
  {
1437
1565
  className: classes.floatingButton,
1438
1566
  onClick: () => {
@@ -1476,7 +1604,7 @@ function downloadBlob(blob, name) {
1476
1604
  a.remove();
1477
1605
  }
1478
1606
 
1479
- const useStyles$3 = makeStyles$1((theme) => ({
1607
+ const useStyles$8 = makeStyles$1((theme) => ({
1480
1608
  root: {
1481
1609
  overflowY: "auto",
1482
1610
  background: theme.palette.background.default
@@ -1493,7 +1621,7 @@ const useStyles$3 = makeStyles$1((theme) => ({
1493
1621
  }
1494
1622
  }));
1495
1623
  function DryRunResultsList() {
1496
- const classes = useStyles$3();
1624
+ const classes = useStyles$8();
1497
1625
  const dryRun = useDryRun();
1498
1626
  return /* @__PURE__ */ React.createElement(List$1, { className: classes.root, dense: true }, dryRun.results.map((result) => {
1499
1627
  var _a;
@@ -1520,11 +1648,11 @@ function DryRunResultsList() {
1520
1648
  {
1521
1649
  className: failed ? classes.iconFailure : classes.iconSuccess
1522
1650
  },
1523
- failed ? /* @__PURE__ */ React.createElement(Cancel, null) : /* @__PURE__ */ React.createElement(Check, null)
1651
+ failed ? /* @__PURE__ */ React.createElement(Cancel, null) : /* @__PURE__ */ React.createElement(CheckIcon, null)
1524
1652
  ),
1525
1653
  /* @__PURE__ */ React.createElement(ListItemText$1, { primary: `Result ${result.id}` }),
1526
1654
  /* @__PURE__ */ React.createElement(ListItemSecondaryAction, null, /* @__PURE__ */ React.createElement(
1527
- IconButton$1,
1655
+ IconButton,
1528
1656
  {
1529
1657
  edge: "end",
1530
1658
  "aria-label": "download",
@@ -1534,7 +1662,7 @@ function DryRunResultsList() {
1534
1662
  },
1535
1663
  /* @__PURE__ */ React.createElement(DownloadIcon, null)
1536
1664
  ), /* @__PURE__ */ React.createElement(
1537
- IconButton$1,
1665
+ IconButton,
1538
1666
  {
1539
1667
  edge: "end",
1540
1668
  "aria-label": "delete",
@@ -1556,32 +1684,195 @@ async function downloadDirectoryContents(directoryContents, name) {
1556
1684
  const blob = await zip.generateAsync({ type: "blob" });
1557
1685
  downloadBlob(blob, name);
1558
1686
  }
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");
1687
+
1688
+ const useStyles$7 = makeStyles$1((theme) => ({
1689
+ root: {
1690
+ display: "grid",
1691
+ gridTemplateColumns: "280px auto 3fr",
1692
+ gridTemplateRows: "1fr"
1693
+ },
1694
+ child: {
1695
+ overflowY: "auto",
1696
+ height: "100%",
1697
+ minHeight: 0
1698
+ },
1699
+ firstChild: {
1700
+ background: theme.palette.background.paper
1701
+ }
1702
+ }));
1703
+ function DryRunResultsSplitView(props) {
1704
+ const classes = useStyles$7();
1705
+ const childArray = Children.toArray(props.children);
1706
+ if (childArray.length !== 2) {
1707
+ throw new Error("must have exactly 2 children");
1708
+ }
1709
+ 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]));
1710
+ }
1711
+
1712
+ const useStyles$6 = makeStyles({
1713
+ svgIcon: {
1714
+ display: "inline-block",
1715
+ "& svg": {
1716
+ display: "inline-block",
1717
+ fontSize: "inherit",
1718
+ verticalAlign: "baseline"
1719
+ }
1720
+ }
1721
+ });
1722
+ const IconLink = (props) => {
1723
+ const { href, text, Icon, ...linkProps } = props;
1724
+ const classes = useStyles$6();
1725
+ 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)));
1726
+ };
1727
+
1728
+ const TaskPageLinks = ({ output }) => {
1729
+ const { links = [] } = output;
1730
+ const app = useApp();
1731
+ const entityRoute = useRouteRef(entityRouteRef);
1732
+ const iconResolver = (key) => {
1733
+ var _a;
1734
+ return key ? (_a = app.getSystemIcon(key)) != null ? _a : LanguageIcon : LanguageIcon;
1735
+ };
1736
+ return /* @__PURE__ */ React.createElement(Box, { px: 3, pb: 3 }, links.filter(({ url, entityRef }) => url || entityRef).map(({ url, entityRef, title, icon }) => {
1737
+ if (entityRef) {
1738
+ const entityName = parseEntityRef(entityRef, {
1739
+ defaultKind: "<unknown>",
1740
+ defaultNamespace: "<unknown>"
1741
+ });
1742
+ const target = entityRoute(entityName);
1743
+ return { title, icon, url: target };
1744
+ }
1745
+ return { title, icon, url };
1746
+ }).map(({ url, title, icon }, i) => /* @__PURE__ */ React.createElement(
1747
+ IconLink,
1748
+ {
1749
+ key: `output-link-${i}`,
1750
+ href: url,
1751
+ text: title != null ? title : url,
1752
+ Icon: iconResolver(icon),
1753
+ target: "_blank"
1754
+ }
1755
+ )));
1756
+ };
1757
+
1758
+ const useStyles$5 = makeStyles(
1759
+ (theme) => createStyles({
1760
+ root: {
1761
+ width: "100%"
1762
+ },
1763
+ button: {
1764
+ marginBottom: theme.spacing(2),
1765
+ marginLeft: theme.spacing(2)
1766
+ },
1767
+ actionsContainer: {
1768
+ marginBottom: theme.spacing(2)
1769
+ },
1770
+ resetContainer: {
1771
+ padding: theme.spacing(3)
1772
+ },
1773
+ labelWrapper: {
1774
+ display: "flex",
1775
+ flex: 1,
1776
+ flexDirection: "row",
1777
+ justifyContent: "space-between"
1778
+ },
1779
+ stepWrapper: {
1780
+ width: "100%"
1781
+ }
1782
+ })
1783
+ );
1784
+ const useStepIconStyles = makeStyles(
1785
+ (theme) => createStyles({
1786
+ root: {
1787
+ color: theme.palette.text.disabled,
1788
+ display: "flex",
1789
+ height: 22,
1790
+ alignItems: "center"
1791
+ },
1792
+ completed: {
1793
+ color: theme.palette.status.ok
1794
+ },
1795
+ error: {
1796
+ color: theme.palette.status.error
1797
+ }
1798
+ })
1799
+ );
1800
+ const StepTimeTicker = ({ step }) => {
1801
+ const [time, setTime] = useState("");
1802
+ useInterval(() => {
1803
+ if (!step.startedAt) {
1804
+ setTime("");
1805
+ return;
1806
+ }
1807
+ const end = step.endedAt ? DateTime.fromISO(step.endedAt) : DateTime.local();
1808
+ const startedAt = DateTime.fromISO(step.startedAt);
1809
+ const formatted = Interval.fromDateTimes(startedAt, end).toDuration().valueOf();
1810
+ setTime(humanizeDuration(formatted, { round: true }));
1811
+ }, 1e3);
1812
+ return /* @__PURE__ */ React.createElement(Typography$1, { variant: "caption" }, time);
1813
+ };
1814
+ function TaskStepIconComponent(props) {
1815
+ const classes = useStepIconStyles();
1816
+ const { active, completed, error } = props;
1817
+ const getMiddle = () => {
1818
+ if (active) {
1819
+ return /* @__PURE__ */ React.createElement(CircularProgress, { size: "24px" });
1820
+ }
1821
+ if (completed) {
1822
+ return /* @__PURE__ */ React.createElement(CheckIcon, null);
1823
+ }
1824
+ if (error) {
1825
+ return /* @__PURE__ */ React.createElement(Cancel, null);
1826
+ }
1827
+ return /* @__PURE__ */ React.createElement(FiberManualRecordIcon, null);
1828
+ };
1829
+ return /* @__PURE__ */ React.createElement(
1830
+ "div",
1831
+ {
1832
+ className: classNames(classes.root, {
1833
+ [classes.completed]: completed,
1834
+ [classes.error]: error
1835
+ })
1836
+ },
1837
+ getMiddle()
1838
+ );
1839
+ }
1840
+ const TaskStatusStepper = memo(
1841
+ (props) => {
1842
+ const { steps, currentStepId, onUserStepChange } = props;
1843
+ const classes = useStyles$5(props);
1844
+ return /* @__PURE__ */ React.createElement("div", { className: classes.root }, /* @__PURE__ */ React.createElement(
1845
+ Stepper$1,
1846
+ {
1847
+ activeStep: steps.findIndex((s) => s.id === currentStepId),
1848
+ orientation: "vertical",
1849
+ nonLinear: true
1850
+ },
1851
+ steps.map((step, index) => {
1852
+ const isCancelled = step.status === "cancelled";
1853
+ const isActive = step.status === "processing";
1854
+ const isCompleted = step.status === "completed";
1855
+ const isFailed = step.status === "failed";
1856
+ const isSkipped = step.status === "skipped";
1857
+ return /* @__PURE__ */ React.createElement(Step, { key: String(index), expanded: true }, /* @__PURE__ */ React.createElement(StepButton, { onClick: () => onUserStepChange(step.id) }, /* @__PURE__ */ React.createElement(
1858
+ StepLabel,
1859
+ {
1860
+ StepIconProps: {
1861
+ completed: isCompleted,
1862
+ error: isFailed || isCancelled,
1863
+ active: isActive
1864
+ },
1865
+ StepIconComponent: TaskStepIconComponent,
1866
+ className: classes.stepWrapper
1867
+ },
1868
+ /* @__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 }))
1869
+ )));
1870
+ })
1871
+ ));
1580
1872
  }
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
- }
1873
+ );
1583
1874
 
1584
- const useStyles$1 = makeStyles$1({
1875
+ const useStyles$4 = makeStyles$1({
1585
1876
  root: {
1586
1877
  display: "flex",
1587
1878
  flexFlow: "column nowrap"
@@ -1607,7 +1898,7 @@ const useStyles$1 = makeStyles$1({
1607
1898
  }
1608
1899
  });
1609
1900
  function FilesContent() {
1610
- const classes = useStyles$1();
1901
+ const classes = useStyles$4();
1611
1902
  const { selectedResult } = useDryRun();
1612
1903
  const [selectedPath, setSelectedPath] = useState("");
1613
1904
  const selectedFile = selectedResult == null ? void 0 : selectedResult.directoryContents.find(
@@ -1683,7 +1974,7 @@ function LogContent() {
1683
1974
  }
1684
1975
  function OutputContent() {
1685
1976
  var _a, _b;
1686
- const classes = useStyles$1();
1977
+ const classes = useStyles$4();
1687
1978
  const { selectedResult } = useDryRun();
1688
1979
  if (!selectedResult) {
1689
1980
  return null;
@@ -1701,14 +1992,14 @@ function OutputContent() {
1701
1992
  ));
1702
1993
  }
1703
1994
  function DryRunResultsView() {
1704
- const classes = useStyles$1();
1995
+ const classes = useStyles$4();
1705
1996
  const [selectedTab, setSelectedTab] = useState(
1706
1997
  "files"
1707
1998
  );
1708
1999
  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
2000
  }
1710
2001
 
1711
- const useStyles = makeStyles$1((theme) => ({
2002
+ const useStyles$3 = makeStyles$1((theme) => ({
1712
2003
  header: {
1713
2004
  height: 48,
1714
2005
  minHeight: 0,
@@ -1727,7 +2018,7 @@ const useStyles = makeStyles$1((theme) => ({
1727
2018
  }
1728
2019
  }));
1729
2020
  function DryRunResults() {
1730
- const classes = useStyles();
2021
+ const classes = useStyles$3();
1731
2022
  const dryRun = useDryRun();
1732
2023
  const [expanded, setExpanded] = useState(false);
1733
2024
  const [hidden, setHidden] = useState(true);
@@ -1763,5 +2054,427 @@ function DryRunResults() {
1763
2054
  ));
1764
2055
  }
1765
2056
 
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
2057
+ const useStyles$2 = makeStyles({
2058
+ // Reset and fix sizing to make sure scrolling behaves correctly
2059
+ root: {
2060
+ gridArea: "pageContent",
2061
+ display: "grid",
2062
+ gridTemplateAreas: `
2063
+ "browser editor preview"
2064
+ "results results results"
2065
+ `,
2066
+ gridTemplateColumns: "1fr 3fr 2fr",
2067
+ gridTemplateRows: "1fr auto"
2068
+ },
2069
+ browser: {
2070
+ gridArea: "browser",
2071
+ overflow: "auto"
2072
+ },
2073
+ editor: {
2074
+ gridArea: "editor",
2075
+ overflow: "auto"
2076
+ },
2077
+ preview: {
2078
+ gridArea: "preview",
2079
+ overflow: "auto"
2080
+ },
2081
+ results: {
2082
+ gridArea: "results"
2083
+ }
2084
+ });
2085
+ const TemplateEditor = (props) => {
2086
+ const classes = useStyles$2();
2087
+ const [errorText, setErrorText] = useState();
2088
+ 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(
2089
+ TemplateEditorForm.DirectoryEditorDryRun,
2090
+ {
2091
+ setErrorText,
2092
+ fieldExtensions: props.fieldExtensions,
2093
+ layouts: props.layouts
2094
+ }
2095
+ )), /* @__PURE__ */ React.createElement("section", { className: classes.results }, /* @__PURE__ */ React.createElement(DryRunResults, null)))));
2096
+ };
2097
+
2098
+ const EXAMPLE_TEMPLATE_PARAMS_YAML = `# Edit the template parameters below to see how they will render in the scaffolder form UI
2099
+ parameters:
2100
+ - title: Fill in some steps
2101
+ required:
2102
+ - name
2103
+ properties:
2104
+ name:
2105
+ title: Name
2106
+ type: string
2107
+ description: Unique name of the component
2108
+ owner:
2109
+ title: Owner
2110
+ type: string
2111
+ description: Owner of the component
2112
+ ui:field: OwnerPicker
2113
+ ui:options:
2114
+ catalogFilter:
2115
+ kind: Group
2116
+ - title: Choose a location
2117
+ required:
2118
+ - repoUrl
2119
+ properties:
2120
+ repoUrl:
2121
+ title: Repository Location
2122
+ type: string
2123
+ ui:field: RepoUrlPicker
2124
+ ui:options:
2125
+ allowedHosts:
2126
+ - github.com
2127
+ steps:
2128
+ - id: fetch-base
2129
+ name: Fetch Base
2130
+ action: fetch:template
2131
+ input:
2132
+ url: ./template
2133
+ values:
2134
+ name: \${{parameters.name}}
2135
+ `;
2136
+ const useStyles$1 = makeStyles((theme) => ({
2137
+ root: {
2138
+ gridArea: "pageContent",
2139
+ display: "grid",
2140
+ gridTemplateAreas: `
2141
+ "controls controls"
2142
+ "textArea preview"
2143
+ `,
2144
+ gridTemplateRows: "auto 1fr",
2145
+ gridTemplateColumns: "1fr 1fr"
2146
+ },
2147
+ controls: {
2148
+ gridArea: "controls",
2149
+ display: "flex",
2150
+ flexFlow: "row nowrap",
2151
+ alignItems: "center",
2152
+ margin: theme.spacing(1)
2153
+ },
2154
+ textArea: {
2155
+ gridArea: "textArea"
2156
+ },
2157
+ preview: {
2158
+ gridArea: "preview"
2159
+ }
2160
+ }));
2161
+ const TemplateFormPreviewer = ({
2162
+ defaultPreviewTemplate = EXAMPLE_TEMPLATE_PARAMS_YAML,
2163
+ customFieldExtensions = [],
2164
+ onClose,
2165
+ layouts = []
2166
+ }) => {
2167
+ const classes = useStyles$1();
2168
+ const alertApi = useApi(alertApiRef);
2169
+ const catalogApi = useApi(catalogApiRef);
2170
+ const [selectedTemplate, setSelectedTemplate] = useState("");
2171
+ const [errorText, setErrorText] = useState();
2172
+ const [templateOptions, setTemplateOptions] = useState([]);
2173
+ const [templateYaml, setTemplateYaml] = useState(defaultPreviewTemplate);
2174
+ const { loading } = useAsync(
2175
+ () => catalogApi.getEntities({
2176
+ filter: { kind: "template" },
2177
+ fields: [
2178
+ "kind",
2179
+ "metadata.namespace",
2180
+ "metadata.name",
2181
+ "metadata.title",
2182
+ "spec.parameters",
2183
+ "spec.steps",
2184
+ "spec.output"
2185
+ ]
2186
+ }).then(
2187
+ ({ items }) => setTemplateOptions(
2188
+ items.map((template) => {
2189
+ var _a;
2190
+ return {
2191
+ label: (_a = template.metadata.title) != null ? _a : humanizeEntityRef(template, { defaultKind: "template" }),
2192
+ value: template
2193
+ };
2194
+ })
2195
+ )
2196
+ ).catch(
2197
+ (e) => alertApi.post({
2198
+ message: `Error loading exisiting templates: ${e.message}`,
2199
+ severity: "error"
2200
+ })
2201
+ ),
2202
+ [catalogApi]
2203
+ );
2204
+ const handleSelectChange = useCallback(
2205
+ // TODO(Rugvip): Afaik this should be Entity, but didn't want to make runtime changes while fixing types
2206
+ (selected) => {
2207
+ setSelectedTemplate(selected);
2208
+ setTemplateYaml(yaml.stringify(selected.spec));
2209
+ },
2210
+ [setTemplateYaml]
2211
+ );
2212
+ 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(
2213
+ Select,
2214
+ {
2215
+ value: selectedTemplate,
2216
+ label: "Load Existing Template",
2217
+ labelId: "select-template-label",
2218
+ onChange: (e) => handleSelectChange(e.target.value)
2219
+ },
2220
+ templateOptions.map((option, idx) => /* @__PURE__ */ React.createElement(MenuItem, { key: idx, value: option.value }, option.label))
2221
+ )), /* @__PURE__ */ React.createElement(IconButton$1, { size: "medium", onClick: onClose }, /* @__PURE__ */ React.createElement(CloseIcon, null))), /* @__PURE__ */ React.createElement("div", { className: classes.textArea }, /* @__PURE__ */ React.createElement(
2222
+ TemplateEditorTextArea,
2223
+ {
2224
+ content: templateYaml,
2225
+ onUpdate: setTemplateYaml,
2226
+ errorText
2227
+ }
2228
+ )), /* @__PURE__ */ React.createElement("div", { className: classes.preview }, /* @__PURE__ */ React.createElement(
2229
+ TemplateEditorForm,
2230
+ {
2231
+ content: templateYaml,
2232
+ contentIsSpec: true,
2233
+ fieldExtensions: customFieldExtensions,
2234
+ setErrorText,
2235
+ layouts
2236
+ }
2237
+ ))));
2238
+ };
2239
+
2240
+ const useStyles = makeStyles$1((theme) => ({
2241
+ introText: {
2242
+ textAlign: "center",
2243
+ marginTop: theme.spacing(2)
2244
+ },
2245
+ card: {
2246
+ position: "relative",
2247
+ maxWidth: 340,
2248
+ marginTop: theme.spacing(4),
2249
+ margin: theme.spacing(0, 2)
2250
+ },
2251
+ infoIcon: {
2252
+ position: "absolute",
2253
+ top: theme.spacing(1),
2254
+ right: theme.spacing(1)
2255
+ }
2256
+ }));
2257
+ function TemplateEditorIntro(props) {
2258
+ const classes = useStyles();
2259
+ const supportsLoad = WebFileSystemAccess.isSupported();
2260
+ const cardLoadLocal = /* @__PURE__ */ React.createElement(Card$1, { className: classes.card, elevation: 4 }, /* @__PURE__ */ React.createElement(
2261
+ CardActionArea,
2262
+ {
2263
+ disabled: !supportsLoad,
2264
+ onClick: () => {
2265
+ var _a;
2266
+ return (_a = props.onSelect) == null ? void 0 : _a.call(props, "local");
2267
+ }
2268
+ },
2269
+ /* @__PURE__ */ React.createElement(CardContent$1, null, /* @__PURE__ */ React.createElement(
2270
+ Typography$1,
2271
+ {
2272
+ variant: "h4",
2273
+ component: "h3",
2274
+ gutterBottom: true,
2275
+ color: supportsLoad ? void 0 : "textSecondary",
2276
+ style: { display: "flex", flexFlow: "row nowrap" }
2277
+ },
2278
+ "Load Template Directory"
2279
+ ), /* @__PURE__ */ React.createElement(
2280
+ Typography$1,
2281
+ {
2282
+ variant: "body1",
2283
+ color: supportsLoad ? void 0 : "textSecondary"
2284
+ },
2285
+ "Load a local template directory, allowing you to both edit and try executing your own template."
2286
+ ))
2287
+ ), !supportsLoad && /* @__PURE__ */ React.createElement("div", { className: classes.infoIcon }, /* @__PURE__ */ React.createElement(
2288
+ Tooltip$1,
2289
+ {
2290
+ placement: "top",
2291
+ title: "Only supported in some Chromium-based browsers"
2292
+ },
2293
+ /* @__PURE__ */ React.createElement(InfoOutlinedIcon, null)
2294
+ )));
2295
+ const cardFormEditor = /* @__PURE__ */ React.createElement(Card$1, { className: classes.card, elevation: 4 }, /* @__PURE__ */ React.createElement(CardActionArea, { onClick: () => {
2296
+ var _a;
2297
+ return (_a = props.onSelect) == null ? void 0 : _a.call(props, "form");
2298
+ } }, /* @__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."))));
2299
+ const cardFieldExplorer = /* @__PURE__ */ React.createElement(Card$1, { className: classes.card, elevation: 4 }, /* @__PURE__ */ React.createElement(CardActionArea, { onClick: () => {
2300
+ var _a;
2301
+ return (_a = props.onSelect) == null ? void 0 : _a.call(props, "field-explorer");
2302
+ } }, /* @__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."))));
2303
+ 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(
2304
+ "div",
2305
+ {
2306
+ style: {
2307
+ display: "flex",
2308
+ flexFlow: "row wrap",
2309
+ alignItems: "flex-start",
2310
+ justifyContent: "center",
2311
+ alignContent: "flex-start"
2312
+ }
2313
+ },
2314
+ supportsLoad && cardLoadLocal,
2315
+ cardFormEditor,
2316
+ !supportsLoad && cardLoadLocal,
2317
+ cardFieldExplorer
2318
+ ));
2319
+ }
2320
+
2321
+ function TemplateEditorPage(props) {
2322
+ const [selection, setSelection] = useState();
2323
+ const navigate = useNavigate();
2324
+ const actionsLink = useRouteRef(actionsRouteRef);
2325
+ const tasksLink = useRouteRef(scaffolderListTaskRouteRef);
2326
+ const createLink = useRouteRef(rootRouteRef);
2327
+ const scaffolderPageContextMenuProps = {
2328
+ onEditorClicked: void 0,
2329
+ onActionsClicked: () => navigate(actionsLink()),
2330
+ onTasksClicked: () => navigate(tasksLink()),
2331
+ onCreateClicked: () => navigate(createLink())
2332
+ };
2333
+ let content = null;
2334
+ if ((selection == null ? void 0 : selection.type) === "local") {
2335
+ content = /* @__PURE__ */ React.createElement(
2336
+ TemplateEditor,
2337
+ {
2338
+ directory: selection.directory,
2339
+ fieldExtensions: props.customFieldExtensions,
2340
+ onClose: () => setSelection(void 0),
2341
+ layouts: props.layouts
2342
+ }
2343
+ );
2344
+ } else if ((selection == null ? void 0 : selection.type) === "form") {
2345
+ content = /* @__PURE__ */ React.createElement(
2346
+ TemplateFormPreviewer,
2347
+ {
2348
+ defaultPreviewTemplate: props.defaultPreviewTemplate,
2349
+ customFieldExtensions: props.customFieldExtensions,
2350
+ onClose: () => setSelection(void 0),
2351
+ layouts: props.layouts
2352
+ }
2353
+ );
2354
+ } else if ((selection == null ? void 0 : selection.type) === "field-explorer") {
2355
+ content = /* @__PURE__ */ React.createElement(
2356
+ CustomFieldExplorer,
2357
+ {
2358
+ customFieldExtensions: props.customFieldExtensions,
2359
+ onClose: () => setSelection(void 0)
2360
+ }
2361
+ );
2362
+ } else {
2363
+ content = /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(
2364
+ TemplateEditorIntro,
2365
+ {
2366
+ onSelect: (option) => {
2367
+ if (option === "local") {
2368
+ WebFileSystemAccess.requestDirectoryAccess().then((directory) => setSelection({ type: "local", directory })).catch(() => {
2369
+ });
2370
+ } else if (option === "form") {
2371
+ setSelection({ type: "form" });
2372
+ } else if (option === "field-explorer") {
2373
+ setSelection({ type: "field-explorer" });
2374
+ }
2375
+ }
2376
+ }
2377
+ ));
2378
+ }
2379
+ return /* @__PURE__ */ React.createElement(Page, { themeId: "home" }, /* @__PURE__ */ React.createElement(
2380
+ Header,
2381
+ {
2382
+ title: "Template Editor",
2383
+ subtitle: "Edit, preview, and try out templates and template forms"
2384
+ },
2385
+ /* @__PURE__ */ React.createElement(ScaffolderPageContextMenu, { ...scaffolderPageContextMenuProps })
2386
+ ), content);
2387
+ }
2388
+
2389
+ const Router = (props) => {
2390
+ const {
2391
+ components: {
2392
+ TemplateCardComponent,
2393
+ TaskPageComponent = OngoingTask,
2394
+ ReviewStepComponent,
2395
+ EXPERIMENTAL_TemplateOutputsComponent: TemplateOutputsComponent,
2396
+ EXPERIMENTAL_TemplateListPageComponent: TemplateListPageComponent = TemplateListPage,
2397
+ EXPERIMENTAL_TemplateWizardPageComponent: TemplateWizardPageComponent = TemplateWizardPage
2398
+ } = {}
2399
+ } = props;
2400
+ const outlet = useOutlet() || props.children;
2401
+ const customFieldExtensions = useCustomFieldExtensions(outlet);
2402
+ const fieldExtensions = [
2403
+ ...customFieldExtensions,
2404
+ ...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter(
2405
+ ({ name }) => !customFieldExtensions.some(
2406
+ (customFieldExtension) => customFieldExtension.name === name
2407
+ )
2408
+ )
2409
+ ];
2410
+ const customLayouts = useCustomLayouts(outlet);
2411
+ return /* @__PURE__ */ React.createElement(Routes, null, /* @__PURE__ */ React.createElement(
2412
+ Route,
2413
+ {
2414
+ path: "/",
2415
+ element: /* @__PURE__ */ React.createElement(
2416
+ TemplateListPageComponent,
2417
+ {
2418
+ TemplateCardComponent,
2419
+ contextMenu: props.contextMenu,
2420
+ groups: props.groups,
2421
+ templateFilter: props.templateFilter,
2422
+ headerOptions: props.headerOptions
2423
+ }
2424
+ )
2425
+ }
2426
+ ), /* @__PURE__ */ React.createElement(
2427
+ Route,
2428
+ {
2429
+ path: selectedTemplateRouteRef.path,
2430
+ element: /* @__PURE__ */ React.createElement(SecretsContextProvider, null, /* @__PURE__ */ React.createElement(
2431
+ TemplateWizardPageComponent,
2432
+ {
2433
+ headerOptions: props.headerOptions,
2434
+ customFieldExtensions: fieldExtensions,
2435
+ layouts: customLayouts,
2436
+ components: { ReviewStepComponent },
2437
+ formProps: props.formProps
2438
+ }
2439
+ ))
2440
+ }
2441
+ ), /* @__PURE__ */ React.createElement(
2442
+ Route,
2443
+ {
2444
+ path: scaffolderTaskRouteRef.path,
2445
+ element: /* @__PURE__ */ React.createElement(
2446
+ TaskPageComponent,
2447
+ {
2448
+ TemplateOutputsComponent
2449
+ }
2450
+ )
2451
+ }
2452
+ ), /* @__PURE__ */ React.createElement(
2453
+ Route,
2454
+ {
2455
+ path: editRouteRef.path,
2456
+ element: /* @__PURE__ */ React.createElement(SecretsContextProvider, null, /* @__PURE__ */ React.createElement(
2457
+ TemplateEditorPage,
2458
+ {
2459
+ customFieldExtensions: fieldExtensions,
2460
+ layouts: customLayouts
2461
+ }
2462
+ ))
2463
+ }
2464
+ ), /* @__PURE__ */ React.createElement(Route, { path: actionsRouteRef.path, element: /* @__PURE__ */ React.createElement(ActionsPage, null) }), /* @__PURE__ */ React.createElement(
2465
+ Route,
2466
+ {
2467
+ path: scaffolderListTaskRouteRef.path,
2468
+ element: /* @__PURE__ */ React.createElement(ListTasksPage, null)
2469
+ }
2470
+ ), /* @__PURE__ */ React.createElement(
2471
+ Route,
2472
+ {
2473
+ path: "*",
2474
+ element: /* @__PURE__ */ React.createElement(ErrorPage, { status: "404", statusMessage: "Page not found" })
2475
+ }
2476
+ ));
2477
+ };
2478
+
2479
+ export { Router };
2480
+ //# sourceMappingURL=index-7840abd4.esm.js.map