@@ -0,0 +1,9 @@
|
||||
---
|
||||
'@backstage/plugin-home': minor
|
||||
---
|
||||
|
||||
Create the `Home` plugin which exports some basic functionality that's used to compose a homepage. An example of a composed homepage is added to the example app.
|
||||
|
||||
This change also introduces the `createCardExtension` which creates a lazy loaded card that is intended to be used for homepage components.
|
||||
|
||||
Adoption of this homepage requires setup similar to what can be found in the example app.
|
||||
@@ -17,6 +17,7 @@
|
||||
/plugins/techdocs @backstage/techdocs-core
|
||||
/plugins/techdocs-backend @backstage/techdocs-core
|
||||
/plugins/ilert @yacut
|
||||
/plugins/home @backstage/techdocs-core
|
||||
/packages/search-common @backstage/techdocs-core
|
||||
/packages/techdocs-common @backstage/techdocs-core
|
||||
/.changeset/cost-insights-* @backstage/silver-lining
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"@backstage/plugin-gcp-projects": "^0.3.2",
|
||||
"@backstage/plugin-github-actions": "^0.4.16",
|
||||
"@backstage/plugin-graphiql": "^0.2.14",
|
||||
"@backstage/plugin-home": "^0.1.1",
|
||||
"@backstage/plugin-jenkins": "^0.5.3",
|
||||
"@backstage/plugin-kafka": "^0.2.13",
|
||||
"@backstage/plugin-kubernetes": "^0.4.11",
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
OAuthRequestDialog,
|
||||
SignInPage,
|
||||
} from '@backstage/core-components';
|
||||
import { HomepageCompositionRoot } from '@backstage/plugin-home';
|
||||
import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs';
|
||||
import {
|
||||
CatalogEntityPage,
|
||||
@@ -63,6 +64,8 @@ import { Root } from './components/Root';
|
||||
import { entityPage } from './components/catalog/EntityPage';
|
||||
import { searchPage } from './components/search/SearchPage';
|
||||
import { LowerCaseValuePickerFieldExtension } from './components/scaffolder/customScaffolderExtensions';
|
||||
import { HomePage } from './components/home/HomePage';
|
||||
|
||||
import { providers } from './identityProviders';
|
||||
import * as plugins from './plugins';
|
||||
|
||||
@@ -108,6 +111,10 @@ const AppRouter = app.getRouter();
|
||||
const routes = (
|
||||
<FlatRoutes>
|
||||
<Navigate key="/" to="catalog" />
|
||||
{/* TODO(rubenl): Move this to / once its more mature and components exist */}
|
||||
<Route path="/home" element={<HomepageCompositionRoot />}>
|
||||
<HomePage />
|
||||
</Route>
|
||||
<Route path="/catalog" element={<CatalogIndexPage />} />
|
||||
<Route
|
||||
path="/catalog/:namespace/:kind/:name"
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2021 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 React from 'react';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import {
|
||||
RandomJokeHomePageComponent,
|
||||
ComponentAccordion,
|
||||
ComponentTabs,
|
||||
ComponentTab,
|
||||
} from '@backstage/plugin-home';
|
||||
|
||||
export const HomePage = () => (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} md={4}>
|
||||
<RandomJokeHomePageComponent />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={4}>
|
||||
<RandomJokeHomePageComponent
|
||||
defaultCategory="any"
|
||||
Renderer={ComponentAccordion}
|
||||
/>
|
||||
<RandomJokeHomePageComponent
|
||||
title="Another Random Joke"
|
||||
Renderer={ComponentAccordion}
|
||||
/>
|
||||
<RandomJokeHomePageComponent
|
||||
title="One More Random Joke"
|
||||
defaultCategory="programming"
|
||||
Renderer={ComponentAccordion}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={4}>
|
||||
<ComponentTabs
|
||||
title="Random Jokes"
|
||||
tabs={[
|
||||
{
|
||||
label: 'Programming',
|
||||
Component: () => (
|
||||
<RandomJokeHomePageComponent
|
||||
defaultCategory="programming"
|
||||
Renderer={ComponentTab}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'Any',
|
||||
Component: () => (
|
||||
<RandomJokeHomePageComponent
|
||||
defaultCategory="any"
|
||||
Renderer={ComponentTab}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: [require.resolve('@backstage/cli/config/eslint')],
|
||||
};
|
||||
@@ -0,0 +1,95 @@
|
||||
# Home
|
||||
|
||||
Development is ongoing. You can follow the progress and contribute at the Backstage [Home Project Board](https://github.com/backstage/backstage/projects/7) or reach out to us in the [`#support` Discord channel](https://discord.com/channels/687207715902193673/687235481154617364).
|
||||
|
||||
## Overview
|
||||
|
||||
The Home plugin introduces a system for composing a Home Page for Backstage in order to surface relevant info and provide convenient shortcuts for common tasks. It's designed with composability in mind with an open ecosystem that allows anyone to contribute with any component, to be included in any Home Page.
|
||||
|
||||
For App Integrators, the system is designed to be composable to give total freedom in designing a Home Page that suits the needs of the organization. From the perspective of a Component Developer who wishes to contribute with building blocks to be included in Home Pages, there's a convenient interface for bundling the different parts and exporting them with both error boundary and lazy loading handled under the surface.
|
||||
|
||||
## Getting started
|
||||
|
||||
If you have a standalone app (you didn't clone this repo), then do
|
||||
|
||||
```bash
|
||||
# From the Backstage repository root
|
||||
cd packages/app
|
||||
yarn add @backstage/plugin-home
|
||||
```
|
||||
|
||||
### Setting up the Home Page
|
||||
|
||||
1. Create a Home Page Component that will be used for composition.
|
||||
|
||||
`packages/app/src/components/home/HomePage.tsx`
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
|
||||
export const HomePage = () => {
|
||||
return {
|
||||
/* TODO: Compose a Home Page here */
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
2. Add a route where the homepage will live, presumably `/`.
|
||||
|
||||
`packages/app/src/App.tsx`
|
||||
|
||||
```tsx
|
||||
import { HomepageCompositionRoot } from '@backstage/plugin-home';
|
||||
import { HomePage } from './components/home/HomePage';
|
||||
|
||||
// ...
|
||||
<Route path="/" element={<HomepageCompositionRoot />}>
|
||||
<HomePage />
|
||||
</Route>;
|
||||
// ...
|
||||
```
|
||||
|
||||
### Creating Components
|
||||
|
||||
The Home Page can be composed with regular React components, so there's no magic in creating components to be used for composition 🪄 🎩 . However, in order to assure that your component fits into a diverse set of Home Pages, there's an extension creator for this purpose, that creates a Card-based layout, for consistency between components (read more about extensions [here](https://backstage.io/docs/plugins/composability#extensions)). The extension creator requires two fields: `title` and `components`. The `components` field is expected to be an asynchronous import that should at least contain a `Content` field. Additionally, you can optionally provide `settings`, `actions` and `contextProvider` as well. These parts will be combined to create a card, where the `content`, `actions` and `settings` will be wrapped within the `contextProvider` in order to be able to access to context and effectively communicate with one another.
|
||||
|
||||
Finally, the `createCardExtension` also accepts a generic, such that Component Developers can indicate to App Integrators what custom props their component will accept, such as the example below where the default category of the random jokes can be set.
|
||||
|
||||
```tsx
|
||||
export const RandomJokeHomePageComponent = homePlugin.provide(
|
||||
createCardExtension<{ defaultCategory?: 'programming' | 'any' }>({
|
||||
title: 'Random Joke',
|
||||
components: () => import('./homePageComponents/RandomJoke'),
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
In summary: it is not necessary to use the `createCardExtension` extension creator to register a home page component, although it is convenient since it provides error boundary and lazy loading, and it also may hook into other functionality in the future.
|
||||
|
||||
### Composing a Home Page
|
||||
|
||||
Composing a Home Page is no different from creating a regular React Component, i.e. the App Integrator is free to include whatever content they like. However, there are components developed with the Home Page in mind, as described in the previous section. If created by the `createCardExtension` extension creator, they are rendered like so
|
||||
|
||||
```tsx
|
||||
import React from 'react'
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import { RandomJokeHomePageComponent } from '@backstage/plugin-home';
|
||||
|
||||
export const HomePage = () => {
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} md={4}>
|
||||
<RandomJokeHomePageComponent>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Additionally, the App Integrator is provided an escape hatch in case the way the card is rendered does not fit their requirements. They may optionally pass the `Renderer`-prop, which will receive the `title`, `content` and optionally `actions`, `settings` and `contextProvider`, if they exist for the component. This allows the App Integrator to render the content in any way they want.
|
||||
|
||||
## Contributing
|
||||
|
||||
We believe that people have great ideas for what makes a useful Home Page, and we want to make it easy for every to benefit from the effort you put in to create something cool for the Home Page. Therefore, a great way of contributing is by simply creating more Home Page Components, than can then be used by everyone when composing their own Home Page. If they are tightly coupled to an existing plugin, it is recommended to allow them to live within that plugin, for convenience and to limit complex dependencies. On the other hand, if there's no clear plugin that the component is based on, it's also fine to contribute them into the [home plugin](/plugins/home/src/homePageComponents)
|
||||
|
||||
Additionally, the API is at a very early state, so contributing with additional use cases may expose weaknesses in the current solution that we may iterate on, to provide more flexibility and ease of use for those who wish to develop components for the Home Page.
|
||||
@@ -0,0 +1,130 @@
|
||||
## API Report File for "@backstage/plugin-home"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
/// <reference types="react" />
|
||||
|
||||
import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { Extension } from '@backstage/core-plugin-api';
|
||||
import { ReactNode } from 'react';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "ComponentAccordion" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const ComponentAccordion: ({
|
||||
title,
|
||||
Content,
|
||||
Actions,
|
||||
Settings,
|
||||
ContextProvider,
|
||||
...childProps
|
||||
}: {
|
||||
title: string;
|
||||
Content: () => JSX.Element;
|
||||
Actions?: (() => JSX.Element) | undefined;
|
||||
Settings?: (() => JSX.Element) | undefined;
|
||||
ContextProvider?: ((props: any) => JSX.Element) | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "ComponentTab" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const ComponentTab: ({
|
||||
title,
|
||||
Content,
|
||||
ContextProvider,
|
||||
...childProps
|
||||
}: {
|
||||
title: string;
|
||||
Content: () => JSX.Element;
|
||||
ContextProvider?: ((props: any) => JSX.Element) | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "ComponentTabs" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const ComponentTabs: ({
|
||||
title,
|
||||
tabs,
|
||||
}: {
|
||||
title: string;
|
||||
tabs: {
|
||||
label: string;
|
||||
Component: () => JSX.Element;
|
||||
}[];
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "ComponentRenderer" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "createCardExtension" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export function createCardExtension<T>({
|
||||
title,
|
||||
components,
|
||||
}: {
|
||||
title: string;
|
||||
components: () => Promise<ComponentParts>;
|
||||
}): Extension<
|
||||
({
|
||||
Renderer,
|
||||
title: overrideTitle,
|
||||
...childProps
|
||||
}: ComponentRenderer & {
|
||||
title?: string;
|
||||
} & T) => JSX.Element
|
||||
>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "HomepageCompositionRoot" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const HomepageCompositionRoot: (props: {
|
||||
title?: string | undefined;
|
||||
children?: ReactNode;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "homePlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const homePlugin: BackstagePlugin<
|
||||
{
|
||||
root: RouteRef<undefined>;
|
||||
},
|
||||
{}
|
||||
>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "RandomJokeHomePageComponent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const RandomJokeHomePageComponent: ({
|
||||
Renderer,
|
||||
title: overrideTitle,
|
||||
...childProps
|
||||
}: ComponentRenderer & {
|
||||
title?: string | undefined;
|
||||
} & {
|
||||
defaultCategory?: 'any' | 'programming' | undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SettingsModal" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const SettingsModal: ({
|
||||
open,
|
||||
close,
|
||||
componentName,
|
||||
children,
|
||||
}: {
|
||||
open: boolean;
|
||||
close: Function;
|
||||
componentName: string;
|
||||
children: JSX.Element;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warnings were encountered during analysis:
|
||||
//
|
||||
// src/extensions.d.ts:16:5 - (ae-forgotten-export) The symbol "ComponentParts" needs to be exported by the entry point index.d.ts
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2021 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 React from 'react';
|
||||
import { createDevApp } from '@backstage/dev-utils';
|
||||
import { homePlugin, HomepageCompositionRoot } from '../src/plugin';
|
||||
|
||||
createDevApp()
|
||||
.registerPlugin(homePlugin)
|
||||
.addPage({
|
||||
element: <HomepageCompositionRoot />,
|
||||
title: 'Root Page',
|
||||
path: '/',
|
||||
})
|
||||
.render();
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "@backstage/plugin-home",
|
||||
"version": "0.1.1",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "backstage-cli plugin:build",
|
||||
"start": "backstage-cli plugin:serve",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
"diff": "backstage-cli plugin:diff",
|
||||
"prepack": "backstage-cli prepack",
|
||||
"postpack": "backstage-cli postpack",
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core-components": "^0.3.1",
|
||||
"@backstage/core-plugin-api": "^0.1.6",
|
||||
"@backstage/theme": "^0.2.9",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"@types/react": "^16.9",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-router": "^6.0.0-beta.0",
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.8",
|
||||
"@backstage/core-app-api": "^0.1.8",
|
||||
"@backstage/dev-utils": "^0.2.6",
|
||||
"@backstage/test-utils": "^0.1.17",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@testing-library/user-event": "^13.1.8",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/node": "^14.14.32",
|
||||
"cross-fetch": "^3.0.6",
|
||||
"msw": "^0.29.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2021 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 React from 'react';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
AccordionSummary,
|
||||
Typography,
|
||||
IconButton,
|
||||
Theme,
|
||||
} from '@material-ui/core';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import SettingsIcon from '@material-ui/icons/Settings';
|
||||
|
||||
import { SettingsModal } from '../components';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
settingsIconButton: {
|
||||
padding: theme.spacing(0, 1, 0, 0),
|
||||
},
|
||||
}));
|
||||
|
||||
export const ComponentAccordion = ({
|
||||
title,
|
||||
Content,
|
||||
Actions,
|
||||
Settings,
|
||||
ContextProvider,
|
||||
...childProps
|
||||
}: {
|
||||
title: string;
|
||||
Content: () => JSX.Element;
|
||||
Actions?: () => JSX.Element;
|
||||
Settings?: () => JSX.Element;
|
||||
ContextProvider?: (props: any) => JSX.Element;
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const [settingsIsExpanded, setSettingsIsExpanded] = React.useState(false);
|
||||
const [isExpanded, setIsExpanded] = React.useState(false);
|
||||
|
||||
const handleOpenSettings = (e: any) => {
|
||||
e.stopPropagation();
|
||||
setSettingsIsExpanded(prevState => !prevState);
|
||||
};
|
||||
|
||||
const innerContent = (
|
||||
<>
|
||||
{Settings && (
|
||||
<SettingsModal
|
||||
open={settingsIsExpanded}
|
||||
close={() => setSettingsIsExpanded(false)}
|
||||
componentName={title}
|
||||
>
|
||||
<Settings />
|
||||
</SettingsModal>
|
||||
)}
|
||||
<Accordion
|
||||
expanded={isExpanded}
|
||||
onChange={(_e: any, expanded: boolean) => setIsExpanded(expanded)}
|
||||
>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
{Settings && (
|
||||
<IconButton
|
||||
onClick={handleOpenSettings}
|
||||
className={classes.settingsIconButton}
|
||||
>
|
||||
<SettingsIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
<Typography>{title}</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<div>
|
||||
<Content />
|
||||
{Actions && <Actions />}
|
||||
</div>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</>
|
||||
);
|
||||
|
||||
return ContextProvider ? (
|
||||
<ContextProvider {...childProps}>{innerContent}</ContextProvider>
|
||||
) : (
|
||||
innerContent
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2021 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 React from 'react';
|
||||
|
||||
export const ComponentTab = ({
|
||||
title,
|
||||
Content,
|
||||
ContextProvider,
|
||||
...childProps
|
||||
}: {
|
||||
title: string;
|
||||
Content: () => JSX.Element;
|
||||
ContextProvider?: (props: any) => JSX.Element;
|
||||
}) => {
|
||||
return ContextProvider ? (
|
||||
<ContextProvider {...childProps}>
|
||||
<Content />
|
||||
</ContextProvider>
|
||||
) : (
|
||||
<Content />
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2021 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 React from 'react';
|
||||
import { Tabs, Tab } from '@material-ui/core';
|
||||
import { InfoCard } from '@backstage/core-components';
|
||||
|
||||
type TabType = {
|
||||
label: string;
|
||||
Component: () => JSX.Element;
|
||||
};
|
||||
|
||||
export const ComponentTabs = ({
|
||||
title,
|
||||
tabs,
|
||||
}: {
|
||||
title: string;
|
||||
tabs: TabType[];
|
||||
}) => {
|
||||
const [value, setValue] = React.useState(0);
|
||||
|
||||
const handleChange = (_event: any, newValue: number) => {
|
||||
setValue(newValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<InfoCard title={title}>
|
||||
<Tabs value={value} onChange={handleChange}>
|
||||
{tabs.map(t => (
|
||||
<Tab key={t.label} label={t.label} />
|
||||
))}
|
||||
</Tabs>
|
||||
{tabs.map(({ Component }, idx) => (
|
||||
<div {...(idx === value ? { style: { display: 'none' } } : {})}>
|
||||
<Component />
|
||||
</div>
|
||||
))}
|
||||
</InfoCard>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
export { ComponentTabs } from './ComponentTabs';
|
||||
export { ComponentTab } from './ComponentTab';
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
export { ComponentAccordion } from './ComponentAccordion';
|
||||
export { ComponentTabs, ComponentTab } from './ComponentTabs';
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2021 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 React, { ReactNode } from 'react';
|
||||
import { useOutlet } from 'react-router';
|
||||
import { Content, Header, Page } from '@backstage/core-components';
|
||||
|
||||
export const HomepageCompositionRoot = (props: {
|
||||
title?: string;
|
||||
children?: ReactNode;
|
||||
}) => {
|
||||
const outlet = useOutlet();
|
||||
const children = props.children ?? outlet;
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header title={props.title ?? 'Home'} />
|
||||
<Content>{children}</Content>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2021 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 React from 'react';
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
} from '@material-ui/core';
|
||||
|
||||
export const SettingsModal = ({
|
||||
open,
|
||||
close,
|
||||
componentName,
|
||||
children,
|
||||
}: {
|
||||
open: boolean;
|
||||
close: Function;
|
||||
componentName: string;
|
||||
children: JSX.Element;
|
||||
}) => {
|
||||
return (
|
||||
<Dialog open={open} onClose={() => close()}>
|
||||
<DialogTitle>Settings - {componentName}</DialogTitle>
|
||||
<DialogContent>{children}</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => close()} color="primary" variant="contained">
|
||||
Close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
export { HomepageCompositionRoot } from './HomepageCompositionRoot';
|
||||
export { SettingsModal } from './SettingsModal';
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2021 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 React, { Suspense } from 'react';
|
||||
import { IconButton } from '@material-ui/core';
|
||||
import SettingsIcon from '@material-ui/icons/Settings';
|
||||
import { InfoCard } from '@backstage/core-components';
|
||||
import { SettingsModal } from './components';
|
||||
import { createReactExtension, useApp } from '@backstage/core-plugin-api';
|
||||
|
||||
export type ComponentRenderer = {
|
||||
Renderer?: (props: RendererProps) => JSX.Element;
|
||||
};
|
||||
|
||||
type ComponentParts = {
|
||||
Content: () => JSX.Element;
|
||||
Actions?: () => JSX.Element;
|
||||
Settings?: () => JSX.Element;
|
||||
ContextProvider?: (props: any) => JSX.Element;
|
||||
};
|
||||
|
||||
type RendererProps = { title: string } & ComponentParts;
|
||||
|
||||
export function createCardExtension<T>({
|
||||
title,
|
||||
components,
|
||||
}: {
|
||||
title: string;
|
||||
components: () => Promise<ComponentParts>;
|
||||
}) {
|
||||
return createReactExtension({
|
||||
component: {
|
||||
lazy: () =>
|
||||
components().then(({ Content, Actions, Settings, ContextProvider }) => {
|
||||
const CardExtension = ({
|
||||
Renderer,
|
||||
title: overrideTitle,
|
||||
...childProps
|
||||
}: ComponentRenderer & { title?: string } & T) => {
|
||||
const app = useApp();
|
||||
const { Progress } = app.getComponents();
|
||||
const [settingsOpen, setSettingsOpen] = React.useState(false);
|
||||
|
||||
if (Renderer) {
|
||||
return (
|
||||
<Suspense fallback={<Progress />}>
|
||||
<Renderer
|
||||
title={overrideTitle || title}
|
||||
{...{
|
||||
Content,
|
||||
...(Actions ? { Actions } : {}),
|
||||
...(Settings ? { Settings } : {}),
|
||||
...(ContextProvider ? { ContextProvider } : {}),
|
||||
...childProps,
|
||||
}}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
const cardProps = {
|
||||
title: overrideTitle ?? title,
|
||||
...(Settings
|
||||
? {
|
||||
action: (
|
||||
<IconButton onClick={() => setSettingsOpen(true)}>
|
||||
<SettingsIcon>Settings</SettingsIcon>
|
||||
</IconButton>
|
||||
),
|
||||
}
|
||||
: {}),
|
||||
...(Actions
|
||||
? {
|
||||
actions: <Actions />,
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
|
||||
const innerContent = (
|
||||
<InfoCard {...cardProps}>
|
||||
{Settings && (
|
||||
<SettingsModal
|
||||
open={settingsOpen}
|
||||
componentName={title}
|
||||
close={() => setSettingsOpen(false)}
|
||||
>
|
||||
<Settings />
|
||||
</SettingsModal>
|
||||
)}
|
||||
<Content />
|
||||
</InfoCard>
|
||||
);
|
||||
|
||||
return (
|
||||
<Suspense fallback={<Progress />}>
|
||||
{ContextProvider ? (
|
||||
<ContextProvider {...childProps}>
|
||||
{innerContent}
|
||||
</ContextProvider>
|
||||
) : (
|
||||
innerContent
|
||||
)}
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
return CardExtension;
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2021 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 React from 'react';
|
||||
|
||||
import { Button } from '@material-ui/core';
|
||||
import { useRandomJoke } from './Context';
|
||||
|
||||
export const Actions = () => {
|
||||
const { rerollJoke } = useRandomJoke();
|
||||
return (
|
||||
<Button variant="contained" color="primary" onClick={() => rerollJoke()}>
|
||||
Reroll
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2021 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 React from 'react';
|
||||
import { useRandomJoke } from './Context';
|
||||
|
||||
export const Content = () => {
|
||||
const { joke, loading } = useRandomJoke();
|
||||
|
||||
if (loading) return <p>Loading...</p>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>{joke.setup}</p>
|
||||
<p>{joke.punchline}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2021 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 React, { createContext } from 'react';
|
||||
|
||||
export type JokeType = 'any' | 'programming';
|
||||
|
||||
type Joke = {
|
||||
setup: string;
|
||||
punchline: string;
|
||||
};
|
||||
|
||||
type RandomJokeContextValue = {
|
||||
loading: boolean;
|
||||
joke: Joke;
|
||||
type: JokeType;
|
||||
rerollJoke: Function;
|
||||
handleChangeType: Function;
|
||||
};
|
||||
|
||||
const Context = createContext<RandomJokeContextValue | undefined>(undefined);
|
||||
|
||||
const getNewJoke = (type: string): Promise<Joke> =>
|
||||
fetch(
|
||||
`https://official-joke-api.appspot.com/jokes${
|
||||
type !== 'any' ? `/${type}` : ''
|
||||
}/random`,
|
||||
)
|
||||
.then(res => res.json())
|
||||
.then(data => (Array.isArray(data) ? data[0] : data));
|
||||
|
||||
export const ContextProvider = ({
|
||||
children,
|
||||
defaultCategory,
|
||||
}: {
|
||||
children: JSX.Element;
|
||||
defaultCategory?: JokeType;
|
||||
}) => {
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [joke, setJoke] = React.useState<Joke>({
|
||||
setup: '',
|
||||
punchline: '',
|
||||
});
|
||||
const [type, setType] = React.useState<JokeType>(
|
||||
defaultCategory || ('programming' as JokeType),
|
||||
);
|
||||
|
||||
const rerollJoke = React.useCallback(() => {
|
||||
setLoading(true);
|
||||
getNewJoke(type).then(newJoke => setJoke(newJoke));
|
||||
}, [type]);
|
||||
|
||||
const handleChangeType = (newType: JokeType) => {
|
||||
setType(newType);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
setLoading(false);
|
||||
}, [joke]);
|
||||
|
||||
React.useEffect(() => {
|
||||
rerollJoke();
|
||||
}, [rerollJoke]);
|
||||
|
||||
const value: RandomJokeContextValue = {
|
||||
loading,
|
||||
joke,
|
||||
type,
|
||||
rerollJoke,
|
||||
handleChangeType,
|
||||
};
|
||||
|
||||
return <Context.Provider value={value}>{children}</Context.Provider>;
|
||||
};
|
||||
|
||||
export const useRandomJoke = () => {
|
||||
const value = React.useContext(Context);
|
||||
|
||||
if (value === undefined) {
|
||||
throw new Error('useRandomJoke must be used within a RandomJokeProvider');
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
export default Context;
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2021 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 {
|
||||
FormControl,
|
||||
FormLabel,
|
||||
RadioGroup,
|
||||
FormControlLabel,
|
||||
Radio,
|
||||
} from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { useRandomJoke, JokeType } from './Context';
|
||||
|
||||
export const Settings = () => {
|
||||
const { type, handleChangeType } = useRandomJoke();
|
||||
const JOKE_TYPES: JokeType[] = ['any' as JokeType, 'programming' as JokeType];
|
||||
return (
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Joke Type</FormLabel>
|
||||
<RadioGroup
|
||||
aria-label="joke type"
|
||||
value={type}
|
||||
onChange={e => handleChangeType(e.target.value)}
|
||||
>
|
||||
{JOKE_TYPES.map(t => (
|
||||
<FormControlLabel
|
||||
key={t}
|
||||
value={t}
|
||||
control={<Radio />}
|
||||
label={`${t.slice(0, 1).toUpperCase()}${t.slice(1)}`}
|
||||
/>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
export { Actions } from './Actions';
|
||||
export { Content } from './Content';
|
||||
export { Settings } from './Settings';
|
||||
export { ContextProvider } from './Context';
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
export {
|
||||
homePlugin,
|
||||
HomepageCompositionRoot,
|
||||
RandomJokeHomePageComponent,
|
||||
ComponentAccordion,
|
||||
ComponentTabs,
|
||||
ComponentTab,
|
||||
} from './plugin';
|
||||
export { SettingsModal } from './components';
|
||||
export { createCardExtension } from './extensions';
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2021 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 { homePlugin } from './plugin';
|
||||
|
||||
describe('home', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(homePlugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2021 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 {
|
||||
createComponentExtension,
|
||||
createPlugin,
|
||||
createRoutableExtension,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { createCardExtension } from './extensions';
|
||||
|
||||
import { rootRouteRef } from './routes';
|
||||
|
||||
export const homePlugin = createPlugin({
|
||||
id: 'home',
|
||||
routes: {
|
||||
root: rootRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
export const HomepageCompositionRoot = homePlugin.provide(
|
||||
createRoutableExtension({
|
||||
component: () =>
|
||||
import('./components').then(m => m.HomepageCompositionRoot),
|
||||
mountPoint: rootRouteRef,
|
||||
}),
|
||||
);
|
||||
|
||||
export const ComponentAccordion = homePlugin.provide(
|
||||
createComponentExtension({
|
||||
component: {
|
||||
lazy: () =>
|
||||
import('./componentRenderers').then(m => m.ComponentAccordion),
|
||||
},
|
||||
}),
|
||||
);
|
||||
export const ComponentTabs = homePlugin.provide(
|
||||
createComponentExtension({
|
||||
component: {
|
||||
lazy: () => import('./componentRenderers').then(m => m.ComponentTabs),
|
||||
},
|
||||
}),
|
||||
);
|
||||
export const ComponentTab = homePlugin.provide(
|
||||
createComponentExtension({
|
||||
component: {
|
||||
lazy: () => import('./componentRenderers').then(m => m.ComponentTab),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export const RandomJokeHomePageComponent = homePlugin.provide(
|
||||
createCardExtension<{ defaultCategory?: 'any' | 'programming' }>({
|
||||
title: 'Random Joke',
|
||||
components: () => import('./homePageComponents/RandomJoke'),
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2021 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 { createRouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
title: 'home',
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 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 '@testing-library/jest-dom';
|
||||
import 'cross-fetch/polyfill';
|
||||
Reference in New Issue
Block a user