@backstage/plugin-home 0.4.33-next.3 → 0.5.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.
package/dist/index.esm.js CHANGED
@@ -1,66 +1,565 @@
1
- import { createReactExtension, useApp, createRouteRef, createPlugin, createRoutableExtension, createComponentExtension } from '@backstage/core-plugin-api';
2
- import React, { Suspense } from 'react';
3
- import { Dialog, DialogTitle, DialogContent, DialogActions, Button, IconButton, makeStyles } from '@material-ui/core';
1
+ import { useElementFilter, useApi, storageApiRef, getComponentData, createReactExtension, useApp, createRouteRef, createPlugin, createRoutableExtension, createComponentExtension } from '@backstage/core-plugin-api';
2
+ import React, { useCallback, useMemo, Suspense } from 'react';
3
+ import { Dialog, DialogTitle, DialogContent, DialogActions, Button, makeStyles, createStyles, Grid, Tooltip, ListItemAvatar, IconButton as IconButton$1 } from '@material-ui/core';
4
4
  import SettingsIcon from '@material-ui/icons/Settings';
5
- import { InfoCard } from '@backstage/core-components';
5
+ import { ContentHeader, ErrorBoundary, InfoCard } from '@backstage/core-components';
6
6
  import 'react-router-dom';
7
+ import { WidthProvider, Responsive } from 'react-grid-layout';
8
+ import 'react-grid-layout/css/styles.css';
9
+ import 'react-resizable/css/styles.css';
10
+ import { compact } from 'lodash';
11
+ import useObservable from 'react-use/lib/useObservable';
12
+ import Typography from '@material-ui/core/Typography';
13
+ import IconButton from '@material-ui/core/IconButton';
14
+ import DeleteIcon from '@material-ui/icons/Delete';
15
+ import Form from '@rjsf/material-ui';
16
+ import List from '@material-ui/core/List';
17
+ import ListItem from '@material-ui/core/ListItem';
18
+ import AddIcon from '@material-ui/icons/Add';
19
+ import ListItemText from '@material-ui/core/ListItemText';
20
+ import Button$1 from '@material-ui/core/Button';
21
+ import SaveIcon from '@material-ui/icons/Save';
22
+ import EditIcon from '@material-ui/icons/Edit';
23
+ import { z } from 'zod';
7
24
 
8
25
  const SettingsModal = (props) => {
9
26
  const { open, close, componentName, children } = props;
10
27
  return /* @__PURE__ */ React.createElement(Dialog, { open, onClose: () => close() }, /* @__PURE__ */ React.createElement(DialogTitle, null, "Settings - ", componentName), /* @__PURE__ */ React.createElement(DialogContent, null, children), /* @__PURE__ */ React.createElement(DialogActions, null, /* @__PURE__ */ React.createElement(Button, { onClick: () => close(), color: "primary", variant: "contained" }, "Close")));
11
28
  };
12
29
 
30
+ const useStyles$3 = makeStyles(
31
+ (theme) => createStyles({
32
+ iconGrid: {
33
+ height: "100%",
34
+ "& *": {
35
+ padding: 0
36
+ }
37
+ },
38
+ settingsOverlay: {
39
+ position: "absolute",
40
+ backgroundColor: "rgba(40, 40, 40, 0.93)",
41
+ width: "100%",
42
+ height: "100%",
43
+ top: 0,
44
+ left: 0,
45
+ padding: theme.spacing(2),
46
+ color: "white"
47
+ }
48
+ })
49
+ );
50
+ const WidgetSettingsOverlay = (props) => {
51
+ const { id, widget, settings, handleRemove, handleSettingsSave } = props;
52
+ const [settingsDialogOpen, setSettingsDialogOpen] = React.useState(false);
53
+ const styles = useStyles$3();
54
+ return /* @__PURE__ */ React.createElement("div", { className: styles.settingsOverlay }, widget.settingsSchema && /* @__PURE__ */ React.createElement(
55
+ Dialog,
56
+ {
57
+ open: settingsDialogOpen,
58
+ className: "widgetSettingsDialog",
59
+ onClose: () => setSettingsDialogOpen(false)
60
+ },
61
+ /* @__PURE__ */ React.createElement(DialogContent, null, /* @__PURE__ */ React.createElement(
62
+ Form,
63
+ {
64
+ showErrorList: false,
65
+ schema: widget.settingsSchema,
66
+ noHtml5Validate: true,
67
+ formData: settings,
68
+ formContext: { settings },
69
+ onSubmit: ({ formData, errors }) => {
70
+ if (errors.length === 0) {
71
+ handleSettingsSave(id, formData);
72
+ setSettingsDialogOpen(false);
73
+ }
74
+ }
75
+ }
76
+ ))
77
+ ), /* @__PURE__ */ React.createElement(
78
+ Grid,
79
+ {
80
+ container: true,
81
+ className: styles.iconGrid,
82
+ alignItems: "center",
83
+ justifyContent: "center"
84
+ },
85
+ widget.settingsSchema && /* @__PURE__ */ React.createElement(Grid, { item: true, className: "overlayGridItem" }, /* @__PURE__ */ React.createElement(Tooltip, { title: "Edit settings" }, /* @__PURE__ */ React.createElement(
86
+ IconButton,
87
+ {
88
+ color: "primary",
89
+ onClick: () => setSettingsDialogOpen(true)
90
+ },
91
+ /* @__PURE__ */ React.createElement(SettingsIcon, { fontSize: "large" })
92
+ ))),
93
+ /* @__PURE__ */ React.createElement(Grid, { item: true, className: "overlayGridItem" }, /* @__PURE__ */ React.createElement(Tooltip, { title: "Delete widget" }, /* @__PURE__ */ React.createElement(IconButton, { color: "secondary", onClick: () => handleRemove(id) }, /* @__PURE__ */ React.createElement(DeleteIcon, { fontSize: "large" }))))
94
+ ));
95
+ };
96
+
97
+ const getTitle = (widget) => {
98
+ var _a;
99
+ return (_a = widget.title) != null ? _a : widget.name;
100
+ };
101
+ const AddWidgetDialog = (props) => {
102
+ const { widgets, handleAdd } = props;
103
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(DialogTitle, null, "Add new widget to dashboard"), /* @__PURE__ */ React.createElement(DialogContent, null, /* @__PURE__ */ React.createElement(List, { dense: true }, widgets.map((widget) => {
104
+ return /* @__PURE__ */ React.createElement(
105
+ ListItem,
106
+ {
107
+ key: widget.name,
108
+ button: true,
109
+ onClick: () => handleAdd(widget)
110
+ },
111
+ /* @__PURE__ */ React.createElement(ListItemAvatar, null, /* @__PURE__ */ React.createElement(AddIcon, null)),
112
+ /* @__PURE__ */ React.createElement(
113
+ ListItemText,
114
+ {
115
+ secondary: widget.description && /* @__PURE__ */ React.createElement(
116
+ Typography,
117
+ {
118
+ component: "span",
119
+ variant: "caption",
120
+ color: "textPrimary"
121
+ },
122
+ widget.description
123
+ ),
124
+ primary: /* @__PURE__ */ React.createElement(Typography, { variant: "body1", color: "textPrimary" }, getTitle(widget))
125
+ }
126
+ )
127
+ );
128
+ }))));
129
+ };
130
+
131
+ const useStyles$2 = makeStyles(
132
+ (theme) => createStyles({
133
+ contentHeaderBtn: {
134
+ marginLeft: theme.spacing(2)
135
+ },
136
+ widgetWrapper: {
137
+ "& > *:first-child": {
138
+ width: "100%",
139
+ height: "100%"
140
+ }
141
+ }
142
+ })
143
+ );
144
+ const CustomHomepageButtons = (props) => {
145
+ const {
146
+ editMode,
147
+ numWidgets,
148
+ clearLayout,
149
+ setAddWidgetDialogOpen,
150
+ changeEditMode
151
+ } = props;
152
+ const styles = useStyles$2();
153
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, !editMode && numWidgets > 0 ? /* @__PURE__ */ React.createElement(
154
+ Button$1,
155
+ {
156
+ variant: "contained",
157
+ color: "primary",
158
+ onClick: () => changeEditMode(true),
159
+ startIcon: /* @__PURE__ */ React.createElement(EditIcon, null)
160
+ },
161
+ "Edit"
162
+ ) : /* @__PURE__ */ React.createElement(React.Fragment, null, numWidgets > 0 && /* @__PURE__ */ React.createElement(
163
+ Button$1,
164
+ {
165
+ variant: "contained",
166
+ color: "secondary",
167
+ className: styles.contentHeaderBtn,
168
+ onClick: clearLayout,
169
+ startIcon: /* @__PURE__ */ React.createElement(DeleteIcon, null)
170
+ },
171
+ "Clear"
172
+ ), /* @__PURE__ */ React.createElement(
173
+ Button$1,
174
+ {
175
+ variant: "contained",
176
+ className: styles.contentHeaderBtn,
177
+ onClick: () => setAddWidgetDialogOpen(true),
178
+ startIcon: /* @__PURE__ */ React.createElement(AddIcon, null)
179
+ },
180
+ "Add widget"
181
+ ), numWidgets > 0 && /* @__PURE__ */ React.createElement(
182
+ Button$1,
183
+ {
184
+ className: styles.contentHeaderBtn,
185
+ variant: "contained",
186
+ color: "primary",
187
+ onClick: () => changeEditMode(false),
188
+ startIcon: /* @__PURE__ */ React.createElement(SaveIcon, null)
189
+ },
190
+ "Save"
191
+ )));
192
+ };
193
+
194
+ const RSJFTypeSchema = z.any();
195
+ const ReactElementSchema = z.any();
196
+ const LayoutSchema = z.any();
197
+ const LayoutConfigurationSchema = z.object({
198
+ component: ReactElementSchema,
199
+ x: z.number().nonnegative("x must be positive number"),
200
+ y: z.number().nonnegative("y must be positive number"),
201
+ width: z.number().positive("width must be positive number"),
202
+ height: z.number().positive("height must be positive number")
203
+ });
204
+ const WidgetSchema = z.object({
205
+ name: z.string(),
206
+ title: z.string().optional(),
207
+ description: z.string().optional(),
208
+ component: ReactElementSchema,
209
+ width: z.number().positive("width must be positive number").optional(),
210
+ height: z.number().positive("height must be positive number").optional(),
211
+ minWidth: z.number().positive("minWidth must be positive number").optional(),
212
+ maxWidth: z.number().positive("maxWidth must be positive number").optional(),
213
+ minHeight: z.number().positive("minHeight must be positive number").optional(),
214
+ maxHeight: z.number().positive("maxHeight must be positive number").optional(),
215
+ settingsSchema: RSJFTypeSchema.optional()
216
+ });
217
+ const GridWidgetSchema = z.object({
218
+ id: z.string(),
219
+ layout: LayoutSchema,
220
+ settings: z.record(z.string(), z.any())
221
+ });
222
+ const CustomHomepageGridStateV1Schema = z.object({
223
+ version: z.literal(1),
224
+ pages: z.record(z.string(), z.array(GridWidgetSchema))
225
+ });
226
+
227
+ const ResponsiveGrid = WidthProvider(Responsive);
228
+ const hash = require("object-hash");
229
+ const useStyles$1 = makeStyles(
230
+ (theme) => createStyles({
231
+ responsiveGrid: {
232
+ "& .react-grid-item > .react-resizable-handle:after": {
233
+ position: "absolute",
234
+ content: '""',
235
+ borderStyle: "solid",
236
+ borderWidth: "0 0 20px 20px",
237
+ borderColor: `transparent transparent ${theme.palette.primary.light} transparent`
238
+ }
239
+ },
240
+ contentHeaderBtn: {
241
+ marginLeft: theme.spacing(2)
242
+ },
243
+ widgetWrapper: {
244
+ "& > *:first-child": {
245
+ width: "100%",
246
+ height: "100%"
247
+ },
248
+ "& + .react-grid-placeholder": {
249
+ backgroundColor: theme.palette.primary.light
250
+ },
251
+ "&.edit > :active": {
252
+ cursor: "move"
253
+ }
254
+ }
255
+ })
256
+ );
257
+ function useHomeStorage(defaultWidgets) {
258
+ const key = "home";
259
+ const storageApi = useApi(storageApiRef).forBucket("home.customHomepage");
260
+ const setWidgets = useCallback(
261
+ (value) => {
262
+ const grid = {
263
+ version: 1,
264
+ pages: {
265
+ default: value
266
+ }
267
+ };
268
+ storageApi.set(key, JSON.stringify(grid));
269
+ },
270
+ [key, storageApi]
271
+ );
272
+ const homeSnapshot = useObservable(
273
+ storageApi.observe$(key),
274
+ storageApi.snapshot(key)
275
+ );
276
+ const widgets = useMemo(() => {
277
+ if (homeSnapshot.presence === "absent") {
278
+ return defaultWidgets;
279
+ }
280
+ try {
281
+ const grid = JSON.parse(homeSnapshot.value);
282
+ return CustomHomepageGridStateV1Schema.parse(grid).pages.default;
283
+ } catch (e) {
284
+ return defaultWidgets;
285
+ }
286
+ }, [homeSnapshot, defaultWidgets]);
287
+ return [widgets, setWidgets];
288
+ }
289
+ const convertConfigToDefaultWidgets = (config, availableWidgets) => {
290
+ const ret = config.map((conf, i) => {
291
+ var _a, _b;
292
+ const c = LayoutConfigurationSchema.parse(conf);
293
+ const name = React.isValidElement(c.component) ? getComponentData(c.component, "core.extensionName") : c.component;
294
+ if (!name) {
295
+ return null;
296
+ }
297
+ const widget = availableWidgets.find((w) => w.name === name);
298
+ if (!widget) {
299
+ return null;
300
+ }
301
+ const widgetId = `${widget.name}__${i}${Math.random().toString(36).slice(2)}`;
302
+ return {
303
+ id: widgetId,
304
+ layout: {
305
+ i: widgetId,
306
+ x: c.x,
307
+ y: c.y,
308
+ w: Math.min((_a = widget.maxWidth) != null ? _a : Number.MAX_VALUE, c.width),
309
+ h: Math.min((_b = widget.maxHeight) != null ? _b : Number.MAX_VALUE, c.height),
310
+ minW: widget.minWidth,
311
+ maxW: widget.maxWidth,
312
+ minH: widget.minHeight,
313
+ maxH: widget.maxHeight,
314
+ isDraggable: false,
315
+ isResizable: false
316
+ },
317
+ settings: {}
318
+ };
319
+ });
320
+ return compact(ret);
321
+ };
322
+ const availableWidgetsFilter = (elements) => {
323
+ return elements.selectByComponentData({
324
+ key: "core.extensionName"
325
+ }).getElements().flatMap((elem) => {
326
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
327
+ const config = getComponentData(elem, "home.widget.config");
328
+ return [
329
+ WidgetSchema.parse({
330
+ component: elem,
331
+ name: getComponentData(elem, "core.extensionName"),
332
+ title: getComponentData(elem, "title"),
333
+ description: getComponentData(elem, "description"),
334
+ settingsSchema: (_a = config == null ? void 0 : config.settings) == null ? void 0 : _a.schema,
335
+ width: (_c = (_b = config == null ? void 0 : config.layout) == null ? void 0 : _b.width) == null ? void 0 : _c.defaultColumns,
336
+ minWidth: (_e = (_d = config == null ? void 0 : config.layout) == null ? void 0 : _d.width) == null ? void 0 : _e.minColumns,
337
+ maxWidth: (_g = (_f = config == null ? void 0 : config.layout) == null ? void 0 : _f.width) == null ? void 0 : _g.maxColumns,
338
+ height: (_i = (_h = config == null ? void 0 : config.layout) == null ? void 0 : _h.height) == null ? void 0 : _i.defaultRows,
339
+ minHeight: (_k = (_j = config == null ? void 0 : config.layout) == null ? void 0 : _j.height) == null ? void 0 : _k.minRows,
340
+ maxHeight: (_m = (_l = config == null ? void 0 : config.layout) == null ? void 0 : _l.height) == null ? void 0 : _m.maxRows
341
+ })
342
+ ];
343
+ });
344
+ };
345
+ const CustomHomepageGrid = (props) => {
346
+ var _a;
347
+ const styles = useStyles$1();
348
+ const availableWidgets = useElementFilter(
349
+ props.children,
350
+ availableWidgetsFilter,
351
+ [props]
352
+ );
353
+ const defaultLayout = props.config ? convertConfigToDefaultWidgets(props.config, availableWidgets) : [];
354
+ const [widgets, setWidgets] = useHomeStorage(defaultLayout);
355
+ const [addWidgetDialogOpen, setAddWidgetDialogOpen] = React.useState(false);
356
+ const editModeOn = widgets.find((w) => w.layout.isResizable) !== void 0;
357
+ const [editMode, setEditMode] = React.useState(editModeOn);
358
+ const getWidgetByName = (name) => {
359
+ return availableWidgets.find((widget) => widget.name === name);
360
+ };
361
+ const getWidgetNameFromKey = (key) => {
362
+ return key.split("__")[0];
363
+ };
364
+ const handleAdd = (widget) => {
365
+ var _a2, _b, _c, _d;
366
+ const widgetId = `${widget.name}__${widgets.length + 1}${Math.random().toString(36).slice(2)}`;
367
+ setWidgets([
368
+ ...widgets,
369
+ {
370
+ id: widgetId,
371
+ layout: {
372
+ i: widgetId,
373
+ x: 0,
374
+ y: Math.max(...widgets.map((w) => w.layout.y + w.layout.h)) + 1,
375
+ w: Math.min((_a2 = widget.maxWidth) != null ? _a2 : Number.MAX_VALUE, (_b = widget.width) != null ? _b : 12),
376
+ h: Math.min((_c = widget.maxHeight) != null ? _c : Number.MAX_VALUE, (_d = widget.height) != null ? _d : 4),
377
+ minW: widget.minWidth,
378
+ maxW: widget.maxWidth,
379
+ minH: widget.minHeight,
380
+ maxH: widget.maxHeight,
381
+ isResizable: editMode,
382
+ isDraggable: editMode
383
+ },
384
+ settings: {}
385
+ }
386
+ ]);
387
+ setAddWidgetDialogOpen(false);
388
+ };
389
+ const handleRemove = (widgetId) => {
390
+ setWidgets(widgets.filter((w) => w.id !== widgetId));
391
+ };
392
+ const handleSettingsSave = (widgetId, widgetSettings) => {
393
+ const idx = widgets.findIndex((w) => w.id === widgetId);
394
+ if (idx >= 0) {
395
+ const widget = widgets[idx];
396
+ widget.settings = widgetSettings;
397
+ widgets[idx] = widget;
398
+ setWidgets(widgets);
399
+ }
400
+ };
401
+ const clearLayout = () => {
402
+ setWidgets([]);
403
+ };
404
+ const changeEditMode = (mode) => {
405
+ setEditMode(mode);
406
+ setWidgets(
407
+ widgets.map((w) => {
408
+ return {
409
+ ...w,
410
+ layout: { ...w.layout, isDraggable: mode, isResizable: mode }
411
+ };
412
+ })
413
+ );
414
+ };
415
+ const handleLayoutChange = (newLayout, _) => {
416
+ if (editMode) {
417
+ const newWidgets = newLayout.map((l) => {
418
+ const widget = widgets.find((w) => w.id === l.i);
419
+ return {
420
+ ...widget,
421
+ layout: l
422
+ };
423
+ });
424
+ setWidgets(newWidgets);
425
+ }
426
+ };
427
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(ContentHeader, { title: "" }, /* @__PURE__ */ React.createElement(
428
+ CustomHomepageButtons,
429
+ {
430
+ editMode,
431
+ numWidgets: widgets.length,
432
+ clearLayout,
433
+ setAddWidgetDialogOpen,
434
+ changeEditMode
435
+ }
436
+ )), /* @__PURE__ */ React.createElement(
437
+ Dialog,
438
+ {
439
+ open: addWidgetDialogOpen,
440
+ onClose: () => setAddWidgetDialogOpen(false)
441
+ },
442
+ /* @__PURE__ */ React.createElement(AddWidgetDialog, { widgets: availableWidgets, handleAdd })
443
+ ), !editMode && widgets.length === 0 && /* @__PURE__ */ React.createElement(Typography, { variant: "h5", align: "center" }, "No widgets added. Start by clicking the 'Add widget' button."), /* @__PURE__ */ React.createElement(
444
+ ResponsiveGrid,
445
+ {
446
+ className: styles.responsiveGrid,
447
+ measureBeforeMount: true,
448
+ compactType: "horizontal",
449
+ draggableCancel: ".overlayGridItem,.widgetSettingsDialog",
450
+ breakpoints: { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 },
451
+ cols: { lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 },
452
+ rowHeight: (_a = props.rowHeight) != null ? _a : 60,
453
+ onLayoutChange: handleLayoutChange,
454
+ layouts: { lg: widgets.map((w) => w.layout) }
455
+ },
456
+ widgets.map((w) => {
457
+ var _a2;
458
+ const l = w.layout;
459
+ const widgetName = getWidgetNameFromKey(l.i);
460
+ const widget = getWidgetByName(widgetName);
461
+ if (!widget || !widget.component) {
462
+ return null;
463
+ }
464
+ const widgetProps = {
465
+ ...widget.component.props,
466
+ ...(_a2 = w.settings) != null ? _a2 : {}
467
+ };
468
+ const propsHash = hash(widgetProps, {});
469
+ return /* @__PURE__ */ React.createElement(
470
+ "div",
471
+ {
472
+ key: l.i,
473
+ className: `${styles.widgetWrapper} ${editMode && "edit"}`
474
+ },
475
+ /* @__PURE__ */ React.createElement(ErrorBoundary, null, /* @__PURE__ */ React.createElement(widget.component.type, { key: propsHash, ...widgetProps })),
476
+ editMode && /* @__PURE__ */ React.createElement(
477
+ WidgetSettingsOverlay,
478
+ {
479
+ id: l.i,
480
+ widget,
481
+ handleRemove,
482
+ handleSettingsSave,
483
+ settings: w.settings
484
+ }
485
+ )
486
+ );
487
+ })
488
+ ));
489
+ };
490
+
13
491
  function createCardExtension(options) {
14
- const { title, components, name } = options;
492
+ const { title, components, name, description, layout, settings } = options;
493
+ const isCustomizable = (settings == null ? void 0 : settings.schema) !== void 0;
15
494
  return createReactExtension({
16
495
  name,
496
+ data: { title, description, "home.widget.config": { layout, settings } },
17
497
  component: {
18
- lazy: () => components().then(({ Content, Actions, Settings, ContextProvider }) => {
19
- const CardExtension = (props) => {
20
- const { Renderer, title: overrideTitle, ...childProps } = props;
21
- const app = useApp();
22
- const { Progress } = app.getComponents();
23
- const [settingsOpen, setSettingsOpen] = React.useState(false);
24
- if (Renderer) {
25
- return /* @__PURE__ */ React.createElement(Suspense, { fallback: /* @__PURE__ */ React.createElement(Progress, null) }, /* @__PURE__ */ React.createElement(
26
- Renderer,
27
- {
28
- title: overrideTitle || title,
29
- ...{
30
- Content,
31
- ...Actions ? { Actions } : {},
32
- ...Settings ? { Settings } : {},
33
- ...ContextProvider ? { ContextProvider } : {},
34
- ...childProps
35
- }
36
- }
37
- ));
38
- }
39
- const cardProps = {
40
- title: overrideTitle != null ? overrideTitle : title,
41
- ...Settings ? {
42
- action: /* @__PURE__ */ React.createElement(IconButton, { onClick: () => setSettingsOpen(true) }, /* @__PURE__ */ React.createElement(SettingsIcon, null, "Settings"))
43
- } : {},
44
- ...Actions ? {
45
- actions: /* @__PURE__ */ React.createElement(Actions, null)
46
- } : {}
47
- };
48
- const innerContent = /* @__PURE__ */ React.createElement(InfoCard, { ...cardProps }, Settings && /* @__PURE__ */ React.createElement(
49
- SettingsModal,
498
+ lazy: () => components().then((componentParts) => {
499
+ return (props) => {
500
+ return /* @__PURE__ */ React.createElement(
501
+ CardExtension,
50
502
  {
51
- open: settingsOpen,
52
- componentName: title,
53
- close: () => setSettingsOpen(false)
54
- },
55
- /* @__PURE__ */ React.createElement(Settings, null)
56
- ), /* @__PURE__ */ React.createElement(Content, { ...childProps }));
57
- return /* @__PURE__ */ React.createElement(Suspense, { fallback: /* @__PURE__ */ React.createElement(Progress, null) }, ContextProvider ? /* @__PURE__ */ React.createElement(ContextProvider, { ...childProps }, innerContent) : innerContent);
503
+ ...props,
504
+ ...componentParts,
505
+ title: props.title || title,
506
+ isCustomizable
507
+ }
508
+ );
58
509
  };
59
- return CardExtension;
60
510
  })
61
511
  }
62
512
  });
63
513
  }
514
+ function CardExtension(props) {
515
+ const {
516
+ Renderer,
517
+ Content,
518
+ Settings,
519
+ Actions,
520
+ ContextProvider,
521
+ isCustomizable,
522
+ title,
523
+ ...childProps
524
+ } = props;
525
+ const app = useApp();
526
+ const { Progress } = app.getComponents();
527
+ const [settingsOpen, setSettingsOpen] = React.useState(false);
528
+ if (Renderer) {
529
+ return /* @__PURE__ */ React.createElement(Suspense, { fallback: /* @__PURE__ */ React.createElement(Progress, null) }, /* @__PURE__ */ React.createElement(
530
+ Renderer,
531
+ {
532
+ title,
533
+ ...{
534
+ Content,
535
+ ...Actions ? { Actions } : {},
536
+ ...Settings && !isCustomizable ? { Settings } : {},
537
+ ...ContextProvider ? { ContextProvider } : {},
538
+ ...childProps
539
+ }
540
+ }
541
+ ));
542
+ }
543
+ const cardProps = {
544
+ title,
545
+ ...Settings && !isCustomizable ? {
546
+ action: /* @__PURE__ */ React.createElement(IconButton$1, { onClick: () => setSettingsOpen(true) }, /* @__PURE__ */ React.createElement(SettingsIcon, null, "Settings"))
547
+ } : {},
548
+ ...Actions ? {
549
+ actions: /* @__PURE__ */ React.createElement(Actions, null)
550
+ } : {}
551
+ };
552
+ const innerContent = /* @__PURE__ */ React.createElement(InfoCard, { ...cardProps }, Settings && !isCustomizable && /* @__PURE__ */ React.createElement(
553
+ SettingsModal,
554
+ {
555
+ open: settingsOpen,
556
+ componentName: title,
557
+ close: () => setSettingsOpen(false)
558
+ },
559
+ /* @__PURE__ */ React.createElement(Settings, null)
560
+ ), /* @__PURE__ */ React.createElement(Content, { ...childProps }));
561
+ return /* @__PURE__ */ React.createElement(Suspense, { fallback: /* @__PURE__ */ React.createElement(Progress, null) }, ContextProvider ? /* @__PURE__ */ React.createElement(ContextProvider, { ...childProps }, innerContent) : innerContent);
562
+ }
64
563
 
65
564
  const rootRouteRef = createRouteRef({
66
565
  id: "home"
@@ -75,7 +574,7 @@ const homePlugin = createPlugin({
75
574
  const HomepageCompositionRoot = homePlugin.provide(
76
575
  createRoutableExtension({
77
576
  name: "HomepageCompositionRoot",
78
- component: () => import('./esm/index-236adacf.esm.js').then((m) => m.HomepageCompositionRoot),
577
+ component: () => import('./esm/index-a42ec1b1.esm.js').then((m) => m.HomepageCompositionRoot),
79
578
  mountPoint: rootRouteRef
80
579
  })
81
580
  );
@@ -83,7 +582,7 @@ const ComponentAccordion = homePlugin.provide(
83
582
  createComponentExtension({
84
583
  name: "ComponentAccordion",
85
584
  component: {
86
- lazy: () => import('./esm/index-6bbcec36.esm.js').then((m) => m.ComponentAccordion)
585
+ lazy: () => import('./esm/index-93442718.esm.js').then((m) => m.ComponentAccordion)
87
586
  }
88
587
  })
89
588
  );
@@ -91,7 +590,7 @@ const ComponentTabs = homePlugin.provide(
91
590
  createComponentExtension({
92
591
  name: "ComponentTabs",
93
592
  component: {
94
- lazy: () => import('./esm/index-6bbcec36.esm.js').then((m) => m.ComponentTabs)
593
+ lazy: () => import('./esm/index-93442718.esm.js').then((m) => m.ComponentTabs)
95
594
  }
96
595
  })
97
596
  );
@@ -99,7 +598,7 @@ const ComponentTab = homePlugin.provide(
99
598
  createComponentExtension({
100
599
  name: "ComponentTab",
101
600
  component: {
102
- lazy: () => import('./esm/index-6bbcec36.esm.js').then((m) => m.ComponentTab)
601
+ lazy: () => import('./esm/index-93442718.esm.js').then((m) => m.ComponentTab)
103
602
  }
104
603
  })
105
604
  );
@@ -123,7 +622,26 @@ const HomePageRandomJoke = homePlugin.provide(
123
622
  createCardExtension({
124
623
  name: "HomePageRandomJoke",
125
624
  title: "Random Joke",
126
- components: () => import('./esm/index-4517c5bc.esm.js')
625
+ components: () => import('./esm/index-4517c5bc.esm.js'),
626
+ description: "Shows a random joke about optional category",
627
+ layout: {
628
+ height: { minRows: 4 },
629
+ width: { minColumns: 3 }
630
+ },
631
+ settings: {
632
+ schema: {
633
+ title: "Random Joke settings",
634
+ type: "object",
635
+ properties: {
636
+ defaultCategory: {
637
+ title: "Category",
638
+ type: "string",
639
+ enum: ["any", "programming", "dad"],
640
+ default: "any"
641
+ }
642
+ }
643
+ }
644
+ }
127
645
  })
128
646
  );
129
647
  const HomePageToolkit = homePlugin.provide(
@@ -197,5 +715,5 @@ const TemplateBackstageLogoIcon = () => {
197
715
  );
198
716
  };
199
717
 
200
- export { ComponentAccordion, ComponentTab, ComponentTabs, HeaderWorldClock, HomePageCompanyLogo, HomePageRandomJoke, HomePageStarredEntities, HomePageToolkit, HomepageCompositionRoot, SettingsModal, TemplateBackstageLogo, TemplateBackstageLogoIcon, WelcomeTitle, createCardExtension, homePlugin };
718
+ export { ComponentAccordion, ComponentTab, ComponentTabs, CustomHomepageGrid, HeaderWorldClock, HomePageCompanyLogo, HomePageRandomJoke, HomePageStarredEntities, HomePageToolkit, HomepageCompositionRoot, SettingsModal, TemplateBackstageLogo, TemplateBackstageLogoIcon, WelcomeTitle, createCardExtension, homePlugin };
201
719
  //# sourceMappingURL=index.esm.js.map