From 2e4940e1a8f8a5490a76692581bd7a896b33332f Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Tue, 2 May 2023 11:03:06 +0300 Subject: [PATCH] feat: allow more customization of the custom homepage This allows users to modify the actual grid properties from backstage interface. Signed-off-by: Heikki Hellgren --- .changeset/dull-bottles-teach.md | 5 + .../CustomizableTemplate.stories.tsx | 153 ++++++++++++++++++ plugins/home/README.md | 3 +- plugins/home/api-report.md | 14 +- .../CustomHomepage/CustomHomepageButtons.tsx | 22 ++- .../CustomHomepage/CustomHomepageGrid.tsx | 103 +++++++++++- .../src/components/CustomHomepage/index.ts | 2 +- 7 files changed, 293 insertions(+), 9 deletions(-) create mode 100644 .changeset/dull-bottles-teach.md create mode 100644 packages/app/src/components/home/templates/CustomizableTemplate.stories.tsx diff --git a/.changeset/dull-bottles-teach.md b/.changeset/dull-bottles-teach.md new file mode 100644 index 0000000000..74203eb758 --- /dev/null +++ b/.changeset/dull-bottles-teach.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-home': patch +--- + +Allow more customization for the CustomHomepageGrid diff --git a/packages/app/src/components/home/templates/CustomizableTemplate.stories.tsx b/packages/app/src/components/home/templates/CustomizableTemplate.stories.tsx new file mode 100644 index 0000000000..e587e52da4 --- /dev/null +++ b/packages/app/src/components/home/templates/CustomizableTemplate.stories.tsx @@ -0,0 +1,153 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + HomePageStarredEntities, + HomePageRandomJoke, + CustomHomepageGrid, +} from '@backstage/plugin-home'; +import { + starredEntitiesApiRef, + MockStarredEntitiesApi, + entityRouteRef, + catalogApiRef, +} from '@backstage/plugin-catalog-react'; +import { wrapInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { configApiRef } from '@backstage/core-plugin-api'; +import { ConfigReader } from '@backstage/config'; +import { searchApiRef } from '@backstage/plugin-search-react'; +import { HomePageSearchBar, searchPlugin } from '@backstage/plugin-search'; +import { HomePageCalendar } from '@backstage/plugin-gcalendar'; +import { MicrosoftCalendarCard } from '@backstage/plugin-microsoft-calendar'; +import React, { ComponentType } from 'react'; + +const entities = [ + { + apiVersion: '1', + kind: 'Component', + metadata: { + name: 'mock-starred-entity', + title: 'Mock Starred Entity!', + }, + }, + { + apiVersion: '1', + kind: 'Component', + metadata: { + name: 'mock-starred-entity-2', + title: 'Mock Starred Entity 2!', + }, + }, + { + apiVersion: '1', + kind: 'Component', + metadata: { + name: 'mock-starred-entity-3', + title: 'Mock Starred Entity 3!', + }, + }, + { + apiVersion: '1', + kind: 'Component', + metadata: { + name: 'mock-starred-entity-4', + title: 'Mock Starred Entity 4!', + }, + }, +]; + +const mockCatalogApi = { + getEntities: async () => ({ items: entities }), +}; + +const starredEntitiesApi = new MockStarredEntitiesApi(); +starredEntitiesApi.toggleStarred('component:default/example-starred-entity'); +starredEntitiesApi.toggleStarred('component:default/example-starred-entity-2'); +starredEntitiesApi.toggleStarred('component:default/example-starred-entity-3'); +starredEntitiesApi.toggleStarred('component:default/example-starred-entity-4'); + +export default { + title: 'Plugins/Home/Templates', + decorators: [ + (Story: ComponentType<{}>) => + wrapInTestApp( + <> + Promise.resolve({ results: [] }) }], + [ + configApiRef, + new ConfigReader({ + backend: { + baseUrl: 'https://localhost:7007', + }, + }), + ], + ]} + > + + + , + { + mountedRoutes: { + '/hello-company': searchPlugin.routes.root, + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, + }, + ), + ], +}; +export const CustomizableTemplate = () => { + // This is the default configuration that is shown to the user + // when first arriving to the homepage. + const defaultConfig = [ + { + component: 'HomePageSearchBar', + x: 0, + y: 0, + width: 12, + height: 5, + }, + { + component: 'HomePageRandomJoke', + x: 0, + y: 2, + width: 6, + height: 16, + }, + { + component: 'HomePageStarredEntities', + x: 6, + y: 2, + width: 6, + height: 12, + }, + ]; + + return ( + + // Insert the allowed widgets inside the grid. User can add, organize and + // remove the widgets as they want. + + + + + + + ); +}; diff --git a/plugins/home/README.md b/plugins/home/README.md index 20b91289b1..42fbf73d18 100644 --- a/plugins/home/README.md +++ b/plugins/home/README.md @@ -247,4 +247,5 @@ Additionally, the API is at a very early state, so contributing with additional ### Homepage Templates -We are hoping that we together can build up a collection of Homepage templates. We therefore put together a place where we can collect all the templates for the Home Plugin in the [storybook](https://backstage.io/storybook/?path=/story/plugins-home-templates). If you would like to contribute with a template, start by taking a look at the [DefaultTemplate storybook example to create your own](/packages/app/src/components/home/templates/DefaultTemplate.stories.tsx), and then open a PR with your suggestion. +We are hoping that we together can build up a collection of Homepage templates. We therefore put together a place where we can collect all the templates for the Home Plugin in the [storybook](https://backstage.io/storybook/?path=/story/plugins-home-templates). +If you would like to contribute with a template, start by taking a look at the [DefaultTemplate storybook example](/packages/app/src/components/home/templates/DefaultTemplate.stories.tsx) or [CustomizableTemplate storybook example](/packages/app/src/components/home/templates/CustomizableTemplate.stories.tsx) to create your own, and then open a PR with your suggestion. diff --git a/plugins/home/api-report.md b/plugins/home/api-report.md index 56b1f54331..e4ff522062 100644 --- a/plugins/home/api-report.md +++ b/plugins/home/api-report.md @@ -19,6 +19,9 @@ import { ReactNode } from 'react'; import { RendererProps as RendererProps_2 } from '@backstage/plugin-home-react'; import { RouteRef } from '@backstage/core-plugin-api'; +// @public +export type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; + // @public @deprecated (undocumented) export type CardConfig = CardConfig_2; @@ -77,11 +80,20 @@ export const CustomHomepageGrid: ( props: CustomHomepageGridProps, ) => JSX.Element; -// @public (undocumented) +// @public export type CustomHomepageGridProps = { children?: ReactNode; config?: LayoutConfiguration[]; rowHeight?: number; + breakpoints?: Record; + cols?: Record; + containerPadding?: [number, number] | Record; + containerMargin?: [number, number] | Record; + maxRows?: number; + style?: React_2.CSSProperties; + compactType?: 'vertical' | 'horizontal' | null; + allowOverlap?: boolean; + preventCollision?: boolean; }; // @public diff --git a/plugins/home/src/components/CustomHomepage/CustomHomepageButtons.tsx b/plugins/home/src/components/CustomHomepage/CustomHomepageButtons.tsx index c2fdf5b979..22c88a30c7 100644 --- a/plugins/home/src/components/CustomHomepage/CustomHomepageButtons.tsx +++ b/plugins/home/src/components/CustomHomepage/CustomHomepageButtons.tsx @@ -20,6 +20,7 @@ import SaveIcon from '@material-ui/icons/Save'; import DeleteIcon from '@material-ui/icons/Delete'; import AddIcon from '@material-ui/icons/Add'; import EditIcon from '@material-ui/icons/Edit'; +import CancelIcon from '@material-ui/icons/Cancel'; const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -41,6 +42,8 @@ interface CustomHomepageButtonsProps { clearLayout: () => void; setAddWidgetDialogOpen: (open: boolean) => void; changeEditMode: (mode: boolean) => void; + defaultConfigAvailable: boolean; + restoreDefault: () => void; } export const CustomHomepageButtons = (props: CustomHomepageButtonsProps) => { const { @@ -49,6 +52,8 @@ export const CustomHomepageButtons = (props: CustomHomepageButtonsProps) => { clearLayout, setAddWidgetDialogOpen, changeEditMode, + defaultConfigAvailable, + restoreDefault, } = props; const styles = useStyles(); @@ -59,27 +64,41 @@ export const CustomHomepageButtons = (props: CustomHomepageButtonsProps) => { variant="contained" color="primary" onClick={() => changeEditMode(true)} + size="small" startIcon={} > Edit ) : ( <> + {defaultConfigAvailable && ( + + )} {numWidgets > 0 && ( )}