@backstage/plugin-scaffolder-react 0.0.0-nightly-20230111022819
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/README.md +12 -0
- package/alpha/package.json +6 -0
- package/dist/index.alpha.d.ts +496 -0
- package/dist/index.beta.d.ts +361 -0
- package/dist/index.d.ts +361 -0
- package/dist/index.esm.js +571 -0
- package/dist/index.esm.js.map +1 -0
- package/package.json +85 -0
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
import { attachComponentData, createApiRef, useElementFilter, useApi, featureFlagsApiRef, useAnalytics, useApiHolder, useApp } from '@backstage/core-plugin-api';
|
|
2
|
+
import { createVersionedContext, createVersionedValueMap, getOrCreateGlobalSingleton } from '@backstage/version-bridge';
|
|
3
|
+
import React, { useState, useContext, useCallback, useMemo } from 'react';
|
|
4
|
+
import { makeStyles, Stepper as Stepper$1, Step, StepLabel, Button, useTheme, Card, CardContent, Grid, Box, Divider, Chip, CardActions, Typography } from '@material-ui/core';
|
|
5
|
+
import { withTheme } from '@rjsf/core-v5';
|
|
6
|
+
import { Draft07 } from 'json-schema-library';
|
|
7
|
+
import { StructuredMetadataTable, ItemCardHeader, Link, MarkdownContent, UserIcon, Content, ItemCardGrid, ContentHeader } from '@backstage/core-components';
|
|
8
|
+
import validator from '@rjsf/validator-ajv6';
|
|
9
|
+
import qs from 'qs';
|
|
10
|
+
import { RELATION_OWNED_BY, stringifyEntityRef } from '@backstage/catalog-model';
|
|
11
|
+
import { FavoriteEntity, getEntityRelations, EntityRefLinks } from '@backstage/plugin-catalog-react';
|
|
12
|
+
import LanguageIcon from '@material-ui/icons/Language';
|
|
13
|
+
|
|
14
|
+
const FIELD_EXTENSION_WRAPPER_KEY = "scaffolder.extensions.wrapper.v1";
|
|
15
|
+
const FIELD_EXTENSION_KEY = "scaffolder.extensions.field.v1";
|
|
16
|
+
|
|
17
|
+
function createScaffolderFieldExtension(options) {
|
|
18
|
+
return {
|
|
19
|
+
expose() {
|
|
20
|
+
const FieldExtensionDataHolder = () => null;
|
|
21
|
+
attachComponentData(
|
|
22
|
+
FieldExtensionDataHolder,
|
|
23
|
+
FIELD_EXTENSION_KEY,
|
|
24
|
+
options
|
|
25
|
+
);
|
|
26
|
+
return FieldExtensionDataHolder;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const ScaffolderFieldExtensions = () => null;
|
|
31
|
+
attachComponentData(
|
|
32
|
+
ScaffolderFieldExtensions,
|
|
33
|
+
FIELD_EXTENSION_WRAPPER_KEY,
|
|
34
|
+
true
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const SecretsContext = createVersionedContext("secrets-context");
|
|
38
|
+
const SecretsContextProvider = ({ children }) => {
|
|
39
|
+
const [secrets, setSecrets] = useState({});
|
|
40
|
+
return /* @__PURE__ */ React.createElement(
|
|
41
|
+
SecretsContext.Provider,
|
|
42
|
+
{
|
|
43
|
+
value: createVersionedValueMap({ 1: { secrets, setSecrets } })
|
|
44
|
+
},
|
|
45
|
+
children
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
const useTemplateSecrets = () => {
|
|
49
|
+
var _a;
|
|
50
|
+
const value = (_a = useContext(SecretsContext)) == null ? void 0 : _a.atVersion(1);
|
|
51
|
+
if (!value) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
"useTemplateSecrets must be used within a SecretsContextProvider"
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
const { setSecrets: updateSecrets, secrets = {} } = value;
|
|
57
|
+
const setSecrets = useCallback(
|
|
58
|
+
(input) => {
|
|
59
|
+
updateSecrets((currentSecrets) => ({ ...currentSecrets, ...input }));
|
|
60
|
+
},
|
|
61
|
+
[updateSecrets]
|
|
62
|
+
);
|
|
63
|
+
return { setSecrets, secrets };
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const scaffolderApiRef = getOrCreateGlobalSingleton(
|
|
67
|
+
"scaffolder:scaffolder-api-ref",
|
|
68
|
+
() => createApiRef({
|
|
69
|
+
id: "plugin.scaffolder.service"
|
|
70
|
+
})
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const useCustomFieldExtensions = (outlet) => {
|
|
74
|
+
return useElementFilter(
|
|
75
|
+
outlet,
|
|
76
|
+
(elements) => elements.selectByComponentData({
|
|
77
|
+
key: FIELD_EXTENSION_WRAPPER_KEY
|
|
78
|
+
}).findComponentData({
|
|
79
|
+
key: FIELD_EXTENSION_KEY
|
|
80
|
+
})
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
function isObject(value) {
|
|
85
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
86
|
+
}
|
|
87
|
+
function extractUiSchema(schema, uiSchema) {
|
|
88
|
+
if (!isObject(schema)) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const { properties, items, anyOf, oneOf, allOf, dependencies } = schema;
|
|
92
|
+
for (const propName in schema) {
|
|
93
|
+
if (!schema.hasOwnProperty(propName)) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (propName.startsWith("ui:")) {
|
|
97
|
+
uiSchema[propName] = schema[propName];
|
|
98
|
+
delete schema[propName];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (isObject(properties)) {
|
|
102
|
+
for (const propName in properties) {
|
|
103
|
+
if (!properties.hasOwnProperty(propName)) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const schemaNode = properties[propName];
|
|
107
|
+
if (!isObject(schemaNode)) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const innerUiSchema = {};
|
|
111
|
+
uiSchema[propName] = innerUiSchema;
|
|
112
|
+
extractUiSchema(schemaNode, innerUiSchema);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (isObject(items)) {
|
|
116
|
+
const innerUiSchema = {};
|
|
117
|
+
uiSchema.items = innerUiSchema;
|
|
118
|
+
extractUiSchema(items, innerUiSchema);
|
|
119
|
+
}
|
|
120
|
+
if (Array.isArray(anyOf)) {
|
|
121
|
+
for (const schemaNode of anyOf) {
|
|
122
|
+
if (!isObject(schemaNode)) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
extractUiSchema(schemaNode, uiSchema);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (Array.isArray(oneOf)) {
|
|
129
|
+
for (const schemaNode of oneOf) {
|
|
130
|
+
if (!isObject(schemaNode)) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
extractUiSchema(schemaNode, uiSchema);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (Array.isArray(allOf)) {
|
|
137
|
+
for (const schemaNode of allOf) {
|
|
138
|
+
if (!isObject(schemaNode)) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
extractUiSchema(schemaNode, uiSchema);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (isObject(dependencies)) {
|
|
145
|
+
for (const depName of Object.keys(dependencies)) {
|
|
146
|
+
const schemaNode = dependencies[depName];
|
|
147
|
+
if (!isObject(schemaNode)) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
extractUiSchema(schemaNode, uiSchema);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const extractSchemaFromStep = (inputStep) => {
|
|
155
|
+
const uiSchema = {};
|
|
156
|
+
const returnSchema = JSON.parse(JSON.stringify(inputStep));
|
|
157
|
+
extractUiSchema(returnSchema, uiSchema);
|
|
158
|
+
return { uiSchema, schema: returnSchema };
|
|
159
|
+
};
|
|
160
|
+
const createFieldValidation = () => {
|
|
161
|
+
const fieldValidation = {
|
|
162
|
+
__errors: [],
|
|
163
|
+
addError: (message) => {
|
|
164
|
+
var _a;
|
|
165
|
+
(_a = fieldValidation.__errors) == null ? void 0 : _a.push(message);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
return fieldValidation;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const createAsyncValidators = (rootSchema, validators, context) => {
|
|
172
|
+
async function validate(formData, pathPrefix = "#") {
|
|
173
|
+
const parsedSchema = new Draft07(rootSchema);
|
|
174
|
+
const formValidation = {};
|
|
175
|
+
for (const [key, value] of Object.entries(formData)) {
|
|
176
|
+
const definitionInSchema = parsedSchema.getSchema(
|
|
177
|
+
`${pathPrefix}/${key}`,
|
|
178
|
+
formData
|
|
179
|
+
);
|
|
180
|
+
if (definitionInSchema && "ui:field" in definitionInSchema) {
|
|
181
|
+
const validator = validators[definitionInSchema["ui:field"]];
|
|
182
|
+
if (validator) {
|
|
183
|
+
const fieldValidation = createFieldValidation();
|
|
184
|
+
try {
|
|
185
|
+
await validator(value, fieldValidation, { ...context, formData });
|
|
186
|
+
} catch (ex) {
|
|
187
|
+
fieldValidation.addError(ex.message);
|
|
188
|
+
}
|
|
189
|
+
formValidation[key] = fieldValidation;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return formValidation;
|
|
194
|
+
}
|
|
195
|
+
return async (formData) => {
|
|
196
|
+
return await validate(formData);
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const useTemplateSchema = (manifest) => {
|
|
201
|
+
const featureFlags = useApi(featureFlagsApiRef);
|
|
202
|
+
const steps = manifest.steps.map(({ title, description, schema }) => ({
|
|
203
|
+
title,
|
|
204
|
+
description,
|
|
205
|
+
mergedSchema: schema,
|
|
206
|
+
...extractSchemaFromStep(schema)
|
|
207
|
+
}));
|
|
208
|
+
const returningSteps = steps.filter((step) => {
|
|
209
|
+
var _a;
|
|
210
|
+
const stepFeatureFlag = (_a = step.uiSchema["ui:backstage"]) == null ? void 0 : _a.featureFlag;
|
|
211
|
+
return stepFeatureFlag ? featureFlags.isActive(stepFeatureFlag) : true;
|
|
212
|
+
}).map((step) => ({
|
|
213
|
+
...step,
|
|
214
|
+
schema: {
|
|
215
|
+
...step.schema,
|
|
216
|
+
// Title is rendered at the top of the page, so let's ignore this from jsonschemaform
|
|
217
|
+
title: void 0,
|
|
218
|
+
properties: Object.fromEntries(
|
|
219
|
+
Object.entries(step.schema.properties).filter(
|
|
220
|
+
([key]) => {
|
|
221
|
+
var _a, _b;
|
|
222
|
+
const stepFeatureFlag = (_b = (_a = step.uiSchema[key]) == null ? void 0 : _a["ui:backstage"]) == null ? void 0 : _b.featureFlag;
|
|
223
|
+
return stepFeatureFlag ? featureFlags.isActive(stepFeatureFlag) : true;
|
|
224
|
+
}
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
}));
|
|
229
|
+
return {
|
|
230
|
+
steps: returningSteps
|
|
231
|
+
};
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const ReviewState = (props) => {
|
|
235
|
+
const reviewData = Object.fromEntries(
|
|
236
|
+
Object.entries(props.formState).map(([key, value]) => {
|
|
237
|
+
var _a;
|
|
238
|
+
for (const step of props.schemas) {
|
|
239
|
+
const parsedSchema = new Draft07(step.mergedSchema);
|
|
240
|
+
const definitionInSchema = parsedSchema.getSchema(
|
|
241
|
+
`#/${key}`,
|
|
242
|
+
props.formState
|
|
243
|
+
);
|
|
244
|
+
if (definitionInSchema) {
|
|
245
|
+
const backstageReviewOptions = (_a = definitionInSchema["ui:backstage"]) == null ? void 0 : _a.review;
|
|
246
|
+
if (backstageReviewOptions) {
|
|
247
|
+
if (backstageReviewOptions.mask) {
|
|
248
|
+
return [key, backstageReviewOptions.mask];
|
|
249
|
+
}
|
|
250
|
+
if (backstageReviewOptions.show === false) {
|
|
251
|
+
return [];
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (definitionInSchema["ui:widget"] === "password") {
|
|
255
|
+
return [key, "******"];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return [key, value];
|
|
260
|
+
})
|
|
261
|
+
);
|
|
262
|
+
return /* @__PURE__ */ React.createElement(StructuredMetadataTable, { metadata: reviewData });
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const useFormDataFromQuery = (initialState) => {
|
|
266
|
+
return useState(() => {
|
|
267
|
+
if (initialState) {
|
|
268
|
+
return initialState;
|
|
269
|
+
}
|
|
270
|
+
const query = qs.parse(window.location.search, {
|
|
271
|
+
ignoreQueryPrefix: true
|
|
272
|
+
});
|
|
273
|
+
try {
|
|
274
|
+
return JSON.parse(query.formData);
|
|
275
|
+
} catch (e) {
|
|
276
|
+
return {};
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const useStyles$3 = makeStyles((theme) => ({
|
|
282
|
+
backButton: {
|
|
283
|
+
marginRight: theme.spacing(1)
|
|
284
|
+
},
|
|
285
|
+
footer: {
|
|
286
|
+
display: "flex",
|
|
287
|
+
flexDirection: "row",
|
|
288
|
+
justifyContent: "right"
|
|
289
|
+
},
|
|
290
|
+
formWrapper: {
|
|
291
|
+
padding: theme.spacing(2)
|
|
292
|
+
}
|
|
293
|
+
}));
|
|
294
|
+
const Form = withTheme(require("@rjsf/material-ui-v5").Theme);
|
|
295
|
+
const Stepper = (props) => {
|
|
296
|
+
var _a;
|
|
297
|
+
const analytics = useAnalytics();
|
|
298
|
+
const { steps } = useTemplateSchema(props.manifest);
|
|
299
|
+
const apiHolder = useApiHolder();
|
|
300
|
+
const [activeStep, setActiveStep] = useState(0);
|
|
301
|
+
const [formState, setFormState] = useFormDataFromQuery(props.initialState);
|
|
302
|
+
const [errors, setErrors] = useState();
|
|
303
|
+
const styles = useStyles$3();
|
|
304
|
+
const extensions = useMemo(() => {
|
|
305
|
+
return Object.fromEntries(
|
|
306
|
+
props.extensions.map(({ name, component }) => [name, component])
|
|
307
|
+
);
|
|
308
|
+
}, [props.extensions]);
|
|
309
|
+
const validators = useMemo(() => {
|
|
310
|
+
return Object.fromEntries(
|
|
311
|
+
props.extensions.map(({ name, validation: validation2 }) => [name, validation2])
|
|
312
|
+
);
|
|
313
|
+
}, [props.extensions]);
|
|
314
|
+
const validation = useMemo(() => {
|
|
315
|
+
var _a2;
|
|
316
|
+
return createAsyncValidators((_a2 = steps[activeStep]) == null ? void 0 : _a2.mergedSchema, validators, {
|
|
317
|
+
apiHolder
|
|
318
|
+
});
|
|
319
|
+
}, [steps, activeStep, validators, apiHolder]);
|
|
320
|
+
const handleBack = () => {
|
|
321
|
+
setActiveStep((prevActiveStep) => prevActiveStep - 1);
|
|
322
|
+
};
|
|
323
|
+
const handleChange = useCallback(
|
|
324
|
+
(e) => setFormState((current) => ({ ...current, ...e.formData })),
|
|
325
|
+
[setFormState]
|
|
326
|
+
);
|
|
327
|
+
const handleNext = async ({
|
|
328
|
+
formData
|
|
329
|
+
}) => {
|
|
330
|
+
setErrors(void 0);
|
|
331
|
+
const returnedValidation = await validation(formData);
|
|
332
|
+
const hasErrors = Object.values(returnedValidation).some(
|
|
333
|
+
(i) => {
|
|
334
|
+
var _a2;
|
|
335
|
+
return (_a2 = i.__errors) == null ? void 0 : _a2.length;
|
|
336
|
+
}
|
|
337
|
+
);
|
|
338
|
+
if (hasErrors) {
|
|
339
|
+
setErrors(returnedValidation);
|
|
340
|
+
} else {
|
|
341
|
+
setErrors(void 0);
|
|
342
|
+
setActiveStep((prevActiveStep) => {
|
|
343
|
+
const stepNum = prevActiveStep + 1;
|
|
344
|
+
analytics.captureEvent("click", `Next Step (${stepNum})`);
|
|
345
|
+
return stepNum;
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
setFormState((current) => ({ ...current, ...formData }));
|
|
349
|
+
};
|
|
350
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Stepper$1, { activeStep, alternativeLabel: true, variant: "elevation" }, steps.map((step, index) => /* @__PURE__ */ React.createElement(Step, { key: index }, /* @__PURE__ */ React.createElement(StepLabel, null, step.title))), /* @__PURE__ */ React.createElement(Step, null, /* @__PURE__ */ React.createElement(StepLabel, null, "Review"))), /* @__PURE__ */ React.createElement("div", { className: styles.formWrapper }, activeStep < steps.length ? /* @__PURE__ */ React.createElement(
|
|
351
|
+
Form,
|
|
352
|
+
{
|
|
353
|
+
validator,
|
|
354
|
+
extraErrors: errors,
|
|
355
|
+
formData: formState,
|
|
356
|
+
formContext: { formData: formState },
|
|
357
|
+
schema: steps[activeStep].schema,
|
|
358
|
+
uiSchema: steps[activeStep].uiSchema,
|
|
359
|
+
onSubmit: handleNext,
|
|
360
|
+
fields: extensions,
|
|
361
|
+
showErrorList: false,
|
|
362
|
+
onChange: handleChange,
|
|
363
|
+
...(_a = props.FormProps) != null ? _a : {}
|
|
364
|
+
},
|
|
365
|
+
/* @__PURE__ */ React.createElement("div", { className: styles.footer }, /* @__PURE__ */ React.createElement(
|
|
366
|
+
Button,
|
|
367
|
+
{
|
|
368
|
+
onClick: handleBack,
|
|
369
|
+
className: styles.backButton,
|
|
370
|
+
disabled: activeStep < 1
|
|
371
|
+
},
|
|
372
|
+
"Back"
|
|
373
|
+
), /* @__PURE__ */ React.createElement(Button, { variant: "contained", color: "primary", type: "submit" }, activeStep === steps.length - 1 ? "Review" : "Next"))
|
|
374
|
+
) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(ReviewState, { formState, schemas: steps }), /* @__PURE__ */ React.createElement("div", { className: styles.footer }, /* @__PURE__ */ React.createElement(
|
|
375
|
+
Button,
|
|
376
|
+
{
|
|
377
|
+
onClick: handleBack,
|
|
378
|
+
className: styles.backButton,
|
|
379
|
+
disabled: activeStep < 1
|
|
380
|
+
},
|
|
381
|
+
"Back"
|
|
382
|
+
), /* @__PURE__ */ React.createElement(
|
|
383
|
+
Button,
|
|
384
|
+
{
|
|
385
|
+
variant: "contained",
|
|
386
|
+
onClick: () => {
|
|
387
|
+
var _a2;
|
|
388
|
+
props.onComplete(formState);
|
|
389
|
+
const name = typeof formState.name === "string" ? formState.name : void 0;
|
|
390
|
+
analytics.captureEvent(
|
|
391
|
+
"create",
|
|
392
|
+
(_a2 = name != null ? name : props.templateName) != null ? _a2 : "unknown"
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
"Create"
|
|
397
|
+
)))));
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
const useStyles$2 = makeStyles(
|
|
401
|
+
() => ({
|
|
402
|
+
header: {
|
|
403
|
+
backgroundImage: ({ cardBackgroundImage }) => cardBackgroundImage
|
|
404
|
+
},
|
|
405
|
+
subtitleWrapper: {
|
|
406
|
+
display: "flex",
|
|
407
|
+
justifyContent: "space-between"
|
|
408
|
+
}
|
|
409
|
+
})
|
|
410
|
+
);
|
|
411
|
+
const CardHeader = (props) => {
|
|
412
|
+
const {
|
|
413
|
+
template: {
|
|
414
|
+
metadata: { title, name },
|
|
415
|
+
spec: { type }
|
|
416
|
+
}
|
|
417
|
+
} = props;
|
|
418
|
+
const { getPageTheme } = useTheme();
|
|
419
|
+
const themeForType = getPageTheme({ themeId: type });
|
|
420
|
+
const styles = useStyles$2({
|
|
421
|
+
cardBackgroundImage: themeForType.backgroundImage
|
|
422
|
+
});
|
|
423
|
+
const SubtitleComponent = /* @__PURE__ */ React.createElement("div", { className: styles.subtitleWrapper }, /* @__PURE__ */ React.createElement("div", null, type), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(FavoriteEntity, { entity: props.template, style: { padding: 0 } })));
|
|
424
|
+
return /* @__PURE__ */ React.createElement(
|
|
425
|
+
ItemCardHeader,
|
|
426
|
+
{
|
|
427
|
+
title: title != null ? title : name,
|
|
428
|
+
subtitle: SubtitleComponent,
|
|
429
|
+
classes: { root: styles.header }
|
|
430
|
+
}
|
|
431
|
+
);
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
const useStyles$1 = makeStyles(() => ({
|
|
435
|
+
linkText: {
|
|
436
|
+
display: "inline-flex",
|
|
437
|
+
alignItems: "center"
|
|
438
|
+
}
|
|
439
|
+
}));
|
|
440
|
+
const CardLink = ({ icon: Icon, text, url }) => {
|
|
441
|
+
const styles = useStyles$1();
|
|
442
|
+
return /* @__PURE__ */ React.createElement("div", { className: styles.linkText }, /* @__PURE__ */ React.createElement(Icon, { fontSize: "small" }), /* @__PURE__ */ React.createElement(Link, { style: { marginLeft: "8px" }, to: url }, text || url));
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
const useStyles = makeStyles((theme) => ({
|
|
446
|
+
box: {
|
|
447
|
+
overflow: "hidden",
|
|
448
|
+
textOverflow: "ellipsis",
|
|
449
|
+
display: "-webkit-box",
|
|
450
|
+
"-webkit-line-clamp": 10,
|
|
451
|
+
"-webkit-box-orient": "vertical"
|
|
452
|
+
},
|
|
453
|
+
markdown: {
|
|
454
|
+
/** to make the styles for React Markdown not leak into the description */
|
|
455
|
+
"& :first-child": {
|
|
456
|
+
margin: 0
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
label: {
|
|
460
|
+
color: theme.palette.text.secondary,
|
|
461
|
+
textTransform: "uppercase",
|
|
462
|
+
fontWeight: "bold",
|
|
463
|
+
letterSpacing: 0.5,
|
|
464
|
+
lineHeight: 1,
|
|
465
|
+
fontSize: "0.75rem"
|
|
466
|
+
},
|
|
467
|
+
footer: {
|
|
468
|
+
display: "flex",
|
|
469
|
+
justifyContent: "space-between",
|
|
470
|
+
flex: 1,
|
|
471
|
+
alignItems: "center"
|
|
472
|
+
},
|
|
473
|
+
ownedBy: {
|
|
474
|
+
display: "flex",
|
|
475
|
+
alignItems: "center",
|
|
476
|
+
flex: 1,
|
|
477
|
+
color: theme.palette.link
|
|
478
|
+
}
|
|
479
|
+
}));
|
|
480
|
+
const TemplateCard = (props) => {
|
|
481
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
482
|
+
const { template } = props;
|
|
483
|
+
const styles = useStyles();
|
|
484
|
+
const ownedByRelations = getEntityRelations(template, RELATION_OWNED_BY);
|
|
485
|
+
const app = useApp();
|
|
486
|
+
const iconResolver = (key) => {
|
|
487
|
+
var _a2;
|
|
488
|
+
return key ? (_a2 = app.getSystemIcon(key)) != null ? _a2 : LanguageIcon : LanguageIcon;
|
|
489
|
+
};
|
|
490
|
+
return /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardHeader, { template }), /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 2 }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(Box, { className: styles.box }, /* @__PURE__ */ React.createElement(
|
|
491
|
+
MarkdownContent,
|
|
492
|
+
{
|
|
493
|
+
className: styles.markdown,
|
|
494
|
+
content: (_a = template.metadata.description) != null ? _a : "No description"
|
|
495
|
+
}
|
|
496
|
+
))), ((_c = (_b = template.metadata.tags) == null ? void 0 : _b.length) != null ? _c : 0) > 0 && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(Divider, null)), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 2 }, (_d = template.metadata.tags) == null ? void 0 : _d.map((tag) => /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(
|
|
497
|
+
Chip,
|
|
498
|
+
{
|
|
499
|
+
style: { margin: 0 },
|
|
500
|
+
size: "small",
|
|
501
|
+
label: tag,
|
|
502
|
+
key: tag
|
|
503
|
+
}
|
|
504
|
+
)))))), (props.additionalLinks || ((_e = template.metadata.links) == null ? void 0 : _e.length)) && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(Divider, null)), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 12 }, /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 2 }, (_f = props.additionalLinks) == null ? void 0 : _f.map(({ icon, text, url }) => /* @__PURE__ */ React.createElement(Grid, { className: styles.linkText, item: true, xs: 6 }, /* @__PURE__ */ React.createElement(CardLink, { icon, text, url }))), (_g = template.metadata.links) == null ? void 0 : _g.map(({ url, icon, title }) => /* @__PURE__ */ React.createElement(Grid, { className: styles.linkText, item: true, xs: 6 }, /* @__PURE__ */ React.createElement(
|
|
505
|
+
CardLink,
|
|
506
|
+
{
|
|
507
|
+
icon: iconResolver(icon),
|
|
508
|
+
text: title || url,
|
|
509
|
+
url
|
|
510
|
+
}
|
|
511
|
+
)))))))), /* @__PURE__ */ React.createElement(CardActions, { style: { padding: "16px" } }, /* @__PURE__ */ React.createElement("div", { className: styles.footer }, /* @__PURE__ */ React.createElement("div", { className: styles.ownedBy }, ownedByRelations.length > 0 && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(UserIcon, { fontSize: "small" }), /* @__PURE__ */ React.createElement(
|
|
512
|
+
EntityRefLinks,
|
|
513
|
+
{
|
|
514
|
+
style: { marginLeft: "8px" },
|
|
515
|
+
entityRefs: ownedByRelations,
|
|
516
|
+
defaultKind: "Group"
|
|
517
|
+
}
|
|
518
|
+
))), /* @__PURE__ */ React.createElement(
|
|
519
|
+
Button,
|
|
520
|
+
{
|
|
521
|
+
size: "small",
|
|
522
|
+
variant: "outlined",
|
|
523
|
+
color: "primary",
|
|
524
|
+
onClick: () => {
|
|
525
|
+
var _a2;
|
|
526
|
+
return (_a2 = props.onSelected) == null ? void 0 : _a2.call(props, template);
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
"Choose"
|
|
530
|
+
))));
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
const TemplateGroup = (props) => {
|
|
534
|
+
const {
|
|
535
|
+
templates,
|
|
536
|
+
title,
|
|
537
|
+
components: { CardComponent } = {},
|
|
538
|
+
onSelected
|
|
539
|
+
} = props;
|
|
540
|
+
const titleComponent = typeof title === "string" ? /* @__PURE__ */ React.createElement(ContentHeader, { title }) : title;
|
|
541
|
+
if (templates.length === 0) {
|
|
542
|
+
return /* @__PURE__ */ React.createElement(Content, null, titleComponent, /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, "No templates found that match your filter. Learn more about", " ", /* @__PURE__ */ React.createElement(Link, { to: "https://backstage.io/docs/features/software-templates/adding-templates" }, "adding templates"), "."));
|
|
543
|
+
}
|
|
544
|
+
const Card = CardComponent || TemplateCard;
|
|
545
|
+
return /* @__PURE__ */ React.createElement(Content, null, titleComponent, /* @__PURE__ */ React.createElement(ItemCardGrid, null, templates.map(({ template, additionalLinks }) => /* @__PURE__ */ React.createElement(
|
|
546
|
+
Card,
|
|
547
|
+
{
|
|
548
|
+
key: stringifyEntityRef(template),
|
|
549
|
+
additionalLinks,
|
|
550
|
+
template,
|
|
551
|
+
onSelected
|
|
552
|
+
}
|
|
553
|
+
))));
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
function createNextScaffolderFieldExtension(options) {
|
|
557
|
+
return {
|
|
558
|
+
expose() {
|
|
559
|
+
const FieldExtensionDataHolder = () => null;
|
|
560
|
+
attachComponentData(
|
|
561
|
+
FieldExtensionDataHolder,
|
|
562
|
+
FIELD_EXTENSION_KEY,
|
|
563
|
+
options
|
|
564
|
+
);
|
|
565
|
+
return FieldExtensionDataHolder;
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
export { ReviewState, ScaffolderFieldExtensions, SecretsContextProvider, Stepper, TemplateCard, TemplateGroup, createFieldValidation, createNextScaffolderFieldExtension, createScaffolderFieldExtension, extractSchemaFromStep, scaffolderApiRef, useCustomFieldExtensions, useFormDataFromQuery, useTemplateSchema, useTemplateSecrets };
|
|
571
|
+
//# sourceMappingURL=index.esm.js.map
|