Merge pull request #18922 from drodil/homepage_static_widgets

feat: allow specifying static widgets to customizable home
This commit is contained in:
Patrik Oldsberg
2023-08-23 01:16:36 +02:00
committed by GitHub
8 changed files with 156 additions and 98 deletions
+5
View File
@@ -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,
},
];
+3
View File
@@ -233,6 +233,9 @@ const defaultConfig = [
y: 0,
width: 12,
height: 1,
movable: true,
resizable: false,
deletable: false,
},
];
+3
View File
@@ -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>;