@backstage/plugin-scaffolder 1.8.0-next.1 → 1.9.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,11 @@
1
- import React, { useState, useContext, useCallback, createContext, useEffect, useRef, useMemo, Children, Component, Fragment } from 'react';
1
+ import React, { useState, useContext, useCallback, createContext, useEffect, useRef, useMemo, Component, Children, Fragment } from 'react';
2
2
  import { useNavigate, Navigate, useOutlet, Routes, Route } from 'react-router';
3
3
  import { ItemCardHeader, MarkdownContent, Button, Link, ContentHeader, Progress, WarningPanel, Content, ItemCardGrid, Page, Header, CreateButton, SupportButton, StructuredMetadataTable, InfoCard, ErrorPage, ErrorPanel, LogViewer, StatusError, StatusOK, StatusPending, Lifecycle, EmptyState, Table as Table$1 } from '@backstage/core-components';
4
4
  import { useApp, useRouteRef, useApi, useRouteRefParams, useAnalytics, errorApiRef, featureFlagsApiRef, useApiHolder, AnalyticsContext, alertApiRef, useElementFilter } from '@backstage/core-plugin-api';
5
5
  import { getEntityRelations, getEntitySourceLocation, FavoriteEntity, EntityRefLinks, useEntityList, EntityListProvider, CatalogFilterLayout, EntitySearchBar, EntityKindPicker, UserListPicker, EntityTagPicker, catalogApiRef, humanizeEntityRef, EntityRefLink } from '@backstage/plugin-catalog-react';
6
- import { s as selectedTemplateRouteRef, v as viewTechDocRouteRef, e as editRouteRef, a as actionsRouteRef, b as scaffolderListTaskRouteRef, r as registerComponentRouteRef, T as TemplateTypePicker, S as SecretsContext, c as scaffolderApiRef, d as scaffolderTaskRouteRef, f as rootRouteRef, g as TaskStatusStepper, h as TaskPageLinks, F as FIELD_EXTENSION_WRAPPER_KEY, i as FIELD_EXTENSION_KEY, L as LAYOUTS_WRAPPER_KEY, j as LAYOUTS_KEY, l as legacySelectedTemplateRouteRef, k as SecretsContextProvider, m as TaskPage } from './index-68c01d69.esm.js';
6
+ import { s as selectedTemplateRouteRef, v as viewTechDocRouteRef, e as editRouteRef, a as actionsRouteRef, b as scaffolderListTaskRouteRef, r as registerComponentRouteRef, T as TemplateTypePicker, S as SecretsContext, c as scaffolderApiRef, d as scaffolderTaskRouteRef, f as rootRouteRef, g as TaskStatusStepper, h as TaskPageLinks, F as FIELD_EXTENSION_WRAPPER_KEY, i as FIELD_EXTENSION_KEY, L as LAYOUTS_WRAPPER_KEY, j as LAYOUTS_KEY, l as legacySelectedTemplateRouteRef, k as SecretsContextProvider, m as TaskPage } from './index-ed1a1fba.esm.js';
7
7
  import { RELATION_OWNED_BY, parseEntityRef, stringifyEntityRef, DEFAULT_NAMESPACE } from '@backstage/catalog-model';
8
- import { makeStyles, useTheme, Card, CardMedia, CardContent, Box, Typography, Chip, CardActions, Tooltip, IconButton, Stepper, Step, StepLabel, StepContent, Button as Button$1, Paper, LinearProgress, TableContainer, Table, TableHead, TableRow, TableCell, TableBody, Divider as Divider$1, FormControl, InputLabel, Select, MenuItem as MenuItem$1, List as List$2, ListItemIcon as ListItemIcon$1, ListItemText as ListItemText$1 } from '@material-ui/core';
8
+ import { makeStyles, useTheme, Card, CardMedia, CardContent, Box, Typography, Chip, CardActions, Tooltip, IconButton, Paper, Button as Button$1, Stepper, Step, StepLabel, StepContent, LinearProgress, TableContainer, Table, TableHead, TableRow, TableCell, TableBody, FormControl, InputLabel, Select, MenuItem as MenuItem$1, CardHeader, Divider as Divider$1, List as List$2, ListItemIcon as ListItemIcon$1, ListItemText as ListItemText$1 } from '@material-ui/core';
9
9
  import { scmIntegrationsApiRef, ScmIntegrationIcon } from '@backstage/integration-react';
10
10
  import LanguageIcon from '@material-ui/icons/Language';
11
11
  import WarningIcon from '@material-ui/icons/Warning';
@@ -27,15 +27,21 @@ import useAsync from 'react-use/lib/useAsync';
27
27
  import { withTheme } from '@rjsf/core';
28
28
  import { Theme } from '@rjsf/material-ui';
29
29
  import cloneDeep from 'lodash/cloneDeep';
30
+ import { e as extractSchemaFromStep, D as DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS } from './schema-b28a29ab.esm.js';
30
31
  import classNames from 'classnames';
32
+ import { StreamLanguage } from '@codemirror/language';
33
+ import { yaml as yaml$1 } from '@codemirror/legacy-modes/mode/yaml';
34
+ import CloseIcon from '@material-ui/icons/Close';
35
+ import CodeMirror from '@uiw/react-codemirror';
36
+ import yaml from 'yaml';
37
+ import useDebounce from 'react-use/lib/useDebounce';
38
+ import { useAsync as useAsync$1, useRerender, usePrevious, useKeyboardEvent } from '@react-hookz/web';
31
39
  import Card$1 from '@material-ui/core/Card';
32
40
  import CardActionArea from '@material-ui/core/CardActionArea';
33
41
  import CardContent$1 from '@material-ui/core/CardContent';
34
42
  import Tooltip$1 from '@material-ui/core/Tooltip';
35
43
  import Typography$1 from '@material-ui/core/Typography';
36
44
  import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined';
37
- import { useAsync as useAsync$1, useRerender, usePrevious, useKeyboardEvent } from '@react-hookz/web';
38
- import yaml from 'yaml';
39
45
  import Accordion from '@material-ui/core/Accordion';
40
46
  import AccordionDetails from '@material-ui/core/AccordionDetails';
41
47
  import AccordionSummary from '@material-ui/core/AccordionSummary';
@@ -47,30 +53,26 @@ import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
47
53
  import Cancel from '@material-ui/icons/Cancel';
48
54
  import Check from '@material-ui/icons/Check';
49
55
  import DeleteIcon from '@material-ui/icons/Delete';
50
- import { StreamLanguage } from '@codemirror/language';
51
- import { yaml as yaml$1 } from '@codemirror/legacy-modes/mode/yaml';
52
56
  import Box$1 from '@material-ui/core/Box';
53
57
  import Tab from '@material-ui/core/Tab';
54
58
  import Tabs from '@material-ui/core/Tabs';
55
- import CodeMirror from '@uiw/react-codemirror';
56
59
  import TreeView from '@material-ui/lab/TreeView';
57
60
  import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
58
61
  import ChevronRightIcon from '@material-ui/icons/ChevronRight';
59
62
  import TreeItem from '@material-ui/lab/TreeItem';
60
- import CloseIcon from '@material-ui/icons/Close';
61
63
  import RefreshIcon from '@material-ui/icons/Refresh';
62
64
  import SaveIcon from '@material-ui/icons/Save';
63
- import useDebounce from 'react-use/lib/useDebounce';
64
65
  import { showPanel } from '@codemirror/view';
65
66
  import SettingsIcon from '@material-ui/icons/Settings';
66
67
  import AllIcon from '@material-ui/icons/FontDownload';
67
68
  import { DateTime, Interval } from 'luxon';
68
69
  import humanizeDuration from 'humanize-duration';
69
- import { D as DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS } from './default-08d14dbd.esm.js';
70
70
  import '@backstage/errors';
71
71
  import 'zen-observable';
72
72
  import '@material-ui/core/FormControl';
73
73
  import '@material-ui/lab/Autocomplete';
74
+ import 'zod';
75
+ import 'zod-to-json-schema';
74
76
  import 'react-use/lib/useEffectOnce';
75
77
  import '@material-ui/lab';
76
78
  import '@material-ui/core/FormHelperText';
@@ -87,7 +89,7 @@ import '@material-ui/icons/FiberManualRecord';
87
89
  import 'react-use/lib/useInterval';
88
90
  import 'use-immer';
89
91
 
90
- const useStyles$e = makeStyles((theme) => ({
92
+ const useStyles$f = makeStyles((theme) => ({
91
93
  cardHeader: {
92
94
  position: "relative"
93
95
  },
@@ -179,7 +181,7 @@ const TemplateCard = ({ template, deprecated }) => {
179
181
  );
180
182
  const themeId = backstageTheme.getPageTheme({ themeId: templateProps.type }) ? templateProps.type : "other";
181
183
  const theme = backstageTheme.getPageTheme({ themeId });
182
- const classes = useStyles$e({ backgroundImage: theme.backgroundImage });
184
+ const classes = useStyles$f({ backgroundImage: theme.backgroundImage });
183
185
  const { name, namespace } = parseEntityRef(stringifyEntityRef(template));
184
186
  const href = templateRoute({ templateName: name, namespace });
185
187
  const viewTechDoc = useRouteRef(viewTechDocRouteRef);
@@ -300,13 +302,13 @@ const TemplateList = ({
300
302
  })))));
301
303
  };
302
304
 
303
- const useStyles$d = makeStyles$1({
305
+ const useStyles$e = makeStyles$1({
304
306
  button: {
305
307
  color: "white"
306
308
  }
307
309
  });
308
310
  function ScaffolderPageContextMenu(props) {
309
- const classes = useStyles$d();
311
+ const classes = useStyles$e();
310
312
  const [anchorEl, setAnchorEl] = useState();
311
313
  const editLink = useRouteRef(editRouteRef);
312
314
  const actionsLink = useRouteRef(actionsRouteRef);
@@ -517,23 +519,7 @@ var fieldOverrides = /*#__PURE__*/Object.freeze({
517
519
  DescriptionField: DescriptionField
518
520
  });
519
521
 
520
- const Form = withTheme(Theme);
521
- function getUiSchemasFromSteps(steps) {
522
- const uiSchemas = [];
523
- steps.forEach((step) => {
524
- const schemaProps = step.schema.properties;
525
- for (const key in schemaProps) {
526
- if (schemaProps.hasOwnProperty(key)) {
527
- const uiSchema = schemaProps[key];
528
- uiSchema.name = key;
529
- uiSchemas.push(uiSchema);
530
- }
531
- }
532
- });
533
- return uiSchemas;
534
- }
535
- function getReviewData(formData, steps) {
536
- const uiSchemas = getUiSchemasFromSteps(steps);
522
+ function getReviewData(formData, uiSchemas) {
537
523
  const reviewData = {};
538
524
  for (const key in formData) {
539
525
  if (formData.hasOwnProperty(key)) {
@@ -563,6 +549,60 @@ function getReviewData(formData, steps) {
563
549
  }
564
550
  return reviewData;
565
551
  }
552
+ function getUiSchemasFromSteps(steps) {
553
+ const uiSchemas = [];
554
+ steps.forEach((step) => {
555
+ const schemaProps = step.schema.properties;
556
+ for (const key in schemaProps) {
557
+ if (schemaProps.hasOwnProperty(key)) {
558
+ const uiSchema = schemaProps[key];
559
+ uiSchema.name = key;
560
+ uiSchemas.push(uiSchema);
561
+ }
562
+ }
563
+ });
564
+ return uiSchemas;
565
+ }
566
+ const ReviewStep = (props) => {
567
+ const {
568
+ disableButtons,
569
+ formData,
570
+ handleBack,
571
+ handleCreate,
572
+ handleReset,
573
+ steps
574
+ } = props;
575
+ return /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(Paper, {
576
+ square: true,
577
+ elevation: 0
578
+ }, /* @__PURE__ */ React.createElement(Typography, {
579
+ variant: "h6"
580
+ }, "Review and create"), /* @__PURE__ */ React.createElement(StructuredMetadataTable, {
581
+ dense: true,
582
+ metadata: getReviewData(formData, getUiSchemasFromSteps(steps))
583
+ }), /* @__PURE__ */ React.createElement(Box, {
584
+ mb: 4
585
+ }), /* @__PURE__ */ React.createElement(Button$1, {
586
+ onClick: handleBack,
587
+ disabled: disableButtons
588
+ }, "Back"), /* @__PURE__ */ React.createElement(Button$1, {
589
+ onClick: handleReset,
590
+ disabled: disableButtons
591
+ }, "Reset"), /* @__PURE__ */ React.createElement(Button$1, {
592
+ variant: "contained",
593
+ color: "primary",
594
+ onClick: handleCreate,
595
+ disabled: disableButtons
596
+ }, "Create")));
597
+ };
598
+
599
+ const Form$1 = withTheme(Theme);
600
+ function getSchemasFromSteps(steps) {
601
+ return steps.map(({ schema }) => ({
602
+ mergedSchema: schema,
603
+ ...extractSchemaFromStep(schema)
604
+ }));
605
+ }
566
606
  const MultistepJsonForm = (props) => {
567
607
  const {
568
608
  formData,
@@ -571,8 +611,8 @@ const MultistepJsonForm = (props) => {
571
611
  onFinish,
572
612
  fields,
573
613
  widgets,
574
- finishButtonLabel,
575
- layouts
614
+ layouts,
615
+ ReviewStepComponent
576
616
  } = props;
577
617
  const { templateName } = useRouteRefParams(selectedTemplateRouteRef);
578
618
  const analytics = useAnalytics();
@@ -634,6 +674,7 @@ const MultistepJsonForm = (props) => {
634
674
  setDisableButtons(false);
635
675
  }
636
676
  };
677
+ const ReviewStepElement = ReviewStepComponent != null ? ReviewStepComponent : ReviewStep;
637
678
  return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Stepper, {
638
679
  activeStep,
639
680
  orientation: "vertical"
@@ -649,7 +690,7 @@ const MultistepJsonForm = (props) => {
649
690
  component: "h3"
650
691
  }, title)), /* @__PURE__ */ React.createElement(StepContent, {
651
692
  key: title
652
- }, /* @__PURE__ */ React.createElement(Form, {
693
+ }, /* @__PURE__ */ React.createElement(Form$1, {
653
694
  showErrorList: false,
654
695
  fields: { ...fieldOverrides, ...fields },
655
696
  widgets,
@@ -671,28 +712,14 @@ const MultistepJsonForm = (props) => {
671
712
  color: "primary",
672
713
  type: "submit"
673
714
  }, "Next step"))));
674
- })), activeStep === steps.length && /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(Paper, {
675
- square: true,
676
- elevation: 0
677
- }, /* @__PURE__ */ React.createElement(Typography, {
678
- variant: "h6"
679
- }, "Review and create"), /* @__PURE__ */ React.createElement(StructuredMetadataTable, {
680
- dense: true,
681
- metadata: getReviewData(formData, steps)
682
- }), /* @__PURE__ */ React.createElement(Box, {
683
- mb: 4
684
- }), /* @__PURE__ */ React.createElement(Button$1, {
685
- onClick: handleBack,
686
- disabled: disableButtons
687
- }, "Back"), /* @__PURE__ */ React.createElement(Button$1, {
688
- onClick: handleReset,
689
- disabled: disableButtons
690
- }, "Reset"), /* @__PURE__ */ React.createElement(Button$1, {
691
- variant: "contained",
692
- color: "primary",
693
- onClick: handleCreate,
694
- disabled: !onFinish || disableButtons
695
- }, finishButtonLabel != null ? finishButtonLabel : "Create"))));
715
+ })), activeStep === steps.length && /* @__PURE__ */ React.createElement(ReviewStepElement, {
716
+ disableButtons,
717
+ handleBack,
718
+ handleCreate,
719
+ handleReset,
720
+ formData,
721
+ steps: getSchemasFromSteps(steps)
722
+ }));
696
723
  };
697
724
 
698
725
  function isObject(obj) {
@@ -751,8 +778,10 @@ const useTemplateParameterSchema = (templateRef) => {
751
778
  return { schema: value, loading, error };
752
779
  };
753
780
  const TemplatePage = ({
781
+ ReviewStepComponent,
754
782
  customFieldExtensions = [],
755
- layouts = []
783
+ layouts = [],
784
+ headerOptions
756
785
  }) => {
757
786
  const apiHolder = useApiHolder();
758
787
  const secretsContext = useContext(SecretsContext);
@@ -826,7 +855,8 @@ const TemplatePage = ({
826
855
  }, /* @__PURE__ */ React.createElement(Header, {
827
856
  pageTitleOverride: "Create a New Component",
828
857
  title: "Create a New Component",
829
- subtitle: "Create new software components using standard templates"
858
+ subtitle: "Create new software components using standard templates",
859
+ ...headerOptions
830
860
  }), /* @__PURE__ */ React.createElement(Content, null, loading && /* @__PURE__ */ React.createElement(LinearProgress, {
831
861
  "data-testid": "loading-progress"
832
862
  }), schema && /* @__PURE__ */ React.createElement(InfoCard, {
@@ -834,6 +864,7 @@ const TemplatePage = ({
834
864
  noPadding: true,
835
865
  titleTypographyProps: { component: "h2" }
836
866
  }, /* @__PURE__ */ React.createElement(MultistepJsonForm, {
867
+ ReviewStepComponent,
837
868
  formData: formState,
838
869
  fields: customFieldComponents,
839
870
  onChange: handleChange,
@@ -853,7 +884,7 @@ const TemplatePage = ({
853
884
  })))));
854
885
  };
855
886
 
856
- const useStyles$c = makeStyles((theme) => ({
887
+ const useStyles$d = makeStyles((theme) => ({
857
888
  code: {
858
889
  fontFamily: "Menlo, monospace",
859
890
  padding: theme.spacing(1),
@@ -876,7 +907,7 @@ const useStyles$c = makeStyles((theme) => ({
876
907
  }));
877
908
  const ActionsPage = () => {
878
909
  const api = useApi(scaffolderApiRef);
879
- const classes = useStyles$c();
910
+ const classes = useStyles$d();
880
911
  const { loading, value, error } = useAsync(async () => {
881
912
  return api.listActions();
882
913
  });
@@ -1015,79 +1046,6 @@ class WebFileSystemAccess {
1015
1046
  }
1016
1047
  }
1017
1048
 
1018
- const useStyles$b = makeStyles$1((theme) => ({
1019
- introText: {
1020
- textAlign: "center",
1021
- marginTop: theme.spacing(2)
1022
- },
1023
- card: {
1024
- position: "relative",
1025
- maxWidth: 340,
1026
- marginTop: theme.spacing(4),
1027
- margin: theme.spacing(0, 2)
1028
- },
1029
- infoIcon: {
1030
- position: "absolute",
1031
- top: theme.spacing(1),
1032
- right: theme.spacing(1)
1033
- }
1034
- }));
1035
- function TemplateEditorIntro(props) {
1036
- const classes = useStyles$b();
1037
- const supportsLoad = WebFileSystemAccess.isSupported();
1038
- const cardLoadLocal = /* @__PURE__ */ React.createElement(Card$1, {
1039
- className: classes.card,
1040
- elevation: 4
1041
- }, /* @__PURE__ */ React.createElement(CardActionArea, {
1042
- disabled: !supportsLoad,
1043
- onClick: () => {
1044
- var _a;
1045
- return (_a = props.onSelect) == null ? void 0 : _a.call(props, "local");
1046
- }
1047
- }, /* @__PURE__ */ React.createElement(CardContent$1, null, /* @__PURE__ */ React.createElement(Typography$1, {
1048
- variant: "h5",
1049
- gutterBottom: true,
1050
- color: supportsLoad ? void 0 : "textSecondary",
1051
- style: { display: "flex", flexFlow: "row nowrap" }
1052
- }, "Load Template Directory"), /* @__PURE__ */ React.createElement(Typography$1, {
1053
- variant: "body1",
1054
- color: supportsLoad ? void 0 : "textSecondary"
1055
- }, "Load a local template directory, allowing you to both edit and try executing your own template."))), !supportsLoad && /* @__PURE__ */ React.createElement("div", {
1056
- className: classes.infoIcon
1057
- }, /* @__PURE__ */ React.createElement(Tooltip$1, {
1058
- placement: "top",
1059
- title: "Only supported in some Chromium-based browsers"
1060
- }, /* @__PURE__ */ React.createElement(InfoOutlinedIcon, null))));
1061
- const cardFormEditor = /* @__PURE__ */ React.createElement(Card$1, {
1062
- className: classes.card,
1063
- elevation: 4
1064
- }, /* @__PURE__ */ React.createElement(CardActionArea, {
1065
- onClick: () => {
1066
- var _a;
1067
- return (_a = props.onSelect) == null ? void 0 : _a.call(props, "form");
1068
- }
1069
- }, /* @__PURE__ */ React.createElement(CardContent$1, null, /* @__PURE__ */ React.createElement(Typography$1, {
1070
- variant: "h5",
1071
- gutterBottom: true
1072
- }, "Edit Template Form"), /* @__PURE__ */ React.createElement(Typography$1, {
1073
- variant: "body1"
1074
- }, "Preview and edit a template form, either using a sample template or by loading a template from the catalog."))));
1075
- return /* @__PURE__ */ React.createElement("div", {
1076
- style: props.style
1077
- }, /* @__PURE__ */ React.createElement(Typography$1, {
1078
- variant: "h6",
1079
- className: classes.introText
1080
- }, "Get started by choosing one of the options below"), /* @__PURE__ */ React.createElement("div", {
1081
- style: {
1082
- display: "flex",
1083
- flexFlow: "row wrap",
1084
- alignItems: "flex-start",
1085
- justifyContent: "center",
1086
- alignContent: "flex-start"
1087
- }
1088
- }, supportsLoad && cardLoadLocal, cardFormEditor, !supportsLoad && cardLoadLocal));
1089
- }
1090
-
1091
1049
  var __accessCheck = (obj, member, msg) => {
1092
1050
  if (!member.has(obj))
1093
1051
  throw TypeError("Cannot " + msg);
@@ -1371,86 +1329,474 @@ function useDryRun() {
1371
1329
  return value;
1372
1330
  }
1373
1331
 
1374
- const useStyles$a = makeStyles$1((theme) => ({
1375
- root: {
1376
- overflowY: "auto",
1377
- background: theme.palette.background.default
1378
- },
1379
- iconSuccess: {
1380
- minWidth: 0,
1381
- marginRight: theme.spacing(1),
1382
- color: theme.palette.status.ok
1332
+ const useStyles$c = makeStyles$1({
1333
+ containerWrapper: {
1334
+ position: "relative",
1335
+ width: "100%",
1336
+ height: "100%"
1383
1337
  },
1384
- iconFailure: {
1385
- minWidth: 0,
1386
- marginRight: theme.spacing(1),
1387
- color: theme.palette.status.error
1388
- }
1389
- }));
1390
- function DryRunResultsList() {
1391
- const classes = useStyles$a();
1392
- const dryRun = useDryRun();
1393
- return /* @__PURE__ */ React.createElement(List$1, {
1394
- className: classes.root,
1395
- dense: true
1396
- }, dryRun.results.map((result) => {
1397
- var _a;
1398
- const failed = result.log.some((l) => l.body.status === "failed");
1399
- return /* @__PURE__ */ React.createElement(ListItem, {
1400
- button: true,
1401
- key: result.id,
1402
- selected: ((_a = dryRun.selectedResult) == null ? void 0 : _a.id) === result.id,
1403
- onClick: () => dryRun.selectResult(result.id)
1404
- }, /* @__PURE__ */ React.createElement(ListItemIcon, {
1405
- className: failed ? classes.iconFailure : classes.iconSuccess
1406
- }, failed ? /* @__PURE__ */ React.createElement(Cancel, null) : /* @__PURE__ */ React.createElement(Check, null)), /* @__PURE__ */ React.createElement(ListItemText, {
1407
- primary: `Result ${result.id}`
1408
- }), /* @__PURE__ */ React.createElement(ListItemSecondaryAction, null, /* @__PURE__ */ React.createElement(IconButton$1, {
1409
- edge: "end",
1410
- "aria-label": "delete",
1411
- onClick: () => dryRun.deleteResult(result.id)
1412
- }, /* @__PURE__ */ React.createElement(DeleteIcon, null))));
1413
- }));
1414
- }
1415
-
1416
- const useStyles$9 = makeStyles$1({
1417
- root: {
1418
- whiteSpace: "nowrap",
1419
- overflowY: "auto"
1338
+ container: {
1339
+ position: "absolute",
1340
+ top: 0,
1341
+ bottom: 0,
1342
+ left: 0,
1343
+ right: 0,
1344
+ overflow: "auto"
1420
1345
  }
1421
1346
  });
1422
- function parseFileEntires(paths) {
1423
- const root = {
1424
- type: "directory",
1425
- name: "",
1426
- path: "",
1427
- children: []
1428
- };
1429
- for (const path of paths.slice().sort()) {
1430
- const parts = path.split("/");
1431
- let current = root;
1432
- for (let i = 0; i < parts.length; i++) {
1433
- const part = parts[i];
1434
- if (part === "") {
1435
- throw new Error(`Invalid path part: ''`);
1436
- }
1437
- const entryPath = parts.slice(0, i + 1).join("/");
1438
- const existing = current.children.find((child) => child.name === part);
1439
- if ((existing == null ? void 0 : existing.type) === "file") {
1440
- throw new Error(`Duplicate filename at '${entryPath}'`);
1441
- } else if (existing) {
1442
- current = existing;
1443
- } else {
1444
- if (i < parts.length - 1) {
1445
- const newEntry = {
1446
- type: "directory",
1447
- name: part,
1448
- path: entryPath,
1449
- children: []
1450
- };
1451
- const firstFileIndex = current.children.findIndex(
1452
- (child) => child.type === "file"
1453
- );
1347
+ class ErrorBoundary extends Component {
1348
+ constructor() {
1349
+ super(...arguments);
1350
+ this.state = {
1351
+ shouldRender: true
1352
+ };
1353
+ }
1354
+ componentDidUpdate(prevProps) {
1355
+ if (prevProps.invalidator !== this.props.invalidator) {
1356
+ this.setState({ shouldRender: true });
1357
+ }
1358
+ }
1359
+ componentDidCatch(error) {
1360
+ this.props.setErrorText(error.message);
1361
+ this.setState({ shouldRender: false });
1362
+ }
1363
+ render() {
1364
+ return this.state.shouldRender ? this.props.children : null;
1365
+ }
1366
+ }
1367
+ function isJsonObject(value) {
1368
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1369
+ }
1370
+ function TemplateEditorForm(props) {
1371
+ const {
1372
+ content,
1373
+ contentIsSpec,
1374
+ data,
1375
+ onUpdate,
1376
+ onDryRun,
1377
+ setErrorText,
1378
+ fieldExtensions = [],
1379
+ layouts = []
1380
+ } = props;
1381
+ const classes = useStyles$c();
1382
+ const apiHolder = useApiHolder();
1383
+ const [steps, setSteps] = useState();
1384
+ const fields = useMemo(() => {
1385
+ return Object.fromEntries(
1386
+ fieldExtensions.map(({ name, component }) => [name, component])
1387
+ );
1388
+ }, [fieldExtensions]);
1389
+ useDebounce(
1390
+ () => {
1391
+ try {
1392
+ if (!content) {
1393
+ setSteps(void 0);
1394
+ return;
1395
+ }
1396
+ const parsed = yaml.parse(content);
1397
+ if (!isJsonObject(parsed)) {
1398
+ setSteps(void 0);
1399
+ return;
1400
+ }
1401
+ let rootObj = parsed;
1402
+ if (!contentIsSpec) {
1403
+ const isTemplate = String(parsed.kind).toLocaleLowerCase("en-US") === "template";
1404
+ if (!isTemplate) {
1405
+ setSteps(void 0);
1406
+ return;
1407
+ }
1408
+ rootObj = isJsonObject(parsed.spec) ? parsed.spec : {};
1409
+ }
1410
+ const { parameters } = rootObj;
1411
+ if (!Array.isArray(parameters)) {
1412
+ setErrorText("Template parameters must be an array");
1413
+ setSteps(void 0);
1414
+ return;
1415
+ }
1416
+ const fieldValidators = Object.fromEntries(
1417
+ fieldExtensions.map(({ name, validation }) => [name, validation])
1418
+ );
1419
+ setErrorText();
1420
+ setSteps(
1421
+ parameters.flatMap(
1422
+ (param) => isJsonObject(param) ? [
1423
+ {
1424
+ title: String(param.title),
1425
+ schema: param,
1426
+ validate: createValidator(param, fieldValidators, {
1427
+ apiHolder
1428
+ })
1429
+ }
1430
+ ] : []
1431
+ )
1432
+ );
1433
+ } catch (e) {
1434
+ setErrorText(e.message);
1435
+ }
1436
+ },
1437
+ 250,
1438
+ [contentIsSpec, content, apiHolder]
1439
+ );
1440
+ if (!steps) {
1441
+ return null;
1442
+ }
1443
+ return /* @__PURE__ */ React.createElement("div", {
1444
+ className: classes.containerWrapper
1445
+ }, /* @__PURE__ */ React.createElement("div", {
1446
+ className: classes.container
1447
+ }, /* @__PURE__ */ React.createElement(ErrorBoundary, {
1448
+ invalidator: steps,
1449
+ setErrorText
1450
+ }, /* @__PURE__ */ React.createElement(MultistepJsonForm, {
1451
+ steps,
1452
+ fields,
1453
+ formData: data,
1454
+ onChange: (e) => onUpdate(e.formData),
1455
+ onReset: () => onUpdate({}),
1456
+ finishButtonLabel: onDryRun && "Try It",
1457
+ onFinish: onDryRun && (() => onDryRun(data)),
1458
+ layouts
1459
+ }))));
1460
+ }
1461
+ function TemplateEditorFormDirectoryEditorDryRun(props) {
1462
+ const { setErrorText, fieldExtensions = [], layouts } = props;
1463
+ const dryRun = useDryRun();
1464
+ const directoryEditor = useDirectoryEditor();
1465
+ const { selectedFile } = directoryEditor;
1466
+ const [data, setData] = useState({});
1467
+ const handleDryRun = async () => {
1468
+ if (!selectedFile) {
1469
+ return;
1470
+ }
1471
+ try {
1472
+ await dryRun.execute({
1473
+ templateContent: selectedFile.content,
1474
+ values: data,
1475
+ files: directoryEditor.files
1476
+ });
1477
+ setErrorText();
1478
+ } catch (e) {
1479
+ setErrorText(String(e.cause || e));
1480
+ throw e;
1481
+ }
1482
+ };
1483
+ const content = selectedFile && selectedFile.path.match(/\.ya?ml$/) ? selectedFile.content : void 0;
1484
+ return /* @__PURE__ */ React.createElement(TemplateEditorForm, {
1485
+ onDryRun: handleDryRun,
1486
+ fieldExtensions,
1487
+ setErrorText,
1488
+ content,
1489
+ data,
1490
+ onUpdate: setData,
1491
+ layouts
1492
+ });
1493
+ }
1494
+ TemplateEditorForm.DirectoryEditorDryRun = TemplateEditorFormDirectoryEditorDryRun;
1495
+
1496
+ const Form = withTheme(Theme);
1497
+ const useStyles$b = makeStyles((theme) => ({
1498
+ root: {
1499
+ gridArea: "pageContent",
1500
+ display: "grid",
1501
+ gridTemplateAreas: `
1502
+ "controls controls"
1503
+ "fieldForm preview"
1504
+ `,
1505
+ gridTemplateRows: "auto 1fr",
1506
+ gridTemplateColumns: "1fr 1fr"
1507
+ },
1508
+ controls: {
1509
+ gridArea: "controls",
1510
+ display: "flex",
1511
+ flexFlow: "row nowrap",
1512
+ alignItems: "center",
1513
+ margin: theme.spacing(1)
1514
+ },
1515
+ fieldForm: {
1516
+ gridArea: "fieldForm"
1517
+ },
1518
+ preview: {
1519
+ gridArea: "preview"
1520
+ }
1521
+ }));
1522
+ const CustomFieldExplorer = ({
1523
+ customFieldExtensions = [],
1524
+ onClose
1525
+ }) => {
1526
+ var _a, _b;
1527
+ const classes = useStyles$b();
1528
+ const fieldOptions = customFieldExtensions.filter((field) => !!field.schema);
1529
+ const [selectedField, setSelectedField] = useState(fieldOptions[0]);
1530
+ const [fieldFormState, setFieldFormState] = useState({});
1531
+ const [formState, setFormState] = useState({});
1532
+ const [refreshKey, setRefreshKey] = useState(Date.now());
1533
+ const sampleFieldTemplate = useMemo(
1534
+ () => {
1535
+ var _a2, _b2;
1536
+ return yaml.stringify({
1537
+ parameters: [
1538
+ {
1539
+ title: `${selectedField.name} Example`,
1540
+ properties: {
1541
+ [selectedField.name]: {
1542
+ type: (_b2 = (_a2 = selectedField.schema) == null ? void 0 : _a2.returnValue) == null ? void 0 : _b2.type,
1543
+ "ui:field": selectedField.name,
1544
+ "ui:options": fieldFormState
1545
+ }
1546
+ }
1547
+ }
1548
+ ]
1549
+ });
1550
+ },
1551
+ [fieldFormState, selectedField]
1552
+ );
1553
+ const fieldComponents = useMemo(() => {
1554
+ return Object.fromEntries(
1555
+ customFieldExtensions.map(({ name, component }) => [name, component])
1556
+ );
1557
+ }, [customFieldExtensions]);
1558
+ const handleSelectionChange = useCallback(
1559
+ (selection) => {
1560
+ setSelectedField(selection);
1561
+ setFieldFormState({});
1562
+ setFormState({});
1563
+ },
1564
+ [setFieldFormState, setFormState, setSelectedField]
1565
+ );
1566
+ const handleFieldConfigChange = useCallback(
1567
+ (state) => {
1568
+ setFieldFormState(state);
1569
+ setFormState({});
1570
+ setRefreshKey(Date.now());
1571
+ },
1572
+ [setFieldFormState, setRefreshKey]
1573
+ );
1574
+ return /* @__PURE__ */ React.createElement("main", {
1575
+ className: classes.root
1576
+ }, /* @__PURE__ */ React.createElement("div", {
1577
+ className: classes.controls
1578
+ }, /* @__PURE__ */ React.createElement(FormControl, {
1579
+ variant: "outlined",
1580
+ size: "small",
1581
+ fullWidth: true
1582
+ }, /* @__PURE__ */ React.createElement(InputLabel, {
1583
+ id: "select-field-label"
1584
+ }, "Choose Custom Field Extension"), /* @__PURE__ */ React.createElement(Select, {
1585
+ value: selectedField,
1586
+ label: "Choose Custom Field Extension",
1587
+ labelId: "select-field-label",
1588
+ onChange: (e) => handleSelectionChange(e.target.value)
1589
+ }, fieldOptions.map((option, idx) => /* @__PURE__ */ React.createElement(MenuItem$1, {
1590
+ key: idx,
1591
+ value: option
1592
+ }, option.name)))), /* @__PURE__ */ React.createElement(IconButton, {
1593
+ size: "medium",
1594
+ onClick: onClose
1595
+ }, /* @__PURE__ */ React.createElement(CloseIcon, null))), /* @__PURE__ */ React.createElement("div", {
1596
+ className: classes.fieldForm
1597
+ }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardHeader, {
1598
+ title: "Field Options"
1599
+ }), /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(Form, {
1600
+ showErrorList: false,
1601
+ fields: { ...fieldOverrides, ...fieldComponents },
1602
+ noHtml5Validate: true,
1603
+ formData: fieldFormState,
1604
+ formContext: { fieldFormState },
1605
+ onSubmit: (e) => handleFieldConfigChange(e.formData),
1606
+ schema: ((_a = selectedField.schema) == null ? void 0 : _a.uiOptions) || {}
1607
+ }, /* @__PURE__ */ React.createElement(Button$1, {
1608
+ variant: "contained",
1609
+ color: "primary",
1610
+ type: "submit",
1611
+ disabled: !((_b = selectedField.schema) == null ? void 0 : _b.uiOptions)
1612
+ }, "Apply"))))), /* @__PURE__ */ React.createElement("div", {
1613
+ className: classes.preview
1614
+ }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardHeader, {
1615
+ title: "Example Template Spec"
1616
+ }), /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(CodeMirror, {
1617
+ readOnly: true,
1618
+ theme: "dark",
1619
+ height: "100%",
1620
+ extensions: [StreamLanguage.define(yaml$1)],
1621
+ value: sampleFieldTemplate
1622
+ }))), /* @__PURE__ */ React.createElement(TemplateEditorForm, {
1623
+ key: refreshKey,
1624
+ content: sampleFieldTemplate,
1625
+ contentIsSpec: true,
1626
+ fieldExtensions: customFieldExtensions,
1627
+ data: formState,
1628
+ onUpdate: setFormState,
1629
+ setErrorText: () => null
1630
+ })));
1631
+ };
1632
+
1633
+ const useStyles$a = makeStyles$1((theme) => ({
1634
+ introText: {
1635
+ textAlign: "center",
1636
+ marginTop: theme.spacing(2)
1637
+ },
1638
+ card: {
1639
+ position: "relative",
1640
+ maxWidth: 340,
1641
+ marginTop: theme.spacing(4),
1642
+ margin: theme.spacing(0, 2)
1643
+ },
1644
+ infoIcon: {
1645
+ position: "absolute",
1646
+ top: theme.spacing(1),
1647
+ right: theme.spacing(1)
1648
+ }
1649
+ }));
1650
+ function TemplateEditorIntro(props) {
1651
+ const classes = useStyles$a();
1652
+ const supportsLoad = WebFileSystemAccess.isSupported();
1653
+ const cardLoadLocal = /* @__PURE__ */ React.createElement(Card$1, {
1654
+ className: classes.card,
1655
+ elevation: 4
1656
+ }, /* @__PURE__ */ React.createElement(CardActionArea, {
1657
+ disabled: !supportsLoad,
1658
+ onClick: () => {
1659
+ var _a;
1660
+ return (_a = props.onSelect) == null ? void 0 : _a.call(props, "local");
1661
+ }
1662
+ }, /* @__PURE__ */ React.createElement(CardContent$1, null, /* @__PURE__ */ React.createElement(Typography$1, {
1663
+ variant: "h5",
1664
+ gutterBottom: true,
1665
+ color: supportsLoad ? void 0 : "textSecondary",
1666
+ style: { display: "flex", flexFlow: "row nowrap" }
1667
+ }, "Load Template Directory"), /* @__PURE__ */ React.createElement(Typography$1, {
1668
+ variant: "body1",
1669
+ color: supportsLoad ? void 0 : "textSecondary"
1670
+ }, "Load a local template directory, allowing you to both edit and try executing your own template."))), !supportsLoad && /* @__PURE__ */ React.createElement("div", {
1671
+ className: classes.infoIcon
1672
+ }, /* @__PURE__ */ React.createElement(Tooltip$1, {
1673
+ placement: "top",
1674
+ title: "Only supported in some Chromium-based browsers"
1675
+ }, /* @__PURE__ */ React.createElement(InfoOutlinedIcon, null))));
1676
+ const cardFormEditor = /* @__PURE__ */ React.createElement(Card$1, {
1677
+ className: classes.card,
1678
+ elevation: 4
1679
+ }, /* @__PURE__ */ React.createElement(CardActionArea, {
1680
+ onClick: () => {
1681
+ var _a;
1682
+ return (_a = props.onSelect) == null ? void 0 : _a.call(props, "form");
1683
+ }
1684
+ }, /* @__PURE__ */ React.createElement(CardContent$1, null, /* @__PURE__ */ React.createElement(Typography$1, {
1685
+ variant: "h5",
1686
+ gutterBottom: true
1687
+ }, "Edit Template Form"), /* @__PURE__ */ React.createElement(Typography$1, {
1688
+ variant: "body1"
1689
+ }, "Preview and edit a template form, either using a sample template or by loading a template from the catalog."))));
1690
+ const cardFieldExplorer = /* @__PURE__ */ React.createElement(Card$1, {
1691
+ className: classes.card,
1692
+ elevation: 4
1693
+ }, /* @__PURE__ */ React.createElement(CardActionArea, {
1694
+ onClick: () => {
1695
+ var _a;
1696
+ return (_a = props.onSelect) == null ? void 0 : _a.call(props, "field-explorer");
1697
+ }
1698
+ }, /* @__PURE__ */ React.createElement(CardContent$1, null, /* @__PURE__ */ React.createElement(Typography$1, {
1699
+ variant: "h5",
1700
+ gutterBottom: true
1701
+ }, "Custom Field Explorer"), /* @__PURE__ */ React.createElement(Typography$1, {
1702
+ variant: "body1"
1703
+ }, "View and play around with available installed custom field extensions."))));
1704
+ return /* @__PURE__ */ React.createElement("div", {
1705
+ style: props.style
1706
+ }, /* @__PURE__ */ React.createElement(Typography$1, {
1707
+ variant: "h6",
1708
+ className: classes.introText
1709
+ }, "Get started by choosing one of the options below"), /* @__PURE__ */ React.createElement("div", {
1710
+ style: {
1711
+ display: "flex",
1712
+ flexFlow: "row wrap",
1713
+ alignItems: "flex-start",
1714
+ justifyContent: "center",
1715
+ alignContent: "flex-start"
1716
+ }
1717
+ }, supportsLoad && cardLoadLocal, cardFormEditor, !supportsLoad && cardLoadLocal, cardFieldExplorer));
1718
+ }
1719
+
1720
+ const useStyles$9 = makeStyles$1((theme) => ({
1721
+ root: {
1722
+ overflowY: "auto",
1723
+ background: theme.palette.background.default
1724
+ },
1725
+ iconSuccess: {
1726
+ minWidth: 0,
1727
+ marginRight: theme.spacing(1),
1728
+ color: theme.palette.status.ok
1729
+ },
1730
+ iconFailure: {
1731
+ minWidth: 0,
1732
+ marginRight: theme.spacing(1),
1733
+ color: theme.palette.status.error
1734
+ }
1735
+ }));
1736
+ function DryRunResultsList() {
1737
+ const classes = useStyles$9();
1738
+ const dryRun = useDryRun();
1739
+ return /* @__PURE__ */ React.createElement(List$1, {
1740
+ className: classes.root,
1741
+ dense: true
1742
+ }, dryRun.results.map((result) => {
1743
+ var _a;
1744
+ const failed = result.log.some((l) => l.body.status === "failed");
1745
+ return /* @__PURE__ */ React.createElement(ListItem, {
1746
+ button: true,
1747
+ key: result.id,
1748
+ selected: ((_a = dryRun.selectedResult) == null ? void 0 : _a.id) === result.id,
1749
+ onClick: () => dryRun.selectResult(result.id)
1750
+ }, /* @__PURE__ */ React.createElement(ListItemIcon, {
1751
+ className: failed ? classes.iconFailure : classes.iconSuccess
1752
+ }, failed ? /* @__PURE__ */ React.createElement(Cancel, null) : /* @__PURE__ */ React.createElement(Check, null)), /* @__PURE__ */ React.createElement(ListItemText, {
1753
+ primary: `Result ${result.id}`
1754
+ }), /* @__PURE__ */ React.createElement(ListItemSecondaryAction, null, /* @__PURE__ */ React.createElement(IconButton$1, {
1755
+ edge: "end",
1756
+ "aria-label": "delete",
1757
+ onClick: () => dryRun.deleteResult(result.id)
1758
+ }, /* @__PURE__ */ React.createElement(DeleteIcon, null))));
1759
+ }));
1760
+ }
1761
+
1762
+ const useStyles$8 = makeStyles$1({
1763
+ root: {
1764
+ whiteSpace: "nowrap",
1765
+ overflowY: "auto"
1766
+ }
1767
+ });
1768
+ function parseFileEntires(paths) {
1769
+ const root = {
1770
+ type: "directory",
1771
+ name: "",
1772
+ path: "",
1773
+ children: []
1774
+ };
1775
+ for (const path of paths.slice().sort()) {
1776
+ const parts = path.split("/");
1777
+ let current = root;
1778
+ for (let i = 0; i < parts.length; i++) {
1779
+ const part = parts[i];
1780
+ if (part === "") {
1781
+ throw new Error(`Invalid path part: ''`);
1782
+ }
1783
+ const entryPath = parts.slice(0, i + 1).join("/");
1784
+ const existing = current.children.find((child) => child.name === part);
1785
+ if ((existing == null ? void 0 : existing.type) === "file") {
1786
+ throw new Error(`Duplicate filename at '${entryPath}'`);
1787
+ } else if (existing) {
1788
+ current = existing;
1789
+ } else {
1790
+ if (i < parts.length - 1) {
1791
+ const newEntry = {
1792
+ type: "directory",
1793
+ name: part,
1794
+ path: entryPath,
1795
+ children: []
1796
+ };
1797
+ const firstFileIndex = current.children.findIndex(
1798
+ (child) => child.type === "file"
1799
+ );
1454
1800
  current.children.splice(firstFileIndex, 0, newEntry);
1455
1801
  current = newEntry;
1456
1802
  } else {
@@ -1481,7 +1827,7 @@ function FileTreeItem({ entry }) {
1481
1827
  })));
1482
1828
  }
1483
1829
  function FileBrowser(props) {
1484
- const classes = useStyles$9();
1830
+ const classes = useStyles$8();
1485
1831
  const fileTree = useMemo(
1486
1832
  () => parseFileEntires(props.filePaths),
1487
1833
  [props.filePaths]
@@ -1502,7 +1848,7 @@ function FileBrowser(props) {
1502
1848
  })));
1503
1849
  }
1504
1850
 
1505
- const useStyles$8 = makeStyles$1((theme) => ({
1851
+ const useStyles$7 = makeStyles$1((theme) => ({
1506
1852
  root: {
1507
1853
  display: "grid",
1508
1854
  gridTemplateColumns: "280px auto 3fr",
@@ -1518,7 +1864,7 @@ const useStyles$8 = makeStyles$1((theme) => ({
1518
1864
  }
1519
1865
  }));
1520
1866
  function DryRunResultsSplitView(props) {
1521
- const classes = useStyles$8();
1867
+ const classes = useStyles$7();
1522
1868
  const childArray = Children.toArray(props.children);
1523
1869
  if (childArray.length !== 2) {
1524
1870
  throw new Error("must have exactly 2 children");
@@ -1534,7 +1880,7 @@ function DryRunResultsSplitView(props) {
1534
1880
  }, childArray[1]));
1535
1881
  }
1536
1882
 
1537
- const useStyles$7 = makeStyles$1({
1883
+ const useStyles$6 = makeStyles$1({
1538
1884
  root: {
1539
1885
  display: "flex",
1540
1886
  flexFlow: "column nowrap"
@@ -1560,7 +1906,7 @@ const useStyles$7 = makeStyles$1({
1560
1906
  }
1561
1907
  });
1562
1908
  function FilesContent() {
1563
- const classes = useStyles$7();
1909
+ const classes = useStyles$6();
1564
1910
  const { selectedResult } = useDryRun();
1565
1911
  const [selectedPath, setSelectedPath] = useState("");
1566
1912
  const selectedFile = selectedResult == null ? void 0 : selectedResult.directoryContents.find(
@@ -1629,7 +1975,7 @@ function LogContent() {
1629
1975
  }
1630
1976
  function OutputContent() {
1631
1977
  var _a, _b;
1632
- const classes = useStyles$7();
1978
+ const classes = useStyles$6();
1633
1979
  const { selectedResult } = useDryRun();
1634
1980
  if (!selectedResult) {
1635
1981
  return null;
@@ -1648,7 +1994,7 @@ function OutputContent() {
1648
1994
  }));
1649
1995
  }
1650
1996
  function DryRunResultsView() {
1651
- const classes = useStyles$7();
1997
+ const classes = useStyles$6();
1652
1998
  const [selectedTab, setSelectedTab] = useState(
1653
1999
  "files"
1654
2000
  );
@@ -1673,7 +2019,7 @@ function DryRunResultsView() {
1673
2019
  }, selectedTab === "files" && /* @__PURE__ */ React.createElement(FilesContent, null), selectedTab === "log" && /* @__PURE__ */ React.createElement(LogContent, null), selectedTab === "output" && /* @__PURE__ */ React.createElement(OutputContent, null))));
1674
2020
  }
1675
2021
 
1676
- const useStyles$6 = makeStyles$1((theme) => ({
2022
+ const useStyles$5 = makeStyles$1((theme) => ({
1677
2023
  header: {
1678
2024
  height: 48,
1679
2025
  minHeight: 0,
@@ -1692,7 +2038,7 @@ const useStyles$6 = makeStyles$1((theme) => ({
1692
2038
  }
1693
2039
  }));
1694
2040
  function DryRunResults() {
1695
- const classes = useStyles$6();
2041
+ const classes = useStyles$5();
1696
2042
  const dryRun = useDryRun();
1697
2043
  const [expanded, setExpanded] = useState(false);
1698
2044
  const [hidden, setHidden] = useState(true);
@@ -1724,7 +2070,7 @@ function DryRunResults() {
1724
2070
  }), /* @__PURE__ */ React.createElement(DryRunResultsView, null))));
1725
2071
  }
1726
2072
 
1727
- const useStyles$5 = makeStyles((theme) => ({
2073
+ const useStyles$4 = makeStyles((theme) => ({
1728
2074
  button: {
1729
2075
  padding: theme.spacing(1)
1730
2076
  },
@@ -1743,7 +2089,7 @@ const useStyles$5 = makeStyles((theme) => ({
1743
2089
  }));
1744
2090
  function TemplateEditorBrowser(props) {
1745
2091
  var _a, _b;
1746
- const classes = useStyles$5();
2092
+ const classes = useStyles$4();
1747
2093
  const directoryEditor = useDirectoryEditor();
1748
2094
  const changedFiles = directoryEditor.files.filter((file) => file.dirty);
1749
2095
  const handleClose = () => {
@@ -1789,170 +2135,6 @@ function TemplateEditorBrowser(props) {
1789
2135
  }));
1790
2136
  }
1791
2137
 
1792
- const useStyles$4 = makeStyles$1({
1793
- containerWrapper: {
1794
- position: "relative",
1795
- width: "100%",
1796
- height: "100%"
1797
- },
1798
- container: {
1799
- position: "absolute",
1800
- top: 0,
1801
- bottom: 0,
1802
- left: 0,
1803
- right: 0,
1804
- overflow: "auto"
1805
- }
1806
- });
1807
- class ErrorBoundary extends Component {
1808
- constructor() {
1809
- super(...arguments);
1810
- this.state = {
1811
- shouldRender: true
1812
- };
1813
- }
1814
- componentDidUpdate(prevProps) {
1815
- if (prevProps.invalidator !== this.props.invalidator) {
1816
- this.setState({ shouldRender: true });
1817
- }
1818
- }
1819
- componentDidCatch(error) {
1820
- this.props.setErrorText(error.message);
1821
- this.setState({ shouldRender: false });
1822
- }
1823
- render() {
1824
- return this.state.shouldRender ? this.props.children : null;
1825
- }
1826
- }
1827
- function isJsonObject(value) {
1828
- return typeof value === "object" && value !== null && !Array.isArray(value);
1829
- }
1830
- function TemplateEditorForm(props) {
1831
- const {
1832
- content,
1833
- contentIsSpec,
1834
- data,
1835
- onUpdate,
1836
- onDryRun,
1837
- setErrorText,
1838
- fieldExtensions = [],
1839
- layouts = []
1840
- } = props;
1841
- const classes = useStyles$4();
1842
- const apiHolder = useApiHolder();
1843
- const [steps, setSteps] = useState();
1844
- const fields = useMemo(() => {
1845
- return Object.fromEntries(
1846
- fieldExtensions.map(({ name, component }) => [name, component])
1847
- );
1848
- }, [fieldExtensions]);
1849
- useDebounce(
1850
- () => {
1851
- try {
1852
- if (!content) {
1853
- setSteps(void 0);
1854
- return;
1855
- }
1856
- const parsed = yaml.parse(content);
1857
- if (!isJsonObject(parsed)) {
1858
- setSteps(void 0);
1859
- return;
1860
- }
1861
- let rootObj = parsed;
1862
- if (!contentIsSpec) {
1863
- const isTemplate = String(parsed.kind).toLocaleLowerCase("en-US") === "template";
1864
- if (!isTemplate) {
1865
- setSteps(void 0);
1866
- return;
1867
- }
1868
- rootObj = isJsonObject(parsed.spec) ? parsed.spec : {};
1869
- }
1870
- const { parameters } = rootObj;
1871
- if (!Array.isArray(parameters)) {
1872
- setErrorText("Template parameters must be an array");
1873
- setSteps(void 0);
1874
- return;
1875
- }
1876
- const fieldValidators = Object.fromEntries(
1877
- fieldExtensions.map(({ name, validation }) => [name, validation])
1878
- );
1879
- setErrorText();
1880
- setSteps(
1881
- parameters.flatMap(
1882
- (param) => isJsonObject(param) ? [
1883
- {
1884
- title: String(param.title),
1885
- schema: param,
1886
- validate: createValidator(param, fieldValidators, {
1887
- apiHolder
1888
- })
1889
- }
1890
- ] : []
1891
- )
1892
- );
1893
- } catch (e) {
1894
- setErrorText(e.message);
1895
- }
1896
- },
1897
- 250,
1898
- [contentIsSpec, content, apiHolder]
1899
- );
1900
- if (!steps) {
1901
- return null;
1902
- }
1903
- return /* @__PURE__ */ React.createElement("div", {
1904
- className: classes.containerWrapper
1905
- }, /* @__PURE__ */ React.createElement("div", {
1906
- className: classes.container
1907
- }, /* @__PURE__ */ React.createElement(ErrorBoundary, {
1908
- invalidator: steps,
1909
- setErrorText
1910
- }, /* @__PURE__ */ React.createElement(MultistepJsonForm, {
1911
- steps,
1912
- fields,
1913
- formData: data,
1914
- onChange: (e) => onUpdate(e.formData),
1915
- onReset: () => onUpdate({}),
1916
- finishButtonLabel: onDryRun && "Try It",
1917
- onFinish: onDryRun && (() => onDryRun(data)),
1918
- layouts
1919
- }))));
1920
- }
1921
- function TemplateEditorFormDirectoryEditorDryRun(props) {
1922
- const { setErrorText, fieldExtensions = [], layouts } = props;
1923
- const dryRun = useDryRun();
1924
- const directoryEditor = useDirectoryEditor();
1925
- const { selectedFile } = directoryEditor;
1926
- const [data, setData] = useState({});
1927
- const handleDryRun = async () => {
1928
- if (!selectedFile) {
1929
- return;
1930
- }
1931
- try {
1932
- await dryRun.execute({
1933
- templateContent: selectedFile.content,
1934
- values: data,
1935
- files: directoryEditor.files
1936
- });
1937
- setErrorText();
1938
- } catch (e) {
1939
- setErrorText(String(e.cause || e));
1940
- throw e;
1941
- }
1942
- };
1943
- const content = selectedFile && selectedFile.path.match(/\.ya?ml$/) ? selectedFile.content : void 0;
1944
- return /* @__PURE__ */ React.createElement(TemplateEditorForm, {
1945
- onDryRun: handleDryRun,
1946
- fieldExtensions,
1947
- setErrorText,
1948
- content,
1949
- data,
1950
- onUpdate: setData,
1951
- layouts
1952
- });
1953
- }
1954
- TemplateEditorForm.DirectoryEditorDryRun = TemplateEditorFormDirectoryEditorDryRun;
1955
-
1956
2138
  const useStyles$3 = makeStyles((theme) => ({
1957
2139
  container: {
1958
2140
  position: "relative",
@@ -2275,6 +2457,11 @@ function TemplateEditorPage(props) {
2275
2457
  onClose: () => setSelection(void 0),
2276
2458
  layouts: props.layouts
2277
2459
  });
2460
+ } else if ((selection == null ? void 0 : selection.type) === "field-explorer") {
2461
+ content = /* @__PURE__ */ React.createElement(CustomFieldExplorer, {
2462
+ customFieldExtensions: props.customFieldExtensions,
2463
+ onClose: () => setSelection(void 0)
2464
+ });
2278
2465
  } else {
2279
2466
  content = /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(TemplateEditorIntro, {
2280
2467
  onSelect: (option) => {
@@ -2283,6 +2470,8 @@ function TemplateEditorPage(props) {
2283
2470
  });
2284
2471
  } else if (option === "form") {
2285
2472
  setSelection({ type: "form" });
2473
+ } else if (option === "field-explorer") {
2474
+ setSelection({ type: "field-explorer" });
2286
2475
  }
2287
2476
  }
2288
2477
  }));
@@ -2522,7 +2711,7 @@ const ListTasksPage = (props) => {
2522
2711
 
2523
2712
  const Router = (props) => {
2524
2713
  const { groups, components = {}, defaultPreviewTemplate } = props;
2525
- const { TemplateCardComponent, TaskPageComponent } = components;
2714
+ const { ReviewStepComponent, TemplateCardComponent, TaskPageComponent } = components;
2526
2715
  const outlet = useOutlet();
2527
2716
  const TaskPageElement = TaskPageComponent != null ? TaskPageComponent : TaskPage;
2528
2717
  const customFieldExtensions = useElementFilter(
@@ -2576,8 +2765,10 @@ const Router = (props) => {
2576
2765
  }), /* @__PURE__ */ React.createElement(Route, {
2577
2766
  path: selectedTemplateRouteRef.path,
2578
2767
  element: /* @__PURE__ */ React.createElement(SecretsContextProvider, null, /* @__PURE__ */ React.createElement(TemplatePage, {
2768
+ ReviewStepComponent,
2579
2769
  customFieldExtensions: fieldExtensions,
2580
- layouts: customLayouts
2770
+ layouts: customLayouts,
2771
+ headerOptions: props.headerOptions
2581
2772
  }))
2582
2773
  }), /* @__PURE__ */ React.createElement(Route, {
2583
2774
  path: scaffolderListTaskRouteRef.path,
@@ -2604,4 +2795,4 @@ const Router = (props) => {
2604
2795
  };
2605
2796
 
2606
2797
  export { Router };
2607
- //# sourceMappingURL=Router-ace3f7b5.esm.js.map
2798
+ //# sourceMappingURL=Router-c72ed8e0.esm.js.map