From e80cd2ce6a58916950338b1ec1e76a8841aa86ff Mon Sep 17 00:00:00 2001 From: Christoph Jerolimov Date: Tue, 19 Nov 2024 04:22:27 +0100 Subject: [PATCH] feat(marketplace): add drawer with initial layout Signed-off-by: Christoph Jerolimov --- .../plugins/marketplace-common/src/types.ts | 2 +- .../plugins/marketplace/package.json | 4 +- .../src/components/MarketplaceCatalogGrid.tsx | 136 ++++++----- .../MarketplaceEntryDetailDrawer.tsx | 227 ++++++++++++++++++ .../src/components/MarketplacePage.tsx | 2 + .../plugins/marketplace/src/routes.ts | 8 +- workspaces/marketplace/yarn.lock | 17 ++ 7 files changed, 338 insertions(+), 58 deletions(-) create mode 100644 workspaces/marketplace/plugins/marketplace/src/components/MarketplaceEntryDetailDrawer.tsx diff --git a/workspaces/marketplace/plugins/marketplace-common/src/types.ts b/workspaces/marketplace/plugins/marketplace-common/src/types.ts index 94a79418c..1110b5161 100644 --- a/workspaces/marketplace/plugins/marketplace-common/src/types.ts +++ b/workspaces/marketplace/plugins/marketplace-common/src/types.ts @@ -19,7 +19,7 @@ */ export interface MarketplacePluginEntry { metadata: MarketplacePluginMetadata; - spec: MarketplacePluginSpec; + spec?: MarketplacePluginSpec; } /** diff --git a/workspaces/marketplace/plugins/marketplace/package.json b/workspaces/marketplace/plugins/marketplace/package.json index 4267d5feb..1b7fb37d4 100644 --- a/workspaces/marketplace/plugins/marketplace/package.json +++ b/workspaces/marketplace/plugins/marketplace/package.json @@ -38,6 +38,7 @@ "@backstage/core-components": "^0.15.1", "@backstage/core-plugin-api": "^1.10.0", "@backstage/theme": "^0.6.0", + "@mui/icons-material": "^5.16.7", "@mui/material": "^5.12.2", "@red-hat-developer-hub/backstage-plugin-marketplace-common": "workspace:^", "@tanstack/react-query": "^5.60.5", @@ -45,7 +46,8 @@ }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" + "react-dom": "^17.0.0 || ^18.0.0", + "react-router-dom": "*" }, "devDependencies": { "@backstage/cli": "^0.28.0", diff --git a/workspaces/marketplace/plugins/marketplace/src/components/MarketplaceCatalogGrid.tsx b/workspaces/marketplace/plugins/marketplace/src/components/MarketplaceCatalogGrid.tsx index 0a9a6623b..b7a3589f3 100644 --- a/workspaces/marketplace/plugins/marketplace/src/components/MarketplaceCatalogGrid.tsx +++ b/workspaces/marketplace/plugins/marketplace/src/components/MarketplaceCatalogGrid.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ import React from 'react'; +import { useNavigate } from 'react-router-dom'; import { useQueryParamState, @@ -21,6 +22,7 @@ import { Link, LinkButton, } from '@backstage/core-components'; +import { useRouteRef } from '@backstage/core-plugin-api'; import Card from '@mui/material/Card'; import CardContent from '@mui/material/CardContent'; @@ -32,6 +34,7 @@ import Typography from '@mui/material/Typography'; import { MarketplacePluginEntry } from '@red-hat-developer-hub/backstage-plugin-marketplace-common'; import { usePlugins } from '../hooks/usePlugins'; +import { detailsRouteRef, rootRouteRef } from '../routes'; const Icon = ({ entry }: { entry: MarketplacePluginEntry }) => entry.metadata.icon ? ( @@ -81,65 +84,88 @@ const EntrySkeleton = ({ ); // TODO: add link around card -const Entry = ({ entry }: { entry: MarketplacePluginEntry }) => ( - - - - - - - - {entry.metadata.title} - - {entry.metadata.developer ? ( - - {' by '} - - {entry.metadata.developer} - +const Entry = ({ entry }: { entry: MarketplacePluginEntry }) => { + const navigate = useNavigate(); + const getIndexPath = useRouteRef(rootRouteRef); + const getDetailsPath = useRouteRef(detailsRouteRef); + + const detailsPath = getDetailsPath({ name: entry.metadata.name }); + const withSearchParameter = (name: string, value: string) => + `${getIndexPath()}?${encodeURIComponent(name)}=${encodeURIComponent( + value, + )}`; + + return ( + navigate(detailsPath)} + > + + + + + + + {entry.metadata.title} - ) : null} - {entry.metadata.categories && - entry.metadata.categories.length > 0 ? ( - - - {entry.metadata.categories[0]} - - - ) : null} + {' by '} + e.stopPropagation()} + > + {entry.metadata.developer} + + + ) : null} + {entry.metadata.categories && + entry.metadata.categories.length > 0 ? ( + + e.stopPropagation()} + > + {entry.metadata.categories[0]} + + + ) : null} + - - {entry.metadata.abstract ? ( - - {entry.metadata.abstract} - - ) : null} - - - - - Read more - - - -); + {entry.metadata.abstract ? ( + + {entry.metadata.abstract} + + ) : null} + + + + e.stopPropagation()}> + Read more + + + + ); +}; export const MarketplaceCatalogGrid = () => { const plugins = usePlugins(); diff --git a/workspaces/marketplace/plugins/marketplace/src/components/MarketplaceEntryDetailDrawer.tsx b/workspaces/marketplace/plugins/marketplace/src/components/MarketplaceEntryDetailDrawer.tsx new file mode 100644 index 000000000..b2044a34a --- /dev/null +++ b/workspaces/marketplace/plugins/marketplace/src/components/MarketplaceEntryDetailDrawer.tsx @@ -0,0 +1,227 @@ +/* + * Copyright 2024 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 { useParams, useNavigate } from 'react-router-dom'; + +import { + Content, + ErrorPanel, + Link, + LinkButton, +} from '@backstage/core-components'; +import { useRouteRef } from '@backstage/core-plugin-api'; + +import CardMedia from '@mui/material/CardMedia'; +import Drawer from '@mui/material/Drawer'; +import IconButton from '@mui/material/IconButton'; +import CloseIcon from '@mui/icons-material/Close'; +import Skeleton from '@mui/material/Skeleton'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; +import Grid from '@mui/material/Grid'; + +import { MarketplacePluginEntry } from '@red-hat-developer-hub/backstage-plugin-marketplace-common'; +import { usePlugins } from '../hooks/usePlugins'; +import { rootRouteRef } from '../routes'; + +const Icon = ({ entry }: { entry: MarketplacePluginEntry }) => + entry.metadata.icon ? ( + + ) : null; + +const EntryContentSkeleton = () => { + return ( + + + + + + Entry name + + + by someone + + + Category + + + +
+
+ + + + Highlights + + + + + + + + About + + + + + + + + + +
+ ); +}; + +const EntryContent = ({ entry }: { entry: MarketplacePluginEntry }) => { + const getIndexPath = useRouteRef(rootRouteRef); + + const withSearchParameter = (name: string, value: string) => + `${getIndexPath()}?${encodeURIComponent(name)}=${encodeURIComponent( + value, + )}`; + + return ( + + + + + + {entry.metadata.title} + + {entry.metadata.developer ? ( + + {' by '} + + {entry.metadata.developer} + + + ) : null} + {entry.metadata.categories?.map(category => ( + + + {category} + + + ))} + + + +
+
+ + + + {entry.spec?.highlights && entry.spec?.highlights.length > 0 ? ( + <> + + Highlights + +
    + {entry.spec.highlights.map(highlight => ( +
  1. {highlight}
  2. + ))} +
+ + ) : null} + + + Install + +
+ + + About + + {entry.metadata.abstract ? ( + + {entry.metadata.abstract} + + ) : null} + +
+
+ ); +}; + +const Entry = ({ entryName }: { entryName: string }) => { + const plugins = usePlugins(); + const entry = plugins.data?.find(e => e.metadata.name === entryName); + + if (plugins.isLoading) { + return ; + } else if (!entry) { + return ( + + + + ); + } + return ; +}; + +export const MarketplaceEntryDetailDrawer = () => { + const params = useParams(); + const navigate = useNavigate(); + const getIndexPath = useRouteRef(rootRouteRef); + + const entryName = params['*']; + + const open = !!entryName; + const handleClose = () => navigate(getIndexPath()); + + return ( + + {/* params: +
+        {JSON.stringify(params, null, 2)}
+      
*/} + theme.palette.grey[500], + }} + > + + + {entryName ? : null} +
+ ); +}; diff --git a/workspaces/marketplace/plugins/marketplace/src/components/MarketplacePage.tsx b/workspaces/marketplace/plugins/marketplace/src/components/MarketplacePage.tsx index c328d8c38..bac5a21b5 100644 --- a/workspaces/marketplace/plugins/marketplace/src/components/MarketplacePage.tsx +++ b/workspaces/marketplace/plugins/marketplace/src/components/MarketplacePage.tsx @@ -18,6 +18,7 @@ import { Page, Header, TabbedLayout } from '@backstage/core-components'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { MarketplaceCatalogTab } from './MarketplaceCatalogTab'; +import { MarketplaceEntryDetailDrawer } from './MarketplaceEntryDetailDrawer'; const queryClient = new QueryClient(); @@ -31,5 +32,6 @@ export const MarketplacePage = () => ( + ); diff --git a/workspaces/marketplace/plugins/marketplace/src/routes.ts b/workspaces/marketplace/plugins/marketplace/src/routes.ts index c4a86fcb4..6b349a4e4 100644 --- a/workspaces/marketplace/plugins/marketplace/src/routes.ts +++ b/workspaces/marketplace/plugins/marketplace/src/routes.ts @@ -13,8 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createRouteRef } from '@backstage/core-plugin-api'; +import { createRouteRef, createSubRouteRef } from '@backstage/core-plugin-api'; export const rootRouteRef = createRouteRef({ id: 'marketplace', }); + +export const detailsRouteRef = createSubRouteRef({ + id: 'details-page', + parent: rootRouteRef, + path: '/:name', +}); diff --git a/workspaces/marketplace/yarn.lock b/workspaces/marketplace/yarn.lock index 0e9e508fa..b06dd2b7e 100644 --- a/workspaces/marketplace/yarn.lock +++ b/workspaces/marketplace/yarn.lock @@ -8337,6 +8337,22 @@ __metadata: languageName: node linkType: hard +"@mui/icons-material@npm:^5.16.7": + version: 5.16.7 + resolution: "@mui/icons-material@npm:5.16.7" + dependencies: + "@babel/runtime": ^7.23.9 + peerDependencies: + "@mui/material": ^5.0.0 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: a875f2837897d79a83173d80461e06ab090b64d08913d26433cf2cbeb8e7c7456468632a7aa495d722718f09111a8043255777d73b4dfbe9e0f863a170fc7190 + languageName: node + linkType: hard + "@mui/material@npm:^5.12.2": version: 5.16.7 resolution: "@mui/material@npm:5.16.7" @@ -10048,6 +10064,7 @@ __metadata: "@backstage/dev-utils": ^1.1.2 "@backstage/test-utils": ^1.7.0 "@backstage/theme": ^0.6.0 + "@mui/icons-material": ^5.16.7 "@mui/material": ^5.12.2 "@red-hat-developer-hub/backstage-plugin-marketplace-common": "workspace:^" "@tanstack/react-query": ^5.60.5