@backstage/plugin-catalog 1.27.0-next.3 → 1.28.0-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.
- package/CHANGELOG.md +71 -0
- package/dist/alpha/EntityOverviewPage.esm.js.map +1 -1
- package/dist/alpha/components/EntityLabels/EntityLabels.esm.js +36 -0
- package/dist/alpha/components/EntityLabels/EntityLabels.esm.js.map +1 -0
- package/dist/alpha/components/EntityLayout/EntityLayout.esm.js +186 -0
- package/dist/alpha/components/EntityLayout/EntityLayout.esm.js.map +1 -0
- package/dist/alpha/components/EntityLayout/EntityLayoutTitle.esm.js +20 -0
- package/dist/alpha/components/EntityLayout/EntityLayoutTitle.esm.js.map +1 -0
- package/dist/alpha/components/EntityTabs/EntityTabs.esm.js +55 -0
- package/dist/alpha/components/EntityTabs/EntityTabs.esm.js.map +1 -0
- package/dist/alpha/components/EntityTabs/EntityTabsGroup.esm.js +240 -0
- package/dist/alpha/components/EntityTabs/EntityTabsGroup.esm.js.map +1 -0
- package/dist/alpha/components/EntityTabs/EntityTabsList.esm.js +94 -0
- package/dist/alpha/components/EntityTabs/EntityTabsList.esm.js.map +1 -0
- package/dist/alpha/components/EntityTabs/EntityTabsPanel.esm.js +47 -0
- package/dist/alpha/components/EntityTabs/EntityTabsPanel.esm.js.map +1 -0
- package/dist/alpha/entityContents.esm.js +62 -17
- package/dist/alpha/entityContents.esm.js.map +1 -1
- package/dist/alpha/pages.esm.js +42 -8
- package/dist/alpha/pages.esm.js.map +1 -1
- package/dist/alpha.d.ts +76 -0
- package/package.json +16 -14
- /package/dist/{components → alpha/components}/EntityLayout/index.esm.js +0 -0
- /package/dist/{components → alpha/components}/EntityLayout/index.esm.js.map +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,76 @@
|
|
|
1
1
|
# @backstage/plugin-catalog
|
|
2
2
|
|
|
3
|
+
## 1.28.0-next.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a3d93ca: The `Overview` entity content now supports custom cards grid layouts.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- ba9649a: Update the default entity page extension component to support grouping multiple entity content items in the same tab.
|
|
12
|
+
|
|
13
|
+
Disable all default groups:
|
|
14
|
+
|
|
15
|
+
```diff
|
|
16
|
+
# app-config.yaml
|
|
17
|
+
app:
|
|
18
|
+
extensions:
|
|
19
|
+
# Pages
|
|
20
|
+
+ - page:catalog/entity:
|
|
21
|
+
+ config:
|
|
22
|
+
+ groups: []
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Create a custom list of :
|
|
26
|
+
|
|
27
|
+
```diff
|
|
28
|
+
# app-config.yaml
|
|
29
|
+
app:
|
|
30
|
+
extensions:
|
|
31
|
+
# Pages
|
|
32
|
+
+ - page:catalog/entity:
|
|
33
|
+
+ config:
|
|
34
|
+
+ groups:
|
|
35
|
+
+ # This array of groups completely replaces the default groups
|
|
36
|
+
+ - custom:
|
|
37
|
+
+ title: 'Custom'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- Updated dependencies
|
|
41
|
+
- @backstage/plugin-search-react@1.8.7-next.0
|
|
42
|
+
- @backstage/plugin-catalog-react@1.16.0-next.0
|
|
43
|
+
- @backstage/frontend-plugin-api@0.9.6-next.0
|
|
44
|
+
- @backstage/core-compat-api@0.3.7-next.0
|
|
45
|
+
- @backstage/integration-react@1.2.4
|
|
46
|
+
|
|
47
|
+
## 1.27.0
|
|
48
|
+
|
|
49
|
+
### Minor Changes
|
|
50
|
+
|
|
51
|
+
- b07756e: The Entity Page now retains the visibility of the Inspect Dialog after a reload. This allows sharing the URL with the dialog open.
|
|
52
|
+
|
|
53
|
+
### Patch Changes
|
|
54
|
+
|
|
55
|
+
- 7a15cff: Revert client side paginated catalog table to using built in Material Table toolbar component
|
|
56
|
+
- 58ec9e7: Removed older versions of React packages as a preparatory step for upgrading to React 19. This commit does not introduce any functional changes, but removes dependencies on previous React versions, allowing for a cleaner upgrade path in subsequent commits.
|
|
57
|
+
- Updated dependencies
|
|
58
|
+
- @backstage/plugin-search-react@1.8.6
|
|
59
|
+
- @backstage/core-components@0.16.4
|
|
60
|
+
- @backstage/plugin-catalog-react@1.15.2
|
|
61
|
+
- @backstage/frontend-plugin-api@0.9.5
|
|
62
|
+
- @backstage/integration-react@1.2.4
|
|
63
|
+
- @backstage/core-compat-api@0.3.6
|
|
64
|
+
- @backstage/core-plugin-api@1.10.4
|
|
65
|
+
- @backstage/plugin-permission-react@0.4.31
|
|
66
|
+
- @backstage/catalog-client@1.9.1
|
|
67
|
+
- @backstage/catalog-model@1.7.3
|
|
68
|
+
- @backstage/errors@1.2.7
|
|
69
|
+
- @backstage/types@1.2.1
|
|
70
|
+
- @backstage/plugin-catalog-common@1.1.3
|
|
71
|
+
- @backstage/plugin-scaffolder-common@1.5.9
|
|
72
|
+
- @backstage/plugin-search-common@1.2.17
|
|
73
|
+
|
|
3
74
|
## 1.27.0-next.3
|
|
4
75
|
|
|
5
76
|
### Patch 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 {
|
|
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;;;;"}
|