@backstage/plugin-app-visualizer 0.1.28-next.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @backstage/plugin-app-visualizer
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ef6916e: Migrated to use `SubPageBlueprint` for tabbed navigation and added a copy-tree-as-JSON plugin header action using `PluginHeaderActionBlueprint`. The plugin now specifies a `title` and `icon`.
8
+
9
+ ### Patch Changes
10
+
11
+ - cb090b4: Bump react-aria-components to v1.14.0
12
+ - c38b74d: Internal updates for blueprint moves to `@backstage/plugin-app-react`.
13
+ - 4137a43: Updated CSS token references to use renamed `--bui-border-2` token.
14
+ - 4d50e1f: Improved rendering performance of the details page.
15
+ - a7e0d50: Updated `react-router-dom` peer dependency to `^6.30.2` and explicitly disabled v7 future flags to suppress deprecation warnings.
16
+ - Updated dependencies
17
+ - @backstage/ui@0.12.0
18
+ - @backstage/core-components@0.18.7
19
+ - @backstage/frontend-plugin-api@0.14.0
20
+ - @backstage/core-plugin-api@1.12.3
21
+
3
22
  ## 0.1.28-next.1
4
23
 
5
24
  ### Patch Changes
@@ -1,6 +1,6 @@
1
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
- import { NavItemBlueprint, coreExtensionData, ApiBlueprint, useApi, routeResolutionApiRef } from '@backstage/frontend-plugin-api';
3
- import { Flex, Box, TooltipTrigger, Text, Tooltip, Link } from '@backstage/ui';
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import { useApi, appTreeApiRef, NavItemBlueprint, coreExtensionData, ApiBlueprint, routeResolutionApiRef } from '@backstage/frontend-plugin-api';
3
+ import { FullPage, Flex, Box, TooltipTrigger, Text, Tooltip, Link } from '@backstage/ui';
4
4
  import { RiCloseCircleLine, RiInputField } from '@remixicon/react';
5
5
  import { Focusable } from 'react-aria-components';
6
6
  import { useMemo, memo, useState, useRef, useEffect, Fragment as Fragment$1 } from 'react';
@@ -263,8 +263,10 @@ function Legend() {
263
263
  }
264
264
  );
265
265
  }
266
- function DetailedVisualizer({ tree }) {
267
- return /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { height: "100%", flex: "1 1 100%" }, children: [
266
+ function DetailedVisualizer() {
267
+ const appTreeApi = useApi(appTreeApiRef);
268
+ const { tree } = appTreeApi.getTree();
269
+ return /* @__PURE__ */ jsx(FullPage, { children: /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { height: "100%", flex: "1 1 100%" }, children: [
268
270
  /* @__PURE__ */ jsx(Box, { ml: "4", mt: "4", style: { flex: "1 1 0", overflow: "auto" }, children: /* @__PURE__ */ jsx(Extension, { node: tree.root, depth: 0 }) }),
269
271
  /* @__PURE__ */ jsx(
270
272
  Box,
@@ -273,13 +275,13 @@ function DetailedVisualizer({ tree }) {
273
275
  style: {
274
276
  flex: "0 0 auto",
275
277
  background: "var(--bui-bg-neutral-1)",
276
- border: "1px solid var(--bui-border)",
278
+ border: "1px solid var(--bui-border-2)",
277
279
  borderRadius: "var(--bui-radius-2)"
278
280
  },
279
281
  children: /* @__PURE__ */ jsx(Legend, {})
280
282
  }
281
283
  )
282
- ] });
284
+ ] }) });
283
285
  }
284
286
 
285
287
  export { DetailedVisualizer };
@@ -1 +1 @@
1
- {"version":3,"file":"DetailedVisualizer.esm.js","sources":["../../../src/components/AppVisualizerPage/DetailedVisualizer.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AppNode,\n AppTree,\n ExtensionDataRef,\n coreExtensionData,\n ApiBlueprint,\n NavItemBlueprint,\n useApi,\n routeResolutionApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { Box, Flex, Link, Text, Tooltip, TooltipTrigger } from '@backstage/ui';\nimport {\n RiInputField as InputIcon,\n RiCloseCircleLine as DisabledIcon,\n} from '@remixicon/react';\nimport { Focusable } from 'react-aria-components';\nimport { memo, useMemo, useState, useEffect, useRef, Fragment } from 'react';\n\nfunction getContrastColor(bgColor: string): string {\n const hex = bgColor.replace('#', '');\n const r = parseInt(hex.substr(0, 2), 16);\n const g = parseInt(hex.substr(2, 2), 16);\n const b = parseInt(hex.substr(4, 2), 16);\n const brightness = (r * 299 + g * 587 + b * 114) / 1000;\n return brightness > 128 ? '#000000' : '#ffffff';\n}\n\nfunction createOutputColorGenerator(\n colorMap: { [extDataId: string]: string },\n availableColors: string[],\n) {\n const map = new Map<string, { backgroundColor: string; color: string }>();\n let i = 0;\n\n return function getOutputColor(id: string) {\n let backgroundColor: string;\n if (id in colorMap) {\n backgroundColor = colorMap[id];\n } else {\n const cached = map.get(id);\n if (cached) {\n return cached;\n }\n backgroundColor = availableColors[i];\n i += 1;\n if (i >= availableColors.length) {\n i = 0;\n }\n }\n const result = {\n backgroundColor,\n color: getContrastColor(backgroundColor),\n };\n map.set(id, result);\n return result;\n };\n}\n\nconst getOutputColor = createOutputColorGenerator(\n {\n [coreExtensionData.reactElement.id]: '#4caf50',\n [coreExtensionData.routePath.id]: '#ffeb3b',\n [coreExtensionData.routeRef.id]: '#9c27b0',\n [ApiBlueprint.dataRefs.factory.id]: '#2196f3',\n [NavItemBlueprint.dataRefs.target.id]: '#ff9800',\n },\n\n ['#90caf9', '#ffcc80', '#a5d6a7', '#ef9a9a', '#fff59d', '#ce93d8', '#e6ee9c'],\n);\n\n// Helper function to get border color based on depth\nfunction getBorderColor(depth: number): string {\n const greyLevels = [8, 7, 6, 5]; // darker levels that contrast well with background\n const index = depth % greyLevels.length;\n const level = greyLevels[index];\n return `var(--bui-gray-${level})`;\n}\n\nfunction getFullPath(node?: AppNode): string {\n if (!node) {\n return '';\n }\n const parent = node.edges.attachedTo?.node;\n const part = node.instance?.getData(coreExtensionData.routePath);\n if (!part) {\n return getFullPath(parent);\n }\n return getFullPath(parent) + part;\n}\n\nfunction ProgressiveCollection({\n items,\n batchSize = 10,\n}: {\n items: Array<() => React.ReactElement>;\n batchSize?: number;\n}) {\n const [maxIndex, setMaxIndex] = useState(0);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n if (maxIndex >= items.length) {\n return undefined;\n }\n\n const processBatch = () => {\n const nextIndex = Math.min(maxIndex + batchSize, items.length);\n setMaxIndex(nextIndex);\n\n if (nextIndex < items.length) {\n timeoutRef.current = setTimeout(processBatch, 0);\n }\n };\n\n timeoutRef.current = setTimeout(processBatch, 0);\n\n // eslint-disable-next-line consistent-return\n return () => {\n if (timeoutRef.current !== null) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n };\n }, [maxIndex, items.length, batchSize]);\n\n return (\n <>\n {items.slice(0, maxIndex).map((item, index) => (\n <Fragment key={index}>{item()}</Fragment>\n ))}\n </>\n );\n}\n\nconst Output = memo(\n function Output(props: {\n dataRef: ExtensionDataRef<unknown>;\n node?: AppNode;\n }) {\n const { dataRef, node } = props;\n const { id } = dataRef;\n const instance = node?.instance;\n\n const routeResolutionApi = useApi(routeResolutionApiRef);\n\n const { backgroundColor, color } = getOutputColor(id);\n\n const chipStyle: React.CSSProperties = {\n height: 20,\n padding: '0 10px',\n borderRadius: '10px',\n color,\n backgroundColor,\n display: 'flex',\n alignItems: 'center',\n fontWeight:\n 'var(--bui-font-weight-regular)' as React.CSSProperties['fontWeight'],\n };\n\n if (id === coreExtensionData.routeRef.id && node) {\n try {\n const routeRef = props.node?.instance?.getData(\n coreExtensionData.routeRef,\n );\n const link = routeRef && routeResolutionApi.resolve(routeRef)?.();\n if (link) {\n return (\n <TooltipTrigger>\n <Link href={link} style={chipStyle}>\n link\n </Link>\n <Tooltip>{id}</Tooltip>\n </TooltipTrigger>\n );\n }\n } catch {\n /* ignore */\n }\n }\n\n let tooltip = id;\n let text: string | undefined = undefined;\n if (id === coreExtensionData.routePath.id) {\n text = String(instance?.getData(dataRef) ?? '');\n tooltip = getFullPath(node);\n }\n\n return (\n <TooltipTrigger>\n <Focusable>\n <Text style={{ ...chipStyle, cursor: 'help' }}>{text}</Text>\n </Focusable>\n <Tooltip style={{ maxWidth: 'unset' }}>{tooltip}</Tooltip>\n </TooltipTrigger>\n );\n },\n (prevProps, nextProps) => {\n return (\n prevProps.dataRef.id === nextProps.dataRef.id &&\n prevProps.node?.spec.id === nextProps.node?.spec.id\n );\n },\n);\n\nfunction Extension({ node, depth }: { node: AppNode; depth: number }) {\n const enabled = Boolean(node.instance);\n\n const tooltipText = useMemo(() => {\n const tooltipParts = [];\n let currentNode = node;\n tooltipParts.push(currentNode.spec.id);\n while (currentNode.edges.attachedTo) {\n const input = currentNode.edges.attachedTo.input;\n currentNode = currentNode.edges.attachedTo.node;\n tooltipParts.push(`${currentNode.spec.id} [${input}]`);\n }\n tooltipParts.reverse();\n return tooltipParts.join('\\n');\n }, [node]);\n\n const sortedDataRefs = useMemo(() => {\n if (!node.instance) {\n return [];\n }\n const dataRefs = [...node.instance.getDataRefs()];\n return dataRefs.sort((a, b) => a.id.localeCompare(b.id));\n }, [node.instance]);\n\n const sortedAttachments = useMemo(() => {\n return [...node.edges.attachments.entries()].sort(([a], [b]) =>\n a.localeCompare(b),\n );\n }, [node.edges.attachments]);\n\n return (\n <Box\n key={node.spec.id}\n style={{\n borderLeftWidth: 'var(--bui-space-1_5)',\n borderLeftStyle: 'solid',\n borderLeftColor: getBorderColor(depth),\n }}\n >\n <Flex\n py=\"1\"\n px=\"2\"\n align=\"center\"\n style={{\n width: 'fit-content',\n color: enabled ? 'var(--bui-fg-primary)' : 'var(--bui-fg-disabled)',\n background: 'var(--bui-bg-neutral-1)',\n borderTopRightRadius: 'var(--bui-radius-2)',\n borderBottomRightRadius: 'var(--bui-radius-2)',\n }}\n >\n <TooltipTrigger>\n <Focusable>\n <Text style={{ userSelect: 'all' }}>{node.spec.id}</Text>\n </Focusable>\n <Tooltip style={{ maxWidth: 'unset' }}>\n <Text style={{ whiteSpace: 'pre-wrap' }}>{tooltipText}</Text>\n </Tooltip>\n </TooltipTrigger>\n <Flex ml=\"2\" align=\"center\" gap=\"2\">\n {sortedDataRefs.length > 0 &&\n sortedDataRefs.map(ref => (\n <Output key={ref.id} dataRef={ref} node={node} />\n ))}\n {!enabled && <DisabledIcon size={16} />}\n </Flex>\n </Flex>\n {sortedAttachments.length > 0 && (\n <Flex direction=\"column\" gap=\"4\">\n {sortedAttachments.map(([key, children], idx) => (\n <Box key={key}>\n <Flex\n p=\"2\"\n align=\"center\"\n style={{\n borderTopWidth: 'var(--bui-space-1_5)',\n borderTopStyle: 'solid',\n borderTopColor: getBorderColor(depth),\n borderTop: idx === 0 ? 'none' : undefined,\n width: 'fit-content',\n }}\n >\n <InputIcon size={16} />\n <div style={{ marginLeft: 'var(--bui-space-2)' }}>{key}</div>\n </Flex>\n <Flex ml=\"2\" mb=\"2\" direction=\"column\" align=\"start\" gap=\"1\">\n <ProgressiveCollection\n items={children.map(childNode => () => (\n <Extension\n key={childNode.spec.id}\n node={childNode}\n depth={depth + 1}\n />\n ))}\n />\n </Flex>\n </Box>\n ))}\n </Flex>\n )}\n </Box>\n );\n}\n\nconst legendMap = {\n 'React Element': coreExtensionData.reactElement,\n 'Utility API': ApiBlueprint.dataRefs.factory,\n 'Route Path': coreExtensionData.routePath,\n 'Route Ref': coreExtensionData.routeRef,\n 'Nav Target': NavItemBlueprint.dataRefs.target,\n};\n\nfunction Legend() {\n return (\n <Box\n p=\"2\"\n style={{\n display: 'grid',\n maxWidth: 600,\n grid: 'auto-flow / repeat(3, 1fr)',\n gap: 'var(--bui-space-4)',\n }}\n >\n {Object.entries(legendMap).map(([label, dataRef]) => (\n <Flex key={dataRef.id} gap=\"2\" align=\"center\">\n <Output dataRef={dataRef} />\n <div>{label}</div>\n </Flex>\n ))}\n </Box>\n );\n}\n\nexport function DetailedVisualizer({ tree }: { tree: AppTree }) {\n return (\n <Flex direction=\"column\" style={{ height: '100%', flex: '1 1 100%' }}>\n <Box ml=\"4\" mt=\"4\" style={{ flex: '1 1 0', overflow: 'auto' }}>\n <Extension node={tree.root} depth={0} />\n </Box>\n\n <Box\n m=\"2\"\n style={{\n flex: '0 0 auto',\n background: 'var(--bui-bg-neutral-1)',\n border: '1px solid var(--bui-border)',\n borderRadius: 'var(--bui-radius-2)',\n }}\n >\n <Legend />\n </Box>\n </Flex>\n );\n}\n"],"names":["getOutputColor","Fragment","Output","DisabledIcon","InputIcon"],"mappings":"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"}
1
+ {"version":3,"file":"DetailedVisualizer.esm.js","sources":["../../../src/components/AppVisualizerPage/DetailedVisualizer.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AppNode,\n ExtensionDataRef,\n coreExtensionData,\n ApiBlueprint,\n NavItemBlueprint,\n useApi,\n routeResolutionApiRef,\n appTreeApiRef,\n} from '@backstage/frontend-plugin-api';\nimport {\n Box,\n Flex,\n FullPage,\n Link,\n Text,\n Tooltip,\n TooltipTrigger,\n} from '@backstage/ui';\nimport {\n RiInputField as InputIcon,\n RiCloseCircleLine as DisabledIcon,\n} from '@remixicon/react';\nimport { Focusable } from 'react-aria-components';\nimport { memo, useMemo, useState, useEffect, useRef, Fragment } from 'react';\n\nfunction getContrastColor(bgColor: string): string {\n const hex = bgColor.replace('#', '');\n const r = parseInt(hex.substr(0, 2), 16);\n const g = parseInt(hex.substr(2, 2), 16);\n const b = parseInt(hex.substr(4, 2), 16);\n const brightness = (r * 299 + g * 587 + b * 114) / 1000;\n return brightness > 128 ? '#000000' : '#ffffff';\n}\n\nfunction createOutputColorGenerator(\n colorMap: { [extDataId: string]: string },\n availableColors: string[],\n) {\n const map = new Map<string, { backgroundColor: string; color: string }>();\n let i = 0;\n\n return function getOutputColor(id: string) {\n let backgroundColor: string;\n if (id in colorMap) {\n backgroundColor = colorMap[id];\n } else {\n const cached = map.get(id);\n if (cached) {\n return cached;\n }\n backgroundColor = availableColors[i];\n i += 1;\n if (i >= availableColors.length) {\n i = 0;\n }\n }\n const result = {\n backgroundColor,\n color: getContrastColor(backgroundColor),\n };\n map.set(id, result);\n return result;\n };\n}\n\nconst getOutputColor = createOutputColorGenerator(\n {\n [coreExtensionData.reactElement.id]: '#4caf50',\n [coreExtensionData.routePath.id]: '#ffeb3b',\n [coreExtensionData.routeRef.id]: '#9c27b0',\n [ApiBlueprint.dataRefs.factory.id]: '#2196f3',\n [NavItemBlueprint.dataRefs.target.id]: '#ff9800',\n },\n\n ['#90caf9', '#ffcc80', '#a5d6a7', '#ef9a9a', '#fff59d', '#ce93d8', '#e6ee9c'],\n);\n\n// Helper function to get border color based on depth\nfunction getBorderColor(depth: number): string {\n const greyLevels = [8, 7, 6, 5]; // darker levels that contrast well with background\n const index = depth % greyLevels.length;\n const level = greyLevels[index];\n return `var(--bui-gray-${level})`;\n}\n\nfunction getFullPath(node?: AppNode): string {\n if (!node) {\n return '';\n }\n const parent = node.edges.attachedTo?.node;\n const part = node.instance?.getData(coreExtensionData.routePath);\n if (!part) {\n return getFullPath(parent);\n }\n return getFullPath(parent) + part;\n}\n\nfunction ProgressiveCollection({\n items,\n batchSize = 10,\n}: {\n items: Array<() => React.ReactElement>;\n batchSize?: number;\n}) {\n const [maxIndex, setMaxIndex] = useState(0);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n if (maxIndex >= items.length) {\n return undefined;\n }\n\n const processBatch = () => {\n const nextIndex = Math.min(maxIndex + batchSize, items.length);\n setMaxIndex(nextIndex);\n\n if (nextIndex < items.length) {\n timeoutRef.current = setTimeout(processBatch, 0);\n }\n };\n\n timeoutRef.current = setTimeout(processBatch, 0);\n\n // eslint-disable-next-line consistent-return\n return () => {\n if (timeoutRef.current !== null) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n };\n }, [maxIndex, items.length, batchSize]);\n\n return (\n <>\n {items.slice(0, maxIndex).map((item, index) => (\n <Fragment key={index}>{item()}</Fragment>\n ))}\n </>\n );\n}\n\nconst Output = memo(\n function Output(props: {\n dataRef: ExtensionDataRef<unknown>;\n node?: AppNode;\n }) {\n const { dataRef, node } = props;\n const { id } = dataRef;\n const instance = node?.instance;\n\n const routeResolutionApi = useApi(routeResolutionApiRef);\n\n const { backgroundColor, color } = getOutputColor(id);\n\n const chipStyle: React.CSSProperties = {\n height: 20,\n padding: '0 10px',\n borderRadius: '10px',\n color,\n backgroundColor,\n display: 'flex',\n alignItems: 'center',\n fontWeight:\n 'var(--bui-font-weight-regular)' as React.CSSProperties['fontWeight'],\n };\n\n if (id === coreExtensionData.routeRef.id && node) {\n try {\n const routeRef = props.node?.instance?.getData(\n coreExtensionData.routeRef,\n );\n const link = routeRef && routeResolutionApi.resolve(routeRef)?.();\n if (link) {\n return (\n <TooltipTrigger>\n <Link href={link} style={chipStyle}>\n link\n </Link>\n <Tooltip>{id}</Tooltip>\n </TooltipTrigger>\n );\n }\n } catch {\n /* ignore */\n }\n }\n\n let tooltip = id;\n let text: string | undefined = undefined;\n if (id === coreExtensionData.routePath.id) {\n text = String(instance?.getData(dataRef) ?? '');\n tooltip = getFullPath(node);\n }\n\n return (\n <TooltipTrigger>\n <Focusable>\n <Text style={{ ...chipStyle, cursor: 'help' }}>{text}</Text>\n </Focusable>\n <Tooltip style={{ maxWidth: 'unset' }}>{tooltip}</Tooltip>\n </TooltipTrigger>\n );\n },\n (prevProps, nextProps) => {\n return (\n prevProps.dataRef.id === nextProps.dataRef.id &&\n prevProps.node?.spec.id === nextProps.node?.spec.id\n );\n },\n);\n\nfunction Extension({ node, depth }: { node: AppNode; depth: number }) {\n const enabled = Boolean(node.instance);\n\n const tooltipText = useMemo(() => {\n const tooltipParts = [];\n let currentNode = node;\n tooltipParts.push(currentNode.spec.id);\n while (currentNode.edges.attachedTo) {\n const input = currentNode.edges.attachedTo.input;\n currentNode = currentNode.edges.attachedTo.node;\n tooltipParts.push(`${currentNode.spec.id} [${input}]`);\n }\n tooltipParts.reverse();\n return tooltipParts.join('\\n');\n }, [node]);\n\n const sortedDataRefs = useMemo(() => {\n if (!node.instance) {\n return [];\n }\n const dataRefs = [...node.instance.getDataRefs()];\n return dataRefs.sort((a, b) => a.id.localeCompare(b.id));\n }, [node.instance]);\n\n const sortedAttachments = useMemo(() => {\n return [...node.edges.attachments.entries()].sort(([a], [b]) =>\n a.localeCompare(b),\n );\n }, [node.edges.attachments]);\n\n return (\n <Box\n key={node.spec.id}\n style={{\n borderLeftWidth: 'var(--bui-space-1_5)',\n borderLeftStyle: 'solid',\n borderLeftColor: getBorderColor(depth),\n }}\n >\n <Flex\n py=\"1\"\n px=\"2\"\n align=\"center\"\n style={{\n width: 'fit-content',\n color: enabled ? 'var(--bui-fg-primary)' : 'var(--bui-fg-disabled)',\n background: 'var(--bui-bg-neutral-1)',\n borderTopRightRadius: 'var(--bui-radius-2)',\n borderBottomRightRadius: 'var(--bui-radius-2)',\n }}\n >\n <TooltipTrigger>\n <Focusable>\n <Text style={{ userSelect: 'all' }}>{node.spec.id}</Text>\n </Focusable>\n <Tooltip style={{ maxWidth: 'unset' }}>\n <Text style={{ whiteSpace: 'pre-wrap' }}>{tooltipText}</Text>\n </Tooltip>\n </TooltipTrigger>\n <Flex ml=\"2\" align=\"center\" gap=\"2\">\n {sortedDataRefs.length > 0 &&\n sortedDataRefs.map(ref => (\n <Output key={ref.id} dataRef={ref} node={node} />\n ))}\n {!enabled && <DisabledIcon size={16} />}\n </Flex>\n </Flex>\n {sortedAttachments.length > 0 && (\n <Flex direction=\"column\" gap=\"4\">\n {sortedAttachments.map(([key, children], idx) => (\n <Box key={key}>\n <Flex\n p=\"2\"\n align=\"center\"\n style={{\n borderTopWidth: 'var(--bui-space-1_5)',\n borderTopStyle: 'solid',\n borderTopColor: getBorderColor(depth),\n borderTop: idx === 0 ? 'none' : undefined,\n width: 'fit-content',\n }}\n >\n <InputIcon size={16} />\n <div style={{ marginLeft: 'var(--bui-space-2)' }}>{key}</div>\n </Flex>\n <Flex ml=\"2\" mb=\"2\" direction=\"column\" align=\"start\" gap=\"1\">\n <ProgressiveCollection\n items={children.map(childNode => () => (\n <Extension\n key={childNode.spec.id}\n node={childNode}\n depth={depth + 1}\n />\n ))}\n />\n </Flex>\n </Box>\n ))}\n </Flex>\n )}\n </Box>\n );\n}\n\nconst legendMap = {\n 'React Element': coreExtensionData.reactElement,\n 'Utility API': ApiBlueprint.dataRefs.factory,\n 'Route Path': coreExtensionData.routePath,\n 'Route Ref': coreExtensionData.routeRef,\n 'Nav Target': NavItemBlueprint.dataRefs.target,\n};\n\nfunction Legend() {\n return (\n <Box\n p=\"2\"\n style={{\n display: 'grid',\n maxWidth: 600,\n grid: 'auto-flow / repeat(3, 1fr)',\n gap: 'var(--bui-space-4)',\n }}\n >\n {Object.entries(legendMap).map(([label, dataRef]) => (\n <Flex key={dataRef.id} gap=\"2\" align=\"center\">\n <Output dataRef={dataRef} />\n <div>{label}</div>\n </Flex>\n ))}\n </Box>\n );\n}\n\nexport function DetailedVisualizer() {\n const appTreeApi = useApi(appTreeApiRef);\n const { tree } = appTreeApi.getTree();\n\n return (\n <FullPage>\n <Flex direction=\"column\" style={{ height: '100%', flex: '1 1 100%' }}>\n <Box ml=\"4\" mt=\"4\" style={{ flex: '1 1 0', overflow: 'auto' }}>\n <Extension node={tree.root} depth={0} />\n </Box>\n\n <Box\n m=\"2\"\n style={{\n flex: '0 0 auto',\n background: 'var(--bui-bg-neutral-1)',\n border: '1px solid var(--bui-border-2)',\n borderRadius: 'var(--bui-radius-2)',\n }}\n >\n <Legend />\n </Box>\n </Flex>\n </FullPage>\n );\n}\n"],"names":["getOutputColor","Fragment","Output","DisabledIcon","InputIcon"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
+ import { useApi, appTreeApiRef } from '@backstage/frontend-plugin-api';
2
3
  import { Box, Checkbox } from '@backstage/ui';
3
4
  import { useState } from 'react';
4
5
 
@@ -43,7 +44,9 @@ function nodeToText(node, options) {
43
44
  mkDiv(`</${node.spec.id}>`, { key: "end", color })
44
45
  ]);
45
46
  }
46
- function TextVisualizer({ tree }) {
47
+ function TextVisualizer() {
48
+ const appTreeApi = useApi(appTreeApiRef);
49
+ const { tree } = appTreeApi.getTree();
47
50
  const [showOutputs, setShowOutputs] = useState(false);
48
51
  const [showDisabled, setShowDisabled] = useState(false);
49
52
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -55,7 +58,7 @@ function TextVisualizer({ tree }) {
55
58
  px: "4",
56
59
  style: {
57
60
  background: "var(--bui-bg-neutral-1)",
58
- borderTop: "1px solid var(--bui-border)"
61
+ borderTop: "1px solid var(--bui-border-2)"
59
62
  },
60
63
  children: [
61
64
  /* @__PURE__ */ jsx(Checkbox, { isSelected: showOutputs, onChange: setShowOutputs, children: "Show Outputs" }),
@@ -1 +1 @@
1
- {"version":3,"file":"TextVisualizer.esm.js","sources":["../../../src/components/AppVisualizerPage/TextVisualizer.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppNode, AppTree } from '@backstage/frontend-plugin-api';\nimport { Box, Checkbox } from '@backstage/ui';\nimport { ReactNode, useState } from 'react';\n\nfunction mkDiv(\n children: ReactNode,\n options?: { indent?: boolean; key?: string | number; color?: string },\n) {\n return (\n <div\n key={options?.key}\n style={{\n color: options?.color,\n marginLeft: options?.indent ? 'var(--bui-space-4)' : undefined,\n }}\n >\n {children}\n </div>\n );\n}\n\nfunction nodeToText(\n node: AppNode,\n options?: { showOutputs?: boolean; showDisabled?: boolean },\n): ReactNode {\n const dataRefIds =\n node.instance && [...node.instance.getDataRefs()].map(r => r.id);\n const out =\n options?.showOutputs && dataRefIds && dataRefIds.length > 0\n ? ` out=\"${[...dataRefIds].sort().join(', ')}\"`\n : '';\n const color = node.instance ? undefined : 'gray';\n\n if (node.edges.attachments.size === 0) {\n return mkDiv(`<${node.spec.id}${out}/>`, { color });\n }\n\n return mkDiv([\n mkDiv(`<${node.spec.id}${out}>`, { key: 'start', color }),\n ...[...node.edges.attachments.entries()]\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([key, v]) => {\n const children = v\n .filter(e => options?.showDisabled || e.instance)\n .sort((a, b) => a.spec.id.localeCompare(b.spec.id));\n if (children.length === 0) {\n return mkDiv(`${key} []`, { key, indent: true });\n }\n return mkDiv(\n [\n mkDiv(`${key} [`, { key: 'start' }),\n ...children.map(e =>\n mkDiv(nodeToText(e, options), { indent: true, key: e.spec.id }),\n ),\n mkDiv(']', { key: 'end' }),\n ],\n { key, indent: true },\n );\n }),\n mkDiv(`</${node.spec.id}>`, { key: 'end', color }),\n ]);\n}\n\nexport function TextVisualizer({ tree }: { tree: AppTree }) {\n const [showOutputs, setShowOutputs] = useState(false);\n const [showDisabled, setShowDisabled] = useState(false);\n\n return (\n <>\n <Box style={{ overflow: 'auto', flex: '1 0 0' }}>\n <Box m=\"4\" style={{ width: 'max-content' }}>\n {nodeToText(tree.root, { showOutputs, showDisabled })}\n </Box>\n </Box>\n <Box\n py=\"2\"\n px=\"4\"\n style={{\n background: 'var(--bui-bg-neutral-1)',\n borderTop: '1px solid var(--bui-border)',\n }}\n >\n <Checkbox isSelected={showOutputs} onChange={setShowOutputs}>\n Show Outputs\n </Checkbox>\n <Checkbox isSelected={showDisabled} onChange={setShowDisabled}>\n Show Disabled\n </Checkbox>\n </Box>\n </>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TextVisualizer.esm.js","sources":["../../../src/components/AppVisualizerPage/TextVisualizer.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppNode, useApi, appTreeApiRef } from '@backstage/frontend-plugin-api';\nimport { Box, Checkbox } from '@backstage/ui';\nimport { ReactNode, useState } from 'react';\n\nfunction mkDiv(\n children: ReactNode,\n options?: { indent?: boolean; key?: string | number; color?: string },\n) {\n return (\n <div\n key={options?.key}\n style={{\n color: options?.color,\n marginLeft: options?.indent ? 'var(--bui-space-4)' : undefined,\n }}\n >\n {children}\n </div>\n );\n}\n\nfunction nodeToText(\n node: AppNode,\n options?: { showOutputs?: boolean; showDisabled?: boolean },\n): ReactNode {\n const dataRefIds =\n node.instance && [...node.instance.getDataRefs()].map(r => r.id);\n const out =\n options?.showOutputs && dataRefIds && dataRefIds.length > 0\n ? ` out=\"${[...dataRefIds].sort().join(', ')}\"`\n : '';\n const color = node.instance ? undefined : 'gray';\n\n if (node.edges.attachments.size === 0) {\n return mkDiv(`<${node.spec.id}${out}/>`, { color });\n }\n\n return mkDiv([\n mkDiv(`<${node.spec.id}${out}>`, { key: 'start', color }),\n ...[...node.edges.attachments.entries()]\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([key, v]) => {\n const children = v\n .filter(e => options?.showDisabled || e.instance)\n .sort((a, b) => a.spec.id.localeCompare(b.spec.id));\n if (children.length === 0) {\n return mkDiv(`${key} []`, { key, indent: true });\n }\n return mkDiv(\n [\n mkDiv(`${key} [`, { key: 'start' }),\n ...children.map(e =>\n mkDiv(nodeToText(e, options), { indent: true, key: e.spec.id }),\n ),\n mkDiv(']', { key: 'end' }),\n ],\n { key, indent: true },\n );\n }),\n mkDiv(`</${node.spec.id}>`, { key: 'end', color }),\n ]);\n}\n\nexport function TextVisualizer() {\n const appTreeApi = useApi(appTreeApiRef);\n const { tree } = appTreeApi.getTree();\n const [showOutputs, setShowOutputs] = useState(false);\n const [showDisabled, setShowDisabled] = useState(false);\n\n return (\n <>\n <Box style={{ overflow: 'auto', flex: '1 0 0' }}>\n <Box m=\"4\" style={{ width: 'max-content' }}>\n {nodeToText(tree.root, { showOutputs, showDisabled })}\n </Box>\n </Box>\n <Box\n py=\"2\"\n px=\"4\"\n style={{\n background: 'var(--bui-bg-neutral-1)',\n borderTop: '1px solid var(--bui-border-2)',\n }}\n >\n <Checkbox isSelected={showOutputs} onChange={setShowOutputs}>\n Show Outputs\n </Checkbox>\n <Checkbox isSelected={showDisabled} onChange={setShowDisabled}>\n Show Disabled\n </Checkbox>\n </Box>\n </>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
2
  import { DependencyGraph, DependencyGraphTypes } from '@backstage/core-components';
3
- import { Flex } from '@backstage/ui';
3
+ import { useApi, appTreeApiRef } from '@backstage/frontend-plugin-api';
4
+ import { FullPage, Flex } from '@backstage/ui';
4
5
  import { useMemo, useState, useRef, useLayoutEffect } from 'react';
5
6
 
6
7
  function inputId({ node, input }) {
@@ -96,32 +97,23 @@ function Node(props) {
96
97
  )
97
98
  ] });
98
99
  }
99
- function TreeVisualizer({ tree }) {
100
+ function TreeVisualizer() {
101
+ const appTreeApi = useApi(appTreeApiRef);
102
+ const { tree } = appTreeApi.getTree();
100
103
  const graphData = useMemo(() => resolveGraphData(tree), [tree]);
101
- return /* @__PURE__ */ jsx(
102
- Flex,
104
+ return /* @__PURE__ */ jsx(FullPage, { children: /* @__PURE__ */ jsx(Flex, { style: { height: "100%" }, children: /* @__PURE__ */ jsx(
105
+ DependencyGraph,
103
106
  {
104
- style: {
105
- flex: "1 1 0",
106
- overflow: "hidden",
107
- justifyContent: "stretch",
108
- alignItems: "stretch"
109
- },
110
- children: /* @__PURE__ */ jsx(
111
- DependencyGraph,
112
- {
113
- fit: "contain",
114
- ...graphData,
115
- nodeMargin: 10,
116
- rankMargin: 50,
117
- paddingX: 50,
118
- renderNode: Node,
119
- ranker: DependencyGraphTypes.Ranker.TIGHT_TREE,
120
- direction: DependencyGraphTypes.Direction.LEFT_RIGHT
121
- }
122
- )
107
+ fit: "contain",
108
+ ...graphData,
109
+ nodeMargin: 10,
110
+ rankMargin: 50,
111
+ paddingX: 50,
112
+ renderNode: Node,
113
+ ranker: DependencyGraphTypes.Ranker.TIGHT_TREE,
114
+ direction: DependencyGraphTypes.Direction.LEFT_RIGHT
123
115
  }
124
- );
116
+ ) }) });
125
117
  }
126
118
 
127
119
  export { Node, TreeVisualizer };
@@ -1 +1 @@
1
- {"version":3,"file":"TreeVisualizer.esm.js","sources":["../../../src/components/AppVisualizerPage/TreeVisualizer.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n DependencyGraph,\n DependencyGraphTypes,\n} from '@backstage/core-components';\nimport { AppNode, AppTree } from '@backstage/frontend-plugin-api';\nimport { Flex } from '@backstage/ui';\nimport { useLayoutEffect, useMemo, useRef, useState } from 'react';\n\ntype NodeType =\n | ({ type: 'node'; id: string } & AppNode)\n | { type: 'input'; id: string; name: string };\n\nfunction inputId({ node, input }: { node: AppNode; input: string }) {\n return `${node.spec.id}$$${input}`;\n}\n\nfunction trimNodeId(id: string) {\n let newId = id;\n if (newId.startsWith('apis.')) {\n newId = newId.slice('apis.'.length);\n }\n if (newId.startsWith('plugin.')) {\n newId = newId.slice('plugin.'.length);\n }\n if (newId.startsWith('catalog.filter.entity.')) {\n newId = newId.slice('catalog.filter.entity.'.length);\n }\n if (newId.endsWith('.nav.index')) {\n newId = newId.slice(0, -'.nav.index'.length);\n }\n return newId;\n}\n\nfunction resolveGraphData(tree: AppTree): {\n nodes: NodeType[];\n edges: { from: string; to: string }[];\n} {\n const nodes = [...tree.nodes.values()]\n .filter(node => node.instance)\n .map(node => ({ ...node, id: node.spec.id, type: 'node' as const }));\n\n return {\n nodes: [\n ...nodes,\n ...nodes.flatMap(node =>\n [...node.edges.attachments.keys()].map(input => ({\n id: inputId({ node, input }),\n type: 'input' as const,\n name: input,\n })),\n ),\n ],\n edges: [\n ...nodes\n .filter(node => node.edges.attachedTo)\n .map(node => ({\n from: inputId(node.edges.attachedTo!),\n to: node.spec.id,\n })),\n ...nodes.flatMap(node =>\n [...node.edges.attachments.keys()].map(input => ({\n from: node.spec.id,\n to: inputId({ node, input }),\n })),\n ),\n ],\n };\n}\n\n/** @public */\nexport function Node(props: { node: NodeType }) {\n const { node } = props;\n const [width, setWidth] = useState(0);\n const [height, setHeight] = useState(0);\n const idRef = useRef<SVGTextElement | null>(null);\n\n useLayoutEffect(() => {\n // set the width to the length of the ID\n if (idRef.current) {\n let { height: renderedHeight, width: renderedWidth } =\n idRef.current.getBBox();\n renderedHeight = Math.round(renderedHeight);\n renderedWidth = Math.round(renderedWidth);\n\n if (renderedHeight !== height || renderedWidth !== width) {\n setWidth(renderedWidth);\n setHeight(renderedHeight);\n }\n }\n }, [width, height]);\n\n const padding = 10;\n const paddedWidth = width + padding * 2;\n const paddedHeight = height + padding * 2;\n\n // Simple inline styles for SVG elements\n const nodeFill = node.type === 'node' ? '#90caf9' : '#9e9e9e';\n const nodeStroke = node.type === 'node' ? '#2196f3' : '#757575';\n const textFill = '#000000';\n\n return (\n <g>\n <rect\n fill={nodeFill}\n stroke={nodeStroke}\n width={paddedWidth}\n height={paddedHeight}\n rx={node.type === 'node' ? 0 : 20}\n />\n <text\n ref={idRef}\n fill={textFill}\n y={paddedHeight / 2}\n x={paddedWidth / 2}\n textAnchor=\"middle\"\n alignmentBaseline=\"middle\"\n >\n {node.type === 'node' ? trimNodeId(node.id) : node.name}\n </text>\n </g>\n );\n}\n\nexport function TreeVisualizer({ tree }: { tree: AppTree }) {\n const graphData = useMemo(() => resolveGraphData(tree), [tree]);\n\n return (\n <Flex\n style={{\n flex: '1 1 0',\n overflow: 'hidden',\n justifyContent: 'stretch',\n alignItems: 'stretch',\n }}\n >\n <DependencyGraph\n fit=\"contain\"\n {...graphData}\n nodeMargin={10}\n rankMargin={50}\n paddingX={50}\n renderNode={Node}\n ranker={DependencyGraphTypes.Ranker.TIGHT_TREE}\n direction={DependencyGraphTypes.Direction.LEFT_RIGHT}\n />\n </Flex>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TreeVisualizer.esm.js","sources":["../../../src/components/AppVisualizerPage/TreeVisualizer.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n DependencyGraph,\n DependencyGraphTypes,\n} from '@backstage/core-components';\nimport {\n AppNode,\n AppTree,\n useApi,\n appTreeApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { Flex, FullPage } from '@backstage/ui';\nimport { useLayoutEffect, useMemo, useRef, useState } from 'react';\n\ntype NodeType =\n | ({ type: 'node'; id: string } & AppNode)\n | { type: 'input'; id: string; name: string };\n\nfunction inputId({ node, input }: { node: AppNode; input: string }) {\n return `${node.spec.id}$$${input}`;\n}\n\nfunction trimNodeId(id: string) {\n let newId = id;\n if (newId.startsWith('apis.')) {\n newId = newId.slice('apis.'.length);\n }\n if (newId.startsWith('plugin.')) {\n newId = newId.slice('plugin.'.length);\n }\n if (newId.startsWith('catalog.filter.entity.')) {\n newId = newId.slice('catalog.filter.entity.'.length);\n }\n if (newId.endsWith('.nav.index')) {\n newId = newId.slice(0, -'.nav.index'.length);\n }\n return newId;\n}\n\nfunction resolveGraphData(tree: AppTree): {\n nodes: NodeType[];\n edges: { from: string; to: string }[];\n} {\n const nodes = [...tree.nodes.values()]\n .filter(node => node.instance)\n .map(node => ({ ...node, id: node.spec.id, type: 'node' as const }));\n\n return {\n nodes: [\n ...nodes,\n ...nodes.flatMap(node =>\n [...node.edges.attachments.keys()].map(input => ({\n id: inputId({ node, input }),\n type: 'input' as const,\n name: input,\n })),\n ),\n ],\n edges: [\n ...nodes\n .filter(node => node.edges.attachedTo)\n .map(node => ({\n from: inputId(node.edges.attachedTo!),\n to: node.spec.id,\n })),\n ...nodes.flatMap(node =>\n [...node.edges.attachments.keys()].map(input => ({\n from: node.spec.id,\n to: inputId({ node, input }),\n })),\n ),\n ],\n };\n}\n\n/** @public */\nexport function Node(props: { node: NodeType }) {\n const { node } = props;\n const [width, setWidth] = useState(0);\n const [height, setHeight] = useState(0);\n const idRef = useRef<SVGTextElement | null>(null);\n\n useLayoutEffect(() => {\n // set the width to the length of the ID\n if (idRef.current) {\n let { height: renderedHeight, width: renderedWidth } =\n idRef.current.getBBox();\n renderedHeight = Math.round(renderedHeight);\n renderedWidth = Math.round(renderedWidth);\n\n if (renderedHeight !== height || renderedWidth !== width) {\n setWidth(renderedWidth);\n setHeight(renderedHeight);\n }\n }\n }, [width, height]);\n\n const padding = 10;\n const paddedWidth = width + padding * 2;\n const paddedHeight = height + padding * 2;\n\n // Simple inline styles for SVG elements\n const nodeFill = node.type === 'node' ? '#90caf9' : '#9e9e9e';\n const nodeStroke = node.type === 'node' ? '#2196f3' : '#757575';\n const textFill = '#000000';\n\n return (\n <g>\n <rect\n fill={nodeFill}\n stroke={nodeStroke}\n width={paddedWidth}\n height={paddedHeight}\n rx={node.type === 'node' ? 0 : 20}\n />\n <text\n ref={idRef}\n fill={textFill}\n y={paddedHeight / 2}\n x={paddedWidth / 2}\n textAnchor=\"middle\"\n alignmentBaseline=\"middle\"\n >\n {node.type === 'node' ? trimNodeId(node.id) : node.name}\n </text>\n </g>\n );\n}\n\nexport function TreeVisualizer() {\n const appTreeApi = useApi(appTreeApiRef);\n const { tree } = appTreeApi.getTree();\n const graphData = useMemo(() => resolveGraphData(tree), [tree]);\n\n return (\n <FullPage>\n <Flex style={{ height: '100%' }}>\n <DependencyGraph\n fit=\"contain\"\n {...graphData}\n nodeMargin={10}\n rankMargin={50}\n paddingX={50}\n renderNode={Node}\n ranker={DependencyGraphTypes.Ranker.TIGHT_TREE}\n direction={DependencyGraphTypes.Direction.LEFT_RIGHT}\n />\n </Flex>\n </FullPage>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { useState } from 'react';
3
+ import { useApi, appTreeApiRef } from '@backstage/frontend-plugin-api';
4
+ import { Button } from '@backstage/ui';
5
+ import { RiCheckLine, RiFileCopyLine } from '@remixicon/react';
6
+
7
+ function nodeToJson(node) {
8
+ const attachments = {};
9
+ for (const [input, children] of node.edges.attachments) {
10
+ attachments[input] = children.map(nodeToJson);
11
+ }
12
+ return {
13
+ id: node.spec.id,
14
+ plugin: node.spec.plugin.pluginId,
15
+ disabled: node.spec.disabled || void 0,
16
+ ...Object.keys(attachments).length > 0 ? { attachments } : {}
17
+ };
18
+ }
19
+ function CopyTreeButton() {
20
+ const appTreeApi = useApi(appTreeApiRef);
21
+ const [copied, setCopied] = useState(false);
22
+ const handlePress = () => {
23
+ const { tree } = appTreeApi.getTree();
24
+ const json = JSON.stringify(nodeToJson(tree.root), null, 2);
25
+ window.navigator.clipboard.writeText(json).then(() => {
26
+ setCopied(true);
27
+ setTimeout(() => setCopied(false), 2e3);
28
+ });
29
+ };
30
+ return /* @__PURE__ */ jsx(
31
+ Button,
32
+ {
33
+ variant: "secondary",
34
+ size: "small",
35
+ iconStart: copied ? /* @__PURE__ */ jsx(RiCheckLine, {}) : /* @__PURE__ */ jsx(RiFileCopyLine, {}),
36
+ onPress: handlePress,
37
+ children: copied ? "Copied!" : "Copy as JSON"
38
+ }
39
+ );
40
+ }
41
+
42
+ export { CopyTreeButton };
43
+ //# sourceMappingURL=CopyTreeButton.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CopyTreeButton.esm.js","sources":["../../src/components/CopyTreeButton.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useState } from 'react';\nimport {\n useApi,\n appTreeApiRef,\n type AppNode,\n} from '@backstage/frontend-plugin-api';\nimport { Button } from '@backstage/ui';\nimport { RiFileCopyLine, RiCheckLine } from '@remixicon/react';\n\nfunction nodeToJson(node: AppNode): object {\n const attachments: Record<string, object[]> = {};\n for (const [input, children] of node.edges.attachments) {\n attachments[input] = children.map(nodeToJson);\n }\n\n return {\n id: node.spec.id,\n plugin: node.spec.plugin.pluginId,\n disabled: node.spec.disabled || undefined,\n ...(Object.keys(attachments).length > 0 ? { attachments } : {}),\n };\n}\n\nexport function CopyTreeButton() {\n const appTreeApi = useApi(appTreeApiRef);\n const [copied, setCopied] = useState(false);\n\n const handlePress = () => {\n const { tree } = appTreeApi.getTree();\n const json = JSON.stringify(nodeToJson(tree.root), null, 2);\n window.navigator.clipboard.writeText(json).then(() => {\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n });\n };\n\n return (\n <Button\n variant=\"secondary\"\n size=\"small\"\n iconStart={copied ? <RiCheckLine /> : <RiFileCopyLine />}\n onPress={handlePress}\n >\n {copied ? 'Copied!' : 'Copy as JSON'}\n </Button>\n );\n}\n"],"names":[],"mappings":";;;;;;AAyBA,SAAS,WAAW,IAAA,EAAuB;AACzC,EAAA,MAAM,cAAwC,EAAC;AAC/C,EAAA,KAAA,MAAW,CAAC,KAAA,EAAO,QAAQ,CAAA,IAAK,IAAA,CAAK,MAAM,WAAA,EAAa;AACtD,IAAA,WAAA,CAAY,KAAK,CAAA,GAAI,QAAA,CAAS,GAAA,CAAI,UAAU,CAAA;AAAA,EAC9C;AAEA,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,KAAK,IAAA,CAAK,EAAA;AAAA,IACd,MAAA,EAAQ,IAAA,CAAK,IAAA,CAAK,MAAA,CAAO,QAAA;AAAA,IACzB,QAAA,EAAU,IAAA,CAAK,IAAA,CAAK,QAAA,IAAY,MAAA;AAAA,IAChC,GAAI,MAAA,CAAO,IAAA,CAAK,WAAW,CAAA,CAAE,SAAS,CAAA,GAAI,EAAE,WAAA,EAAY,GAAI;AAAC,GAC/D;AACF;AAEO,SAAS,cAAA,GAAiB;AAC/B,EAAA,MAAM,UAAA,GAAa,OAAO,aAAa,CAAA;AACvC,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAAS,KAAK,CAAA;AAE1C,EAAA,MAAM,cAAc,MAAM;AACxB,IAAA,MAAM,EAAE,IAAA,EAAK,GAAI,UAAA,CAAW,OAAA,EAAQ;AACpC,IAAA,MAAM,IAAA,GAAO,KAAK,SAAA,CAAU,UAAA,CAAW,KAAK,IAAI,CAAA,EAAG,MAAM,CAAC,CAAA;AAC1D,IAAA,MAAA,CAAO,UAAU,SAAA,CAAU,SAAA,CAAU,IAAI,CAAA,CAAE,KAAK,MAAM;AACpD,MAAA,SAAA,CAAU,IAAI,CAAA;AACd,MAAA,UAAA,CAAW,MAAM,SAAA,CAAU,KAAK,CAAA,EAAG,GAAI,CAAA;AAAA,IACzC,CAAC,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,uBACE,GAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MACC,OAAA,EAAQ,WAAA;AAAA,MACR,IAAA,EAAK,OAAA;AAAA,MACL,WAAW,MAAA,mBAAS,GAAA,CAAC,WAAA,EAAA,EAAY,CAAA,uBAAM,cAAA,EAAA,EAAe,CAAA;AAAA,MACtD,OAAA,EAAS,WAAA;AAAA,MAER,mBAAS,SAAA,GAAY;AAAA;AAAA,GACxB;AAEJ;;;;"}
package/dist/index.d.ts CHANGED
@@ -25,17 +25,126 @@ declare const visualizerPlugin: _backstage_frontend_plugin_api.OverridableFronte
25
25
  name: undefined;
26
26
  config: {
27
27
  path: string | undefined;
28
+ title: string | undefined;
28
29
  };
29
30
  configInput: {
31
+ title?: string | undefined;
30
32
  path?: string | undefined;
31
33
  };
32
- output: _backstage_frontend_plugin_api.ExtensionDataRef<string, "core.routing.path", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<react.JSX.Element, "core.reactElement", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<_backstage_frontend_plugin_api.RouteRef<_backstage_frontend_plugin_api.AnyRouteRefParams>, "core.routing.ref", {
34
+ output: _backstage_frontend_plugin_api.ExtensionDataRef<string, "core.routing.path", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<_backstage_frontend_plugin_api.RouteRef<_backstage_frontend_plugin_api.AnyRouteRefParams>, "core.routing.ref", {
35
+ optional: true;
36
+ }> | _backstage_frontend_plugin_api.ExtensionDataRef<react.JSX.Element, "core.reactElement", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<string, "core.title", {
37
+ optional: true;
38
+ }> | _backstage_frontend_plugin_api.ExtensionDataRef<_backstage_frontend_plugin_api.IconElement, "core.icon", {
33
39
  optional: true;
34
40
  }>;
35
- inputs: {};
41
+ inputs: {
42
+ pages: _backstage_frontend_plugin_api.ExtensionInput<_backstage_frontend_plugin_api.ConfigurableExtensionDataRef<react.JSX.Element, "core.reactElement", {}> | _backstage_frontend_plugin_api.ConfigurableExtensionDataRef<string, "core.routing.path", {}> | _backstage_frontend_plugin_api.ConfigurableExtensionDataRef<_backstage_frontend_plugin_api.RouteRef<_backstage_frontend_plugin_api.AnyRouteRefParams>, "core.routing.ref", {
43
+ optional: true;
44
+ }> | _backstage_frontend_plugin_api.ConfigurableExtensionDataRef<string, "core.title", {
45
+ optional: true;
46
+ }> | _backstage_frontend_plugin_api.ConfigurableExtensionDataRef<_backstage_frontend_plugin_api.IconElement, "core.icon", {
47
+ optional: true;
48
+ }>, {
49
+ singleton: false;
50
+ optional: false;
51
+ internal: false;
52
+ }>;
53
+ };
36
54
  params: {
37
55
  defaultPath?: [Error: `Use the 'path' param instead`];
38
56
  path: string;
57
+ title?: string;
58
+ icon?: _backstage_frontend_plugin_api.IconElement;
59
+ loader?: () => Promise<react.JSX.Element>;
60
+ routeRef?: _backstage_frontend_plugin_api.RouteRef;
61
+ noHeader?: boolean;
62
+ };
63
+ }>;
64
+ "plugin-header-action:app-visualizer": _backstage_frontend_plugin_api.OverridableExtensionDefinition<{
65
+ kind: "plugin-header-action";
66
+ name: undefined;
67
+ config: {};
68
+ configInput: {};
69
+ output: _backstage_frontend_plugin_api.ExtensionDataRef<react.JSX.Element, "core.reactElement", {}>;
70
+ inputs: {};
71
+ params: (params: {
72
+ loader: () => Promise<JSX.Element>;
73
+ }) => _backstage_frontend_plugin_api.ExtensionBlueprintParams<{
74
+ loader: () => Promise<JSX.Element>;
75
+ }>;
76
+ }>;
77
+ "sub-page:app-visualizer/details": _backstage_frontend_plugin_api.OverridableExtensionDefinition<{
78
+ kind: "sub-page";
79
+ name: "details";
80
+ config: {
81
+ path: string | undefined;
82
+ title: string | undefined;
83
+ };
84
+ configInput: {
85
+ title?: string | undefined;
86
+ path?: string | undefined;
87
+ };
88
+ output: _backstage_frontend_plugin_api.ExtensionDataRef<string, "core.routing.path", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<_backstage_frontend_plugin_api.RouteRef<_backstage_frontend_plugin_api.AnyRouteRefParams>, "core.routing.ref", {
89
+ optional: true;
90
+ }> | _backstage_frontend_plugin_api.ExtensionDataRef<react.JSX.Element, "core.reactElement", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<string, "core.title", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<_backstage_frontend_plugin_api.IconElement, "core.icon", {
91
+ optional: true;
92
+ }>;
93
+ inputs: {};
94
+ params: {
95
+ path: string;
96
+ title: string;
97
+ icon?: _backstage_frontend_plugin_api.IconElement;
98
+ loader: () => Promise<JSX.Element>;
99
+ routeRef?: _backstage_frontend_plugin_api.RouteRef;
100
+ };
101
+ }>;
102
+ "sub-page:app-visualizer/text": _backstage_frontend_plugin_api.OverridableExtensionDefinition<{
103
+ kind: "sub-page";
104
+ name: "text";
105
+ config: {
106
+ path: string | undefined;
107
+ title: string | undefined;
108
+ };
109
+ configInput: {
110
+ title?: string | undefined;
111
+ path?: string | undefined;
112
+ };
113
+ output: _backstage_frontend_plugin_api.ExtensionDataRef<string, "core.routing.path", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<_backstage_frontend_plugin_api.RouteRef<_backstage_frontend_plugin_api.AnyRouteRefParams>, "core.routing.ref", {
114
+ optional: true;
115
+ }> | _backstage_frontend_plugin_api.ExtensionDataRef<react.JSX.Element, "core.reactElement", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<string, "core.title", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<_backstage_frontend_plugin_api.IconElement, "core.icon", {
116
+ optional: true;
117
+ }>;
118
+ inputs: {};
119
+ params: {
120
+ path: string;
121
+ title: string;
122
+ icon?: _backstage_frontend_plugin_api.IconElement;
123
+ loader: () => Promise<JSX.Element>;
124
+ routeRef?: _backstage_frontend_plugin_api.RouteRef;
125
+ };
126
+ }>;
127
+ "sub-page:app-visualizer/tree": _backstage_frontend_plugin_api.OverridableExtensionDefinition<{
128
+ kind: "sub-page";
129
+ name: "tree";
130
+ config: {
131
+ path: string | undefined;
132
+ title: string | undefined;
133
+ };
134
+ configInput: {
135
+ title?: string | undefined;
136
+ path?: string | undefined;
137
+ };
138
+ output: _backstage_frontend_plugin_api.ExtensionDataRef<string, "core.routing.path", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<_backstage_frontend_plugin_api.RouteRef<_backstage_frontend_plugin_api.AnyRouteRefParams>, "core.routing.ref", {
139
+ optional: true;
140
+ }> | _backstage_frontend_plugin_api.ExtensionDataRef<react.JSX.Element, "core.reactElement", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<string, "core.title", {}> | _backstage_frontend_plugin_api.ExtensionDataRef<_backstage_frontend_plugin_api.IconElement, "core.icon", {
141
+ optional: true;
142
+ }>;
143
+ inputs: {};
144
+ params: {
145
+ path: string;
146
+ title: string;
147
+ icon?: _backstage_frontend_plugin_api.IconElement;
39
148
  loader: () => Promise<JSX.Element>;
40
149
  routeRef?: _backstage_frontend_plugin_api.RouteRef;
41
150
  };
@@ -1,5 +1,5 @@
1
1
  var name = "@backstage/plugin-app-visualizer";
2
- var version = "0.1.28-next.1";
2
+ var version = "0.2.0";
3
3
  var description = "Visualizes the Backstage app structure";
4
4
  var backstage = {
5
5
  role: "frontend-plugin",
@@ -1,5 +1,5 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
- import { createRouteRef, PageBlueprint, NavItemBlueprint, createFrontendPlugin } from '@backstage/frontend-plugin-api';
2
+ import { createRouteRef, PageBlueprint, SubPageBlueprint, PluginHeaderActionBlueprint, NavItemBlueprint, createFrontendPlugin } from '@backstage/frontend-plugin-api';
3
3
  import { RiEyeLine } from '@remixicon/react';
4
4
 
5
5
  const rootRouteRef = createRouteRef();
@@ -7,9 +7,44 @@ const appVisualizerPage = PageBlueprint.make({
7
7
  params: {
8
8
  path: "/visualizer",
9
9
  routeRef: rootRouteRef,
10
- loader: () => import('./components/AppVisualizerPage/index.esm.js').then((m) => /* @__PURE__ */ jsx(m.AppVisualizerPage, {}))
10
+ title: "Visualizer"
11
11
  }
12
12
  });
13
+ const treeRouteRef = createRouteRef();
14
+ const detailedRouteRef = createRouteRef();
15
+ const textRouteRef = createRouteRef();
16
+ const appVisualizerTreePage = SubPageBlueprint.make({
17
+ name: "tree",
18
+ params: {
19
+ path: "tree",
20
+ routeRef: treeRouteRef,
21
+ title: "Tree",
22
+ loader: () => import('./components/AppVisualizerPage/TreeVisualizer.esm.js').then((m) => /* @__PURE__ */ jsx(m.TreeVisualizer, {}))
23
+ }
24
+ });
25
+ const appVisualizerDetailedPage = SubPageBlueprint.make({
26
+ name: "details",
27
+ params: {
28
+ path: "details",
29
+ routeRef: detailedRouteRef,
30
+ title: "Detailed",
31
+ loader: () => import('./components/AppVisualizerPage/DetailedVisualizer.esm.js').then((m) => /* @__PURE__ */ jsx(m.DetailedVisualizer, {}))
32
+ }
33
+ });
34
+ const appVisualizerTextPage = SubPageBlueprint.make({
35
+ name: "text",
36
+ params: {
37
+ path: "text",
38
+ routeRef: textRouteRef,
39
+ title: "Text",
40
+ loader: () => import('./components/AppVisualizerPage/TextVisualizer.esm.js').then((m) => /* @__PURE__ */ jsx(m.TextVisualizer, {}))
41
+ }
42
+ });
43
+ const copyTreeAsJson = PluginHeaderActionBlueprint.make({
44
+ params: (defineParams) => defineParams({
45
+ loader: () => import('./components/CopyTreeButton.esm.js').then((m) => /* @__PURE__ */ jsx(m.CopyTreeButton, {}))
46
+ })
47
+ });
13
48
  const appVisualizerNavItem = NavItemBlueprint.make({
14
49
  params: {
15
50
  title: "Visualizer",
@@ -19,8 +54,17 @@ const appVisualizerNavItem = NavItemBlueprint.make({
19
54
  });
20
55
  const visualizerPlugin = createFrontendPlugin({
21
56
  pluginId: "app-visualizer",
57
+ title: "App Visualizer",
58
+ icon: /* @__PURE__ */ jsx(RiEyeLine, {}),
22
59
  info: { packageJson: () => import('./package.json.esm.js') },
23
- extensions: [appVisualizerPage, appVisualizerNavItem]
60
+ extensions: [
61
+ appVisualizerPage,
62
+ appVisualizerTreePage,
63
+ appVisualizerDetailedPage,
64
+ appVisualizerTextPage,
65
+ appVisualizerNavItem,
66
+ copyTreeAsJson
67
+ ]
24
68
  });
25
69
 
26
70
  export { appVisualizerNavItem, visualizerPlugin };
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.esm.js","sources":["../src/plugin.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n createFrontendPlugin,\n createRouteRef,\n NavItemBlueprint,\n PageBlueprint,\n} from '@backstage/frontend-plugin-api';\nimport { RiEyeLine as VisualizerIcon } from '@remixicon/react';\n\nconst rootRouteRef = createRouteRef();\n\nconst appVisualizerPage = PageBlueprint.make({\n params: {\n path: '/visualizer',\n routeRef: rootRouteRef,\n loader: () =>\n import('./components/AppVisualizerPage').then(m => (\n <m.AppVisualizerPage />\n )),\n },\n});\n\nexport const appVisualizerNavItem = NavItemBlueprint.make({\n params: {\n title: 'Visualizer',\n icon: () => <VisualizerIcon />,\n routeRef: rootRouteRef,\n },\n});\n\n/** @public */\nexport const visualizerPlugin = createFrontendPlugin({\n pluginId: 'app-visualizer',\n info: { packageJson: () => import('../package.json') },\n extensions: [appVisualizerPage, appVisualizerNavItem],\n});\n"],"names":["VisualizerIcon"],"mappings":";;;;AAwBA,MAAM,eAAe,cAAA,EAAe;AAEpC,MAAM,iBAAA,GAAoB,cAAc,IAAA,CAAK;AAAA,EAC3C,MAAA,EAAQ;AAAA,IACN,IAAA,EAAM,aAAA;AAAA,IACN,QAAA,EAAU,YAAA;AAAA,IACV,MAAA,EAAQ,MACN,OAAO,6CAAgC,CAAA,CAAE,IAAA,CAAK,CAAA,CAAA,qBAC5C,GAAA,CAAC,CAAA,CAAE,iBAAA,EAAF,EAAoB,CACtB;AAAA;AAEP,CAAC,CAAA;AAEM,MAAM,oBAAA,GAAuB,iBAAiB,IAAA,CAAK;AAAA,EACxD,MAAA,EAAQ;AAAA,IACN,KAAA,EAAO,YAAA;AAAA,IACP,IAAA,EAAM,sBAAM,GAAA,CAACA,SAAA,EAAA,EAAe,CAAA;AAAA,IAC5B,QAAA,EAAU;AAAA;AAEd,CAAC;AAGM,MAAM,mBAAmB,oBAAA,CAAqB;AAAA,EACnD,QAAA,EAAU,gBAAA;AAAA,EACV,MAAM,EAAE,WAAA,EAAa,MAAM,OAAO,uBAAiB,CAAA,EAAE;AAAA,EACrD,UAAA,EAAY,CAAC,iBAAA,EAAmB,oBAAoB;AACtD,CAAC;;;;"}
1
+ {"version":3,"file":"plugin.esm.js","sources":["../src/plugin.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n createFrontendPlugin,\n createRouteRef,\n NavItemBlueprint,\n PageBlueprint,\n PluginHeaderActionBlueprint,\n SubPageBlueprint,\n} from '@backstage/frontend-plugin-api';\nimport { RiEyeLine } from '@remixicon/react';\n\nconst rootRouteRef = createRouteRef();\n\nconst appVisualizerPage = PageBlueprint.make({\n params: {\n path: '/visualizer',\n routeRef: rootRouteRef,\n title: 'Visualizer',\n },\n});\n\nconst treeRouteRef = createRouteRef();\nconst detailedRouteRef = createRouteRef();\nconst textRouteRef = createRouteRef();\n\nconst appVisualizerTreePage = SubPageBlueprint.make({\n name: 'tree',\n params: {\n path: 'tree',\n routeRef: treeRouteRef,\n title: 'Tree',\n loader: () =>\n import('./components/AppVisualizerPage/TreeVisualizer').then(m => (\n <m.TreeVisualizer />\n )),\n },\n});\nconst appVisualizerDetailedPage = SubPageBlueprint.make({\n name: 'details',\n params: {\n path: 'details',\n routeRef: detailedRouteRef,\n title: 'Detailed',\n loader: () =>\n import('./components/AppVisualizerPage/DetailedVisualizer').then(m => (\n <m.DetailedVisualizer />\n )),\n },\n});\nconst appVisualizerTextPage = SubPageBlueprint.make({\n name: 'text',\n params: {\n path: 'text',\n routeRef: textRouteRef,\n title: 'Text',\n loader: () =>\n import('./components/AppVisualizerPage/TextVisualizer').then(m => (\n <m.TextVisualizer />\n )),\n },\n});\n\nconst copyTreeAsJson = PluginHeaderActionBlueprint.make({\n params: defineParams =>\n defineParams({\n loader: () =>\n import('./components/CopyTreeButton').then(m => <m.CopyTreeButton />),\n }),\n});\n\nexport const appVisualizerNavItem = NavItemBlueprint.make({\n params: {\n title: 'Visualizer',\n icon: () => <RiEyeLine />,\n routeRef: rootRouteRef,\n },\n});\n\n/** @public */\nexport const visualizerPlugin = createFrontendPlugin({\n pluginId: 'app-visualizer',\n title: 'App Visualizer',\n icon: <RiEyeLine />,\n info: { packageJson: () => import('../package.json') },\n extensions: [\n appVisualizerPage,\n appVisualizerTreePage,\n appVisualizerDetailedPage,\n appVisualizerTextPage,\n appVisualizerNavItem,\n copyTreeAsJson,\n ],\n});\n"],"names":[],"mappings":";;;;AA0BA,MAAM,eAAe,cAAA,EAAe;AAEpC,MAAM,iBAAA,GAAoB,cAAc,IAAA,CAAK;AAAA,EAC3C,MAAA,EAAQ;AAAA,IACN,IAAA,EAAM,aAAA;AAAA,IACN,QAAA,EAAU,YAAA;AAAA,IACV,KAAA,EAAO;AAAA;AAEX,CAAC,CAAA;AAED,MAAM,eAAe,cAAA,EAAe;AACpC,MAAM,mBAAmB,cAAA,EAAe;AACxC,MAAM,eAAe,cAAA,EAAe;AAEpC,MAAM,qBAAA,GAAwB,iBAAiB,IAAA,CAAK;AAAA,EAClD,IAAA,EAAM,MAAA;AAAA,EACN,MAAA,EAAQ;AAAA,IACN,IAAA,EAAM,MAAA;AAAA,IACN,QAAA,EAAU,YAAA;AAAA,IACV,KAAA,EAAO,MAAA;AAAA,IACP,MAAA,EAAQ,MACN,OAAO,sDAA+C,CAAA,CAAE,IAAA,CAAK,CAAA,CAAA,qBAC3D,GAAA,CAAC,CAAA,CAAE,cAAA,EAAF,EAAiB,CACnB;AAAA;AAEP,CAAC,CAAA;AACD,MAAM,yBAAA,GAA4B,iBAAiB,IAAA,CAAK;AAAA,EACtD,IAAA,EAAM,SAAA;AAAA,EACN,MAAA,EAAQ;AAAA,IACN,IAAA,EAAM,SAAA;AAAA,IACN,QAAA,EAAU,gBAAA;AAAA,IACV,KAAA,EAAO,UAAA;AAAA,IACP,MAAA,EAAQ,MACN,OAAO,0DAAmD,CAAA,CAAE,IAAA,CAAK,CAAA,CAAA,qBAC/D,GAAA,CAAC,CAAA,CAAE,kBAAA,EAAF,EAAqB,CACvB;AAAA;AAEP,CAAC,CAAA;AACD,MAAM,qBAAA,GAAwB,iBAAiB,IAAA,CAAK;AAAA,EAClD,IAAA,EAAM,MAAA;AAAA,EACN,MAAA,EAAQ;AAAA,IACN,IAAA,EAAM,MAAA;AAAA,IACN,QAAA,EAAU,YAAA;AAAA,IACV,KAAA,EAAO,MAAA;AAAA,IACP,MAAA,EAAQ,MACN,OAAO,sDAA+C,CAAA,CAAE,IAAA,CAAK,CAAA,CAAA,qBAC3D,GAAA,CAAC,CAAA,CAAE,cAAA,EAAF,EAAiB,CACnB;AAAA;AAEP,CAAC,CAAA;AAED,MAAM,cAAA,GAAiB,4BAA4B,IAAA,CAAK;AAAA,EACtD,MAAA,EAAQ,kBACN,YAAA,CAAa;AAAA,IACX,MAAA,EAAQ,MACN,OAAO,oCAA6B,CAAA,CAAE,IAAA,CAAK,CAAA,CAAA,qBAAK,GAAA,CAAC,CAAA,CAAE,cAAA,EAAF,EAAiB,CAAE;AAAA,GACvE;AACL,CAAC,CAAA;AAEM,MAAM,oBAAA,GAAuB,iBAAiB,IAAA,CAAK;AAAA,EACxD,MAAA,EAAQ;AAAA,IACN,KAAA,EAAO,YAAA;AAAA,IACP,IAAA,EAAM,sBAAM,GAAA,CAAC,SAAA,EAAA,EAAU,CAAA;AAAA,IACvB,QAAA,EAAU;AAAA;AAEd,CAAC;AAGM,MAAM,mBAAmB,oBAAA,CAAqB;AAAA,EACnD,QAAA,EAAU,gBAAA;AAAA,EACV,KAAA,EAAO,gBAAA;AAAA,EACP,IAAA,sBAAO,SAAA,EAAA,EAAU,CAAA;AAAA,EACjB,MAAM,EAAE,WAAA,EAAa,MAAM,OAAO,uBAAiB,CAAA,EAAE;AAAA,EACrD,UAAA,EAAY;AAAA,IACV,iBAAA;AAAA,IACA,qBAAA;AAAA,IACA,yBAAA;AAAA,IACA,qBAAA;AAAA,IACA,oBAAA;AAAA,IACA;AAAA;AAEJ,CAAC;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-app-visualizer",
3
- "version": "0.1.28-next.1",
3
+ "version": "0.2.0",
4
4
  "description": "Visualizes the Backstage app structure",
5
5
  "backstage": {
6
6
  "role": "frontend-plugin",
@@ -37,16 +37,16 @@
37
37
  "test": "backstage-cli package test"
38
38
  },
39
39
  "dependencies": {
40
- "@backstage/core-components": "0.18.7-next.2",
41
- "@backstage/core-plugin-api": "1.12.3-next.1",
42
- "@backstage/frontend-plugin-api": "0.14.0-next.2",
43
- "@backstage/ui": "0.12.0-next.2",
40
+ "@backstage/core-components": "^0.18.7",
41
+ "@backstage/core-plugin-api": "^1.12.3",
42
+ "@backstage/frontend-plugin-api": "^0.14.0",
43
+ "@backstage/ui": "^0.12.0",
44
44
  "@remixicon/react": "^4.6.0",
45
45
  "react-aria-components": "^1.14.0"
46
46
  },
47
47
  "devDependencies": {
48
- "@backstage/cli": "0.35.4-next.2",
49
- "@backstage/frontend-defaults": "0.4.0-next.2",
48
+ "@backstage/cli": "^0.35.4",
49
+ "@backstage/frontend-defaults": "^0.4.0",
50
50
  "@types/react": "^18.0.0",
51
51
  "react": "^18.0.2",
52
52
  "react-dom": "^18.0.2",
@@ -1,72 +0,0 @@
1
- import { jsx, jsxs } from 'react/jsx-runtime';
2
- import { Page, Header, Content, HeaderTabs } from '@backstage/core-components';
3
- import { useApi } from '@backstage/core-plugin-api';
4
- import { appTreeApiRef } from '@backstage/frontend-plugin-api';
5
- import { Flex } from '@backstage/ui';
6
- import { useMemo, useCallback, useEffect } from 'react';
7
- import { DetailedVisualizer } from './DetailedVisualizer.esm.js';
8
- import { TextVisualizer } from './TextVisualizer.esm.js';
9
- import { TreeVisualizer } from './TreeVisualizer.esm.js';
10
- import { useLocation, useRoutes, useParams, matchRoutes, useNavigate } from 'react-router-dom';
11
-
12
- function AppVisualizerPage() {
13
- const appTreeApi = useApi(appTreeApiRef);
14
- const { tree } = appTreeApi.getTree();
15
- const tabs = useMemo(
16
- () => [
17
- {
18
- id: "tree",
19
- path: "tree",
20
- label: "Tree",
21
- element: /* @__PURE__ */ jsx(TreeVisualizer, { tree })
22
- },
23
- {
24
- id: "detailed",
25
- path: "detailed",
26
- label: "Detailed",
27
- element: /* @__PURE__ */ jsx(DetailedVisualizer, { tree })
28
- },
29
- {
30
- id: "text",
31
- path: "text",
32
- label: "Text",
33
- element: /* @__PURE__ */ jsx(TextVisualizer, { tree })
34
- }
35
- ],
36
- [tree]
37
- );
38
- const location = useLocation();
39
- const element = useRoutes(tabs, location);
40
- const currentPath = `/${useParams()["*"]}`;
41
- const [matchedRoute] = matchRoutes(tabs, currentPath) ?? [];
42
- const currentTabIndex = matchedRoute ? tabs.findIndex((t) => t.path === matchedRoute.route.path) : 0;
43
- const navigate = useNavigate();
44
- const handleTabChange = useCallback(
45
- (index) => {
46
- navigate(tabs[index].id);
47
- },
48
- [navigate, tabs]
49
- );
50
- useEffect(() => {
51
- if (!element) {
52
- navigate(tabs[0].path);
53
- }
54
- }, [element, navigate, tabs]);
55
- return /* @__PURE__ */ jsxs(Page, { themeId: "tool", children: [
56
- /* @__PURE__ */ jsx(Header, { title: "App Visualizer" }),
57
- /* @__PURE__ */ jsx(Content, { noPadding: true, stretch: true, children: /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { height: "100%" }, children: [
58
- /* @__PURE__ */ jsx(
59
- HeaderTabs,
60
- {
61
- tabs,
62
- selectedIndex: currentTabIndex,
63
- onChange: handleTabChange
64
- }
65
- ),
66
- element
67
- ] }) })
68
- ] });
69
- }
70
-
71
- export { AppVisualizerPage };
72
- //# sourceMappingURL=AppVisualizerPage.esm.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"AppVisualizerPage.esm.js","sources":["../../../src/components/AppVisualizerPage/AppVisualizerPage.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Content, Header, HeaderTabs, Page } from '@backstage/core-components';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { appTreeApiRef } from '@backstage/frontend-plugin-api';\nimport { Flex } from '@backstage/ui';\nimport { useCallback, useEffect, useMemo } from 'react';\nimport { DetailedVisualizer } from './DetailedVisualizer';\nimport { TextVisualizer } from './TextVisualizer';\nimport { TreeVisualizer } from './TreeVisualizer';\nimport {\n matchRoutes,\n useLocation,\n useNavigate,\n useParams,\n useRoutes,\n} from 'react-router-dom';\n\nexport function AppVisualizerPage() {\n const appTreeApi = useApi(appTreeApiRef);\n const { tree } = appTreeApi.getTree();\n\n const tabs = useMemo(\n () => [\n {\n id: 'tree',\n path: 'tree',\n label: 'Tree',\n element: <TreeVisualizer tree={tree} />,\n },\n {\n id: 'detailed',\n path: 'detailed',\n label: 'Detailed',\n element: <DetailedVisualizer tree={tree} />,\n },\n {\n id: 'text',\n path: 'text',\n label: 'Text',\n element: <TextVisualizer tree={tree} />,\n },\n ],\n [tree],\n );\n\n const location = useLocation();\n const element = useRoutes(tabs, location);\n\n const currentPath = `/${useParams()['*']}`;\n const [matchedRoute] = matchRoutes(tabs, currentPath) ?? [];\n\n const currentTabIndex = matchedRoute\n ? tabs.findIndex(t => t.path === matchedRoute.route.path)\n : 0;\n\n const navigate = useNavigate();\n const handleTabChange = useCallback(\n (index: number) => {\n navigate(tabs[index].id);\n },\n [navigate, tabs],\n );\n\n useEffect(() => {\n if (!element) {\n navigate(tabs[0].path);\n }\n }, [element, navigate, tabs]);\n\n return (\n <Page themeId=\"tool\">\n <Header title=\"App Visualizer\" />\n <Content noPadding stretch>\n <Flex direction=\"column\" style={{ height: '100%' }}>\n <HeaderTabs\n tabs={tabs}\n selectedIndex={currentTabIndex}\n onChange={handleTabChange}\n />\n {element}\n </Flex>\n </Content>\n </Page>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;AAgCO,SAAS,iBAAA,GAAoB;AAClC,EAAA,MAAM,UAAA,GAAa,OAAO,aAAa,CAAA;AACvC,EAAA,MAAM,EAAE,IAAA,EAAK,GAAI,UAAA,CAAW,OAAA,EAAQ;AAEpC,EAAA,MAAM,IAAA,GAAO,OAAA;AAAA,IACX,MAAM;AAAA,MACJ;AAAA,QACE,EAAA,EAAI,MAAA;AAAA,QACJ,IAAA,EAAM,MAAA;AAAA,QACN,KAAA,EAAO,MAAA;AAAA,QACP,OAAA,kBAAS,GAAA,CAAC,cAAA,EAAA,EAAe,IAAA,EAAY;AAAA,OACvC;AAAA,MACA;AAAA,QACE,EAAA,EAAI,UAAA;AAAA,QACJ,IAAA,EAAM,UAAA;AAAA,QACN,KAAA,EAAO,UAAA;AAAA,QACP,OAAA,kBAAS,GAAA,CAAC,kBAAA,EAAA,EAAmB,IAAA,EAAY;AAAA,OAC3C;AAAA,MACA;AAAA,QACE,EAAA,EAAI,MAAA;AAAA,QACJ,IAAA,EAAM,MAAA;AAAA,QACN,KAAA,EAAO,MAAA;AAAA,QACP,OAAA,kBAAS,GAAA,CAAC,cAAA,EAAA,EAAe,IAAA,EAAY;AAAA;AACvC,KACF;AAAA,IACA,CAAC,IAAI;AAAA,GACP;AAEA,EAAA,MAAM,WAAW,WAAA,EAAY;AAC7B,EAAA,MAAM,OAAA,GAAU,SAAA,CAAU,IAAA,EAAM,QAAQ,CAAA;AAExC,EAAA,MAAM,WAAA,GAAc,CAAA,CAAA,EAAI,SAAA,EAAU,CAAE,GAAG,CAAC,CAAA,CAAA;AACxC,EAAA,MAAM,CAAC,YAAY,CAAA,GAAI,YAAY,IAAA,EAAM,WAAW,KAAK,EAAC;AAE1D,EAAA,MAAM,eAAA,GAAkB,YAAA,GACpB,IAAA,CAAK,SAAA,CAAU,CAAA,CAAA,KAAK,EAAE,IAAA,KAAS,YAAA,CAAa,KAAA,CAAM,IAAI,CAAA,GACtD,CAAA;AAEJ,EAAA,MAAM,WAAW,WAAA,EAAY;AAC7B,EAAA,MAAM,eAAA,GAAkB,WAAA;AAAA,IACtB,CAAC,KAAA,KAAkB;AACjB,MAAA,QAAA,CAAS,IAAA,CAAK,KAAK,CAAA,CAAE,EAAE,CAAA;AAAA,IACzB,CAAA;AAAA,IACA,CAAC,UAAU,IAAI;AAAA,GACjB;AAEA,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,OAAA,EAAS;AACZ,MAAA,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,CAAE,IAAI,CAAA;AAAA,IACvB;AAAA,EACF,CAAA,EAAG,CAAC,OAAA,EAAS,QAAA,EAAU,IAAI,CAAC,CAAA;AAE5B,EAAA,uBACE,IAAA,CAAC,IAAA,EAAA,EAAK,OAAA,EAAQ,MAAA,EACZ,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,MAAA,EAAA,EAAO,OAAM,gBAAA,EAAiB,CAAA;AAAA,oBAC/B,GAAA,CAAC,OAAA,EAAA,EAAQ,SAAA,EAAS,IAAA,EAAC,SAAO,IAAA,EACxB,QAAA,kBAAA,IAAA,CAAC,IAAA,EAAA,EAAK,SAAA,EAAU,QAAA,EAAS,KAAA,EAAO,EAAE,MAAA,EAAQ,QAAO,EAC/C,QAAA,EAAA;AAAA,sBAAA,GAAA;AAAA,QAAC,UAAA;AAAA,QAAA;AAAA,UACC,IAAA;AAAA,UACA,aAAA,EAAe,eAAA;AAAA,UACf,QAAA,EAAU;AAAA;AAAA,OACZ;AAAA,MACC;AAAA,KAAA,EACH,CAAA,EACF;AAAA,GAAA,EACF,CAAA;AAEJ;;;;"}
@@ -1,2 +0,0 @@
1
- export { AppVisualizerPage } from './AppVisualizerPage.esm.js';
2
- //# sourceMappingURL=index.esm.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}