@backstage/plugin-catalog 1.27.0 → 1.28.0-next.1

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 (27) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/dist/alpha/EntityOverviewPage.esm.js.map +1 -1
  3. package/dist/alpha/components/EntityLabels/EntityLabels.esm.js +36 -0
  4. package/dist/alpha/components/EntityLabels/EntityLabels.esm.js.map +1 -0
  5. package/dist/alpha/components/EntityLayout/EntityLayout.esm.js +186 -0
  6. package/dist/alpha/components/EntityLayout/EntityLayout.esm.js.map +1 -0
  7. package/dist/alpha/components/EntityLayout/EntityLayoutTitle.esm.js +20 -0
  8. package/dist/alpha/components/EntityLayout/EntityLayoutTitle.esm.js.map +1 -0
  9. package/dist/alpha/components/EntityTabs/EntityTabs.esm.js +55 -0
  10. package/dist/alpha/components/EntityTabs/EntityTabs.esm.js.map +1 -0
  11. package/dist/alpha/components/EntityTabs/EntityTabsGroup.esm.js +240 -0
  12. package/dist/alpha/components/EntityTabs/EntityTabsGroup.esm.js.map +1 -0
  13. package/dist/alpha/components/EntityTabs/EntityTabsList.esm.js +94 -0
  14. package/dist/alpha/components/EntityTabs/EntityTabsList.esm.js.map +1 -0
  15. package/dist/alpha/components/EntityTabs/EntityTabsPanel.esm.js +47 -0
  16. package/dist/alpha/components/EntityTabs/EntityTabsPanel.esm.js.map +1 -0
  17. package/dist/alpha/entityContents.esm.js +62 -17
  18. package/dist/alpha/entityContents.esm.js.map +1 -1
  19. package/dist/alpha/pages.esm.js +42 -8
  20. package/dist/alpha/pages.esm.js.map +1 -1
  21. package/dist/alpha.d.ts +76 -0
  22. package/dist/components/HasSubcomponentsCard/HasSubcomponentsCard.esm.js +3 -2
  23. package/dist/components/HasSubcomponentsCard/HasSubcomponentsCard.esm.js.map +1 -1
  24. package/dist/index.d.ts +1 -0
  25. package/package.json +24 -22
  26. /package/dist/{components → alpha/components}/EntityLayout/index.esm.js +0 -0
  27. /package/dist/{components → alpha/components}/EntityLayout/index.esm.js.map +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,74 @@
1
1
  # @backstage/plugin-catalog
2
2
 
3
+ ## 1.28.0-next.1
4
+
5
+ ### Minor Changes
6
+
7
+ - 06d1226: Allow providing `kind` parameters to replace the default `Component` kind for `SubComponents` card
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies
12
+ - @backstage/core-components@0.16.5-next.0
13
+ - @backstage/plugin-scaffolder-common@1.5.10-next.0
14
+ - @backstage/plugin-catalog-react@1.16.0-next.1
15
+ - @backstage/core-compat-api@0.3.7-next.1
16
+ - @backstage/catalog-client@1.9.1
17
+ - @backstage/catalog-model@1.7.3
18
+ - @backstage/core-plugin-api@1.10.4
19
+ - @backstage/errors@1.2.7
20
+ - @backstage/frontend-plugin-api@0.9.6-next.1
21
+ - @backstage/integration-react@1.2.4
22
+ - @backstage/types@1.2.1
23
+ - @backstage/plugin-catalog-common@1.1.3
24
+ - @backstage/plugin-permission-react@0.4.31
25
+ - @backstage/plugin-search-common@1.2.17
26
+ - @backstage/plugin-search-react@1.8.7-next.1
27
+
28
+ ## 1.28.0-next.0
29
+
30
+ ### Minor Changes
31
+
32
+ - a3d93ca: The `Overview` entity content now supports custom cards grid layouts.
33
+
34
+ ### Patch Changes
35
+
36
+ - ba9649a: Update the default entity page extension component to support grouping multiple entity content items in the same tab.
37
+
38
+ Disable all default groups:
39
+
40
+ ```diff
41
+ # app-config.yaml
42
+ app:
43
+ extensions:
44
+ # Pages
45
+ + - page:catalog/entity:
46
+ + config:
47
+ + groups: []
48
+ ```
49
+
50
+ Create a custom list of :
51
+
52
+ ```diff
53
+ # app-config.yaml
54
+ app:
55
+ extensions:
56
+ # Pages
57
+ + - page:catalog/entity:
58
+ + config:
59
+ + groups:
60
+ + # This array of groups completely replaces the default groups
61
+ + - custom:
62
+ + title: 'Custom'
63
+ ```
64
+
65
+ - Updated dependencies
66
+ - @backstage/plugin-search-react@1.8.7-next.0
67
+ - @backstage/plugin-catalog-react@1.16.0-next.0
68
+ - @backstage/frontend-plugin-api@0.9.6-next.0
69
+ - @backstage/core-compat-api@0.3.7-next.0
70
+ - @backstage/integration-react@1.2.4
71
+
3
72
  ## 1.27.0
4
73
 
5
74
  ### Minor Changes
@@ -1 +1 @@
1
- {"version":3,"file":"EntityOverviewPage.esm.js","sources":["../../src/alpha/EntityOverviewPage.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 { Entity } from '@backstage/catalog-model';\nimport { useEntity } from '@backstage/plugin-catalog-react';\nimport Grid from '@material-ui/core/Grid';\nimport React from 'react';\nimport { FilterWrapper } from './filter/FilterWrapper';\nimport { EntitySwitch } from '../components/EntitySwitch';\nimport {\n EntityOrphanWarning,\n isOrphan,\n} from '../components/EntityOrphanWarning';\nimport {\n EntityRelationWarning,\n hasRelationWarnings,\n} from '../components/EntityRelationWarning';\nimport {\n EntityProcessingErrorsPanel,\n hasCatalogProcessingErrors,\n} from '../components/EntityProcessingErrorsPanel';\n\ninterface EntityOverviewPageProps {\n cards: Array<{\n element: React.JSX.Element;\n filterFunction?: (entity: Entity) => boolean;\n filterExpression?: string;\n }>;\n}\n\nconst entityWarningContent = (\n <>\n <EntitySwitch>\n <EntitySwitch.Case if={isOrphan}>\n <Grid item xs={12}>\n <EntityOrphanWarning />\n </Grid>\n </EntitySwitch.Case>\n </EntitySwitch>\n\n <EntitySwitch>\n <EntitySwitch.Case if={hasRelationWarnings}>\n <Grid item xs={12}>\n <EntityRelationWarning />\n </Grid>\n </EntitySwitch.Case>\n </EntitySwitch>\n\n <EntitySwitch>\n <EntitySwitch.Case if={hasCatalogProcessingErrors}>\n <Grid item xs={12}>\n <EntityProcessingErrorsPanel />\n </Grid>\n </EntitySwitch.Case>\n </EntitySwitch>\n </>\n);\n\nexport function EntityOverviewPage(props: EntityOverviewPageProps) {\n const { entity } = useEntity();\n return (\n <Grid container spacing={3} alignItems=\"stretch\">\n {entityWarningContent}\n {props.cards.map((card, index) => (\n <FilterWrapper key={index} entity={entity} {...card} />\n ))}\n </Grid>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;AA2CA,MAAM,oBACJ,mBAAA,KAAA,CAAA,aAAA,CAAA,KAAA,CAAA,QAAA,EAAA,IAAA,kBACG,KAAA,CAAA,aAAA,CAAA,YAAA,EAAA,IAAA,kBACE,KAAA,CAAA,aAAA,CAAA,YAAA,CAAa,IAAb,EAAA,EAAkB,EAAI,EAAA,QAAA,EAAA,kBACpB,KAAA,CAAA,aAAA,CAAA,IAAA,EAAA,EAAK,IAAI,EAAA,IAAA,EAAC,EAAI,EAAA,EAAA,EAAA,kBACZ,KAAA,CAAA,aAAA,CAAA,mBAAA,EAAA,IAAoB,CACvB,CACF,CACF,CAAA,kBAEC,KAAA,CAAA,aAAA,CAAA,YAAA,EAAA,IAAA,kBACE,KAAA,CAAA,aAAA,CAAA,YAAA,CAAa,IAAb,EAAA,EAAkB,EAAI,EAAA,mBAAA,EAAA,kBACpB,KAAA,CAAA,aAAA,CAAA,IAAA,EAAA,EAAK,IAAI,EAAA,IAAA,EAAC,EAAI,EAAA,EAAA,EAAA,kBACZ,KAAA,CAAA,aAAA,CAAA,qBAAA,EAAA,IAAsB,CACzB,CACF,CACF,CAAA,kBAEC,KAAA,CAAA,aAAA,CAAA,YAAA,EAAA,IAAA,kBACE,KAAA,CAAA,aAAA,CAAA,YAAA,CAAa,IAAb,EAAA,EAAkB,EAAI,EAAA,0BAAA,EAAA,kBACpB,KAAA,CAAA,aAAA,CAAA,IAAA,EAAA,EAAK,IAAI,EAAA,IAAA,EAAC,EAAI,EAAA,EAAA,EAAA,kBACZ,KAAA,CAAA,aAAA,CAAA,2BAAA,EAAA,IAA4B,CAC/B,CACF,CACF,CACF,CAAA;AAGK,SAAS,mBAAmB,KAAgC,EAAA;AACjE,EAAM,MAAA,EAAE,MAAO,EAAA,GAAI,SAAU,EAAA;AAC7B,EACE,uBAAA,KAAA,CAAA,aAAA,CAAC,QAAK,SAAS,EAAA,IAAA,EAAC,SAAS,CAAG,EAAA,UAAA,EAAW,SACpC,EAAA,EAAA,oBAAA,EACA,KAAM,CAAA,KAAA,CAAM,IAAI,CAAC,IAAA,EAAM,KACtB,qBAAA,KAAA,CAAA,aAAA,CAAC,aAAc,EAAA,EAAA,GAAA,EAAK,OAAO,MAAiB,EAAA,GAAG,IAAM,EAAA,CACtD,CACH,CAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"EntityOverviewPage.esm.js","sources":["../../src/alpha/EntityOverviewPage.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 { useEntity } from '@backstage/plugin-catalog-react';\nimport Grid from '@material-ui/core/Grid';\nimport React from 'react';\nimport { FilterWrapper } from './filter/FilterWrapper';\nimport { EntitySwitch } from '../components/EntitySwitch';\nimport {\n EntityOrphanWarning,\n isOrphan,\n} from '../components/EntityOrphanWarning';\nimport {\n EntityRelationWarning,\n hasRelationWarnings,\n} from '../components/EntityRelationWarning';\nimport {\n EntityProcessingErrorsPanel,\n hasCatalogProcessingErrors,\n} from '../components/EntityProcessingErrorsPanel';\n\ninterface EntityOverviewPageProps {\n cards: Array<{\n element: React.JSX.Element;\n }>;\n}\n\nconst entityWarningContent = (\n <>\n <EntitySwitch>\n <EntitySwitch.Case if={isOrphan}>\n <Grid item xs={12}>\n <EntityOrphanWarning />\n </Grid>\n </EntitySwitch.Case>\n </EntitySwitch>\n\n <EntitySwitch>\n <EntitySwitch.Case if={hasRelationWarnings}>\n <Grid item xs={12}>\n <EntityRelationWarning />\n </Grid>\n </EntitySwitch.Case>\n </EntitySwitch>\n\n <EntitySwitch>\n <EntitySwitch.Case if={hasCatalogProcessingErrors}>\n <Grid item xs={12}>\n <EntityProcessingErrorsPanel />\n </Grid>\n </EntitySwitch.Case>\n </EntitySwitch>\n </>\n);\n\nexport function EntityOverviewPage(props: EntityOverviewPageProps) {\n const { entity } = useEntity();\n return (\n <Grid container spacing={3} alignItems=\"stretch\">\n {entityWarningContent}\n {props.cards.map((card, index) => (\n <FilterWrapper key={index} entity={entity} {...card} />\n ))}\n </Grid>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;AAwCA,MAAM,oBACJ,mBAAA,KAAA,CAAA,aAAA,CAAA,KAAA,CAAA,QAAA,EAAA,IAAA,kBACG,KAAA,CAAA,aAAA,CAAA,YAAA,EAAA,IAAA,kBACE,KAAA,CAAA,aAAA,CAAA,YAAA,CAAa,IAAb,EAAA,EAAkB,EAAI,EAAA,QAAA,EAAA,kBACpB,KAAA,CAAA,aAAA,CAAA,IAAA,EAAA,EAAK,IAAI,EAAA,IAAA,EAAC,EAAI,EAAA,EAAA,EAAA,kBACZ,KAAA,CAAA,aAAA,CAAA,mBAAA,EAAA,IAAoB,CACvB,CACF,CACF,CAAA,kBAEC,KAAA,CAAA,aAAA,CAAA,YAAA,EAAA,IAAA,kBACE,KAAA,CAAA,aAAA,CAAA,YAAA,CAAa,IAAb,EAAA,EAAkB,EAAI,EAAA,mBAAA,EAAA,kBACpB,KAAA,CAAA,aAAA,CAAA,IAAA,EAAA,EAAK,IAAI,EAAA,IAAA,EAAC,EAAI,EAAA,EAAA,EAAA,kBACZ,KAAA,CAAA,aAAA,CAAA,qBAAA,EAAA,IAAsB,CACzB,CACF,CACF,CAAA,kBAEC,KAAA,CAAA,aAAA,CAAA,YAAA,EAAA,IAAA,kBACE,KAAA,CAAA,aAAA,CAAA,YAAA,CAAa,IAAb,EAAA,EAAkB,EAAI,EAAA,0BAAA,EAAA,kBACpB,KAAA,CAAA,aAAA,CAAA,IAAA,EAAA,EAAK,IAAI,EAAA,IAAA,EAAC,EAAI,EAAA,EAAA,EAAA,kBACZ,KAAA,CAAA,aAAA,CAAA,2BAAA,EAAA,IAA4B,CAC/B,CACF,CACF,CACF,CAAA;AAGK,SAAS,mBAAmB,KAAgC,EAAA;AACjE,EAAM,MAAA,EAAE,MAAO,EAAA,GAAI,SAAU,EAAA;AAC7B,EACE,uBAAA,KAAA,CAAA,aAAA,CAAC,QAAK,SAAS,EAAA,IAAA,EAAC,SAAS,CAAG,EAAA,UAAA,EAAW,SACpC,EAAA,EAAA,oBAAA,EACA,KAAM,CAAA,KAAA,CAAM,IAAI,CAAC,IAAA,EAAM,KACtB,qBAAA,KAAA,CAAA,aAAA,CAAC,aAAc,EAAA,EAAA,GAAA,EAAK,OAAO,MAAiB,EAAA,GAAG,IAAM,EAAA,CACtD,CACH,CAAA;AAEJ;;;;"}
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ import { HeaderLabel } from '@backstage/core-components';
3
+ import { RELATION_OWNED_BY } from '@backstage/catalog-model';
4
+ import { getEntityRelations, EntityRefLinks } from '@backstage/plugin-catalog-react';
5
+ import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
6
+ import { catalogTranslationRef } from '../../translation.esm.js';
7
+
8
+ function EntityLabels(props) {
9
+ const { entity } = props;
10
+ const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY);
11
+ const { t } = useTranslationRef(catalogTranslationRef);
12
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, ownedByRelations.length > 0 && /* @__PURE__ */ React.createElement(
13
+ HeaderLabel,
14
+ {
15
+ label: t("entityLabels.ownerLabel"),
16
+ contentTypograpyRootComponent: "p",
17
+ value: /* @__PURE__ */ React.createElement(
18
+ EntityRefLinks,
19
+ {
20
+ entityRefs: ownedByRelations,
21
+ defaultKind: "Group",
22
+ color: "inherit"
23
+ }
24
+ )
25
+ }
26
+ ), entity.spec?.lifecycle && /* @__PURE__ */ React.createElement(
27
+ HeaderLabel,
28
+ {
29
+ label: t("entityLabels.lifecycleLabel"),
30
+ value: entity.spec.lifecycle?.toString()
31
+ }
32
+ ));
33
+ }
34
+
35
+ export { EntityLabels };
36
+ //# sourceMappingURL=EntityLabels.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EntityLabels.esm.js","sources":["../../../../src/alpha/components/EntityLabels/EntityLabels.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 React from 'react';\nimport { HeaderLabel } from '@backstage/core-components';\nimport { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model';\nimport {\n EntityRefLinks,\n getEntityRelations,\n} from '@backstage/plugin-catalog-react';\nimport { useTranslationRef } from '@backstage/core-plugin-api/alpha';\nimport { catalogTranslationRef } from '../../../alpha/translation';\n\ntype EntityLabelsProps = {\n entity: Entity;\n};\n\nexport function EntityLabels(props: EntityLabelsProps) {\n const { entity } = props;\n const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY);\n const { t } = useTranslationRef(catalogTranslationRef);\n return (\n <>\n {ownedByRelations.length > 0 && (\n <HeaderLabel\n label={t('entityLabels.ownerLabel')}\n contentTypograpyRootComponent=\"p\"\n value={\n <EntityRefLinks\n entityRefs={ownedByRelations}\n defaultKind=\"Group\"\n color=\"inherit\"\n />\n }\n />\n )}\n {entity.spec?.lifecycle && (\n <HeaderLabel\n label={t('entityLabels.lifecycleLabel')}\n value={entity.spec.lifecycle?.toString()}\n />\n )}\n </>\n );\n}\n"],"names":[],"mappings":";;;;;;;AA8BO,SAAS,aAAa,KAA0B,EAAA;AACrD,EAAM,MAAA,EAAE,QAAW,GAAA,KAAA;AACnB,EAAM,MAAA,gBAAA,GAAmB,kBAAmB,CAAA,MAAA,EAAQ,iBAAiB,CAAA;AACrE,EAAA,MAAM,EAAE,CAAA,EAAM,GAAA,iBAAA,CAAkB,qBAAqB,CAAA;AACrD,EACE,uBAAA,KAAA,CAAA,aAAA,CAAA,KAAA,CAAA,QAAA,EAAA,IAAA,EACG,gBAAiB,CAAA,MAAA,GAAS,CACzB,oBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,WAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAO,EAAE,yBAAyB,CAAA;AAAA,MAClC,6BAA8B,EAAA,GAAA;AAAA,MAC9B,KACE,kBAAA,KAAA,CAAA,aAAA;AAAA,QAAC,cAAA;AAAA,QAAA;AAAA,UACC,UAAY,EAAA,gBAAA;AAAA,UACZ,WAAY,EAAA,OAAA;AAAA,UACZ,KAAM,EAAA;AAAA;AAAA;AACR;AAAA,GAEJ,EAED,MAAO,CAAA,IAAA,EAAM,SACZ,oBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,WAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAO,EAAE,6BAA6B,CAAA;AAAA,MACtC,KAAO,EAAA,MAAA,CAAO,IAAK,CAAA,SAAA,EAAW,QAAS;AAAA;AAAA,GAG7C,CAAA;AAEJ;;;;"}
@@ -0,0 +1,186 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
3
+ import useAsync from 'react-use/esm/useAsync';
4
+ import { makeStyles } from '@material-ui/core/styles';
5
+ import Alert from '@material-ui/lab/Alert';
6
+ import { attachComponentData, useRouteRefParams, useElementFilter, useRouteRef, useApi } from '@backstage/core-plugin-api';
7
+ import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
8
+ import { Page, Header, Breadcrumbs, Progress, Content, WarningPanel, Link } from '@backstage/core-components';
9
+ import { DEFAULT_NAMESPACE } from '@backstage/catalog-model';
10
+ import { entityRouteRef, useAsyncEntity, catalogApiRef, EntityRefLink, InspectEntityDialog, UnregisterEntityDialog } from '@backstage/plugin-catalog-react';
11
+ import { catalogTranslationRef } from '../../translation.esm.js';
12
+ import { rootRouteRef, unregisterRedirectRouteRef } from '../../../routes.esm.js';
13
+ import { EntityContextMenu } from '../../../components/EntityContextMenu/EntityContextMenu.esm.js';
14
+ import { EntityTabs } from '../EntityTabs/EntityTabs.esm.js';
15
+ import { EntityLabels } from '../EntityLabels/EntityLabels.esm.js';
16
+ import { EntityLayoutTitle } from './EntityLayoutTitle.esm.js';
17
+
18
+ const dataKey = "plugin.catalog.entityLayoutRoute";
19
+ const Route = () => null;
20
+ attachComponentData(Route, dataKey, true);
21
+ attachComponentData(Route, "core.gatherMountPoints", true);
22
+ function headerProps(paramKind, paramNamespace, paramName, entity) {
23
+ const kind = paramKind ?? entity?.kind ?? "";
24
+ const namespace = paramNamespace ?? entity?.metadata.namespace ?? "";
25
+ const name = entity?.metadata.title ?? paramName ?? entity?.metadata.name ?? "";
26
+ return {
27
+ headerTitle: `${name}${namespace && namespace !== DEFAULT_NAMESPACE ? ` in ${namespace}` : ""}`,
28
+ headerType: (() => {
29
+ let t = kind.toLocaleLowerCase("en-US");
30
+ if (entity && entity.spec && "type" in entity.spec) {
31
+ t += " \u2014 ";
32
+ t += entity.spec.type.toLocaleLowerCase("en-US");
33
+ }
34
+ return t;
35
+ })()
36
+ };
37
+ }
38
+ function findParentRelation(entityRelations = [], relationTypes = []) {
39
+ for (const type of relationTypes) {
40
+ const foundRelation = entityRelations.find(
41
+ (relation) => relation.type === type
42
+ );
43
+ if (foundRelation) {
44
+ return foundRelation;
45
+ }
46
+ }
47
+ return null;
48
+ }
49
+ const useStyles = makeStyles((theme) => ({
50
+ breadcrumbs: {
51
+ color: theme.page.fontColor,
52
+ fontSize: theme.typography.caption.fontSize,
53
+ textTransform: "uppercase",
54
+ marginTop: theme.spacing(1),
55
+ opacity: 0.8,
56
+ "& span ": {
57
+ color: theme.page.fontColor,
58
+ textDecoration: "underline",
59
+ textUnderlineOffset: "3px"
60
+ }
61
+ }
62
+ }));
63
+ const EntityLayout = (props) => {
64
+ const {
65
+ UNSTABLE_extraContextMenuItems,
66
+ UNSTABLE_contextMenuOptions,
67
+ children,
68
+ NotFoundComponent,
69
+ parentEntityRelations
70
+ } = props;
71
+ const classes = useStyles();
72
+ const { kind, namespace, name } = useRouteRefParams(entityRouteRef);
73
+ const { entity, loading, error } = useAsyncEntity();
74
+ const location = useLocation();
75
+ const routes = useElementFilter(
76
+ children,
77
+ (elements) => elements.selectByComponentData({
78
+ key: dataKey,
79
+ withStrictError: "Child of EntityLayout must be an EntityLayout.Route"
80
+ }).getElements().flatMap(({ props: elementProps }) => {
81
+ if (!entity) {
82
+ return [];
83
+ }
84
+ if (elementProps.if && !elementProps.if(entity)) {
85
+ return [];
86
+ }
87
+ return [
88
+ {
89
+ path: elementProps.path,
90
+ title: elementProps.title,
91
+ group: elementProps.group,
92
+ children: elementProps.children
93
+ }
94
+ ];
95
+ }),
96
+ [entity]
97
+ );
98
+ const { headerTitle, headerType } = headerProps(
99
+ kind,
100
+ namespace,
101
+ name,
102
+ entity
103
+ );
104
+ const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false);
105
+ const navigate = useNavigate();
106
+ const [searchParams, setSearchParams] = useSearchParams();
107
+ const catalogRoute = useRouteRef(rootRouteRef);
108
+ const unregisterRedirectRoute = useRouteRef(unregisterRedirectRouteRef);
109
+ const { t } = useTranslationRef(catalogTranslationRef);
110
+ const cleanUpAfterRemoval = async () => {
111
+ setConfirmationDialogOpen(false);
112
+ navigate(
113
+ unregisterRedirectRoute ? unregisterRedirectRoute() : catalogRoute()
114
+ );
115
+ };
116
+ const parentEntity = findParentRelation(
117
+ entity?.relations ?? [],
118
+ parentEntityRelations ?? []
119
+ );
120
+ const catalogApi = useApi(catalogApiRef);
121
+ const { value: ancestorEntity } = useAsync(async () => {
122
+ if (parentEntity) {
123
+ return findParentRelation(
124
+ (await catalogApi.getEntityByRef(parentEntity?.targetRef))?.relations,
125
+ parentEntityRelations
126
+ );
127
+ }
128
+ return null;
129
+ }, [parentEntity]);
130
+ useEffect(() => {
131
+ setConfirmationDialogOpen(false);
132
+ }, [location.pathname]);
133
+ const selectedInspectTab = searchParams.get("inspect");
134
+ const showInspectTab = typeof selectedInspectTab === "string";
135
+ return /* @__PURE__ */ React.createElement(Page, { themeId: entity?.spec?.type?.toString() ?? "home" }, /* @__PURE__ */ React.createElement(
136
+ Header,
137
+ {
138
+ title: /* @__PURE__ */ React.createElement(EntityLayoutTitle, { title: headerTitle, entity }),
139
+ pageTitleOverride: headerTitle,
140
+ type: headerType,
141
+ subtitle: parentEntity && /* @__PURE__ */ React.createElement(Breadcrumbs, { separator: ">", className: classes.breadcrumbs }, ancestorEntity && /* @__PURE__ */ React.createElement(
142
+ EntityRefLink,
143
+ {
144
+ entityRef: ancestorEntity.targetRef,
145
+ disableTooltip: true
146
+ }
147
+ ), /* @__PURE__ */ React.createElement(
148
+ EntityRefLink,
149
+ {
150
+ entityRef: parentEntity.targetRef,
151
+ disableTooltip: true
152
+ }
153
+ ), name)
154
+ },
155
+ entity && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(EntityLabels, { entity }), /* @__PURE__ */ React.createElement(
156
+ EntityContextMenu,
157
+ {
158
+ UNSTABLE_extraContextMenuItems,
159
+ UNSTABLE_contextMenuOptions,
160
+ onUnregisterEntity: () => setConfirmationDialogOpen(true),
161
+ onInspectEntity: () => setSearchParams("inspect")
162
+ }
163
+ ))
164
+ ), loading && /* @__PURE__ */ React.createElement(Progress, null), entity && /* @__PURE__ */ React.createElement(EntityTabs, { routes }), error && /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(Alert, { severity: "error" }, error.toString())), !loading && !error && !entity && /* @__PURE__ */ React.createElement(Content, null, NotFoundComponent ? NotFoundComponent : /* @__PURE__ */ React.createElement(WarningPanel, { title: t("entityLabels.warningPanelTitle") }, "There is no ", kind, " with the requested", " ", /* @__PURE__ */ React.createElement(Link, { to: "https://backstage.io/docs/features/software-catalog/references" }, "kind, namespace, and name"), ".")), showInspectTab && /* @__PURE__ */ React.createElement(
165
+ InspectEntityDialog,
166
+ {
167
+ entity,
168
+ initialTab: selectedInspectTab || void 0,
169
+ onSelect: (newTab) => setSearchParams(`inspect=${newTab}`),
170
+ open: true,
171
+ onClose: () => setSearchParams()
172
+ }
173
+ ), /* @__PURE__ */ React.createElement(
174
+ UnregisterEntityDialog,
175
+ {
176
+ open: confirmationDialogOpen,
177
+ entity,
178
+ onConfirm: cleanUpAfterRemoval,
179
+ onClose: () => setConfirmationDialogOpen(false)
180
+ }
181
+ ));
182
+ };
183
+ EntityLayout.Route = Route;
184
+
185
+ export { EntityLayout };
186
+ //# sourceMappingURL=EntityLayout.esm.js.map
@@ -0,0 +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 React, { ComponentProps, useEffect, useState } from 'react';\nimport { useLocation, useNavigate, useSearchParams } from 'react-router-dom';\nimport useAsync from 'react-use/esm/useAsync';\n\nimport { makeStyles } from '@material-ui/core/styles';\nimport Alert from '@material-ui/lab/Alert';\n\nimport {\n attachComponentData,\n IconComponent,\n useApi,\n useElementFilter,\n useRouteRef,\n useRouteRefParams,\n} from '@backstage/core-plugin-api';\nimport { useTranslationRef } from '@backstage/core-plugin-api/alpha';\nimport {\n Breadcrumbs,\n Content,\n Header,\n Link,\n Page,\n Progress,\n WarningPanel,\n} from '@backstage/core-components';\nimport {\n DEFAULT_NAMESPACE,\n Entity,\n EntityRelation,\n} from '@backstage/catalog-model';\nimport {\n catalogApiRef,\n EntityRefLink,\n entityRouteRef,\n InspectEntityDialog,\n UnregisterEntityDialog,\n useAsyncEntity,\n} from '@backstage/plugin-catalog-react';\n\nimport { catalogTranslationRef } from '../../../alpha/translation';\nimport { rootRouteRef, unregisterRedirectRouteRef } from '../../../routes';\nimport { EntityContextMenu } from '../../../components/EntityContextMenu/EntityContextMenu';\nimport { EntityTabs } from '../EntityTabs';\nimport { EntityLabels } from '../EntityLabels/EntityLabels';\nimport { EntityLayoutTitle } from './EntityLayoutTitle';\n\nexport type EntityLayoutRouteProps = {\n path: string;\n title: string;\n group: string;\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\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\n// NOTE(freben): Intentionally not exported at this point, since it's part of\n// the unstable extra context menu items concept below\ninterface ExtraContextMenuItem {\n title: string;\n Icon: IconComponent;\n onClick: () => void;\n}\n\ntype VisibleType = 'visible' | 'hidden' | 'disable';\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\ninterface EntityContextMenuOptions {\n disableUnregister: boolean | VisibleType;\n}\n\n/** @public */\nexport interface EntityLayoutProps {\n UNSTABLE_extraContextMenuItems?: ExtraContextMenuItem[];\n UNSTABLE_contextMenuOptions?: EntityContextMenuOptions;\n children?: React.ReactNode;\n NotFoundComponent?: React.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}\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 children,\n NotFoundComponent,\n parentEntityRelations,\n } = props;\n const classes = useStyles();\n const { kind, namespace, name } = useRouteRefParams(entityRouteRef);\n const { entity, loading, error } = useAsyncEntity();\n const location = useLocation();\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 },\n ];\n }),\n [entity],\n );\n\n const { headerTitle, headerType } = headerProps(\n kind,\n namespace,\n name,\n entity,\n );\n\n const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false);\n const navigate = useNavigate();\n const [searchParams, setSearchParams] = useSearchParams();\n\n const catalogRoute = useRouteRef(rootRouteRef);\n const unregisterRedirectRoute = useRouteRef(unregisterRedirectRouteRef);\n const { t } = useTranslationRef(catalogTranslationRef);\n\n const cleanUpAfterRemoval = async () => {\n setConfirmationDialogOpen(false);\n navigate(\n unregisterRedirectRoute ? unregisterRedirectRoute() : catalogRoute(),\n );\n };\n\n const parentEntity = findParentRelation(\n entity?.relations ?? [],\n parentEntityRelations ?? [],\n );\n\n const catalogApi = useApi(catalogApiRef);\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]);\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 selectedInspectTab = searchParams.get('inspect');\n const showInspectTab = typeof selectedInspectTab === 'string';\n\n return (\n <Page themeId={entity?.spec?.type?.toString() ?? 'home'}>\n <Header\n title={<EntityLayoutTitle title={headerTitle} entity={entity!} />}\n pageTitleOverride={headerTitle}\n type={headerType}\n subtitle={\n parentEntity && (\n <Breadcrumbs separator=\">\" className={classes.breadcrumbs}>\n {ancestorEntity && (\n <EntityRefLink\n entityRef={ancestorEntity.targetRef}\n disableTooltip\n />\n )}\n <EntityRefLink\n entityRef={parentEntity.targetRef}\n disableTooltip\n />\n {name}\n </Breadcrumbs>\n )\n }\n >\n {entity && (\n <>\n <EntityLabels entity={entity} />\n <EntityContextMenu\n UNSTABLE_extraContextMenuItems={UNSTABLE_extraContextMenuItems}\n UNSTABLE_contextMenuOptions={UNSTABLE_contextMenuOptions}\n onUnregisterEntity={() => setConfirmationDialogOpen(true)}\n onInspectEntity={() => setSearchParams('inspect')}\n />\n </>\n )}\n </Header>\n\n {loading && <Progress />}\n\n {entity && <EntityTabs routes={routes} />}\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 There is no {kind} with the requested{' '}\n <Link to=\"https://backstage.io/docs/features/software-catalog/references\">\n kind, namespace, and name\n </Link>\n .\n </WarningPanel>\n )}\n </Content>\n )}\n\n {showInspectTab && (\n <InspectEntityDialog\n entity={entity!}\n initialTab={\n (selectedInspectTab as ComponentProps<\n typeof InspectEntityDialog\n >['initialTab']) || undefined\n }\n onSelect={newTab => setSearchParams(`inspect=${newTab}`)}\n open\n onClose={() => setSearchParams()}\n />\n )}\n\n <UnregisterEntityDialog\n open={confirmationDialogOpen}\n entity={entity!}\n onConfirm={cleanUpAfterRemoval}\n onClose={() => setConfirmationDialogOpen(false)}\n />\n </Page>\n );\n};\n\nEntityLayout.Route = Route;\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ import Box from '@material-ui/core/Box';
3
+ import { EntityDisplayName, FavoriteEntity } from '@backstage/plugin-catalog-react';
4
+
5
+ function EntityLayoutTitle(props) {
6
+ const { entity, title } = props;
7
+ return /* @__PURE__ */ React.createElement(Box, { display: "inline-flex", alignItems: "center", height: "1em", maxWidth: "100%" }, /* @__PURE__ */ React.createElement(
8
+ Box,
9
+ {
10
+ component: "span",
11
+ textOverflow: "ellipsis",
12
+ whiteSpace: "nowrap",
13
+ overflow: "hidden"
14
+ },
15
+ entity ? /* @__PURE__ */ React.createElement(EntityDisplayName, { entityRef: entity, hideIcon: true }) : title
16
+ ), entity && /* @__PURE__ */ React.createElement(FavoriteEntity, { entity }));
17
+ }
18
+
19
+ export { EntityLayoutTitle };
20
+ //# sourceMappingURL=EntityLayoutTitle.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EntityLayoutTitle.esm.js","sources":["../../../../src/alpha/components/EntityLayout/EntityLayoutTitle.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 React from 'react';\nimport Box from '@material-ui/core/Box';\nimport { Entity } from '@backstage/catalog-model';\nimport {\n EntityDisplayName,\n FavoriteEntity,\n} from '@backstage/plugin-catalog-react';\n\ntype EntityLayoutTitleProps = {\n title: string;\n entity: Entity | undefined;\n};\n\nexport function EntityLayoutTitle(props: EntityLayoutTitleProps) {\n const { entity, title } = props;\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"],"names":[],"mappings":";;;;AA6BO,SAAS,kBAAkB,KAA+B,EAAA;AAC/D,EAAM,MAAA,EAAE,MAAQ,EAAA,KAAA,EAAU,GAAA,KAAA;AAC1B,EACE,uBAAA,KAAA,CAAA,aAAA,CAAC,OAAI,OAAQ,EAAA,aAAA,EAAc,YAAW,QAAS,EAAA,MAAA,EAAO,KAAM,EAAA,QAAA,EAAS,MACnE,EAAA,kBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,GAAA;AAAA,IAAA;AAAA,MACC,SAAU,EAAA,MAAA;AAAA,MACV,YAAa,EAAA,UAAA;AAAA,MACb,UAAW,EAAA,QAAA;AAAA,MACX,QAAS,EAAA;AAAA,KAAA;AAAA,IAER,yBAAU,KAAA,CAAA,aAAA,CAAA,iBAAA,EAAA,EAAkB,WAAW,MAAQ,EAAA,QAAA,EAAQ,MAAC,CAAK,GAAA;AAAA,GAE/D,EAAA,MAAA,oBAAW,KAAA,CAAA,aAAA,CAAA,cAAA,EAAA,EAAe,QAAgB,CAC7C,CAAA;AAEJ;;;;"}
@@ -0,0 +1,55 @@
1
+ import React, { useMemo } from 'react';
2
+ import { Helmet } from 'react-helmet';
3
+ import { useParams, useRoutes, matchRoutes } from 'react-router-dom';
4
+ import { EntityTabsPanel } from './EntityTabsPanel.esm.js';
5
+ import { EntityTabsList } from './EntityTabsList.esm.js';
6
+
7
+ function useSelectedSubRoute(subRoutes) {
8
+ const params = useParams();
9
+ const routes = subRoutes.map(({ path, children }) => ({
10
+ caseSensitive: false,
11
+ path: `${path}/*`,
12
+ element: children
13
+ }));
14
+ const sortedRoutes = routes.sort(
15
+ (a, b) => (
16
+ // remove "/*" symbols from path end before comparing
17
+ b.path.replace(/\/\*$/, "").localeCompare(a.path.replace(/\/\*$/, ""))
18
+ )
19
+ );
20
+ const element = useRoutes(sortedRoutes) ?? subRoutes[0]?.children;
21
+ let currentRoute = params["*"] ?? "";
22
+ if (!currentRoute.startsWith("/")) {
23
+ currentRoute = `/${currentRoute}`;
24
+ }
25
+ const [matchedRoute] = matchRoutes(sortedRoutes, currentRoute) ?? [];
26
+ const foundIndex = matchedRoute ? subRoutes.findIndex((t) => `${t.path}/*` === matchedRoute.route.path) : 0;
27
+ return {
28
+ index: foundIndex === -1 ? 0 : foundIndex,
29
+ element,
30
+ route: subRoutes[foundIndex] ?? subRoutes[0]
31
+ };
32
+ }
33
+ function EntityTabs(props) {
34
+ const { routes } = props;
35
+ const { index, route, element } = useSelectedSubRoute(routes);
36
+ const tabs = useMemo(
37
+ () => routes.map((t) => {
38
+ const { path, title, group } = t;
39
+ let to = path;
40
+ to = to.replace(/\/\*$/, "");
41
+ to = to.replace(/^\//, "");
42
+ return {
43
+ group,
44
+ id: path,
45
+ path: to,
46
+ label: title
47
+ };
48
+ }),
49
+ [routes]
50
+ );
51
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(EntityTabsList, { tabs, selectedIndex: index }), /* @__PURE__ */ React.createElement(EntityTabsPanel, null, /* @__PURE__ */ React.createElement(Helmet, { title: route?.title }), element));
52
+ }
53
+
54
+ export { EntityTabs, useSelectedSubRoute };
55
+ //# sourceMappingURL=EntityTabs.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EntityTabs.esm.js","sources":["../../../../src/alpha/components/EntityTabs/EntityTabs.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 */\nimport React, { useMemo } from 'react';\nimport { Helmet } from 'react-helmet';\nimport { matchRoutes, useParams, useRoutes } from 'react-router-dom';\nimport { EntityTabsPanel } from './EntityTabsPanel';\nimport { EntityTabsList } from './EntityTabsList';\n\ntype SubRoute = {\n group: string;\n path: string;\n title: string;\n children: JSX.Element;\n};\n\nexport function useSelectedSubRoute(subRoutes: SubRoute[]): {\n index: number;\n route?: SubRoute;\n element?: JSX.Element;\n} {\n const params = useParams();\n\n const routes = subRoutes.map(({ path, children }) => ({\n caseSensitive: false,\n path: `${path}/*`,\n element: children,\n }));\n\n // TODO: remove once react-router updated\n const sortedRoutes = routes.sort((a, b) =>\n // remove \"/*\" symbols from path end before comparing\n b.path.replace(/\\/\\*$/, '').localeCompare(a.path.replace(/\\/\\*$/, '')),\n );\n\n const element = useRoutes(sortedRoutes) ?? subRoutes[0]?.children;\n\n // TODO(Rugvip): Once we only support v6 stable we can always prefix\n // This avoids having a double / prefix for react-router v6 beta, which in turn breaks\n // the tab highlighting when using relative paths for the tabs.\n let currentRoute = params['*'] ?? '';\n if (!currentRoute.startsWith('/')) {\n currentRoute = `/${currentRoute}`;\n }\n\n const [matchedRoute] = matchRoutes(sortedRoutes, currentRoute) ?? [];\n const foundIndex = matchedRoute\n ? subRoutes.findIndex(t => `${t.path}/*` === matchedRoute.route.path)\n : 0;\n\n return {\n index: foundIndex === -1 ? 0 : foundIndex,\n element,\n route: subRoutes[foundIndex] ?? subRoutes[0],\n };\n}\n\ntype EntityTabsProps = {\n routes: SubRoute[];\n};\n\nexport function EntityTabs(props: EntityTabsProps) {\n const { routes } = props;\n\n const { index, route, element } = useSelectedSubRoute(routes);\n\n const tabs = useMemo(\n () =>\n routes.map(t => {\n const { path, title, group } = t;\n let to = path;\n // Remove trailing /*\n to = to.replace(/\\/\\*$/, '');\n // And remove leading / for relative navigation\n to = to.replace(/^\\//, '');\n return {\n group,\n id: path,\n path: to,\n label: title,\n };\n }),\n [routes],\n );\n\n return (\n <>\n <EntityTabsList tabs={tabs} selectedIndex={index} />\n <EntityTabsPanel>\n <Helmet title={route?.title} />\n {element}\n </EntityTabsPanel>\n </>\n );\n}\n"],"names":[],"mappings":";;;;;;AA4BO,SAAS,oBAAoB,SAIlC,EAAA;AACA,EAAA,MAAM,SAAS,SAAU,EAAA;AAEzB,EAAA,MAAM,SAAS,SAAU,CAAA,GAAA,CAAI,CAAC,EAAE,IAAA,EAAM,UAAgB,MAAA;AAAA,IACpD,aAAe,EAAA,KAAA;AAAA,IACf,IAAA,EAAM,GAAG,IAAI,CAAA,EAAA,CAAA;AAAA,IACb,OAAS,EAAA;AAAA,GACT,CAAA,CAAA;AAGF,EAAA,MAAM,eAAe,MAAO,CAAA,IAAA;AAAA,IAAK,CAAC,CAAG,EAAA,CAAA;AAAA;AAAA,MAEnC,CAAE,CAAA,IAAA,CAAK,OAAQ,CAAA,OAAA,EAAS,EAAE,CAAA,CAAE,aAAc,CAAA,CAAA,CAAE,IAAK,CAAA,OAAA,CAAQ,OAAS,EAAA,EAAE,CAAC;AAAA;AAAA,GACvE;AAEA,EAAA,MAAM,UAAU,SAAU,CAAA,YAAY,CAAK,IAAA,SAAA,CAAU,CAAC,CAAG,EAAA,QAAA;AAKzD,EAAI,IAAA,YAAA,GAAe,MAAO,CAAA,GAAG,CAAK,IAAA,EAAA;AAClC,EAAA,IAAI,CAAC,YAAA,CAAa,UAAW,CAAA,GAAG,CAAG,EAAA;AACjC,IAAA,YAAA,GAAe,IAAI,YAAY,CAAA,CAAA;AAAA;AAGjC,EAAA,MAAM,CAAC,YAAY,CAAA,GAAI,YAAY,YAAc,EAAA,YAAY,KAAK,EAAC;AACnE,EAAA,MAAM,UAAa,GAAA,YAAA,GACf,SAAU,CAAA,SAAA,CAAU,CAAK,CAAA,KAAA,CAAA,EAAG,CAAE,CAAA,IAAI,CAAS,EAAA,CAAA,KAAA,YAAA,CAAa,KAAM,CAAA,IAAI,CAClE,GAAA,CAAA;AAEJ,EAAO,OAAA;AAAA,IACL,KAAA,EAAO,UAAe,KAAA,CAAA,CAAA,GAAK,CAAI,GAAA,UAAA;AAAA,IAC/B,OAAA;AAAA,IACA,KAAO,EAAA,SAAA,CAAU,UAAU,CAAA,IAAK,UAAU,CAAC;AAAA,GAC7C;AACF;AAMO,SAAS,WAAW,KAAwB,EAAA;AACjD,EAAM,MAAA,EAAE,QAAW,GAAA,KAAA;AAEnB,EAAA,MAAM,EAAE,KAAO,EAAA,KAAA,EAAO,OAAQ,EAAA,GAAI,oBAAoB,MAAM,CAAA;AAE5D,EAAA,MAAM,IAAO,GAAA,OAAA;AAAA,IACX,MACE,MAAO,CAAA,GAAA,CAAI,CAAK,CAAA,KAAA;AACd,MAAA,MAAM,EAAE,IAAA,EAAM,KAAO,EAAA,KAAA,EAAU,GAAA,CAAA;AAC/B,MAAA,IAAI,EAAK,GAAA,IAAA;AAET,MAAK,EAAA,GAAA,EAAA,CAAG,OAAQ,CAAA,OAAA,EAAS,EAAE,CAAA;AAE3B,MAAK,EAAA,GAAA,EAAA,CAAG,OAAQ,CAAA,KAAA,EAAO,EAAE,CAAA;AACzB,MAAO,OAAA;AAAA,QACL,KAAA;AAAA,QACA,EAAI,EAAA,IAAA;AAAA,QACJ,IAAM,EAAA,EAAA;AAAA,QACN,KAAO,EAAA;AAAA,OACT;AAAA,KACD,CAAA;AAAA,IACH,CAAC,MAAM;AAAA,GACT;AAEA,EAAA,iFAEK,KAAA,CAAA,aAAA,CAAA,cAAA,EAAA,EAAe,IAAY,EAAA,aAAA,EAAe,OAAO,CAClD,kBAAA,KAAA,CAAA,aAAA,CAAC,eACC,EAAA,IAAA,kBAAA,KAAA,CAAA,aAAA,CAAC,UAAO,KAAO,EAAA,KAAA,EAAO,KAAO,EAAA,CAAA,EAC5B,OACH,CACF,CAAA;AAEJ;;;;"}