Merge pull request #18922 from drodil/homepage_static_widgets
feat: allow specifying static widgets to customizable home
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-home': patch
|
||||
---
|
||||
|
||||
Allow specifying static widgets to custom home page
|
||||
@@ -15,14 +15,14 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
HomePageRandomJoke,
|
||||
WelcomeTitle,
|
||||
HeaderWorldClock,
|
||||
ClockConfig,
|
||||
HomePageStarredEntities,
|
||||
CustomHomepageGrid,
|
||||
HomePageToolkit,
|
||||
HeaderWorldClock,
|
||||
HomePageCompanyLogo,
|
||||
HomePageRandomJoke,
|
||||
HomePageStarredEntities,
|
||||
HomePageToolkit,
|
||||
WelcomeTitle,
|
||||
} from '@backstage/plugin-home';
|
||||
import { Content, Header, Page } from '@backstage/core-components';
|
||||
import { HomePageSearchBar } from '@backstage/plugin-search';
|
||||
@@ -64,6 +64,9 @@ const defaultConfig = [
|
||||
y: 0,
|
||||
width: 12,
|
||||
height: 1,
|
||||
movable: false,
|
||||
resizable: false,
|
||||
deletable: false,
|
||||
},
|
||||
{
|
||||
component: 'WelcomeTitle',
|
||||
@@ -77,7 +80,7 @@ const defaultConfig = [
|
||||
x: 0,
|
||||
y: 2,
|
||||
width: 12,
|
||||
height: 1,
|
||||
height: 2,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -233,6 +233,9 @@ const defaultConfig = [
|
||||
y: 0,
|
||||
width: 12,
|
||||
height: 1,
|
||||
movable: true,
|
||||
resizable: false,
|
||||
deletable: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -148,6 +148,9 @@ export type LayoutConfiguration = {
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
movable?: boolean;
|
||||
deletable?: boolean;
|
||||
resizable?: boolean;
|
||||
};
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ReactNode, useCallback, useMemo } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { Layout, Layouts, Responsive, WidthProvider } from 'react-grid-layout';
|
||||
import {
|
||||
ElementCollection,
|
||||
@@ -40,6 +40,7 @@ import { WidgetSettingsOverlay } from './WidgetSettingsOverlay';
|
||||
import { AddWidgetDialog } from './AddWidgetDialog';
|
||||
import { CustomHomepageButtons } from './CustomHomepageButtons';
|
||||
import {
|
||||
CustomHomepageGridProps,
|
||||
CustomHomepageGridStateV1,
|
||||
CustomHomepageGridStateV1Schema,
|
||||
GridWidget,
|
||||
@@ -158,6 +159,9 @@ const convertConfigToDefaultWidgets = (
|
||||
isResizable: false,
|
||||
},
|
||||
settings: {},
|
||||
movable: conf.movable,
|
||||
deletable: conf.deletable,
|
||||
resizable: conf.resizable,
|
||||
};
|
||||
});
|
||||
return compact(ret);
|
||||
@@ -190,82 +194,6 @@ const availableWidgetsFilter = (elements: ElementCollection) => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Breakpoint options for <CustomHomepageGridProps/>
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type Breakpoint = 'xxs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
||||
|
||||
/**
|
||||
* Props customizing the <CustomHomepageGrid/> component.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type CustomHomepageGridProps = {
|
||||
/**
|
||||
* Children contain all widgets user can configure on their own homepage.
|
||||
*/
|
||||
children?: ReactNode;
|
||||
/**
|
||||
* Default layout for the homepage before users have modified it.
|
||||
*/
|
||||
config?: LayoutConfiguration[];
|
||||
/**
|
||||
* Height of grid row in pixels.
|
||||
* @defaultValue 60
|
||||
*/
|
||||
rowHeight?: number;
|
||||
/**
|
||||
* Screen width in pixels for different breakpoints.
|
||||
* @defaultValue theme breakpoints
|
||||
*/
|
||||
breakpoints?: Record<Breakpoint, number>;
|
||||
/**
|
||||
* Number of grid columns for different breakpoints.
|
||||
* @defaultValue \{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 \}
|
||||
*/
|
||||
cols?: Record<Breakpoint, number>;
|
||||
/**
|
||||
* Grid container padding (x, y) in pixels for all or specific breakpoints.
|
||||
* @defaultValue [0, 0]
|
||||
* @example [10, 10]
|
||||
* @example \{ lg: [10, 10] \}
|
||||
*/
|
||||
containerPadding?: [number, number] | Record<Breakpoint, [number, number]>;
|
||||
/**
|
||||
* Grid container margin (x, y) in pixels for all or specific breakpoints.
|
||||
* @defaultValue [0, 0]
|
||||
* @example [10, 10]
|
||||
* @example \{ lg: [10, 10] \}
|
||||
*/
|
||||
containerMargin?: [number, number] | Record<Breakpoint, [number, number]>;
|
||||
/**
|
||||
* Maximum number of rows user can have in the grid.
|
||||
* @defaultValue unlimited
|
||||
*/
|
||||
maxRows?: number;
|
||||
/**
|
||||
* Custom style for grid.
|
||||
*/
|
||||
style?: React.CSSProperties;
|
||||
/**
|
||||
* Compaction type of widgets in the grid. This controls where widgets are moved in case
|
||||
* they are overlapping in the grid.
|
||||
*/
|
||||
compactType?: 'vertical' | 'horizontal' | null;
|
||||
/**
|
||||
* Controls if widgets can overlap in the grid. If true, grid can be placed one over the other.
|
||||
* @defaultValue false
|
||||
*/
|
||||
allowOverlap?: boolean;
|
||||
/**
|
||||
* Controls if widgets can collide with each other. If true, grid items won't change position when being dragged over.
|
||||
* @defaultValue false
|
||||
*/
|
||||
preventCollision?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* A component that allows customizing components in home grid layout.
|
||||
*
|
||||
@@ -318,6 +246,9 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => {
|
||||
isDraggable: editMode,
|
||||
},
|
||||
settings: {},
|
||||
movable: widget.movable,
|
||||
deletable: widget.deletable,
|
||||
resizable: widget.resizable,
|
||||
},
|
||||
]);
|
||||
setAddWidgetDialogOpen(false);
|
||||
@@ -348,9 +279,11 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => {
|
||||
setEditMode(mode);
|
||||
setWidgets(
|
||||
widgets.map(w => {
|
||||
const resizable = w.resizable === false ? false : mode;
|
||||
const movable = w.movable === false ? false : mode;
|
||||
return {
|
||||
...w,
|
||||
layout: { ...w.layout, isDraggable: mode, isResizable: mode },
|
||||
layout: { ...w.layout, isDraggable: movable, isResizable: resizable },
|
||||
};
|
||||
}),
|
||||
);
|
||||
@@ -370,7 +303,20 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => {
|
||||
};
|
||||
|
||||
const handleRestoreDefaultConfig = () => {
|
||||
setWidgets(defaultLayout);
|
||||
setWidgets(
|
||||
defaultLayout.map(w => {
|
||||
const resizable = w.resizable === false ? false : editMode;
|
||||
const movable = w.movable === false ? false : editMode;
|
||||
return {
|
||||
...w,
|
||||
layout: {
|
||||
...w.layout,
|
||||
isDraggable: movable,
|
||||
isResizable: resizable,
|
||||
},
|
||||
};
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -404,7 +350,7 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => {
|
||||
style={props.style}
|
||||
allowOverlap={props.allowOverlap}
|
||||
preventCollision={props.preventCollision}
|
||||
draggableCancel=".overlayGridItem,.widgetSettingsDialog"
|
||||
draggableCancel=".overlayGridItem,.widgetSettingsDialog,.disabled"
|
||||
containerPadding={props.containerPadding}
|
||||
margin={props.containerMargin}
|
||||
breakpoints={
|
||||
@@ -435,7 +381,9 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => {
|
||||
return (
|
||||
<div
|
||||
key={l.i}
|
||||
className={`${styles.widgetWrapper} ${editMode && 'edit'}`}
|
||||
className={`${styles.widgetWrapper} ${editMode && 'edit'} ${
|
||||
w.movable === false && 'disabled'
|
||||
}`}
|
||||
>
|
||||
<ErrorBoundary>
|
||||
<widget.component.type {...widgetProps} />
|
||||
@@ -447,6 +395,7 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => {
|
||||
handleRemove={handleRemove}
|
||||
handleSettingsSave={handleSettingsSave}
|
||||
settings={w.settings}
|
||||
deletable={w.deletable}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -58,10 +58,12 @@ interface WidgetSettingsOverlayProps {
|
||||
handleRemove: (id: string) => void;
|
||||
handleSettingsSave: (id: string, settings: Record<string, any>) => void;
|
||||
settings?: Record<string, any>;
|
||||
deletable?: boolean;
|
||||
}
|
||||
|
||||
export const WidgetSettingsOverlay = (props: WidgetSettingsOverlayProps) => {
|
||||
const { id, widget, settings, handleRemove, handleSettingsSave } = props;
|
||||
const { id, widget, settings, handleRemove, handleSettingsSave, deletable } =
|
||||
props;
|
||||
const [settingsDialogOpen, setSettingsDialogOpen] = React.useState(false);
|
||||
const styles = useStyles();
|
||||
|
||||
@@ -110,13 +112,15 @@ export const WidgetSettingsOverlay = (props: WidgetSettingsOverlayProps) => {
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item className="overlayGridItem">
|
||||
<Tooltip title="Delete widget">
|
||||
<IconButton color="secondary" onClick={() => handleRemove(id)}>
|
||||
<DeleteIcon fontSize="large" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
{deletable !== false && (
|
||||
<Grid item className="overlayGridItem">
|
||||
<Tooltip title="Delete widget">
|
||||
<IconButton color="secondary" onClick={() => handleRemove(id)}>
|
||||
<DeleteIcon fontSize="large" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -14,5 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { CustomHomepageGrid } from './CustomHomepageGrid';
|
||||
export type { CustomHomepageGridProps, Breakpoint } from './CustomHomepageGrid';
|
||||
export type { LayoutConfiguration } from './types';
|
||||
export type {
|
||||
CustomHomepageGridProps,
|
||||
Breakpoint,
|
||||
LayoutConfiguration,
|
||||
} from './types';
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ReactElement } from 'react';
|
||||
import React, { ReactElement, ReactNode } from 'react';
|
||||
import { Layout } from 'react-grid-layout';
|
||||
import { z } from 'zod';
|
||||
import { RJSFSchema, UiSchema } from '@rjsf/utils';
|
||||
@@ -24,12 +24,91 @@ const RSJFTypeUiSchema: z.ZodType<UiSchema> = z.any();
|
||||
const ReactElementSchema: z.ZodType<ReactElement> = z.any();
|
||||
const LayoutSchema: z.ZodType<Layout> = z.any();
|
||||
|
||||
/**
|
||||
* Breakpoint options for <CustomHomepageGridProps/>
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type Breakpoint = 'xxs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
||||
|
||||
/**
|
||||
* Props customizing the <CustomHomepageGrid/> component.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type CustomHomepageGridProps = {
|
||||
/**
|
||||
* Children contain all widgets user can configure on their own homepage.
|
||||
*/
|
||||
children?: ReactNode;
|
||||
/**
|
||||
* Default layout for the homepage before users have modified it.
|
||||
*/
|
||||
config?: LayoutConfiguration[];
|
||||
/**
|
||||
* Height of grid row in pixels.
|
||||
* @defaultValue 60
|
||||
*/
|
||||
rowHeight?: number;
|
||||
/**
|
||||
* Screen width in pixels for different breakpoints.
|
||||
* @defaultValue theme breakpoints
|
||||
*/
|
||||
breakpoints?: Record<Breakpoint, number>;
|
||||
/**
|
||||
* Number of grid columns for different breakpoints.
|
||||
* @defaultValue \{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 \}
|
||||
*/
|
||||
cols?: Record<Breakpoint, number>;
|
||||
/**
|
||||
* Grid container padding (x, y) in pixels for all or specific breakpoints.
|
||||
* @defaultValue [0, 0]
|
||||
* @example [10, 10]
|
||||
* @example \{ lg: [10, 10] \}
|
||||
*/
|
||||
containerPadding?: [number, number] | Record<Breakpoint, [number, number]>;
|
||||
/**
|
||||
* Grid container margin (x, y) in pixels for all or specific breakpoints.
|
||||
* @defaultValue [0, 0]
|
||||
* @example [10, 10]
|
||||
* @example \{ lg: [10, 10] \}
|
||||
*/
|
||||
containerMargin?: [number, number] | Record<Breakpoint, [number, number]>;
|
||||
/**
|
||||
* Maximum number of rows user can have in the grid.
|
||||
* @defaultValue unlimited
|
||||
*/
|
||||
maxRows?: number;
|
||||
/**
|
||||
* Custom style for grid.
|
||||
*/
|
||||
style?: React.CSSProperties;
|
||||
/**
|
||||
* Compaction type of widgets in the grid. This controls where widgets are moved in case
|
||||
* they are overlapping in the grid.
|
||||
*/
|
||||
compactType?: 'vertical' | 'horizontal' | null;
|
||||
/**
|
||||
* Controls if widgets can overlap in the grid. If true, grid can be placed one over the other.
|
||||
* @defaultValue false
|
||||
*/
|
||||
allowOverlap?: boolean;
|
||||
/**
|
||||
* Controls if widgets can collide with each other. If true, grid items won't change position when being dragged over.
|
||||
* @defaultValue false
|
||||
*/
|
||||
preventCollision?: boolean;
|
||||
};
|
||||
|
||||
export const LayoutConfigurationSchema = z.object({
|
||||
component: ReactElementSchema,
|
||||
x: z.number().nonnegative('x must be positive number'),
|
||||
y: z.number().nonnegative('y must be positive number'),
|
||||
width: z.number().positive('width must be positive number'),
|
||||
height: z.number().positive('height must be positive number'),
|
||||
movable: z.boolean().optional(),
|
||||
deletable: z.boolean().optional(),
|
||||
resizable: z.boolean().optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -43,6 +122,9 @@ export type LayoutConfiguration = {
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
movable?: boolean;
|
||||
deletable?: boolean;
|
||||
resizable?: boolean;
|
||||
};
|
||||
|
||||
export const WidgetSchema = z.object({
|
||||
@@ -64,6 +146,9 @@ export const WidgetSchema = z.object({
|
||||
.optional(),
|
||||
settingsSchema: RSJFTypeSchema.optional(),
|
||||
uiSchema: RSJFTypeUiSchema.optional(),
|
||||
movable: z.boolean().optional(),
|
||||
deletable: z.boolean().optional(),
|
||||
resizable: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export type Widget = z.infer<typeof WidgetSchema>;
|
||||
@@ -72,6 +157,9 @@ const GridWidgetSchema = z.object({
|
||||
id: z.string(),
|
||||
layout: LayoutSchema,
|
||||
settings: z.record(z.string(), z.any()),
|
||||
movable: z.boolean().optional(),
|
||||
deletable: z.boolean().optional(),
|
||||
resizable: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export type GridWidget = z.infer<typeof GridWidgetSchema>;
|
||||
|
||||
Reference in New Issue
Block a user