diff --git a/plugins/app-visualizer/package.json b/plugins/app-visualizer/package.json index 3e6a0c7d85..89bf7b3c5b 100644 --- a/plugins/app-visualizer/package.json +++ b/plugins/app-visualizer/package.json @@ -38,7 +38,8 @@ "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@backstage/ui": "workspace:^", - "@remixicon/react": "^4.6.0" + "@remixicon/react": "^4.6.0", + "react-aria-components": "^1.13.0" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx b/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx index df9308ce96..1d38ec8267 100644 --- a/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx +++ b/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx @@ -18,19 +18,19 @@ import { AppNode, AppTree, ExtensionDataRef, - RouteRef, coreExtensionData, ApiBlueprint, NavItemBlueprint, ThemeBlueprint, - useRouteRef, + useApi, + routeResolutionApiRef, } from '@backstage/frontend-plugin-api'; -import { Box, Flex } from '@backstage/ui'; -import { Link } from 'react-router-dom'; +import { Box, Flex, Link, Text, Tooltip, TooltipTrigger } from '@backstage/ui'; import { RiInputField as InputIcon, RiCloseCircleLine as DisabledIcon, } from '@remixicon/react'; +import { Focusable } from 'react-aria-components'; function getContrastColor(bgColor: string): string { const hex = bgColor.replace('#', ''); @@ -72,36 +72,17 @@ function createOutputColorGenerator( }; } -// Color palette for output visualization -const colorPalette = { - green: { 500: '#4caf50', 200: '#a5d6a7' }, - yellow: { 500: '#ffeb3b', 200: '#fff59d' }, - purple: { 500: '#9c27b0', 200: '#ce93d8' }, - blue: { 500: '#2196f3', 200: '#90caf9' }, - lime: { 500: '#cddc39', 200: '#e6ee9c' }, - orange: { 500: '#ff9800', 200: '#ffcc80' }, - red: { 200: '#ef9a9a' }, -}; - const getOutputColor = createOutputColorGenerator( { - [coreExtensionData.reactElement.id]: colorPalette.green[500], - [coreExtensionData.routePath.id]: colorPalette.yellow[500], - [coreExtensionData.routeRef.id]: colorPalette.purple[500], - [ApiBlueprint.dataRefs.factory.id]: colorPalette.blue[500], - [ThemeBlueprint.dataRefs.theme.id]: colorPalette.lime[500], - [NavItemBlueprint.dataRefs.target.id]: colorPalette.orange[500], + [coreExtensionData.reactElement.id]: '#4caf50', + [coreExtensionData.routePath.id]: '#ffeb3b', + [coreExtensionData.routeRef.id]: '#9c27b0', + [ApiBlueprint.dataRefs.factory.id]: '#2196f3', + [ThemeBlueprint.dataRefs.theme.id]: '#cddc39', + [NavItemBlueprint.dataRefs.target.id]: '#ff9800', }, - [ - colorPalette.blue[200], - colorPalette.orange[200], - colorPalette.green[200], - colorPalette.red[200], - colorPalette.yellow[200], - colorPalette.purple[200], - colorPalette.lime[200], - ], + ['#90caf9', '#ffcc80', '#a5d6a7', '#ef9a9a', '#fff59d', '#ce93d8', '#e6ee9c'], ); // Helper function to get border color based on depth @@ -124,73 +105,62 @@ function getFullPath(node?: AppNode): string { return getFullPath(parent) + part; } -function OutputLink(props: { - dataRef: ExtensionDataRef; - node?: AppNode; - style: React.CSSProperties; -}) { - const routeRef = props.node?.instance?.getData(coreExtensionData.routeRef); - - try { - const link = useRouteRef(routeRef as RouteRef); - - return ( -
- - {link ? link : null} - -
- ); - } catch (ex) { - // eslint-disable-next-line no-console - console.warn( - props.node?.spec.id - ? `Unable to generate output link for ${props.node.spec.id}` - : 'Unable to generate output link', - ex, - ); - return null; - } -} - function Output(props: { dataRef: ExtensionDataRef; node?: AppNode }) { const { dataRef, node } = props; const { id } = dataRef; const instance = node?.instance; + const routeResolutionApi = useApi(routeResolutionApiRef); + const { backgroundColor, color } = getOutputColor(id); const chipStyle: React.CSSProperties = { - padding: '0 var(--bui-space-2_5, 10px)', height: 20, - borderRadius: 'var(--bui-radius-full)', + padding: '0 10px', + borderRadius: '10px', color, backgroundColor, + display: 'flex', + alignItems: 'center', + fontWeight: + 'var(--bui-font-weight-regular)' as React.CSSProperties['fontWeight'], }; - if (id === coreExtensionData.routePath.id) { - return ( -
- - {String(instance?.getData(dataRef) ?? '')} - -
- ); + if (id === coreExtensionData.routeRef.id && node) { + try { + const routeRef = props.node?.instance?.getData( + coreExtensionData.routeRef, + ); + const link = routeRef && routeResolutionApi.resolve(routeRef)?.(); + if (link) { + return ( + + + link + + {id} + + ); + } + } catch { + /* ignore */ + } } - if (id === coreExtensionData.routeRef.id && node) { - return ( - - ); + let tooltip = id; + let text: string | undefined = undefined; + if (id === coreExtensionData.routePath.id) { + text = String(instance?.getData(dataRef) ?? ''); + tooltip = getFullPath(node); } return ( -
- -
+ + + {text} + + {tooltip} + ); } @@ -259,7 +229,7 @@ function Extension(props: { node: AppNode; depth: number }) { tooltipParts.push(`${currentNode.spec.id} [${input}]`); } tooltipParts.reverse(); - const tooltipText = tooltipParts.join(' → '); + const tooltipText = tooltipParts.join('\n'); return ( -
- {node.spec.id} -
+ + + {node.spec.id} + + + {tooltipText} + + {dataRefs && dataRefs.length > 0 && diff --git a/yarn.lock b/yarn.lock index 056de1c49a..a559f0b8cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4121,10 +4121,11 @@ __metadata: "@backstage/core-plugin-api": "workspace:^" "@backstage/frontend-defaults": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" - "@material-ui/core": "npm:^4.12.2" - "@material-ui/icons": "npm:^4.9.1" + "@backstage/ui": "workspace:^" + "@remixicon/react": "npm:^4.6.0" "@types/react": "npm:^18.0.0" react: "npm:^18.0.2" + react-aria-components: "npm:^1.13.0" react-dom: "npm:^18.0.2" react-router-dom: "npm:^6.3.0" peerDependencies: