@backstage/plugin-catalog 2.0.6-next.1 → 2.0.7-next.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/config.schema.json +27 -0
  3. package/dist/alpha/components/EntityContextMenu/EntityContextMenu.esm.js +63 -0
  4. package/dist/alpha/components/EntityContextMenu/EntityContextMenu.esm.js.map +1 -0
  5. package/dist/alpha/components/EntityHeader/EntityHeader.esm.js +6 -51
  6. package/dist/alpha/components/EntityHeader/EntityHeader.esm.js.map +1 -1
  7. package/dist/alpha/components/EntityHeader/EntityHeaderBui.esm.js +143 -0
  8. package/dist/alpha/components/EntityHeader/EntityHeaderBui.esm.js.map +1 -0
  9. package/dist/alpha/components/EntityLayout/EntityLayout.esm.js +4 -32
  10. package/dist/alpha/components/EntityLayout/EntityLayout.esm.js.map +1 -1
  11. package/dist/alpha/components/EntityLayout/EntityLayoutBui.esm.js +126 -0
  12. package/dist/alpha/components/EntityLayout/EntityLayoutBui.esm.js.map +1 -0
  13. package/dist/alpha/components/EntityLayout/entityLayoutRoutes.esm.js +30 -0
  14. package/dist/alpha/components/EntityLayout/entityLayoutRoutes.esm.js.map +1 -0
  15. package/dist/alpha/components/EntityLayout/index.esm.js +1 -5
  16. package/dist/alpha/components/EntityLayout/index.esm.js.map +1 -1
  17. package/dist/alpha/components/EntityTabs/EntityTabs.esm.js.map +1 -1
  18. package/dist/alpha/components/EntityTabs/useEntityTabs.esm.js +63 -0
  19. package/dist/alpha/components/EntityTabs/useEntityTabs.esm.js.map +1 -0
  20. package/dist/alpha/contextMenuItems.esm.js +18 -9
  21. package/dist/alpha/contextMenuItems.esm.js.map +1 -1
  22. package/dist/alpha/pages.esm.js +71 -41
  23. package/dist/alpha/pages.esm.js.map +1 -1
  24. package/dist/alpha/plugin.esm.js +1 -1
  25. package/dist/alpha/plugin.esm.js.map +1 -1
  26. package/dist/alpha/translation.esm.js +5 -2
  27. package/dist/alpha/translation.esm.js.map +1 -1
  28. package/dist/alpha.d.ts +24 -14
  29. package/dist/components/CatalogExportButton/CatalogExportButton.esm.js +12 -3
  30. package/dist/components/CatalogExportButton/CatalogExportButton.esm.js.map +1 -1
  31. package/dist/components/CatalogPage/DefaultCatalogPage.esm.js +16 -11
  32. package/dist/components/CatalogPage/DefaultCatalogPage.esm.js.map +1 -1
  33. package/dist/components/CatalogPage/DefaultCatalogPage.module.css.esm.js +8 -0
  34. package/dist/components/CatalogPage/DefaultCatalogPage.module.css.esm.js.map +1 -0
  35. package/dist/components/EntityContextMenu/EntityContextMenu.esm.js +2 -2
  36. package/dist/components/EntityContextMenu/EntityContextMenu.esm.js.map +1 -1
  37. package/dist/context/EntityContextMenuContext.esm.js +1 -1
  38. package/dist/index.d.ts +10 -7
  39. package/dist/node_modules_dist/style-inject/dist/style-inject.es.esm.js +29 -0
  40. package/dist/node_modules_dist/style-inject/dist/style-inject.es.esm.js.map +1 -0
  41. package/dist/{package.json.esm.js → plugins/catalog/package.json.esm.js} +1 -1
  42. package/package.json +20 -20
  43. package/config.d.ts +0 -27
  44. /package/dist/{package.json.esm.js.map → plugins/catalog/package.json.esm.js.map} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,62 @@
1
1
  # @backstage/plugin-catalog
2
2
 
3
+ ## 2.0.7-next.0
4
+
5
+ ### Patch Changes
6
+
7
+ - ba49e37: Migrated the new frontend system Catalog entity context menu to BUI and switched its built-in action icons to Remix icons. The old frontend system Catalog context menu remains unchanged.
8
+
9
+ **BREAKING ALPHA**: The new frontend system Catalog entity page now consumes data-driven context menu item extensions. Its `contextMenuItems` input expects the `EntityContextMenuItemBlueprint` data output rather than a rendered React element.
10
+
11
+ The default English value of the `entityContextMenu.moreButtonAriaLabel` translation changed from `more` to `More actions`. If you provide localized Catalog messages, update this label as appropriate for your locale.
12
+
13
+ - 15719cc: **BREAKING ALPHA**: Migrated the new frontend system Catalog entity page to the automatic Catalog plugin header and a BUI page header with entity tags, title, metadata, favorite and context-menu actions, and Catalog-composed navigation.
14
+
15
+ Existing alpha opaque entity header customizations continue to render through a temporary per-entity legacy fallback with the previous MUI tabs and page shell. Migrate those customizations to the new BUI-ready entity header layout extension point to receive composed tabs and the active tab ID. The new extension point wins when both customization types match an entity.
16
+
17
+ The default BUI navigation does not render entity-content tab icons because the BUI Header tab API does not expose an icon slot. Legacy fallback pages retain their existing tab-icon behavior.
18
+
19
+ Added the translation keys `entityLabels.systemLabel`, `entityLabels.domainLabel`, and `entityLabels.partOfLabel`. Apps that provide Catalog translations should add translations for these new messages.
20
+
21
+ - Updated dependencies
22
+ - @backstage/plugin-catalog-react@3.2.0-next.0
23
+ - @backstage/ui@0.17.0-next.0
24
+ - @backstage/core-compat-api@0.5.13-next.0
25
+ - @backstage/frontend-plugin-api@0.17.3-next.0
26
+ - @backstage/plugin-search-react@1.11.6-next.0
27
+ - @backstage/catalog-client@1.16.1-next.0
28
+ - @backstage/core-components@0.18.12-next.0
29
+ - @backstage/core-plugin-api@1.12.8-next.0
30
+ - @backstage/integration-react@1.2.20-next.0
31
+ - @backstage/plugin-permission-react@0.5.3-next.0
32
+ - @backstage/plugin-techdocs-react@1.3.13-next.0
33
+
34
+ ## 2.0.6
35
+
36
+ ### Patch Changes
37
+
38
+ - 7172386: Updated the new frontend system Catalog index page to use the current Backstage UI page header and content container.
39
+ - d8757b1: The entity list provider now fetches the entity list and the total count as two separate parallel requests when using cursor or offset pagination. The list query skips the expensive count computation (using `totalItems: 'exclude'`), so the table populates immediately. The count arrives asynchronously and updates the title. A new `totalItemsLoading` field is exposed on `EntityListContextProps` so consumers can distinguish a stale count from a fresh one.
40
+
41
+ The catalog table now keeps stale rows visible during filter changes and page navigation instead of replacing the entire table body with a spinner. The full-table spinner is only shown on the very first load when no data exists yet. The entity count in the title is dimmed while the count is refreshing, and a small spinner appears next to the title while rows are loading.
42
+
43
+ - 82cf16f: Added `CatalogExportButton`, which adds CSV and JSON export support to the `CatalogIndexPage`.
44
+ - d7c1dcf: Fixed a missing React key warning for context menu items on the entity page.
45
+ - a07e6a3: Added the correctly-spelled `RelatedEntitiesCard.domainEntityColumns` static property and deprecated the previous typoed `RelatedEntitiesCard.domainEntityColums` property. Existing references to the old property continue to work; switch to `domainEntityColumns` to avoid future removal.
46
+ - Updated dependencies
47
+ - @backstage/catalog-client@1.16.0
48
+ - @backstage/plugin-catalog-react@3.1.0
49
+ - @backstage/core-components@0.18.11
50
+ - @backstage/plugin-search-react@1.11.5
51
+ - @backstage/frontend-plugin-api@0.17.2
52
+ - @backstage/ui@0.16.0
53
+ - @backstage/plugin-scaffolder-common@2.2.1
54
+ - @backstage/core-compat-api@0.5.12
55
+ - @backstage/core-plugin-api@1.12.7
56
+ - @backstage/integration-react@1.2.19
57
+ - @backstage/plugin-permission-react@0.5.2
58
+ - @backstage/plugin-techdocs-react@1.3.12
59
+
3
60
  ## 2.0.6-next.1
4
61
 
5
62
  ### Patch Changes
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "type": "object",
4
+ "properties": {
5
+ "catalog": {
6
+ "type": "object",
7
+ "properties": {
8
+ "experimentalPagination": {
9
+ "anyOf": [
10
+ {
11
+ "type": "boolean"
12
+ },
13
+ {
14
+ "type": "object",
15
+ "properties": {
16
+ "limit": {
17
+ "type": "number"
18
+ }
19
+ }
20
+ }
21
+ ],
22
+ "deepVisibility": "frontend"
23
+ }
24
+ }
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,63 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { useTranslationRef, ExtensionBoundary } from '@backstage/frontend-plugin-api';
3
+ import { MenuTrigger, ButtonIcon, Menu, MenuItem, MenuSeparator } from '@backstage/ui';
4
+ import { RiMore2Line } from '@remixicon/react';
5
+ import { catalogTranslationRef } from '../../translation.esm.js';
6
+
7
+ function EntityContextMenuItemContent(props) {
8
+ const { icon, useProps } = props.data;
9
+ const { title, disabled, onClick, ...menuItemProps } = useProps();
10
+ const onAction = onClick ? () => {
11
+ const result = onClick();
12
+ if (result) {
13
+ void result.catch(() => {
14
+ });
15
+ }
16
+ } : void 0;
17
+ if ("href" in menuItemProps) {
18
+ return /* @__PURE__ */ jsx(
19
+ MenuItem,
20
+ {
21
+ iconStart: icon,
22
+ href: menuItemProps.href,
23
+ onAction,
24
+ isDisabled: disabled,
25
+ children: title
26
+ }
27
+ );
28
+ }
29
+ return /* @__PURE__ */ jsx(MenuItem, { iconStart: icon, onAction, isDisabled: disabled, children: title });
30
+ }
31
+ function EntityContextMenuItem(props) {
32
+ return /* @__PURE__ */ jsx(ExtensionBoundary, { node: props.item.node, children: /* @__PURE__ */ jsx(EntityContextMenuItemContent, { data: props.item.data }) });
33
+ }
34
+ function EntityContextMenu(props) {
35
+ const { UNSTABLE_extraContextMenuItems, contextMenuItems } = props;
36
+ const { t } = useTranslationRef(catalogTranslationRef);
37
+ return /* @__PURE__ */ jsxs(MenuTrigger, { children: [
38
+ /* @__PURE__ */ jsx(
39
+ ButtonIcon,
40
+ {
41
+ variant: "secondary",
42
+ icon: /* @__PURE__ */ jsx(RiMore2Line, {}),
43
+ "aria-label": t("entityContextMenu.moreButtonAriaLabel")
44
+ }
45
+ ),
46
+ /* @__PURE__ */ jsxs(Menu, { placement: "bottom end", children: [
47
+ UNSTABLE_extraContextMenuItems?.map((item, index) => /* @__PURE__ */ jsx(
48
+ MenuItem,
49
+ {
50
+ iconStart: /* @__PURE__ */ jsx(item.Icon, {}),
51
+ onAction: () => item.onClick(),
52
+ children: item.title
53
+ },
54
+ `${item.title}-${index}`
55
+ )),
56
+ UNSTABLE_extraContextMenuItems?.length && contextMenuItems?.length ? /* @__PURE__ */ jsx(MenuSeparator, {}) : null,
57
+ contextMenuItems?.map((item) => /* @__PURE__ */ jsx(EntityContextMenuItem, { item }, item.node.spec.id))
58
+ ] })
59
+ ] });
60
+ }
61
+
62
+ export { EntityContextMenu };
63
+ //# sourceMappingURL=EntityContextMenu.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EntityContextMenu.esm.js","sources":["../../../../src/alpha/components/EntityContextMenu/EntityContextMenu.tsx"],"sourcesContent":["/*\n * Copyright 2026 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 type AppNode,\n ExtensionBoundary,\n IconComponent,\n useTranslationRef,\n} from '@backstage/frontend-plugin-api';\nimport type { EntityContextMenuItemData } from '@backstage/plugin-catalog-react/alpha';\nimport {\n ButtonIcon,\n Menu,\n MenuItem,\n MenuSeparator,\n MenuTrigger,\n} from '@backstage/ui';\nimport { RiMore2Line } from '@remixicon/react';\nimport { catalogTranslationRef } from '../../translation';\n\nexport type EntityContextMenuItemDataWithNode = {\n data: EntityContextMenuItemData;\n node: AppNode;\n};\n\nfunction EntityContextMenuItemContent(props: {\n data: EntityContextMenuItemData;\n}) {\n const { icon, useProps } = props.data;\n const { title, disabled, onClick, ...menuItemProps } = useProps();\n const onAction = onClick\n ? () => {\n const result = onClick();\n if (result) {\n // Prevent rejected async actions from becoming unhandled rejections.\n void result.catch(() => {});\n }\n }\n : undefined;\n\n if ('href' in menuItemProps) {\n return (\n <MenuItem\n iconStart={icon}\n href={menuItemProps.href}\n onAction={onAction}\n isDisabled={disabled}\n >\n {title}\n </MenuItem>\n );\n }\n\n return (\n <MenuItem iconStart={icon} onAction={onAction} isDisabled={disabled}>\n {title}\n </MenuItem>\n );\n}\n\nfunction EntityContextMenuItem(props: {\n item: EntityContextMenuItemDataWithNode;\n}) {\n return (\n <ExtensionBoundary node={props.item.node}>\n <EntityContextMenuItemContent data={props.item.data} />\n </ExtensionBoundary>\n );\n}\n\n/** @alpha */\nexport function EntityContextMenu(props: {\n UNSTABLE_extraContextMenuItems?: {\n title: string;\n Icon: IconComponent;\n onClick: () => void;\n }[];\n contextMenuItems?: EntityContextMenuItemDataWithNode[];\n}) {\n const { UNSTABLE_extraContextMenuItems, contextMenuItems } = props;\n const { t } = useTranslationRef(catalogTranslationRef);\n\n return (\n <MenuTrigger>\n <ButtonIcon\n variant=\"secondary\"\n icon={<RiMore2Line />}\n aria-label={t('entityContextMenu.moreButtonAriaLabel')}\n />\n <Menu placement=\"bottom end\">\n {UNSTABLE_extraContextMenuItems?.map((item, index) => (\n <MenuItem\n key={`${item.title}-${index}`}\n iconStart={<item.Icon />}\n onAction={() => item.onClick()}\n >\n {item.title}\n </MenuItem>\n ))}\n {UNSTABLE_extraContextMenuItems?.length && contextMenuItems?.length ? (\n <MenuSeparator />\n ) : null}\n {contextMenuItems?.map(item => (\n <EntityContextMenuItem key={item.node.spec.id} item={item} />\n ))}\n </Menu>\n </MenuTrigger>\n );\n}\n"],"names":[],"mappings":";;;;;;AAsCA,SAAS,6BAA6B,KAAA,EAEnC;AACD,EAAA,MAAM,EAAE,IAAA,EAAM,QAAA,EAAS,GAAI,KAAA,CAAM,IAAA;AACjC,EAAA,MAAM,EAAE,KAAA,EAAO,QAAA,EAAU,SAAS,GAAG,aAAA,KAAkB,QAAA,EAAS;AAChE,EAAA,MAAM,QAAA,GAAW,UACb,MAAM;AACJ,IAAA,MAAM,SAAS,OAAA,EAAQ;AACvB,IAAA,IAAI,MAAA,EAAQ;AAEV,MAAA,KAAK,MAAA,CAAO,MAAM,MAAM;AAAA,MAAC,CAAC,CAAA;AAAA,IAC5B;AAAA,EACF,CAAA,GACA,MAAA;AAEJ,EAAA,IAAI,UAAU,aAAA,EAAe;AAC3B,IAAA,uBACE,GAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAW,IAAA;AAAA,QACX,MAAM,aAAA,CAAc,IAAA;AAAA,QACpB,QAAA;AAAA,QACA,UAAA,EAAY,QAAA;AAAA,QAEX,QAAA,EAAA;AAAA;AAAA,KACH;AAAA,EAEJ;AAEA,EAAA,2BACG,QAAA,EAAA,EAAS,SAAA,EAAW,MAAM,QAAA,EAAoB,UAAA,EAAY,UACxD,QAAA,EAAA,KAAA,EACH,CAAA;AAEJ;AAEA,SAAS,sBAAsB,KAAA,EAE5B;AACD,EAAA,uBACE,GAAA,CAAC,iBAAA,EAAA,EAAkB,IAAA,EAAM,KAAA,CAAM,IAAA,CAAK,IAAA,EAClC,QAAA,kBAAA,GAAA,CAAC,4BAAA,EAAA,EAA6B,IAAA,EAAM,KAAA,CAAM,IAAA,CAAK,IAAA,EAAM,CAAA,EACvD,CAAA;AAEJ;AAGO,SAAS,kBAAkB,KAAA,EAO/B;AACD,EAAA,MAAM,EAAE,8BAAA,EAAgC,gBAAA,EAAiB,GAAI,KAAA;AAC7D,EAAA,MAAM,EAAE,CAAA,EAAE,GAAI,iBAAA,CAAkB,qBAAqB,CAAA;AAErD,EAAA,4BACG,WAAA,EAAA,EACC,QAAA,EAAA;AAAA,oBAAA,GAAA;AAAA,MAAC,UAAA;AAAA,MAAA;AAAA,QACC,OAAA,EAAQ,WAAA;AAAA,QACR,IAAA,sBAAO,WAAA,EAAA,EAAY,CAAA;AAAA,QACnB,YAAA,EAAY,EAAE,uCAAuC;AAAA;AAAA,KACvD;AAAA,oBACA,IAAA,CAAC,IAAA,EAAA,EAAK,SAAA,EAAU,YAAA,EACb,QAAA,EAAA;AAAA,MAAA,8BAAA,EAAgC,GAAA,CAAI,CAAC,IAAA,EAAM,KAAA,qBAC1C,GAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UAEC,SAAA,kBAAW,GAAA,CAAC,IAAA,CAAK,IAAA,EAAL,EAAU,CAAA;AAAA,UACtB,QAAA,EAAU,MAAM,IAAA,CAAK,OAAA,EAAQ;AAAA,UAE5B,QAAA,EAAA,IAAA,CAAK;AAAA,SAAA;AAAA,QAJD,CAAA,EAAG,IAAA,CAAK,KAAK,CAAA,CAAA,EAAI,KAAK,CAAA;AAAA,OAM9B,CAAA;AAAA,MACA,gCAAgC,MAAA,IAAU,gBAAA,EAAkB,MAAA,mBAC3D,GAAA,CAAC,iBAAc,CAAA,GACb,IAAA;AAAA,MACH,gBAAA,EAAkB,GAAA,CAAI,CAAA,IAAA,qBACrB,GAAA,CAAC,qBAAA,EAAA,EAA8C,QAAnB,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,EAAgB,CAC5D;AAAA,KAAA,EACH;AAAA,GAAA,EACF,CAAA;AAEJ;;;;"}
@@ -1,16 +1,15 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import { useState, useCallback, useEffect } from 'react';
3
- import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
2
+ import { useCallback } from 'react';
3
+ import { useSearchParams } from 'react-router-dom';
4
4
  import useAsync from 'react-use/esm/useAsync';
5
5
  import { makeStyles } from '@material-ui/core/styles';
6
6
  import Box from '@material-ui/core/Box';
7
7
  import { Header, Breadcrumbs } from '@backstage/core-components';
8
- import { useRouteRefParams, useRouteRef, useApi } from '@backstage/core-plugin-api';
8
+ import { useRouteRefParams, useApi } from '@backstage/core-plugin-api';
9
9
  import { DEFAULT_NAMESPACE } from '@backstage/catalog-model';
10
- import { useAsyncEntity, entityRouteRef, InspectEntityDialog, UnregisterEntityDialog, catalogApiRef, EntityRefLink, EntityDisplayName, FavoriteEntity } from '@backstage/plugin-catalog-react';
10
+ import { useAsyncEntity, entityRouteRef, InspectEntityDialog, catalogApiRef, EntityRefLink, EntityDisplayName, FavoriteEntity } from '@backstage/plugin-catalog-react';
11
11
  import { EntityLabels } from '../EntityLabels/EntityLabels.esm.js';
12
- import { EntityContextMenu } from '../../../components/EntityContextMenu/EntityContextMenu.esm.js';
13
- import { rootRouteRef, unregisterRedirectRouteRef } from '../../../routes.esm.js';
12
+ import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu.esm.js';
14
13
 
15
14
  function headerProps(paramKind, paramNamespace, paramName, entity) {
16
15
  const kind = paramKind ?? entity?.kind ?? "";
@@ -99,7 +98,6 @@ function EntityHeaderSubtitle(props) {
99
98
  function EntityHeader(props) {
100
99
  const {
101
100
  UNSTABLE_extraContextMenuItems,
102
- UNSTABLE_contextMenuOptions,
103
101
  contextMenuItems,
104
102
  parentEntityRelations,
105
103
  title,
@@ -113,43 +111,12 @@ function EntityHeader(props) {
113
111
  name,
114
112
  entity
115
113
  );
116
- const location = useLocation();
117
- const navigate = useNavigate();
118
- const catalogRoute = useRouteRef(rootRouteRef);
119
- const unregisterRedirectRoute = useRouteRef(unregisterRedirectRouteRef);
120
- const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false);
121
- const openUnregisterEntityDialog = useCallback(
122
- () => setConfirmationDialogOpen(true),
123
- [setConfirmationDialogOpen]
124
- );
125
- const closeUnregisterEntityDialog = useCallback(
126
- () => setConfirmationDialogOpen(false),
127
- [setConfirmationDialogOpen]
128
- );
129
- const cleanUpAfterUnregisterConfirmation = useCallback(async () => {
130
- setConfirmationDialogOpen(false);
131
- navigate(
132
- unregisterRedirectRoute ? unregisterRedirectRoute() : catalogRoute()
133
- );
134
- }, [
135
- navigate,
136
- catalogRoute,
137
- unregisterRedirectRoute,
138
- setConfirmationDialogOpen
139
- ]);
140
- useEffect(() => {
141
- setConfirmationDialogOpen(false);
142
- }, [location.pathname]);
143
114
  const [searchParams, setSearchParams] = useSearchParams();
144
115
  const selectedInspectEntityDialogTab = searchParams.get("inspect");
145
116
  const setInspectEntityDialogTab = useCallback(
146
117
  (newTab) => setSearchParams(`inspect=${newTab}`),
147
118
  [setSearchParams]
148
119
  );
149
- const openInspectEntityDialog = useCallback(
150
- () => setSearchParams("inspect"),
151
- [setSearchParams]
152
- );
153
120
  const closeInspectEntityDialog = useCallback(
154
121
  () => setSearchParams(),
155
122
  [setSearchParams]
@@ -168,10 +135,7 @@ function EntityHeader(props) {
168
135
  EntityContextMenu,
169
136
  {
170
137
  UNSTABLE_extraContextMenuItems,
171
- UNSTABLE_contextMenuOptions,
172
- contextMenuItems,
173
- onInspectEntity: openInspectEntityDialog,
174
- onUnregisterEntity: openUnregisterEntityDialog
138
+ contextMenuItems
175
139
  }
176
140
  ),
177
141
  /* @__PURE__ */ jsx(
@@ -183,15 +147,6 @@ function EntityHeader(props) {
183
147
  onClose: closeInspectEntityDialog,
184
148
  onSelect: setInspectEntityDialogTab
185
149
  }
186
- ),
187
- /* @__PURE__ */ jsx(
188
- UnregisterEntityDialog,
189
- {
190
- entity,
191
- open: confirmationDialogOpen,
192
- onClose: closeUnregisterEntityDialog,
193
- onConfirm: cleanUpAfterUnregisterConfirmation
194
- }
195
150
  )
196
151
  ] })
197
152
  }
@@ -1 +1 @@
1
- {"version":3,"file":"EntityHeader.esm.js","sources":["../../../../src/alpha/components/EntityHeader/EntityHeader.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 {\n useState,\n useCallback,\n useEffect,\n ComponentProps,\n ReactNode,\n} from 'react';\nimport { useNavigate, useLocation, useSearchParams } from 'react-router-dom';\nimport useAsync from 'react-use/esm/useAsync';\n\nimport { makeStyles } from '@material-ui/core/styles';\nimport Box from '@material-ui/core/Box';\n\nimport { Header, Breadcrumbs } from '@backstage/core-components';\nimport {\n useApi,\n useRouteRef,\n useRouteRefParams,\n} from '@backstage/core-plugin-api';\nimport { IconComponent } from '@backstage/frontend-plugin-api';\n\nimport {\n Entity,\n EntityRelation,\n DEFAULT_NAMESPACE,\n} from '@backstage/catalog-model';\n\nimport {\n useAsyncEntity,\n entityRouteRef,\n catalogApiRef,\n EntityRefLink,\n InspectEntityDialog,\n UnregisterEntityDialog,\n EntityDisplayName,\n FavoriteEntity,\n} from '@backstage/plugin-catalog-react';\n\nimport { EntityLabels } from '../EntityLabels';\nimport { EntityContextMenu } from '../../../components/EntityContextMenu';\nimport { rootRouteRef, unregisterRedirectRouteRef } from '../../../routes';\n\nfunction headerProps(\n paramKind: string | undefined,\n paramNamespace: string | undefined,\n paramName: string | undefined,\n entity: Entity | undefined,\n): { headerTitle: string; headerType: string } {\n const kind = paramKind ?? entity?.kind ?? '';\n const namespace = paramNamespace ?? entity?.metadata.namespace ?? '';\n const name =\n entity?.metadata.title ?? paramName ?? entity?.metadata.name ?? '';\n\n return {\n headerTitle: `${name}${\n namespace && namespace !== DEFAULT_NAMESPACE ? ` in ${namespace}` : ''\n }`,\n headerType: (() => {\n let t = kind.toLocaleLowerCase('en-US');\n if (entity && entity.spec && 'type' in entity.spec) {\n t += ' — ';\n t += (entity.spec as { type: string }).type.toLocaleLowerCase('en-US');\n }\n return t;\n })(),\n };\n}\n\nfunction findParentRelation(\n entityRelations: EntityRelation[] = [],\n relationTypes: string[] = [],\n) {\n for (const type of relationTypes) {\n const foundRelation = entityRelations.find(\n relation => relation.type === type,\n );\n if (foundRelation) {\n return foundRelation; // Return the first found relation and stop\n }\n }\n return null;\n}\n\nconst useStyles = makeStyles(theme => ({\n breadcrumbs: {\n color: theme.page.fontColor,\n fontSize: theme.typography.caption.fontSize,\n textTransform: 'uppercase',\n marginTop: theme.spacing(1),\n opacity: 0.8,\n '& span ': {\n color: theme.page.fontColor,\n textDecoration: 'underline',\n textUnderlineOffset: '3px',\n },\n },\n}));\n\nfunction EntityHeaderTitle() {\n const { entity } = useAsyncEntity();\n const { kind, namespace, name } = useRouteRefParams(entityRouteRef);\n const { headerTitle: title } = headerProps(kind, namespace, name, entity);\n return (\n <Box display=\"inline-flex\" alignItems=\"center\" height=\"1em\" maxWidth=\"100%\">\n <Box\n component=\"span\"\n textOverflow=\"ellipsis\"\n whiteSpace=\"nowrap\"\n overflow=\"hidden\"\n >\n {entity ? <EntityDisplayName entityRef={entity} hideIcon /> : title}\n </Box>\n {entity && <FavoriteEntity entity={entity} />}\n </Box>\n );\n}\n\nfunction EntityHeaderSubtitle(props: { parentEntityRelations?: string[] }) {\n const { parentEntityRelations } = props;\n const classes = useStyles();\n const { entity } = useAsyncEntity();\n const { name } = useRouteRefParams(entityRouteRef);\n const parentEntity = findParentRelation(\n entity?.relations ?? [],\n parentEntityRelations ?? [],\n );\n\n const catalogApi = useApi(catalogApiRef);\n\n const { value: ancestorEntity } = useAsync(async () => {\n if (parentEntity) {\n return findParentRelation(\n (await catalogApi.getEntityByRef(parentEntity?.targetRef))?.relations,\n parentEntityRelations,\n );\n }\n return null;\n }, [parentEntity, catalogApi]);\n\n return parentEntity ? (\n <Breadcrumbs separator=\">\" className={classes.breadcrumbs}>\n {ancestorEntity && (\n <EntityRefLink entityRef={ancestorEntity.targetRef} disableTooltip />\n )}\n <EntityRefLink entityRef={parentEntity.targetRef} disableTooltip />\n {name}\n </Breadcrumbs>\n ) : null;\n}\n\n/** @alpha */\nexport function EntityHeader(props: {\n // NOTE(freben): Intentionally not exported at this point, since it's part of\n // the unstable extra context menu items concept below\n UNSTABLE_extraContextMenuItems?: {\n title: string;\n Icon: IconComponent;\n onClick: () => void;\n }[];\n // NOTE(blam): Intentionally not exported at this point, since it's part of\n // unstable context menu option, eg: disable the unregister entity menu\n UNSTABLE_contextMenuOptions?: {\n disableUnregister: boolean | 'visible' | 'hidden' | 'disable';\n };\n contextMenuItems?: React.JSX.Element[];\n /**\n * An array of relation types used to determine the parent entities in the hierarchy.\n * These relations are prioritized in the order provided, allowing for flexible\n * navigation through entity relationships.\n *\n * For example, use relation types like `[\"partOf\", \"memberOf\", \"ownedBy\"]` to define how the entity is related to\n * its parents in the Entity Catalog.\n *\n * It adds breadcrumbs in the Entity page to enhance user navigation and context awareness.\n */\n parentEntityRelations?: string[];\n title?: ReactNode;\n subtitle?: ReactNode;\n}) {\n const {\n UNSTABLE_extraContextMenuItems,\n UNSTABLE_contextMenuOptions,\n contextMenuItems,\n parentEntityRelations,\n title,\n subtitle,\n } = props;\n const { entity } = useAsyncEntity();\n const { kind, namespace, name } = useRouteRefParams(entityRouteRef);\n const { headerTitle: entityFallbackText, headerType: type } = headerProps(\n kind,\n namespace,\n name,\n entity,\n );\n\n const location = useLocation();\n const navigate = useNavigate();\n const catalogRoute = useRouteRef(rootRouteRef);\n const unregisterRedirectRoute = useRouteRef(unregisterRedirectRouteRef);\n\n const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false);\n\n const openUnregisterEntityDialog = useCallback(\n () => setConfirmationDialogOpen(true),\n [setConfirmationDialogOpen],\n );\n\n const closeUnregisterEntityDialog = useCallback(\n () => setConfirmationDialogOpen(false),\n [setConfirmationDialogOpen],\n );\n\n const cleanUpAfterUnregisterConfirmation = useCallback(async () => {\n setConfirmationDialogOpen(false);\n navigate(\n unregisterRedirectRoute ? unregisterRedirectRoute() : catalogRoute(),\n );\n }, [\n navigate,\n catalogRoute,\n unregisterRedirectRoute,\n setConfirmationDialogOpen,\n ]);\n\n // Make sure to close the dialog if the user clicks links in it that navigate\n // to another entity.\n useEffect(() => {\n setConfirmationDialogOpen(false);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [location.pathname]);\n\n const [searchParams, setSearchParams] = useSearchParams();\n const selectedInspectEntityDialogTab = searchParams.get('inspect');\n\n const setInspectEntityDialogTab = useCallback(\n (newTab: string) => setSearchParams(`inspect=${newTab}`),\n [setSearchParams],\n );\n\n const openInspectEntityDialog = useCallback(\n () => setSearchParams('inspect'),\n [setSearchParams],\n );\n\n const closeInspectEntityDialog = useCallback(\n () => setSearchParams(),\n [setSearchParams],\n );\n\n const inspectDialogOpen = typeof selectedInspectEntityDialogTab === 'string';\n\n return (\n <Header\n pageTitleOverride={entityFallbackText}\n type={type}\n title={title ?? <EntityHeaderTitle />}\n subtitle={\n subtitle ?? (\n <EntityHeaderSubtitle parentEntityRelations={parentEntityRelations} />\n )\n }\n >\n {entity && (\n <>\n <EntityLabels entity={entity} />\n <EntityContextMenu\n UNSTABLE_extraContextMenuItems={UNSTABLE_extraContextMenuItems}\n UNSTABLE_contextMenuOptions={UNSTABLE_contextMenuOptions}\n contextMenuItems={contextMenuItems}\n onInspectEntity={openInspectEntityDialog}\n onUnregisterEntity={openUnregisterEntityDialog}\n />\n <InspectEntityDialog\n entity={entity!}\n initialTab={\n (selectedInspectEntityDialogTab as ComponentProps<\n typeof InspectEntityDialog\n >['initialTab']) || undefined\n }\n open={inspectDialogOpen}\n onClose={closeInspectEntityDialog}\n onSelect={setInspectEntityDialogTab}\n />\n <UnregisterEntityDialog\n entity={entity!}\n open={confirmationDialogOpen}\n onClose={closeUnregisterEntityDialog}\n onConfirm={cleanUpAfterUnregisterConfirmation}\n />\n </>\n )}\n </Header>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"EntityHeader.esm.js","sources":["../../../../src/alpha/components/EntityHeader/EntityHeader.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 { useCallback, ComponentProps, ReactNode } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport useAsync from 'react-use/esm/useAsync';\n\nimport { makeStyles } from '@material-ui/core/styles';\nimport Box from '@material-ui/core/Box';\n\nimport { Header, Breadcrumbs } from '@backstage/core-components';\nimport { useApi, useRouteRefParams } from '@backstage/core-plugin-api';\nimport { IconComponent } from '@backstage/frontend-plugin-api';\n\nimport {\n Entity,\n EntityRelation,\n DEFAULT_NAMESPACE,\n} from '@backstage/catalog-model';\n\nimport {\n useAsyncEntity,\n entityRouteRef,\n catalogApiRef,\n EntityRefLink,\n InspectEntityDialog,\n EntityDisplayName,\n FavoriteEntity,\n} from '@backstage/plugin-catalog-react';\n\nimport { EntityLabels } from '../EntityLabels';\nimport {\n EntityContextMenu,\n type EntityContextMenuItemDataWithNode,\n} from '../EntityContextMenu';\n\nfunction headerProps(\n paramKind: string | undefined,\n paramNamespace: string | undefined,\n paramName: string | undefined,\n entity: Entity | undefined,\n): { headerTitle: string; headerType: string } {\n const kind = paramKind ?? entity?.kind ?? '';\n const namespace = paramNamespace ?? entity?.metadata.namespace ?? '';\n const name =\n entity?.metadata.title ?? paramName ?? entity?.metadata.name ?? '';\n\n return {\n headerTitle: `${name}${\n namespace && namespace !== DEFAULT_NAMESPACE ? ` in ${namespace}` : ''\n }`,\n headerType: (() => {\n let t = kind.toLocaleLowerCase('en-US');\n if (entity && entity.spec && 'type' in entity.spec) {\n t += ' — ';\n t += (entity.spec as { type: string }).type.toLocaleLowerCase('en-US');\n }\n return t;\n })(),\n };\n}\n\nfunction findParentRelation(\n entityRelations: EntityRelation[] = [],\n relationTypes: string[] = [],\n) {\n for (const type of relationTypes) {\n const foundRelation = entityRelations.find(\n relation => relation.type === type,\n );\n if (foundRelation) {\n return foundRelation; // Return the first found relation and stop\n }\n }\n return null;\n}\n\nconst useStyles = makeStyles(theme => ({\n breadcrumbs: {\n color: theme.page.fontColor,\n fontSize: theme.typography.caption.fontSize,\n textTransform: 'uppercase',\n marginTop: theme.spacing(1),\n opacity: 0.8,\n '& span ': {\n color: theme.page.fontColor,\n textDecoration: 'underline',\n textUnderlineOffset: '3px',\n },\n },\n}));\n\nfunction EntityHeaderTitle() {\n const { entity } = useAsyncEntity();\n const { kind, namespace, name } = useRouteRefParams(entityRouteRef);\n const { headerTitle: title } = headerProps(kind, namespace, name, entity);\n return (\n <Box display=\"inline-flex\" alignItems=\"center\" height=\"1em\" maxWidth=\"100%\">\n <Box\n component=\"span\"\n textOverflow=\"ellipsis\"\n whiteSpace=\"nowrap\"\n overflow=\"hidden\"\n >\n {entity ? <EntityDisplayName entityRef={entity} hideIcon /> : title}\n </Box>\n {entity && <FavoriteEntity entity={entity} />}\n </Box>\n );\n}\n\nfunction EntityHeaderSubtitle(props: { parentEntityRelations?: string[] }) {\n const { parentEntityRelations } = props;\n const classes = useStyles();\n const { entity } = useAsyncEntity();\n const { name } = useRouteRefParams(entityRouteRef);\n const parentEntity = findParentRelation(\n entity?.relations ?? [],\n parentEntityRelations ?? [],\n );\n\n const catalogApi = useApi(catalogApiRef);\n\n const { value: ancestorEntity } = useAsync(async () => {\n if (parentEntity) {\n return findParentRelation(\n (await catalogApi.getEntityByRef(parentEntity?.targetRef))?.relations,\n parentEntityRelations,\n );\n }\n return null;\n }, [parentEntity, catalogApi]);\n\n return parentEntity ? (\n <Breadcrumbs separator=\">\" className={classes.breadcrumbs}>\n {ancestorEntity && (\n <EntityRefLink entityRef={ancestorEntity.targetRef} disableTooltip />\n )}\n <EntityRefLink entityRef={parentEntity.targetRef} disableTooltip />\n {name}\n </Breadcrumbs>\n ) : null;\n}\n\n/** @alpha */\nexport function EntityHeader(props: {\n // NOTE(freben): Intentionally not exported at this point, since it's part of\n // the unstable extra context menu items concept below\n UNSTABLE_extraContextMenuItems?: {\n title: string;\n Icon: IconComponent;\n onClick: () => void;\n }[];\n contextMenuItems?: EntityContextMenuItemDataWithNode[];\n /**\n * An array of relation types used to determine the parent entities in the hierarchy.\n * These relations are prioritized in the order provided, allowing for flexible\n * navigation through entity relationships.\n *\n * For example, use relation types like `[\"partOf\", \"memberOf\", \"ownedBy\"]` to define how the entity is related to\n * its parents in the Entity Catalog.\n *\n * It adds breadcrumbs in the Entity page to enhance user navigation and context awareness.\n */\n parentEntityRelations?: string[];\n title?: ReactNode;\n subtitle?: ReactNode;\n}) {\n const {\n UNSTABLE_extraContextMenuItems,\n contextMenuItems,\n parentEntityRelations,\n title,\n subtitle,\n } = props;\n const { entity } = useAsyncEntity();\n const { kind, namespace, name } = useRouteRefParams(entityRouteRef);\n const { headerTitle: entityFallbackText, headerType: type } = headerProps(\n kind,\n namespace,\n name,\n entity,\n );\n\n const [searchParams, setSearchParams] = useSearchParams();\n const selectedInspectEntityDialogTab = searchParams.get('inspect');\n\n const setInspectEntityDialogTab = useCallback(\n (newTab: string) => setSearchParams(`inspect=${newTab}`),\n [setSearchParams],\n );\n\n const closeInspectEntityDialog = useCallback(\n () => setSearchParams(),\n [setSearchParams],\n );\n\n const inspectDialogOpen = typeof selectedInspectEntityDialogTab === 'string';\n\n return (\n <Header\n pageTitleOverride={entityFallbackText}\n type={type}\n title={title ?? <EntityHeaderTitle />}\n subtitle={\n subtitle ?? (\n <EntityHeaderSubtitle parentEntityRelations={parentEntityRelations} />\n )\n }\n >\n {entity && (\n <>\n <EntityLabels entity={entity} />\n <EntityContextMenu\n UNSTABLE_extraContextMenuItems={UNSTABLE_extraContextMenuItems}\n contextMenuItems={contextMenuItems}\n />\n <InspectEntityDialog\n entity={entity!}\n initialTab={\n (selectedInspectEntityDialogTab as ComponentProps<\n typeof InspectEntityDialog\n >['initialTab']) || undefined\n }\n open={inspectDialogOpen}\n onClose={closeInspectEntityDialog}\n onSelect={setInspectEntityDialogTab}\n />\n </>\n )}\n </Header>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,143 @@
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import { useMemo } from 'react';
3
+ import { Header, HeaderMetadataUsers, ButtonIcon, Box, Link } from '@backstage/ui';
4
+ import { RELATION_PART_OF, RELATION_OWNED_BY, stringifyEntityRef } from '@backstage/catalog-model';
5
+ import { useRouteRefParams, useApi } from '@backstage/core-plugin-api';
6
+ import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
7
+ import { useAsyncEntity, entityRouteRef, useEntityPresentation, getEntityRelations, catalogReactTranslationRef, useStarredEntity, catalogApiRef, useEntityRefLink } from '@backstage/plugin-catalog-react';
8
+ import { RiStarFill, RiStarLine } from '@remixicon/react';
9
+ import useAsync from 'react-use/esm/useAsync';
10
+ import { catalogTranslationRef } from '../../translation.esm.js';
11
+ import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu.esm.js';
12
+
13
+ function useOwnerUsers(entity) {
14
+ const catalogApi = useApi(catalogApiRef);
15
+ const entityLink = useEntityRefLink();
16
+ const ownerRefs = useMemo(
17
+ () => getEntityRelations(entity, RELATION_OWNED_BY),
18
+ [entity]
19
+ );
20
+ const ownerRefStrings = useMemo(
21
+ () => ownerRefs.map((ref) => stringifyEntityRef(ref)),
22
+ [ownerRefs]
23
+ );
24
+ const { value: ownerEntities } = useAsync(async () => {
25
+ if (ownerRefStrings.length === 0) return [];
26
+ return (await catalogApi.getEntitiesByRefs({
27
+ entityRefs: ownerRefStrings,
28
+ fields: [
29
+ "kind",
30
+ "metadata.name",
31
+ "metadata.namespace",
32
+ "metadata.title",
33
+ "spec.profile"
34
+ ]
35
+ })).items;
36
+ }, [catalogApi, ownerRefStrings]);
37
+ return ownerRefs.map((ref, index) => {
38
+ const owner = ownerEntities?.[index];
39
+ const profile = owner?.spec?.profile;
40
+ return {
41
+ name: owner?.metadata.title ?? owner?.metadata.name ?? ref.name,
42
+ src: profile?.picture,
43
+ href: entityLink(ref)
44
+ };
45
+ });
46
+ }
47
+ function HierarchyLinks(props) {
48
+ const entityLink = useEntityRefLink();
49
+ return /* @__PURE__ */ jsx(Box, { as: "ul", display: "inline", m: "0", p: "0", style: { listStyle: "none" }, children: props.refs.map((ref, index) => /* @__PURE__ */ jsxs(Box, { as: "li", display: "inline", children: [
50
+ index > 0 ? ", " : null,
51
+ /* @__PURE__ */ jsx(Link, { href: entityLink(ref), standalone: true, children: ref.name })
52
+ ] }, stringifyEntityRef(ref))) });
53
+ }
54
+ function hierarchyLabel(ref) {
55
+ switch (ref.kind.toLocaleLowerCase("en-US")) {
56
+ case "system":
57
+ return "systemLabel";
58
+ case "domain":
59
+ return "domainLabel";
60
+ default:
61
+ return "partOfLabel";
62
+ }
63
+ }
64
+ function useMetadata(entity) {
65
+ const owners = useOwnerUsers(entity);
66
+ const { t } = useTranslationRef(catalogTranslationRef);
67
+ return useMemo(() => {
68
+ if (!entity) return [];
69
+ const metadata = [];
70
+ const lifecycle = entity.spec?.lifecycle?.toString();
71
+ if (lifecycle) {
72
+ metadata.push({
73
+ label: t("entityLabels.lifecycleLabel"),
74
+ value: lifecycle
75
+ });
76
+ }
77
+ if (owners.length > 0) {
78
+ metadata.push({
79
+ label: t("entityLabels.ownerLabel"),
80
+ value: /* @__PURE__ */ jsx(HeaderMetadataUsers, { users: owners })
81
+ });
82
+ }
83
+ const hierarchy = getEntityRelations(entity, RELATION_PART_OF).reduce(
84
+ (groups, ref) => {
85
+ const label = hierarchyLabel(ref);
86
+ groups[label] = [...groups[label] ?? [], ref];
87
+ return groups;
88
+ },
89
+ {}
90
+ );
91
+ for (const [label, refs] of Object.entries(hierarchy)) {
92
+ metadata.push({
93
+ label: t(
94
+ `entityLabels.${label}`
95
+ ),
96
+ value: /* @__PURE__ */ jsx(HierarchyLinks, { refs })
97
+ });
98
+ }
99
+ return metadata;
100
+ }, [entity, owners, t]);
101
+ }
102
+ function FavoriteEntityButton(props) {
103
+ const { t } = useTranslationRef(catalogReactTranslationRef);
104
+ const { isStarredEntity, toggleStarredEntity } = useStarredEntity(
105
+ props.entity
106
+ );
107
+ return /* @__PURE__ */ jsx(
108
+ ButtonIcon,
109
+ {
110
+ variant: "secondary",
111
+ "aria-label": isStarredEntity ? t("favoriteEntity.removeFromFavorites") : t("favoriteEntity.addToFavorites"),
112
+ icon: isStarredEntity ? /* @__PURE__ */ jsx(RiStarFill, {}) : /* @__PURE__ */ jsx(RiStarLine, {}),
113
+ onPress: () => toggleStarredEntity()
114
+ }
115
+ );
116
+ }
117
+ function EntityHeaderBui(props) {
118
+ const { entity } = useAsyncEntity();
119
+ const routeParams = useRouteRefParams(entityRouteRef);
120
+ const presentation = useEntityPresentation(entity ?? routeParams);
121
+ const metadata = useMetadata(entity);
122
+ const type = entity?.spec?.type?.toString();
123
+ return /* @__PURE__ */ jsx(
124
+ Header,
125
+ {
126
+ title: presentation.primaryTitle,
127
+ tags: [
128
+ { label: entity?.kind ?? routeParams.kind },
129
+ ...type ? [{ label: type }] : []
130
+ ],
131
+ metadata,
132
+ tabs: entity ? props.tabs : void 0,
133
+ activeTabId: props.activeTabId,
134
+ customActions: entity ? /* @__PURE__ */ jsxs(Fragment, { children: [
135
+ /* @__PURE__ */ jsx(FavoriteEntityButton, { entity }),
136
+ /* @__PURE__ */ jsx(EntityContextMenu, { contextMenuItems: props.contextMenuItems })
137
+ ] }) : void 0
138
+ }
139
+ );
140
+ }
141
+
142
+ export { EntityHeaderBui };
143
+ //# sourceMappingURL=EntityHeaderBui.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EntityHeaderBui.esm.js","sources":["../../../../src/alpha/components/EntityHeader/EntityHeaderBui.tsx"],"sourcesContent":["/*\n * Copyright 2026 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 { useMemo } from 'react';\nimport {\n Box,\n ButtonIcon,\n Header,\n HeaderMetadataUsers,\n Link,\n type HeaderMetadataItem,\n type HeaderMetadataUser,\n type HeaderNavTabItem,\n} from '@backstage/ui';\nimport {\n Entity,\n RELATION_OWNED_BY,\n RELATION_PART_OF,\n stringifyEntityRef,\n type CompoundEntityRef,\n} from '@backstage/catalog-model';\nimport { useApi, useRouteRefParams } from '@backstage/core-plugin-api';\nimport { useTranslationRef } from '@backstage/core-plugin-api/alpha';\nimport {\n catalogApiRef,\n catalogReactTranslationRef,\n entityRouteRef,\n getEntityRelations,\n useAsyncEntity,\n useEntityPresentation,\n useEntityRefLink,\n useStarredEntity,\n} from '@backstage/plugin-catalog-react';\nimport { RiStarFill, RiStarLine } from '@remixicon/react';\nimport useAsync from 'react-use/esm/useAsync';\nimport { catalogTranslationRef } from '../../translation';\nimport {\n EntityContextMenu,\n type EntityContextMenuItemDataWithNode,\n} from '../EntityContextMenu';\n\nfunction useOwnerUsers(entity: Entity | undefined): HeaderMetadataUser[] {\n const catalogApi = useApi(catalogApiRef);\n const entityLink = useEntityRefLink();\n const ownerRefs = useMemo(\n () => getEntityRelations(entity, RELATION_OWNED_BY),\n [entity],\n );\n const ownerRefStrings = useMemo(\n () => ownerRefs.map(ref => stringifyEntityRef(ref)),\n [ownerRefs],\n );\n const { value: ownerEntities } = useAsync(async () => {\n if (ownerRefStrings.length === 0) return [];\n return (\n await catalogApi.getEntitiesByRefs({\n entityRefs: ownerRefStrings,\n fields: [\n 'kind',\n 'metadata.name',\n 'metadata.namespace',\n 'metadata.title',\n 'spec.profile',\n ],\n })\n ).items;\n }, [catalogApi, ownerRefStrings]);\n\n return ownerRefs.map((ref, index) => {\n const owner = ownerEntities?.[index];\n const profile = (owner?.spec as { profile?: { picture?: string } })\n ?.profile;\n return {\n name: owner?.metadata.title ?? owner?.metadata.name ?? ref.name,\n src: profile?.picture,\n href: entityLink(ref),\n };\n });\n}\n\nfunction HierarchyLinks(props: { refs: CompoundEntityRef[] }) {\n const entityLink = useEntityRefLink();\n return (\n <Box as=\"ul\" display=\"inline\" m=\"0\" p=\"0\" style={{ listStyle: 'none' }}>\n {props.refs.map((ref, index) => (\n <Box as=\"li\" display=\"inline\" key={stringifyEntityRef(ref)}>\n {index > 0 ? ', ' : null}\n <Link href={entityLink(ref)} standalone>\n {ref.name}\n </Link>\n </Box>\n ))}\n </Box>\n );\n}\n\nfunction hierarchyLabel(\n ref: CompoundEntityRef,\n): 'systemLabel' | 'domainLabel' | 'partOfLabel' {\n switch (ref.kind.toLocaleLowerCase('en-US')) {\n case 'system':\n return 'systemLabel';\n case 'domain':\n return 'domainLabel';\n default:\n return 'partOfLabel';\n }\n}\n\nfunction useMetadata(entity: Entity | undefined): HeaderMetadataItem[] {\n const owners = useOwnerUsers(entity);\n const { t } = useTranslationRef(catalogTranslationRef);\n return useMemo(() => {\n if (!entity) return [];\n const metadata: HeaderMetadataItem[] = [];\n const lifecycle = entity.spec?.lifecycle?.toString();\n if (lifecycle) {\n metadata.push({\n label: t('entityLabels.lifecycleLabel'),\n value: lifecycle,\n });\n }\n if (owners.length > 0) {\n metadata.push({\n label: t('entityLabels.ownerLabel'),\n value: <HeaderMetadataUsers users={owners} />,\n });\n }\n\n const hierarchy = getEntityRelations(entity, RELATION_PART_OF).reduce(\n (groups, ref) => {\n const label = hierarchyLabel(ref);\n groups[label] = [...(groups[label] ?? []), ref];\n return groups;\n },\n {} as Record<string, CompoundEntityRef[]>,\n );\n for (const [label, refs] of Object.entries(hierarchy)) {\n metadata.push({\n label: t(\n `entityLabels.${label}` as\n | 'entityLabels.systemLabel'\n | 'entityLabels.domainLabel'\n | 'entityLabels.partOfLabel',\n ),\n value: <HierarchyLinks refs={refs} />,\n });\n }\n return metadata;\n }, [entity, owners, t]);\n}\n\nfunction FavoriteEntityButton(props: { entity: Entity }) {\n const { t } = useTranslationRef(catalogReactTranslationRef);\n const { isStarredEntity, toggleStarredEntity } = useStarredEntity(\n props.entity,\n );\n return (\n <ButtonIcon\n variant=\"secondary\"\n aria-label={\n isStarredEntity\n ? t('favoriteEntity.removeFromFavorites')\n : t('favoriteEntity.addToFavorites')\n }\n icon={isStarredEntity ? <RiStarFill /> : <RiStarLine />}\n onPress={() => toggleStarredEntity()}\n />\n );\n}\n\nexport function EntityHeaderBui(props: {\n tabs: HeaderNavTabItem[];\n activeTabId?: string;\n contextMenuItems?: EntityContextMenuItemDataWithNode[];\n}) {\n const { entity } = useAsyncEntity();\n const routeParams = useRouteRefParams(entityRouteRef);\n const presentation = useEntityPresentation(entity ?? routeParams);\n const metadata = useMetadata(entity);\n const type = entity?.spec?.type?.toString();\n\n return (\n <Header\n title={presentation.primaryTitle}\n tags={[\n { label: entity?.kind ?? routeParams.kind },\n ...(type ? [{ label: type }] : []),\n ]}\n metadata={metadata}\n tabs={entity ? props.tabs : undefined}\n activeTabId={props.activeTabId}\n customActions={\n entity ? (\n <>\n <FavoriteEntityButton entity={entity} />\n <EntityContextMenu contextMenuItems={props.contextMenuItems} />\n </>\n ) : undefined\n }\n />\n );\n}\n"],"names":[],"mappings":"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"}
@@ -1,21 +1,17 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import Alert from '@material-ui/lab/Alert';
3
- import { useRouteRefParams, useElementFilter, attachComponentData } from '@backstage/core-plugin-api';
3
+ import { useRouteRefParams } from '@backstage/core-plugin-api';
4
4
  import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
5
5
  import { Page, Progress, Content, WarningPanel, Link } from '@backstage/core-components';
6
6
  import { entityRouteRef, useAsyncEntity } from '@backstage/plugin-catalog-react';
7
7
  import { catalogTranslationRef } from '../../translation.esm.js';
8
8
  import { EntityHeader } from '../EntityHeader/EntityHeader.esm.js';
9
9
  import { EntityTabs } from '../EntityTabs/EntityTabs.esm.js';
10
+ import { useEntityLayoutRoutes, EntityLayoutRoute } from './entityLayoutRoutes.esm.js';
10
11
 
11
- const dataKey = "plugin.catalog.entityLayoutRoute";
12
- const Route = () => null;
13
- attachComponentData(Route, dataKey, true);
14
- attachComponentData(Route, "core.gatherMountPoints", true);
15
12
  const EntityLayout = (props) => {
16
13
  const {
17
14
  UNSTABLE_extraContextMenuItems,
18
- UNSTABLE_contextMenuOptions,
19
15
  contextMenuItems,
20
16
  children,
21
17
  header,
@@ -27,37 +23,13 @@ const EntityLayout = (props) => {
27
23
  } = props;
28
24
  const { kind } = useRouteRefParams(entityRouteRef);
29
25
  const { entity, loading, error } = useAsyncEntity();
30
- const routes = useElementFilter(
31
- children,
32
- (elements) => elements.selectByComponentData({
33
- key: dataKey,
34
- withStrictError: "Child of EntityLayout must be an EntityLayout.Route"
35
- }).getElements().flatMap(({ props: elementProps }) => {
36
- if (!entity) {
37
- return [];
38
- }
39
- if (elementProps.if && !elementProps.if(entity)) {
40
- return [];
41
- }
42
- return [
43
- {
44
- path: elementProps.path,
45
- title: elementProps.title,
46
- group: elementProps.group,
47
- children: elementProps.children,
48
- icon: elementProps.icon
49
- }
50
- ];
51
- }),
52
- [entity]
53
- );
26
+ const routes = useEntityLayoutRoutes(children, entity);
54
27
  const { t } = useTranslationRef(catalogTranslationRef);
55
28
  return /* @__PURE__ */ jsxs(Page, { themeId: entity?.spec?.type?.toString() ?? "home", children: [
56
29
  header ?? /* @__PURE__ */ jsx(
57
30
  EntityHeader,
58
31
  {
59
32
  parentEntityRelations,
60
- UNSTABLE_contextMenuOptions,
61
33
  UNSTABLE_extraContextMenuItems,
62
34
  contextMenuItems
63
35
  }
@@ -79,7 +51,7 @@ const EntityLayout = (props) => {
79
51
  }) }) })
80
52
  ] });
81
53
  };
82
- EntityLayout.Route = Route;
54
+ EntityLayout.Route = EntityLayoutRoute;
83
55
 
84
56
  export { EntityLayout };
85
57
  //# sourceMappingURL=EntityLayout.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"EntityLayout.esm.js","sources":["../../../../src/alpha/components/EntityLayout/EntityLayout.tsx"],"sourcesContent":["/*\n * Copyright 2020 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 { ComponentProps, ReactNode, ReactElement } from 'react';\n\nimport Alert from '@material-ui/lab/Alert';\n\nimport {\n attachComponentData,\n useElementFilter,\n useRouteRefParams,\n} from '@backstage/core-plugin-api';\nimport { useTranslationRef } from '@backstage/core-plugin-api/alpha';\nimport {\n Content,\n Link,\n Page,\n Progress,\n WarningPanel,\n} from '@backstage/core-components';\nimport { Entity } from '@backstage/catalog-model';\nimport {\n entityRouteRef,\n useAsyncEntity,\n} from '@backstage/plugin-catalog-react';\n\nimport { catalogTranslationRef } from '../../translation';\nimport { EntityHeader } from '../EntityHeader';\nimport { EntityTabs } from '../EntityTabs';\nimport { EntityContentGroupDefinitions } from '@backstage/plugin-catalog-react/alpha';\n\nexport type EntityLayoutRouteProps = {\n path: string;\n title: string;\n group?: string;\n icon?: string | ReactElement;\n children: JSX.Element;\n if?: (entity: Entity) => boolean;\n};\n\nconst dataKey = 'plugin.catalog.entityLayoutRoute';\nconst Route: (props: EntityLayoutRouteProps) => null = () => null;\nattachComponentData(Route, dataKey, true);\nattachComponentData(Route, 'core.gatherMountPoints', true); // This causes all mount points that are discovered within this route to use the path of the route itself\n\n/** @public */\nexport interface EntityLayoutProps {\n UNSTABLE_contextMenuOptions?: ComponentProps<\n typeof EntityHeader\n >['UNSTABLE_contextMenuOptions'];\n UNSTABLE_extraContextMenuItems?: ComponentProps<\n typeof EntityHeader\n >['UNSTABLE_extraContextMenuItems'];\n contextMenuItems?: ComponentProps<typeof EntityHeader>['contextMenuItems'];\n children?: ReactNode;\n header?: JSX.Element;\n NotFoundComponent?: ReactNode;\n /**\n * An array of relation types used to determine the parent entities in the hierarchy.\n * These relations are prioritized in the order provided, allowing for flexible\n * navigation through entity relationships.\n *\n * For example, use relation types like `[\"partOf\", \"memberOf\", \"ownedBy\"]` to define how the entity is related to\n * its parents in the Entity Catalog.\n *\n * It adds breadcrumbs in the Entity page to enhance user navigation and context awareness.\n */\n parentEntityRelations?: string[];\n groupDefinitions: EntityContentGroupDefinitions;\n defaultContentOrder?: 'title' | 'natural';\n showNavItemIcons?: boolean;\n}\n\n/**\n * EntityLayout is a compound component, which allows you to define a layout for\n * entities using a sub-navigation mechanism.\n *\n * Consists of two parts: EntityLayout and EntityLayout.Route\n *\n * @example\n * ```jsx\n * <EntityLayout>\n * <EntityLayout.Route path=\"/example\" title=\"Example tab\">\n * <div>This is rendered under /example/anything-here route</div>\n * </EntityLayout.Route>\n * </EntityLayout>\n * ```\n *\n * @public\n */\nexport const EntityLayout = (props: EntityLayoutProps) => {\n const {\n UNSTABLE_extraContextMenuItems,\n UNSTABLE_contextMenuOptions,\n contextMenuItems,\n children,\n header,\n NotFoundComponent,\n parentEntityRelations,\n groupDefinitions,\n defaultContentOrder,\n showNavItemIcons,\n } = props;\n const { kind } = useRouteRefParams(entityRouteRef);\n const { entity, loading, error } = useAsyncEntity();\n\n const routes = useElementFilter(\n children,\n elements =>\n elements\n .selectByComponentData({\n key: dataKey,\n withStrictError:\n 'Child of EntityLayout must be an EntityLayout.Route',\n })\n .getElements<EntityLayoutRouteProps>() // all nodes, element data, maintain structure or not?\n .flatMap(({ props: elementProps }) => {\n if (!entity) {\n return [];\n }\n if (elementProps.if && !elementProps.if(entity)) {\n return [];\n }\n return [\n {\n path: elementProps.path,\n title: elementProps.title,\n group: elementProps.group,\n children: elementProps.children,\n icon: elementProps.icon,\n },\n ];\n }),\n [entity],\n );\n\n const { t } = useTranslationRef(catalogTranslationRef);\n\n return (\n <Page themeId={entity?.spec?.type?.toString() ?? 'home'}>\n {header ?? (\n <EntityHeader\n parentEntityRelations={parentEntityRelations}\n UNSTABLE_contextMenuOptions={UNSTABLE_contextMenuOptions}\n UNSTABLE_extraContextMenuItems={UNSTABLE_extraContextMenuItems}\n contextMenuItems={contextMenuItems}\n />\n )}\n\n {loading && <Progress />}\n\n {entity && (\n <EntityTabs\n routes={routes}\n groupDefinitions={groupDefinitions}\n defaultContentOrder={defaultContentOrder}\n showIcons={showNavItemIcons}\n />\n )}\n\n {error && (\n <Content>\n <Alert severity=\"error\">{error.toString()}</Alert>\n </Content>\n )}\n\n {!loading && !error && !entity && (\n <Content>\n {NotFoundComponent ? (\n NotFoundComponent\n ) : (\n <WarningPanel title={t('entityLabels.warningPanelTitle')}>\n {t('entityPage.notFoundMessage', {\n kind,\n link: (\n <Link to=\"https://backstage.io/docs/features/software-catalog/references\">\n {t('entityPage.notFoundLinkText')}\n </Link>\n ),\n })}\n </WarningPanel>\n )}\n </Content>\n )}\n </Page>\n );\n};\n\nEntityLayout.Route = Route;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"EntityLayout.esm.js","sources":["../../../../src/alpha/components/EntityLayout/EntityLayout.tsx"],"sourcesContent":["/*\n * Copyright 2020 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 { ComponentProps, ReactNode } from 'react';\n\nimport Alert from '@material-ui/lab/Alert';\n\nimport { useRouteRefParams } from '@backstage/core-plugin-api';\nimport { useTranslationRef } from '@backstage/core-plugin-api/alpha';\nimport {\n Content,\n Link,\n Page,\n Progress,\n WarningPanel,\n} from '@backstage/core-components';\nimport {\n entityRouteRef,\n useAsyncEntity,\n} from '@backstage/plugin-catalog-react';\n\nimport { catalogTranslationRef } from '../../translation';\nimport { EntityHeader } from '../EntityHeader';\nimport { EntityTabs } from '../EntityTabs';\nimport { EntityContentGroupDefinitions } from '@backstage/plugin-catalog-react/alpha';\nimport { EntityLayoutRoute, useEntityLayoutRoutes } from './entityLayoutRoutes';\n\nexport type { EntityLayoutRouteProps } from './entityLayoutRoutes';\n\n/** @public */\nexport interface EntityLayoutProps {\n UNSTABLE_extraContextMenuItems?: ComponentProps<\n typeof EntityHeader\n >['UNSTABLE_extraContextMenuItems'];\n contextMenuItems?: ComponentProps<typeof EntityHeader>['contextMenuItems'];\n children?: ReactNode;\n header?: JSX.Element;\n NotFoundComponent?: ReactNode;\n /**\n * An array of relation types used to determine the parent entities in the hierarchy.\n * These relations are prioritized in the order provided, allowing for flexible\n * navigation through entity relationships.\n *\n * For example, use relation types like `[\"partOf\", \"memberOf\", \"ownedBy\"]` to define how the entity is related to\n * its parents in the Entity Catalog.\n *\n * It adds breadcrumbs in the Entity page to enhance user navigation and context awareness.\n */\n parentEntityRelations?: string[];\n groupDefinitions: EntityContentGroupDefinitions;\n defaultContentOrder?: 'title' | 'natural';\n showNavItemIcons?: boolean;\n}\n\n/**\n * EntityLayout is a compound component, which allows you to define a layout for\n * entities using a sub-navigation mechanism.\n *\n * Consists of two parts: EntityLayout and EntityLayout.Route\n *\n * @example\n * ```jsx\n * <EntityLayout>\n * <EntityLayout.Route path=\"/example\" title=\"Example tab\">\n * <div>This is rendered under /example/anything-here route</div>\n * </EntityLayout.Route>\n * </EntityLayout>\n * ```\n *\n * @public\n */\nexport const EntityLayout = (props: EntityLayoutProps) => {\n const {\n UNSTABLE_extraContextMenuItems,\n contextMenuItems,\n children,\n header,\n NotFoundComponent,\n parentEntityRelations,\n groupDefinitions,\n defaultContentOrder,\n showNavItemIcons,\n } = props;\n const { kind } = useRouteRefParams(entityRouteRef);\n const { entity, loading, error } = useAsyncEntity();\n\n const routes = useEntityLayoutRoutes(children, entity);\n\n const { t } = useTranslationRef(catalogTranslationRef);\n\n return (\n <Page themeId={entity?.spec?.type?.toString() ?? 'home'}>\n {header ?? (\n <EntityHeader\n parentEntityRelations={parentEntityRelations}\n UNSTABLE_extraContextMenuItems={UNSTABLE_extraContextMenuItems}\n contextMenuItems={contextMenuItems}\n />\n )}\n\n {loading && <Progress />}\n\n {entity && (\n <EntityTabs\n routes={routes}\n groupDefinitions={groupDefinitions}\n defaultContentOrder={defaultContentOrder}\n showIcons={showNavItemIcons}\n />\n )}\n\n {error && (\n <Content>\n <Alert severity=\"error\">{error.toString()}</Alert>\n </Content>\n )}\n\n {!loading && !error && !entity && (\n <Content>\n {NotFoundComponent ? (\n NotFoundComponent\n ) : (\n <WarningPanel title={t('entityLabels.warningPanelTitle')}>\n {t('entityPage.notFoundMessage', {\n kind,\n link: (\n <Link to=\"https://backstage.io/docs/features/software-catalog/references\">\n {t('entityPage.notFoundLinkText')}\n </Link>\n ),\n })}\n </WarningPanel>\n )}\n </Content>\n )}\n </Page>\n );\n};\n\nEntityLayout.Route = EntityLayoutRoute;\n"],"names":[],"mappings":";;;;;;;;;;;AAoFO,MAAM,YAAA,GAAe,CAAC,KAAA,KAA6B;AACxD,EAAA,MAAM;AAAA,IACJ,8BAAA;AAAA,IACA,gBAAA;AAAA,IACA,QAAA;AAAA,IACA,MAAA;AAAA,IACA,iBAAA;AAAA,IACA,qBAAA;AAAA,IACA,gBAAA;AAAA,IACA,mBAAA;AAAA,IACA;AAAA,GACF,GAAI,KAAA;AACJ,EAAA,MAAM,EAAE,IAAA,EAAK,GAAI,iBAAA,CAAkB,cAAc,CAAA;AACjD,EAAA,MAAM,EAAE,MAAA,EAAQ,OAAA,EAAS,KAAA,KAAU,cAAA,EAAe;AAElD,EAAA,MAAM,MAAA,GAAS,qBAAA,CAAsB,QAAA,EAAU,MAAM,CAAA;AAErD,EAAA,MAAM,EAAE,CAAA,EAAE,GAAI,iBAAA,CAAkB,qBAAqB,CAAA;AAErD,EAAA,uBACE,IAAA,CAAC,QAAK,OAAA,EAAS,MAAA,EAAQ,MAAM,IAAA,EAAM,QAAA,MAAc,MAAA,EAC9C,QAAA,EAAA;AAAA,IAAA,MAAA,oBACC,GAAA;AAAA,MAAC,YAAA;AAAA,MAAA;AAAA,QACC,qBAAA;AAAA,QACA,8BAAA;AAAA,QACA;AAAA;AAAA,KACF;AAAA,IAGD,OAAA,wBAAY,QAAA,EAAA,EAAS,CAAA;AAAA,IAErB,MAAA,oBACC,GAAA;AAAA,MAAC,UAAA;AAAA,MAAA;AAAA,QACC,MAAA;AAAA,QACA,gBAAA;AAAA,QACA,mBAAA;AAAA,QACA,SAAA,EAAW;AAAA;AAAA,KACb;AAAA,IAGD,KAAA,oBACC,GAAA,CAAC,OAAA,EAAA,EACC,QAAA,kBAAA,GAAA,CAAC,KAAA,EAAA,EAAM,UAAS,OAAA,EAAS,QAAA,EAAA,KAAA,CAAM,QAAA,EAAS,EAAE,CAAA,EAC5C,CAAA;AAAA,IAGD,CAAC,OAAA,IAAW,CAAC,KAAA,IAAS,CAAC,0BACtB,GAAA,CAAC,OAAA,EAAA,EACE,QAAA,EAAA,iBAAA,GACC,iBAAA,uBAEC,YAAA,EAAA,EAAa,KAAA,EAAO,EAAE,gCAAgC,CAAA,EACpD,YAAE,4BAAA,EAA8B;AAAA,MAC/B,IAAA;AAAA,MACA,sBACE,GAAA,CAAC,IAAA,EAAA,EAAK,IAAG,gEAAA,EACN,QAAA,EAAA,CAAA,CAAE,6BAA6B,CAAA,EAClC;AAAA,KAEH,GACH,CAAA,EAEJ;AAAA,GAAA,EAEJ,CAAA;AAEJ;AAEA,YAAA,CAAa,KAAA,GAAQ,iBAAA;;;;"}