@nospt/backstage-plugin-apigee 1.1.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.
- package/README.md +98 -0
- package/config.d.ts +30 -0
- package/dist/components/ApiCard.esm.js +105 -0
- package/dist/components/ApiCard.esm.js.map +1 -0
- package/dist/components/ApiCard.module.css.esm.js +8 -0
- package/dist/components/ApiCard.module.css.esm.js.map +1 -0
- package/dist/components/ApiCardGrid.esm.js +14 -0
- package/dist/components/ApiCardGrid.esm.js.map +1 -0
- package/dist/components/ApiDetailsDrawer.esm.js +709 -0
- package/dist/components/ApiDetailsDrawer.esm.js.map +1 -0
- package/dist/components/ApiDetailsDrawer.module.css.esm.js +8 -0
- package/dist/components/ApiDetailsDrawer.module.css.esm.js.map +1 -0
- package/dist/components/ApiTable.esm.js +184 -0
- package/dist/components/ApiTable.esm.js.map +1 -0
- package/dist/components/ApiTable.module.css.esm.js +8 -0
- package/dist/components/ApiTable.module.css.esm.js.map +1 -0
- package/dist/components/ApigeeApiCatalogPage.esm.js +245 -0
- package/dist/components/ApigeeApiCatalogPage.esm.js.map +1 -0
- package/dist/components/ApigeeHomePage.esm.js +190 -0
- package/dist/components/ApigeeHomePage.esm.js.map +1 -0
- package/dist/components/ApigeeHomePage.module.css.esm.js +8 -0
- package/dist/components/ApigeeHomePage.module.css.esm.js.map +1 -0
- package/dist/components/ApigeeNavContent.esm.js +28 -0
- package/dist/components/ApigeeNavContent.esm.js.map +1 -0
- package/dist/components/CountCards.esm.js +120 -0
- package/dist/components/CountCards.esm.js.map +1 -0
- package/dist/components/CountCards.module.css.esm.js +8 -0
- package/dist/components/CountCards.module.css.esm.js.map +1 -0
- package/dist/components/FilterToolbar.esm.js +131 -0
- package/dist/components/FilterToolbar.esm.js.map +1 -0
- package/dist/components/FilterToolbar.module.css.esm.js +8 -0
- package/dist/components/FilterToolbar.module.css.esm.js.map +1 -0
- package/dist/components/SearchBar.esm.js +20 -0
- package/dist/components/SearchBar.esm.js.map +1 -0
- package/dist/components/apiCardViewModel.esm.js +57 -0
- package/dist/components/apiCardViewModel.esm.js.map +1 -0
- package/dist/components/apiDetailViewModel.esm.js +98 -0
- package/dist/components/apiDetailViewModel.esm.js.map +1 -0
- package/dist/components/apiOperations.esm.js +196 -0
- package/dist/components/apiOperations.esm.js.map +1 -0
- package/dist/components/apigeeFacetFilters.esm.js +62 -0
- package/dist/components/apigeeFacetFilters.esm.js.map +1 -0
- package/dist/components/apigeeOrgFilter.esm.js +11 -0
- package/dist/components/apigeeOrgFilter.esm.js.map +1 -0
- package/dist/components/apigeeSearchParams.esm.js +55 -0
- package/dist/components/apigeeSearchParams.esm.js.map +1 -0
- package/dist/components/brandIcons.esm.js +36 -0
- package/dist/components/brandIcons.esm.js.map +1 -0
- package/dist/components/codeHighlight.esm.js +71 -0
- package/dist/components/codeHighlight.esm.js.map +1 -0
- package/dist/components/codeSnippet.esm.js +128 -0
- package/dist/components/codeSnippet.esm.js.map +1 -0
- package/dist/components/facetModel.esm.js +86 -0
- package/dist/components/facetModel.esm.js.map +1 -0
- package/dist/components/grouping.esm.js +6 -0
- package/dist/components/grouping.esm.js.map +1 -0
- package/dist/components/states.esm.js +65 -0
- package/dist/components/states.esm.js.map +1 -0
- package/dist/components/useApigeeFilters.esm.js +191 -0
- package/dist/components/useApigeeFilters.esm.js.map +1 -0
- package/dist/components/useDebouncedValue.esm.js +13 -0
- package/dist/components/useDebouncedValue.esm.js.map +1 -0
- package/dist/constants.esm.js +9 -0
- package/dist/constants.esm.js.map +1 -0
- package/dist/index.d.ts +203 -0
- package/dist/index.esm.js +2 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/node_modules_dist/style-inject/dist/style-inject.es.esm.js +29 -0
- package/dist/node_modules_dist/style-inject/dist/style-inject.es.esm.js.map +1 -0
- package/dist/plugin.esm.js +59 -0
- package/dist/plugin.esm.js.map +1 -0
- package/dist/routes.esm.js +8 -0
- package/dist/routes.esm.js.map +1 -0
- package/package.json +95 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
2
|
+
import { Page, Content } from '@backstage/core-components';
|
|
3
|
+
import { useApi, configApiRef } from '@backstage/core-plugin-api';
|
|
4
|
+
import { EntityListProvider, useEntityList } from '@backstage/plugin-catalog-react';
|
|
5
|
+
import { TablePagination, ToggleButton } from '@backstage/ui';
|
|
6
|
+
import { RiLayoutGridLine, RiListUnordered } from '@remixicon/react';
|
|
7
|
+
import { useEffect, useMemo, useState, useCallback } from 'react';
|
|
8
|
+
import { ANNOTATION_HAS_SPEC } from '../constants.esm.js';
|
|
9
|
+
import { toApiCardViewModel } from './apiCardViewModel.esm.js';
|
|
10
|
+
import { ApiCardGrid } from './ApiCardGrid.esm.js';
|
|
11
|
+
import { ApiDetailsDrawer } from './ApiDetailsDrawer.esm.js';
|
|
12
|
+
import { ApiKindFilter, isApigeeEntity } from './apigeeFacetFilters.esm.js';
|
|
13
|
+
import { ApigeeOrgFilter } from './apigeeOrgFilter.esm.js';
|
|
14
|
+
import { isActive } from './apigeeSearchParams.esm.js';
|
|
15
|
+
import { CountCards } from './CountCards.esm.js';
|
|
16
|
+
import { deriveFacets, deriveCounts } from './facetModel.esm.js';
|
|
17
|
+
import { FilterToolbar } from './FilterToolbar.esm.js';
|
|
18
|
+
import { SearchBar } from './SearchBar.esm.js';
|
|
19
|
+
import { ApiCatalogError, SkeletonGrid, NoMatchState, ApiCatalogEmptyState } from './states.esm.js';
|
|
20
|
+
import { useApigeeFilters } from './useApigeeFilters.esm.js';
|
|
21
|
+
|
|
22
|
+
const DEFAULT_PAGE_SIZE = 20;
|
|
23
|
+
const PAGE_SIZE_OPTIONS = [10, 20, 30, 40, 50];
|
|
24
|
+
function ApplyApigeeFilters() {
|
|
25
|
+
const { updateFilters } = useEntityList();
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
updateFilters({
|
|
28
|
+
apigeeKind: new ApiKindFilter(),
|
|
29
|
+
apigeeOrg: new ApigeeOrgFilter()
|
|
30
|
+
});
|
|
31
|
+
}, [updateFilters]);
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
function ApigeeCatalogContent() {
|
|
35
|
+
const { entities, backendEntities, loading, error } = useEntityList();
|
|
36
|
+
const {
|
|
37
|
+
state,
|
|
38
|
+
inputValue,
|
|
39
|
+
setInputValue,
|
|
40
|
+
toggleFacet,
|
|
41
|
+
selectOnlyFacet,
|
|
42
|
+
clearFacet,
|
|
43
|
+
clearAll,
|
|
44
|
+
page,
|
|
45
|
+
setPage,
|
|
46
|
+
pageSizeParam,
|
|
47
|
+
setPageSize,
|
|
48
|
+
openApiRef,
|
|
49
|
+
openApi,
|
|
50
|
+
closeApi
|
|
51
|
+
} = useApigeeFilters();
|
|
52
|
+
const configApi = useApi(configApiRef);
|
|
53
|
+
const orgLabels = useMemo(() => {
|
|
54
|
+
const config = configApi.getOptionalConfig("apigee.catalog.orgLabels");
|
|
55
|
+
if (!config) {
|
|
56
|
+
return void 0;
|
|
57
|
+
}
|
|
58
|
+
const out = {};
|
|
59
|
+
for (const key of config.keys()) {
|
|
60
|
+
const value = config.getOptionalString(key);
|
|
61
|
+
if (value) {
|
|
62
|
+
out[key] = value;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
66
|
+
}, [configApi]);
|
|
67
|
+
const defaultPageSize = useMemo(() => {
|
|
68
|
+
const configured = configApi.getOptionalNumber("apigee.catalog.pageSize");
|
|
69
|
+
return configured === void 0 ? DEFAULT_PAGE_SIZE : Math.max(1, Math.floor(configured));
|
|
70
|
+
}, [configApi]);
|
|
71
|
+
const pageSizeOptions = useMemo(
|
|
72
|
+
() => PAGE_SIZE_OPTIONS.includes(defaultPageSize) ? PAGE_SIZE_OPTIONS : [...PAGE_SIZE_OPTIONS, defaultPageSize].sort((a, b) => a - b),
|
|
73
|
+
[defaultPageSize]
|
|
74
|
+
);
|
|
75
|
+
const pageSize = pageSizeParam !== void 0 && pageSizeOptions.includes(pageSizeParam) ? pageSizeParam : defaultPageSize;
|
|
76
|
+
const apigeeEntities = useMemo(
|
|
77
|
+
() => backendEntities.filter(isApigeeEntity),
|
|
78
|
+
[backendEntities]
|
|
79
|
+
);
|
|
80
|
+
const vms = useMemo(() => entities.map(toApiCardViewModel), [entities]);
|
|
81
|
+
const facets = useMemo(
|
|
82
|
+
() => deriveFacets(apigeeEntities, orgLabels),
|
|
83
|
+
[apigeeEntities, orgLabels]
|
|
84
|
+
);
|
|
85
|
+
const counts = useMemo(
|
|
86
|
+
() => deriveCounts(
|
|
87
|
+
apigeeEntities,
|
|
88
|
+
["org", "lifecycle", "department"],
|
|
89
|
+
orgLabels
|
|
90
|
+
),
|
|
91
|
+
[apigeeEntities, orgLabels]
|
|
92
|
+
);
|
|
93
|
+
const specCount = useMemo(
|
|
94
|
+
() => apigeeEntities.filter(
|
|
95
|
+
(entity) => entity.metadata.annotations?.[ANNOTATION_HAS_SPEC] === "true"
|
|
96
|
+
).length,
|
|
97
|
+
[apigeeEntities]
|
|
98
|
+
);
|
|
99
|
+
const active = isActive(state);
|
|
100
|
+
const [viewMode, setViewMode] = useState("flat");
|
|
101
|
+
const totalPages = Math.max(1, Math.ceil(vms.length / pageSize));
|
|
102
|
+
const currentPage = Math.min(page, totalPages);
|
|
103
|
+
const pageItems = useMemo(
|
|
104
|
+
() => vms.slice((currentPage - 1) * pageSize, currentPage * pageSize),
|
|
105
|
+
[currentPage, pageSize, vms]
|
|
106
|
+
);
|
|
107
|
+
const entitiesByRef = useMemo(() => {
|
|
108
|
+
const map = /* @__PURE__ */ new Map();
|
|
109
|
+
for (const entity of apigeeEntities) {
|
|
110
|
+
const namespace = entity.metadata.namespace ?? "default";
|
|
111
|
+
map.set(`${namespace}/${entity.metadata.name}`, entity);
|
|
112
|
+
}
|
|
113
|
+
return map;
|
|
114
|
+
}, [apigeeEntities]);
|
|
115
|
+
const selected = openApiRef ? entitiesByRef.get(openApiRef) : void 0;
|
|
116
|
+
const handleSelect = useCallback(
|
|
117
|
+
(vm) => {
|
|
118
|
+
const { namespace, name } = vm.targetRef;
|
|
119
|
+
openApi(`${namespace}/${name}`);
|
|
120
|
+
},
|
|
121
|
+
[openApi]
|
|
122
|
+
);
|
|
123
|
+
const handleClose = useCallback(() => closeApi(), [closeApi]);
|
|
124
|
+
const renderBody = () => {
|
|
125
|
+
if (error) {
|
|
126
|
+
return /* @__PURE__ */ jsx(ApiCatalogError, { error });
|
|
127
|
+
}
|
|
128
|
+
if (loading) {
|
|
129
|
+
return /* @__PURE__ */ jsx(SkeletonGrid, {});
|
|
130
|
+
}
|
|
131
|
+
if (vms.length === 0) {
|
|
132
|
+
return active ? /* @__PURE__ */ jsx(NoMatchState, { onClearAll: clearAll }) : /* @__PURE__ */ jsx(ApiCatalogEmptyState, {});
|
|
133
|
+
}
|
|
134
|
+
return /* @__PURE__ */ jsx(
|
|
135
|
+
ApiCardGrid,
|
|
136
|
+
{
|
|
137
|
+
vms: pageItems,
|
|
138
|
+
viewMode,
|
|
139
|
+
onSelect: handleSelect
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
};
|
|
143
|
+
const viewToggle = /* @__PURE__ */ jsxs(
|
|
144
|
+
"div",
|
|
145
|
+
{
|
|
146
|
+
role: "group",
|
|
147
|
+
"aria-label": "API view mode",
|
|
148
|
+
style: { display: "inline-flex", gap: "var(--bui-space-1)" },
|
|
149
|
+
children: [
|
|
150
|
+
/* @__PURE__ */ jsx(
|
|
151
|
+
ToggleButton,
|
|
152
|
+
{
|
|
153
|
+
"aria-label": "Flat grid view",
|
|
154
|
+
isSelected: viewMode === "flat",
|
|
155
|
+
onChange: () => setViewMode("flat"),
|
|
156
|
+
iconStart: /* @__PURE__ */ jsx(RiLayoutGridLine, {}),
|
|
157
|
+
children: "Flat"
|
|
158
|
+
}
|
|
159
|
+
),
|
|
160
|
+
/* @__PURE__ */ jsx(
|
|
161
|
+
ToggleButton,
|
|
162
|
+
{
|
|
163
|
+
"aria-label": "List view",
|
|
164
|
+
isSelected: viewMode === "list",
|
|
165
|
+
onChange: () => setViewMode("list"),
|
|
166
|
+
iconStart: /* @__PURE__ */ jsx(RiListUnordered, {}),
|
|
167
|
+
children: "List"
|
|
168
|
+
}
|
|
169
|
+
)
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
);
|
|
173
|
+
return /* @__PURE__ */ jsxs(Page, { themeId: "apis", children: [
|
|
174
|
+
/* @__PURE__ */ jsxs(Content, { children: [
|
|
175
|
+
!loading && !error && apigeeEntities.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
176
|
+
/* @__PURE__ */ jsx(
|
|
177
|
+
CountCards,
|
|
178
|
+
{
|
|
179
|
+
total: apigeeEntities.length,
|
|
180
|
+
specCount,
|
|
181
|
+
counts,
|
|
182
|
+
selected: state.selected,
|
|
183
|
+
onSelectFacet: selectOnlyFacet
|
|
184
|
+
}
|
|
185
|
+
),
|
|
186
|
+
/* @__PURE__ */ jsx(
|
|
187
|
+
FilterToolbar,
|
|
188
|
+
{
|
|
189
|
+
facets,
|
|
190
|
+
selected: state.selected,
|
|
191
|
+
onToggleFacet: toggleFacet,
|
|
192
|
+
onClearFacet: clearFacet,
|
|
193
|
+
onClearAll: clearAll,
|
|
194
|
+
active,
|
|
195
|
+
resultCount: vms.length,
|
|
196
|
+
searchBar: /* @__PURE__ */ jsx(SearchBar, { value: inputValue, onChange: setInputValue }),
|
|
197
|
+
viewToggle
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
] }) : null,
|
|
201
|
+
renderBody(),
|
|
202
|
+
!loading && !error && vms.length > Math.min(...pageSizeOptions) ? /* @__PURE__ */ jsx(
|
|
203
|
+
"div",
|
|
204
|
+
{
|
|
205
|
+
style: {
|
|
206
|
+
display: "flex",
|
|
207
|
+
justifyContent: "center",
|
|
208
|
+
marginTop: "var(--bui-space-5)"
|
|
209
|
+
},
|
|
210
|
+
children: /* @__PURE__ */ jsx(
|
|
211
|
+
TablePagination,
|
|
212
|
+
{
|
|
213
|
+
pageSize,
|
|
214
|
+
pageSizeOptions,
|
|
215
|
+
offset: (currentPage - 1) * pageSize,
|
|
216
|
+
totalCount: vms.length,
|
|
217
|
+
hasNextPage: currentPage < totalPages,
|
|
218
|
+
hasPreviousPage: currentPage > 1,
|
|
219
|
+
onNextPage: () => setPage(currentPage + 1),
|
|
220
|
+
onPreviousPage: () => setPage(currentPage - 1),
|
|
221
|
+
onPageSizeChange: setPageSize
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
) : null
|
|
226
|
+
] }),
|
|
227
|
+
/* @__PURE__ */ jsx(
|
|
228
|
+
ApiDetailsDrawer,
|
|
229
|
+
{
|
|
230
|
+
entity: selected,
|
|
231
|
+
orgLabels,
|
|
232
|
+
onClose: handleClose
|
|
233
|
+
}
|
|
234
|
+
)
|
|
235
|
+
] });
|
|
236
|
+
}
|
|
237
|
+
function ApigeeApiCatalogPage() {
|
|
238
|
+
return /* @__PURE__ */ jsxs(EntityListProvider, { children: [
|
|
239
|
+
/* @__PURE__ */ jsx(ApplyApigeeFilters, {}),
|
|
240
|
+
/* @__PURE__ */ jsx(ApigeeCatalogContent, {})
|
|
241
|
+
] });
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export { ApigeeApiCatalogPage, ApigeeCatalogContent };
|
|
245
|
+
//# sourceMappingURL=ApigeeApiCatalogPage.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApigeeApiCatalogPage.esm.js","sources":["../../src/components/ApigeeApiCatalogPage.tsx"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport type { Entity } from '@backstage/catalog-model';\nimport { Content, Page } from '@backstage/core-components';\nimport { configApiRef, useApi } from '@backstage/core-plugin-api';\nimport {\n EntityListProvider,\n useEntityList,\n type DefaultEntityFilters,\n} from '@backstage/plugin-catalog-react';\nimport { TablePagination, ToggleButton } from '@backstage/ui';\nimport { RiLayoutGridLine, RiListUnordered } from '@remixicon/react';\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport { ANNOTATION_HAS_SPEC } from '../constants';\nimport { toApiCardViewModel, type ApiCardViewModel } from './apiCardViewModel';\nimport { ApiCardGrid, type ViewMode } from './ApiCardGrid';\nimport { ApiDetailsDrawer } from './ApiDetailsDrawer';\nimport {\n ApiKindFilter,\n isApigeeEntity,\n type ApiSearchFilter,\n type FieldFacetFilter,\n} from './apigeeFacetFilters';\nimport { ApigeeOrgFilter } from './apigeeOrgFilter';\nimport { isActive } from './apigeeSearchParams';\nimport { CountCards } from './CountCards';\nimport { deriveCounts, deriveFacets } from './facetModel';\nimport { FilterToolbar } from './FilterToolbar';\nimport { SearchBar } from './SearchBar';\nimport {\n ApiCatalogEmptyState,\n ApiCatalogError,\n NoMatchState,\n SkeletonGrid,\n} from './states';\nimport { useApigeeFilters } from './useApigeeFilters';\n\n/** Initial page size when `apigee.catalog.pageSize` is unset; the selector lets users change it. */\nconst DEFAULT_PAGE_SIZE = 20;\n\n/** Page-size choices offered by the catalog pagination selector. */\nconst PAGE_SIZE_OPTIONS = [10, 20, 30, 40, 50];\n\n/**\n * Entity-list filters registered on the Apigee catalog page. `apigeeKind` (the seam-free\n * `ApiKindFilter`) and `apigeeOrg` (the dual-key is-Apigee gate) are the backend filters applied\n * once by {@link ApplyApigeeFilters}; the facet + search filters\n * (`apigeeLifecycle`/`apigeeOrgFacet`/`apigeeOwner`/`apigeeDepartment`/`apigeeSearch`)\n * are driven from the URL by `useApigeeFilters`. Exported so the hook can import the filter contract.\n */\nexport type ApigeeFilters = DefaultEntityFilters & {\n apigeeKind?: ApiKindFilter;\n apigeeOrg?: ApigeeOrgFilter;\n apigeeLifecycle?: FieldFacetFilter;\n apigeeOrgFacet?: FieldFacetFilter;\n apigeeOwner?: FieldFacetFilter;\n apigeeDepartment?: FieldFacetFilter;\n apigeeSearch?: ApiSearchFilter;\n};\n\n/**\n * Registers the Apigee backend filters on the shared EntityListProvider ONCE: the seam-free\n * `ApiKindFilter` (kind=api with NO `toQueryValue`, so the provider's URL writer stays a no-op and\n * `useSearchParams` owns a clean URL) plus the dual-key `ApigeeOrgFilter` is-Apigee gate. The facet\n * + search filters are applied separately, URL-driven, by `useApigeeFilters`. Rendered inside the\n * provider; renders nothing itself.\n */\nfunction ApplyApigeeFilters() {\n const { updateFilters } = useEntityList<ApigeeFilters>();\n useEffect(() => {\n updateFilters({\n apigeeKind: new ApiKindFilter(),\n apigeeOrg: new ApigeeOrgFilter(),\n });\n }, [updateFilters]);\n return null;\n}\n\n/**\n * The page body: reads the filtered entity list, maps to view-models, and branches on\n * loading/empty/error. Exposes a Flat/List view toggle (component state only, no view\n * URL param — that is a Phase 6 seam). (UI-01, UI-05, UI-11)\n */\nexport function ApigeeCatalogContent() {\n const { entities, backendEntities, loading, error } =\n useEntityList<ApigeeFilters>();\n const {\n state,\n inputValue,\n setInputValue,\n toggleFacet,\n selectOnlyFacet,\n clearFacet,\n clearAll,\n page,\n setPage,\n pageSizeParam,\n setPageSize,\n openApiRef,\n openApi,\n closeApi,\n } = useApigeeFilters();\n const configApi = useApi(configApiRef);\n\n const orgLabels = useMemo<Record<string, string> | undefined>(() => {\n const config = configApi.getOptionalConfig('apigee.catalog.orgLabels');\n if (!config) {\n return undefined;\n }\n const out: Record<string, string> = {};\n for (const key of config.keys()) {\n const value = config.getOptionalString(key);\n if (value) {\n out[key] = value;\n }\n }\n return Object.keys(out).length > 0 ? out : undefined;\n }, [configApi]);\n\n const defaultPageSize = useMemo(() => {\n const configured = configApi.getOptionalNumber('apigee.catalog.pageSize');\n return configured === undefined\n ? DEFAULT_PAGE_SIZE\n : Math.max(1, Math.floor(configured));\n }, [configApi]);\n\n // The selector's choices always include the configured default, so its display can't\n // drift from the size the grid actually slices by.\n const pageSizeOptions = useMemo(\n () =>\n PAGE_SIZE_OPTIONS.includes(defaultPageSize)\n ? PAGE_SIZE_OPTIONS\n : [...PAGE_SIZE_OPTIONS, defaultPageSize].sort((a, b) => a - b),\n [defaultPageSize],\n );\n\n // Active size comes from the URL (`?size=`), falling back to the configured default; an\n // out-of-range or hand-edited value degrades to that default.\n const pageSize =\n pageSizeParam !== undefined && pageSizeOptions.includes(pageSizeParam)\n ? pageSizeParam\n : defaultPageSize;\n\n const apigeeEntities = useMemo(\n () => backendEntities.filter(isApigeeEntity),\n [backendEntities],\n );\n const vms = useMemo(() => entities.map(toApiCardViewModel), [entities]);\n const facets = useMemo(\n () => deriveFacets(apigeeEntities, orgLabels),\n [apigeeEntities, orgLabels],\n );\n // Static full-catalog counts: the cards stay fixed as filters apply; only the grid narrows.\n const counts = useMemo(\n () =>\n deriveCounts(\n apigeeEntities,\n ['org', 'lifecycle', 'department'],\n orgLabels,\n ),\n [apigeeEntities, orgLabels],\n );\n const specCount = useMemo(\n () =>\n apigeeEntities.filter(\n entity => entity.metadata.annotations?.[ANNOTATION_HAS_SPEC] === 'true',\n ).length,\n [apigeeEntities],\n );\n const active = isActive(state);\n const [viewMode, setViewMode] = useState<ViewMode>('flat');\n\n // Pagination: the current page lives in the URL (shareable + reload-safe, reset on\n // filter/search changes by useApigeeFilters). Clamp to the available range so a stale\n // or hand-edited `page=` value never strands the user on an empty grid.\n const totalPages = Math.max(1, Math.ceil(vms.length / pageSize));\n const currentPage = Math.min(page, totalPages);\n const pageItems = useMemo(\n () => vms.slice((currentPage - 1) * pageSize, currentPage * pageSize),\n [currentPage, pageSize, vms],\n );\n\n // Clicking a card/row opens a details drawer instead of navigating to the catalog entity\n // page. The open API's `<namespace>/<name>` ref lives in the URL (`?api=`) so the drawer is\n // shareable and reload-safe. Resolve against the FULL Apigee set (not the filtered view) so a\n // shared link still opens the drawer when active filters would hide the card.\n const entitiesByRef = useMemo(() => {\n const map = new Map<string, Entity>();\n for (const entity of apigeeEntities) {\n const namespace = entity.metadata.namespace ?? 'default';\n map.set(`${namespace}/${entity.metadata.name}`, entity);\n }\n return map;\n }, [apigeeEntities]);\n const selected = openApiRef ? entitiesByRef.get(openApiRef) : undefined;\n const handleSelect = useCallback(\n (vm: ApiCardViewModel) => {\n const { namespace, name } = vm.targetRef;\n openApi(`${namespace}/${name}`);\n },\n [openApi],\n );\n const handleClose = useCallback(() => closeApi(), [closeApi]);\n\n const renderBody = () => {\n if (error) {\n return <ApiCatalogError error={error} />;\n }\n if (loading) {\n return <SkeletonGrid />;\n }\n if (vms.length === 0) {\n return active ? (\n <NoMatchState onClearAll={clearAll} />\n ) : (\n <ApiCatalogEmptyState />\n );\n }\n return (\n <ApiCardGrid\n vms={pageItems}\n viewMode={viewMode}\n onSelect={handleSelect}\n />\n );\n };\n\n const viewToggle = (\n <div\n role=\"group\"\n aria-label=\"API view mode\"\n style={{ display: 'inline-flex', gap: 'var(--bui-space-1)' }}\n >\n <ToggleButton\n aria-label=\"Flat grid view\"\n isSelected={viewMode === 'flat'}\n onChange={() => setViewMode('flat')}\n iconStart={<RiLayoutGridLine />}\n >\n Flat\n </ToggleButton>\n <ToggleButton\n aria-label=\"List view\"\n isSelected={viewMode === 'list'}\n onChange={() => setViewMode('list')}\n iconStart={<RiListUnordered />}\n >\n List\n </ToggleButton>\n </div>\n );\n\n return (\n <Page themeId=\"apis\">\n <Content>\n {!loading && !error && apigeeEntities.length > 0 ? (\n <>\n <CountCards\n total={apigeeEntities.length}\n specCount={specCount}\n counts={counts}\n selected={state.selected}\n onSelectFacet={selectOnlyFacet}\n />\n <FilterToolbar\n facets={facets}\n selected={state.selected}\n onToggleFacet={toggleFacet}\n onClearFacet={clearFacet}\n onClearAll={clearAll}\n active={active}\n resultCount={vms.length}\n searchBar={\n <SearchBar value={inputValue} onChange={setInputValue} />\n }\n viewToggle={viewToggle}\n />\n </>\n ) : null}\n {renderBody()}\n {!loading && !error && vms.length > Math.min(...pageSizeOptions) ? (\n <div\n style={{\n display: 'flex',\n justifyContent: 'center',\n marginTop: 'var(--bui-space-5)',\n }}\n >\n <TablePagination\n pageSize={pageSize}\n pageSizeOptions={pageSizeOptions}\n offset={(currentPage - 1) * pageSize}\n totalCount={vms.length}\n hasNextPage={currentPage < totalPages}\n hasPreviousPage={currentPage > 1}\n onNextPage={() => setPage(currentPage + 1)}\n onPreviousPage={() => setPage(currentPage - 1)}\n onPageSizeChange={setPageSize}\n />\n </div>\n ) : null}\n </Content>\n <ApiDetailsDrawer\n entity={selected}\n orgLabels={orgLabels}\n onClose={handleClose}\n />\n </Page>\n );\n}\n\n/**\n * Route target for `/api-hub/catalog` (05-06). Wraps the content in an `EntityListProvider` and\n * registers the Apigee filters — the shared filter context Phase 6's facets + search reuse.\n */\nexport function ApigeeApiCatalogPage() {\n return (\n <EntityListProvider>\n <ApplyApigeeFilters />\n <ApigeeCatalogContent />\n </EntityListProvider>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
+
import { Page, Content, Link } from '@backstage/core-components';
|
|
3
|
+
import { useApi } from '@backstage/core-plugin-api';
|
|
4
|
+
import { useRouteRef } from '@backstage/frontend-plugin-api';
|
|
5
|
+
import { catalogApiRef } from '@backstage/plugin-catalog-react';
|
|
6
|
+
import { Text, Skeleton } from '@backstage/ui';
|
|
7
|
+
import { RiStackLine, RiPulseLine, RiFlaskLine, RiArchiveLine, RiFileTextLine, RiArrowRightLine } from '@remixicon/react';
|
|
8
|
+
import { useState, useEffect } from 'react';
|
|
9
|
+
import { catalogRouteRef } from '../routes.esm.js';
|
|
10
|
+
import { isApigeeEntity, entityLifecycle } from './apigeeFacetFilters.esm.js';
|
|
11
|
+
import styles from './ApigeeHomePage.module.css.esm.js';
|
|
12
|
+
|
|
13
|
+
function computeStats(entities) {
|
|
14
|
+
let production = 0;
|
|
15
|
+
let experimental = 0;
|
|
16
|
+
let deprecated = 0;
|
|
17
|
+
for (const entity of entities) {
|
|
18
|
+
switch (entityLifecycle(entity)) {
|
|
19
|
+
case "production":
|
|
20
|
+
production += 1;
|
|
21
|
+
break;
|
|
22
|
+
case "experimental":
|
|
23
|
+
experimental += 1;
|
|
24
|
+
break;
|
|
25
|
+
case "deprecated":
|
|
26
|
+
deprecated += 1;
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return { total: entities.length, production, experimental, deprecated };
|
|
31
|
+
}
|
|
32
|
+
const STAT_CARDS = [
|
|
33
|
+
{ key: "total", label: "APIs", icon: RiStackLine },
|
|
34
|
+
{
|
|
35
|
+
key: "production",
|
|
36
|
+
label: "Production",
|
|
37
|
+
icon: RiPulseLine,
|
|
38
|
+
query: "lifecycle=production"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
key: "experimental",
|
|
42
|
+
label: "Experimental",
|
|
43
|
+
icon: RiFlaskLine,
|
|
44
|
+
query: "lifecycle=experimental"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
key: "deprecated",
|
|
48
|
+
label: "Deprecated",
|
|
49
|
+
icon: RiArchiveLine,
|
|
50
|
+
query: "lifecycle=deprecated"
|
|
51
|
+
}
|
|
52
|
+
];
|
|
53
|
+
function StatCard({
|
|
54
|
+
icon: Icon,
|
|
55
|
+
label,
|
|
56
|
+
value,
|
|
57
|
+
loading,
|
|
58
|
+
to
|
|
59
|
+
}) {
|
|
60
|
+
return /* @__PURE__ */ jsxs(Link, { to, className: styles.statCard, children: [
|
|
61
|
+
loading || value === void 0 ? /* @__PURE__ */ jsx(Skeleton, { width: 72, height: 34 }) : /* @__PURE__ */ jsx(
|
|
62
|
+
Text,
|
|
63
|
+
{
|
|
64
|
+
as: "p",
|
|
65
|
+
variant: "title-large",
|
|
66
|
+
weight: "bold",
|
|
67
|
+
className: styles.stat,
|
|
68
|
+
children: value
|
|
69
|
+
}
|
|
70
|
+
),
|
|
71
|
+
/* @__PURE__ */ jsxs("div", { className: styles.statTitleRow, children: [
|
|
72
|
+
/* @__PURE__ */ jsx(Icon, { "aria-hidden": true, size: 20, className: styles.statIcon }),
|
|
73
|
+
/* @__PURE__ */ jsx(
|
|
74
|
+
Text,
|
|
75
|
+
{
|
|
76
|
+
as: "span",
|
|
77
|
+
variant: "body-medium",
|
|
78
|
+
color: "secondary",
|
|
79
|
+
className: styles.statTitle,
|
|
80
|
+
children: label
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
] })
|
|
84
|
+
] });
|
|
85
|
+
}
|
|
86
|
+
function QuickLinkTile({ link }) {
|
|
87
|
+
const { title, description, icon: Icon, to } = link;
|
|
88
|
+
const body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
89
|
+
/* @__PURE__ */ jsxs("div", { className: styles.tileTitleRow, children: [
|
|
90
|
+
/* @__PURE__ */ jsx(Icon, { "aria-hidden": true, size: 22, className: styles.tileIcon }),
|
|
91
|
+
/* @__PURE__ */ jsx(Text, { as: "h3", variant: "body-large", weight: "bold", children: title }),
|
|
92
|
+
to ? /* @__PURE__ */ jsx(
|
|
93
|
+
RiArrowRightLine,
|
|
94
|
+
{
|
|
95
|
+
"aria-hidden": true,
|
|
96
|
+
size: 18,
|
|
97
|
+
className: styles.tileArrow
|
|
98
|
+
}
|
|
99
|
+
) : /* @__PURE__ */ jsx("span", { className: styles.tileBadge, children: "Coming soon" })
|
|
100
|
+
] }),
|
|
101
|
+
/* @__PURE__ */ jsx(Text, { as: "p", variant: "body-medium", color: "secondary", children: description })
|
|
102
|
+
] });
|
|
103
|
+
return to ? /* @__PURE__ */ jsx(Link, { to, className: styles.tile, children: body }) : /* @__PURE__ */ jsx(
|
|
104
|
+
"div",
|
|
105
|
+
{
|
|
106
|
+
className: `${styles.tile} ${styles.tileDisabled}`,
|
|
107
|
+
"aria-disabled": "true",
|
|
108
|
+
children: body
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
function ApigeeHomePage() {
|
|
113
|
+
const catalogRoute = useRouteRef(catalogRouteRef);
|
|
114
|
+
const catalogHref = catalogRoute ? catalogRoute() : "catalog";
|
|
115
|
+
const catalogApi = useApi(catalogApiRef);
|
|
116
|
+
const [stats, setStats] = useState();
|
|
117
|
+
const [loading, setLoading] = useState(true);
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
let active = true;
|
|
120
|
+
setLoading(true);
|
|
121
|
+
catalogApi.getEntities({ filter: { kind: "API" } }).then(({ items }) => {
|
|
122
|
+
if (active) {
|
|
123
|
+
setStats(computeStats(items.filter(isApigeeEntity)));
|
|
124
|
+
}
|
|
125
|
+
}).catch(() => {
|
|
126
|
+
if (active) {
|
|
127
|
+
setStats(void 0);
|
|
128
|
+
}
|
|
129
|
+
}).finally(() => {
|
|
130
|
+
if (active) {
|
|
131
|
+
setLoading(false);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
return () => {
|
|
135
|
+
active = false;
|
|
136
|
+
};
|
|
137
|
+
}, [catalogApi]);
|
|
138
|
+
const quickLinks = [
|
|
139
|
+
{
|
|
140
|
+
title: "API catalog",
|
|
141
|
+
description: "Browse, search and filter every Apigee API in the catalog.",
|
|
142
|
+
icon: RiStackLine,
|
|
143
|
+
to: catalogHref
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
title: "Runtime & deployments",
|
|
147
|
+
description: "Per-environment revisions and deployment status for each proxy.",
|
|
148
|
+
icon: RiPulseLine
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
title: "API specifications",
|
|
152
|
+
description: "Open OpenAPI definitions and try endpoints from the catalog.",
|
|
153
|
+
icon: RiFileTextLine
|
|
154
|
+
}
|
|
155
|
+
];
|
|
156
|
+
return /* @__PURE__ */ jsx(Page, { themeId: "apis", children: /* @__PURE__ */ jsxs(Content, { children: [
|
|
157
|
+
/* @__PURE__ */ jsxs("div", { className: styles.hero, children: [
|
|
158
|
+
/* @__PURE__ */ jsx(RiStackLine, { "aria-hidden": true, size: 40, className: styles.heroIcon }),
|
|
159
|
+
/* @__PURE__ */ jsx(Text, { as: "h2", variant: "title-large", weight: "bold", children: "Welcome to API Hub" }),
|
|
160
|
+
/* @__PURE__ */ jsx(Text, { as: "p", variant: "body-large", color: "secondary", children: "API Hub is the single place to discover the APIs NOS runs on Apigee. Browse proxies and their API Hub definitions; filter by lifecycle, org, owner and department; and open an API to inspect its OpenAPI spec and deployments." })
|
|
161
|
+
] }),
|
|
162
|
+
/* @__PURE__ */ jsx("section", { "aria-label": "Catalog overview", className: styles.cards, children: STAT_CARDS.map((card) => /* @__PURE__ */ jsx(
|
|
163
|
+
StatCard,
|
|
164
|
+
{
|
|
165
|
+
icon: card.icon,
|
|
166
|
+
label: card.label,
|
|
167
|
+
value: stats?.[card.key],
|
|
168
|
+
loading,
|
|
169
|
+
to: card.query ? `${catalogHref}?${card.query}` : catalogHref
|
|
170
|
+
},
|
|
171
|
+
card.key
|
|
172
|
+
)) }),
|
|
173
|
+
/* @__PURE__ */ jsxs("section", { "aria-label": "Quick links", children: [
|
|
174
|
+
/* @__PURE__ */ jsx(
|
|
175
|
+
Text,
|
|
176
|
+
{
|
|
177
|
+
as: "h3",
|
|
178
|
+
variant: "body-large",
|
|
179
|
+
weight: "bold",
|
|
180
|
+
className: styles.sectionTitle,
|
|
181
|
+
children: "Quick links"
|
|
182
|
+
}
|
|
183
|
+
),
|
|
184
|
+
/* @__PURE__ */ jsx("div", { className: styles.tiles, children: quickLinks.map((link) => /* @__PURE__ */ jsx(QuickLinkTile, { link }, link.title)) })
|
|
185
|
+
] })
|
|
186
|
+
] }) });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export { ApigeeHomePage };
|
|
190
|
+
//# sourceMappingURL=ApigeeHomePage.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApigeeHomePage.esm.js","sources":["../../src/components/ApigeeHomePage.tsx"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport type { Entity } from '@backstage/catalog-model';\nimport { Content, Link, Page } from '@backstage/core-components';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { useRouteRef } from '@backstage/frontend-plugin-api';\nimport { catalogApiRef } from '@backstage/plugin-catalog-react';\nimport { Skeleton, Text } from '@backstage/ui';\nimport {\n RiArchiveLine,\n RiArrowRightLine,\n RiFileTextLine,\n RiFlaskLine,\n RiPulseLine,\n RiStackLine,\n type RemixiconComponentType,\n} from '@remixicon/react';\nimport { useEffect, useState } from 'react';\nimport { catalogRouteRef } from '../routes';\nimport { entityLifecycle, isApigeeEntity } from './apigeeFacetFilters';\nimport styles from './ApigeeHomePage.module.css';\n\ninterface HomeStats {\n total: number;\n production: number;\n experimental: number;\n deprecated: number;\n}\n\n/** Top-line dashboard counts (per lifecycle) derived from the Apigee API entities. */\nfunction computeStats(entities: Entity[]): HomeStats {\n let production = 0;\n let experimental = 0;\n let deprecated = 0;\n for (const entity of entities) {\n switch (entityLifecycle(entity)) {\n case 'production':\n production += 1;\n break;\n case 'experimental':\n experimental += 1;\n break;\n case 'deprecated':\n deprecated += 1;\n break;\n default:\n break;\n }\n }\n return { total: entities.length, production, experimental, deprecated };\n}\n\nconst STAT_CARDS: {\n key: keyof HomeStats;\n label: string;\n icon: RemixiconComponentType;\n /** Catalog query string this card drills into; absent = the whole catalog. */\n query?: string;\n}[] = [\n { key: 'total', label: 'APIs', icon: RiStackLine },\n {\n key: 'production',\n label: 'Production',\n icon: RiPulseLine,\n query: 'lifecycle=production',\n },\n {\n key: 'experimental',\n label: 'Experimental',\n icon: RiFlaskLine,\n query: 'lifecycle=experimental',\n },\n {\n key: 'deprecated',\n label: 'Deprecated',\n icon: RiArchiveLine,\n query: 'lifecycle=deprecated',\n },\n];\n\nfunction StatCard({\n icon: Icon,\n label,\n value,\n loading,\n to,\n}: {\n icon: RemixiconComponentType;\n label: string;\n value: number | undefined;\n loading: boolean;\n to: string;\n}) {\n return (\n <Link to={to} className={styles.statCard}>\n {loading || value === undefined ? (\n <Skeleton width={72} height={34} />\n ) : (\n <Text\n as=\"p\"\n variant=\"title-large\"\n weight=\"bold\"\n className={styles.stat}\n >\n {value}\n </Text>\n )}\n <div className={styles.statTitleRow}>\n <Icon aria-hidden size={20} className={styles.statIcon} />\n <Text\n as=\"span\"\n variant=\"body-medium\"\n color=\"secondary\"\n className={styles.statTitle}\n >\n {label}\n </Text>\n </div>\n </Link>\n );\n}\n\ninterface QuickLink {\n title: string;\n description: string;\n icon: RemixiconComponentType;\n /** When set, the tile is a live link; otherwise it renders as a \"Coming soon\" placeholder. */\n to?: string;\n}\n\nfunction QuickLinkTile({ link }: { link: QuickLink }) {\n const { title, description, icon: Icon, to } = link;\n const body = (\n <>\n <div className={styles.tileTitleRow}>\n <Icon aria-hidden size={22} className={styles.tileIcon} />\n <Text as=\"h3\" variant=\"body-large\" weight=\"bold\">\n {title}\n </Text>\n {to ? (\n <RiArrowRightLine\n aria-hidden\n size={18}\n className={styles.tileArrow}\n />\n ) : (\n <span className={styles.tileBadge}>Coming soon</span>\n )}\n </div>\n <Text as=\"p\" variant=\"body-medium\" color=\"secondary\">\n {description}\n </Text>\n </>\n );\n return to ? (\n <Link to={to} className={styles.tile}>\n {body}\n </Link>\n ) : (\n <div\n className={`${styles.tile} ${styles.tileDisabled}`}\n aria-disabled=\"true\"\n >\n {body}\n </div>\n );\n}\n\n/**\n * API Hub landing page mounted at `/api-hub`: a plugin overview, live catalog stat cards,\n * and quick-link tiles into the plugin's features. The catalog link resolves via\n * `catalogRouteRef` (never hardcoded); it degrades to a relative path when the route ref\n * is unbound (dev harness).\n */\nexport function ApigeeHomePage() {\n const catalogRoute = useRouteRef(catalogRouteRef);\n const catalogHref = catalogRoute ? catalogRoute() : 'catalog';\n const catalogApi = useApi(catalogApiRef);\n const [stats, setStats] = useState<HomeStats | undefined>();\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n let active = true;\n setLoading(true);\n catalogApi\n .getEntities({ filter: { kind: 'API' } })\n .then(({ items }) => {\n if (active) {\n setStats(computeStats(items.filter(isApigeeEntity)));\n }\n })\n .catch(() => {\n if (active) {\n setStats(undefined);\n }\n })\n .finally(() => {\n if (active) {\n setLoading(false);\n }\n });\n return () => {\n active = false;\n };\n }, [catalogApi]);\n\n const quickLinks: QuickLink[] = [\n {\n title: 'API catalog',\n description: 'Browse, search and filter every Apigee API in the catalog.',\n icon: RiStackLine,\n to: catalogHref,\n },\n {\n title: 'Runtime & deployments',\n description:\n 'Per-environment revisions and deployment status for each proxy.',\n icon: RiPulseLine,\n },\n {\n title: 'API specifications',\n description:\n 'Open OpenAPI definitions and try endpoints from the catalog.',\n icon: RiFileTextLine,\n },\n ];\n\n return (\n <Page themeId=\"apis\">\n <Content>\n <div className={styles.hero}>\n <RiStackLine aria-hidden size={40} className={styles.heroIcon} />\n <Text as=\"h2\" variant=\"title-large\" weight=\"bold\">\n Welcome to API Hub\n </Text>\n <Text as=\"p\" variant=\"body-large\" color=\"secondary\">\n API Hub is the single place to discover the APIs NOS runs on Apigee.\n Browse proxies and their API Hub definitions; filter by lifecycle,\n org, owner and department; and open an API to inspect its OpenAPI\n spec and deployments.\n </Text>\n </div>\n\n <section aria-label=\"Catalog overview\" className={styles.cards}>\n {STAT_CARDS.map(card => (\n <StatCard\n key={card.key}\n icon={card.icon}\n label={card.label}\n value={stats?.[card.key]}\n loading={loading}\n to={card.query ? `${catalogHref}?${card.query}` : catalogHref}\n />\n ))}\n </section>\n\n <section aria-label=\"Quick links\">\n <Text\n as=\"h3\"\n variant=\"body-large\"\n weight=\"bold\"\n className={styles.sectionTitle}\n >\n Quick links\n </Text>\n <div className={styles.tiles}>\n {quickLinks.map(link => (\n <QuickLinkTile key={link.title} link={link} />\n ))}\n </div>\n </section>\n </Content>\n </Page>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;AA6BA,SAAS,aAAa,QAAA,EAA+B;AACnD,EAAA,IAAI,UAAA,GAAa,CAAA;AACjB,EAAA,IAAI,YAAA,GAAe,CAAA;AACnB,EAAA,IAAI,UAAA,GAAa,CAAA;AACjB,EAAA,KAAA,MAAW,UAAU,QAAA,EAAU;AAC7B,IAAA,QAAQ,eAAA,CAAgB,MAAM,CAAA;AAAG,MAC/B,KAAK,YAAA;AACH,QAAA,UAAA,IAAc,CAAA;AACd,QAAA;AAAA,MACF,KAAK,cAAA;AACH,QAAA,YAAA,IAAgB,CAAA;AAChB,QAAA;AAAA,MACF,KAAK,YAAA;AACH,QAAA,UAAA,IAAc,CAAA;AACd,QAAA;AAEA;AACJ,EACF;AACA,EAAA,OAAO,EAAE,KAAA,EAAO,QAAA,CAAS,MAAA,EAAQ,UAAA,EAAY,cAAc,UAAA,EAAW;AACxE;AAEA,MAAM,UAAA,GAMA;AAAA,EACJ,EAAE,GAAA,EAAK,OAAA,EAAS,KAAA,EAAO,MAAA,EAAQ,MAAM,WAAA,EAAY;AAAA,EACjD;AAAA,IACE,GAAA,EAAK,YAAA;AAAA,IACL,KAAA,EAAO,YAAA;AAAA,IACP,IAAA,EAAM,WAAA;AAAA,IACN,KAAA,EAAO;AAAA,GACT;AAAA,EACA;AAAA,IACE,GAAA,EAAK,cAAA;AAAA,IACL,KAAA,EAAO,cAAA;AAAA,IACP,IAAA,EAAM,WAAA;AAAA,IACN,KAAA,EAAO;AAAA,GACT;AAAA,EACA;AAAA,IACE,GAAA,EAAK,YAAA;AAAA,IACL,KAAA,EAAO,YAAA;AAAA,IACP,IAAA,EAAM,aAAA;AAAA,IACN,KAAA,EAAO;AAAA;AAEX,CAAA;AAEA,SAAS,QAAA,CAAS;AAAA,EAChB,IAAA,EAAM,IAAA;AAAA,EACN,KAAA;AAAA,EACA,KAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAMG;AACD,EAAA,uBACE,IAAA,CAAC,IAAA,EAAA,EAAK,EAAA,EAAQ,SAAA,EAAW,OAAO,QAAA,EAC7B,QAAA,EAAA;AAAA,IAAA,OAAA,IAAW,KAAA,KAAU,yBACpB,GAAA,CAAC,QAAA,EAAA,EAAS,OAAO,EAAA,EAAI,MAAA,EAAQ,IAAI,CAAA,mBAEjC,GAAA;AAAA,MAAC,IAAA;AAAA,MAAA;AAAA,QACC,EAAA,EAAG,GAAA;AAAA,QACH,OAAA,EAAQ,aAAA;AAAA,QACR,MAAA,EAAO,MAAA;AAAA,QACP,WAAW,MAAA,CAAO,IAAA;AAAA,QAEjB,QAAA,EAAA;AAAA;AAAA,KACH;AAAA,oBAEF,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,MAAA,CAAO,YAAA,EACrB,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,QAAK,aAAA,EAAW,IAAA,EAAC,MAAM,EAAA,EAAI,SAAA,EAAW,OAAO,QAAA,EAAU,CAAA;AAAA,sBACxD,GAAA;AAAA,QAAC,IAAA;AAAA,QAAA;AAAA,UACC,EAAA,EAAG,MAAA;AAAA,UACH,OAAA,EAAQ,aAAA;AAAA,UACR,KAAA,EAAM,WAAA;AAAA,UACN,WAAW,MAAA,CAAO,SAAA;AAAA,UAEjB,QAAA,EAAA;AAAA;AAAA;AACH,KAAA,EACF;AAAA,GAAA,EACF,CAAA;AAEJ;AAUA,SAAS,aAAA,CAAc,EAAE,IAAA,EAAK,EAAwB;AACpD,EAAA,MAAM,EAAE,KAAA,EAAO,WAAA,EAAa,IAAA,EAAM,IAAA,EAAM,IAAG,GAAI,IAAA;AAC/C,EAAA,MAAM,uBACJ,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,oBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,MAAA,CAAO,YAAA,EACrB,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,QAAK,aAAA,EAAW,IAAA,EAAC,MAAM,EAAA,EAAI,SAAA,EAAW,OAAO,QAAA,EAAU,CAAA;AAAA,sBACxD,GAAA,CAAC,QAAK,EAAA,EAAG,IAAA,EAAK,SAAQ,YAAA,EAAa,MAAA,EAAO,QACvC,QAAA,EAAA,KAAA,EACH,CAAA;AAAA,MACC,EAAA,mBACC,GAAA;AAAA,QAAC,gBAAA;AAAA,QAAA;AAAA,UACC,aAAA,EAAW,IAAA;AAAA,UACX,IAAA,EAAM,EAAA;AAAA,UACN,WAAW,MAAA,CAAO;AAAA;AAAA,0BAGpB,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAW,MAAA,CAAO,WAAW,QAAA,EAAA,aAAA,EAAW;AAAA,KAAA,EAElD,CAAA;AAAA,oBACA,GAAA,CAAC,QAAK,EAAA,EAAG,GAAA,EAAI,SAAQ,aAAA,EAAc,KAAA,EAAM,aACtC,QAAA,EAAA,WAAA,EACH;AAAA,GAAA,EACF,CAAA;AAEF,EAAA,OAAO,EAAA,uBACJ,IAAA,EAAA,EAAK,EAAA,EAAQ,WAAW,MAAA,CAAO,IAAA,EAC7B,gBACH,CAAA,mBAEA,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,WAAW,CAAA,EAAG,MAAA,CAAO,IAAI,CAAA,CAAA,EAAI,OAAO,YAAY,CAAA,CAAA;AAAA,MAChD,eAAA,EAAc,MAAA;AAAA,MAEb,QAAA,EAAA;AAAA;AAAA,GACH;AAEJ;AAQO,SAAS,cAAA,GAAiB;AAC/B,EAAA,MAAM,YAAA,GAAe,YAAY,eAAe,CAAA;AAChD,EAAA,MAAM,WAAA,GAAc,YAAA,GAAe,YAAA,EAAa,GAAI,SAAA;AACpD,EAAA,MAAM,UAAA,GAAa,OAAO,aAAa,CAAA;AACvC,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,QAAA,EAAgC;AAC1D,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,IAAI,CAAA;AAE3C,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,MAAA,GAAS,IAAA;AACb,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,UAAA,CACG,WAAA,CAAY,EAAE,MAAA,EAAQ,EAAE,IAAA,EAAM,KAAA,EAAM,EAAG,CAAA,CACvC,IAAA,CAAK,CAAC,EAAE,OAAM,KAAM;AACnB,MAAA,IAAI,MAAA,EAAQ;AACV,QAAA,QAAA,CAAS,YAAA,CAAa,KAAA,CAAM,MAAA,CAAO,cAAc,CAAC,CAAC,CAAA;AAAA,MACrD;AAAA,IACF,CAAC,CAAA,CACA,KAAA,CAAM,MAAM;AACX,MAAA,IAAI,MAAA,EAAQ;AACV,QAAA,QAAA,CAAS,MAAS,CAAA;AAAA,MACpB;AAAA,IACF,CAAC,CAAA,CACA,OAAA,CAAQ,MAAM;AACb,MAAA,IAAI,MAAA,EAAQ;AACV,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA,MAClB;AAAA,IACF,CAAC,CAAA;AACH,IAAA,OAAO,MAAM;AACX,MAAA,MAAA,GAAS,KAAA;AAAA,IACX,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,UAAU,CAAC,CAAA;AAEf,EAAA,MAAM,UAAA,GAA0B;AAAA,IAC9B;AAAA,MACE,KAAA,EAAO,aAAA;AAAA,MACP,WAAA,EAAa,4DAAA;AAAA,MACb,IAAA,EAAM,WAAA;AAAA,MACN,EAAA,EAAI;AAAA,KACN;AAAA,IACA;AAAA,MACE,KAAA,EAAO,uBAAA;AAAA,MACP,WAAA,EACE,iEAAA;AAAA,MACF,IAAA,EAAM;AAAA,KACR;AAAA,IACA;AAAA,MACE,KAAA,EAAO,oBAAA;AAAA,MACP,WAAA,EACE,8DAAA;AAAA,MACF,IAAA,EAAM;AAAA;AACR,GACF;AAEA,EAAA,uBACE,GAAA,CAAC,IAAA,EAAA,EAAK,OAAA,EAAQ,MAAA,EACZ,+BAAC,OAAA,EAAA,EACC,QAAA,EAAA;AAAA,oBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,MAAA,CAAO,IAAA,EACrB,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,eAAY,aAAA,EAAW,IAAA,EAAC,MAAM,EAAA,EAAI,SAAA,EAAW,OAAO,QAAA,EAAU,CAAA;AAAA,sBAC/D,GAAA,CAAC,QAAK,EAAA,EAAG,IAAA,EAAK,SAAQ,aAAA,EAAc,MAAA,EAAO,QAAO,QAAA,EAAA,oBAAA,EAElD,CAAA;AAAA,sBACA,GAAA,CAAC,QAAK,EAAA,EAAG,GAAA,EAAI,SAAQ,YAAA,EAAa,KAAA,EAAM,aAAY,QAAA,EAAA,iOAAA,EAKpD;AAAA,KAAA,EACF,CAAA;AAAA,oBAEA,GAAA,CAAC,aAAQ,YAAA,EAAW,kBAAA,EAAmB,WAAW,MAAA,CAAO,KAAA,EACtD,QAAA,EAAA,UAAA,CAAW,GAAA,CAAI,CAAA,IAAA,qBACd,GAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QAEC,MAAM,IAAA,CAAK,IAAA;AAAA,QACX,OAAO,IAAA,CAAK,KAAA;AAAA,QACZ,KAAA,EAAO,KAAA,GAAQ,IAAA,CAAK,GAAG,CAAA;AAAA,QACvB,OAAA;AAAA,QACA,EAAA,EAAI,KAAK,KAAA,GAAQ,CAAA,EAAG,WAAW,CAAA,CAAA,EAAI,IAAA,CAAK,KAAK,CAAA,CAAA,GAAK;AAAA,OAAA;AAAA,MAL7C,IAAA,CAAK;AAAA,KAOb,CAAA,EACH,CAAA;AAAA,oBAEA,IAAA,CAAC,SAAA,EAAA,EAAQ,YAAA,EAAW,aAAA,EAClB,QAAA,EAAA;AAAA,sBAAA,GAAA;AAAA,QAAC,IAAA;AAAA,QAAA;AAAA,UACC,EAAA,EAAG,IAAA;AAAA,UACH,OAAA,EAAQ,YAAA;AAAA,UACR,MAAA,EAAO,MAAA;AAAA,UACP,WAAW,MAAA,CAAO,YAAA;AAAA,UACnB,QAAA,EAAA;AAAA;AAAA,OAED;AAAA,sBACA,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,MAAA,CAAO,OACpB,QAAA,EAAA,UAAA,CAAW,GAAA,CAAI,CAAA,IAAA,qBACd,GAAA,CAAC,aAAA,EAAA,EAA+B,IAAA,EAAA,EAAZ,IAAA,CAAK,KAAmB,CAC7C,CAAA,EACH;AAAA,KAAA,EACF;AAAA,GAAA,EACF,CAAA,EACF,CAAA;AAEJ;;;;"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import styleInject from '../node_modules_dist/style-inject/dist/style-inject.es.esm.js';
|
|
2
|
+
|
|
3
|
+
var css_248z = ".ApigeeHomePage_hero__28ec1c5981 {\n display: flex;\n flex-direction: column;\n gap: var(--bui-space-2);\n max-width: 720px;\n margin-bottom: var(--bui-space-5);\n}\n\n.ApigeeHomePage_heroIcon__28ec1c5981 {\n color: var(--bui-fg-secondary);\n}\n\n.ApigeeHomePage_cards__28ec1c5981 {\n display: flex;\n flex-wrap: wrap;\n gap: var(--bui-space-4);\n margin-bottom: var(--bui-space-5);\n}\n\n.ApigeeHomePage_statCard__28ec1c5981 {\n display: flex;\n flex-direction: column;\n gap: var(--bui-space-2);\n flex: 1 1 160px;\n min-width: 160px;\n padding: var(--bui-space-2) var(--bui-space-4);\n border: 1px solid var(--bui-border-1);\n border-radius: var(--bui-radius-3);\n background-color: var(--bui-bg-neutral-1);\n color: inherit;\n text-decoration: none;\n transition: box-shadow 200ms ease;\n}\n\n/* The card is a MUI Link; element+class beats MUI's class so its link colour/underline does not bleed onto the card content. */\na.ApigeeHomePage_statCard__28ec1c5981,\na.ApigeeHomePage_statCard__28ec1c5981:hover {\n color: inherit;\n text-decoration: none;\n}\n\n.ApigeeHomePage_statCard__28ec1c5981:hover {\n box-shadow: var(--bui-shadow);\n}\n\na.ApigeeHomePage_statCard__28ec1c5981:focus-visible {\n outline: 2px solid var(--bui-ring);\n outline-offset: 2px;\n}\n\n.ApigeeHomePage_stat__28ec1c5981 {\n line-height: 1.1;\n}\n\n.ApigeeHomePage_statTitleRow__28ec1c5981 {\n display: flex;\n align-items: center;\n gap: var(--bui-space-1);\n}\n\n.ApigeeHomePage_statIcon__28ec1c5981 {\n flex-shrink: 0;\n color: var(--bui-fg-secondary);\n}\n\n.ApigeeHomePage_statTitle__28ec1c5981 {\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.ApigeeHomePage_sectionTitle__28ec1c5981 {\n margin-bottom: var(--bui-space-2);\n}\n\n.ApigeeHomePage_tiles__28ec1c5981 {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));\n gap: var(--bui-space-4);\n}\n\n.ApigeeHomePage_tile__28ec1c5981 {\n display: flex;\n flex-direction: column;\n gap: var(--bui-space-2);\n padding: var(--bui-space-4);\n border: 1px solid var(--bui-border-1);\n border-radius: var(--bui-radius-3);\n background-color: var(--bui-bg-neutral-1);\n color: inherit;\n text-decoration: none;\n transition: box-shadow 200ms ease;\n}\n\n/* The tile can be a MUI Link; element+class beats MUI's class so its link colour/underline does not bleed onto the tile content. */\na.ApigeeHomePage_tile__28ec1c5981,\na.ApigeeHomePage_tile__28ec1c5981:hover {\n color: inherit;\n text-decoration: none;\n}\n\n.ApigeeHomePage_tile__28ec1c5981:hover {\n box-shadow: var(--bui-shadow);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .ApigeeHomePage_statCard__28ec1c5981,\n .ApigeeHomePage_tile__28ec1c5981 {\n transition: none;\n }\n}\n\na.ApigeeHomePage_tile__28ec1c5981:focus-visible {\n outline: 2px solid var(--bui-ring);\n outline-offset: 2px;\n}\n\n.ApigeeHomePage_tileDisabled__28ec1c5981 {\n opacity: 0.6;\n}\n\n.ApigeeHomePage_tileDisabled__28ec1c5981:hover {\n box-shadow: none;\n}\n\n.ApigeeHomePage_tileTitleRow__28ec1c5981 {\n display: flex;\n align-items: center;\n gap: var(--bui-space-2);\n}\n\n.ApigeeHomePage_tileIcon__28ec1c5981 {\n flex-shrink: 0;\n color: var(--bui-fg-secondary);\n}\n\n.ApigeeHomePage_tileArrow__28ec1c5981 {\n margin-left: auto;\n color: var(--bui-fg-secondary);\n}\n\n.ApigeeHomePage_tileBadge__28ec1c5981 {\n margin-left: auto;\n padding: 2px var(--bui-space-2);\n border-radius: var(--bui-radius-2);\n background-color: var(--bui-bg-neutral-1-pressed);\n color: var(--bui-fg-secondary);\n font-size: 0.72rem;\n text-transform: uppercase;\n letter-spacing: 0.4px;\n white-space: nowrap;\n}\n";
|
|
4
|
+
var styles = {"hero":"ApigeeHomePage_hero__28ec1c5981","heroIcon":"ApigeeHomePage_heroIcon__28ec1c5981","cards":"ApigeeHomePage_cards__28ec1c5981","statCard":"ApigeeHomePage_statCard__28ec1c5981","stat":"ApigeeHomePage_stat__28ec1c5981","statTitleRow":"ApigeeHomePage_statTitleRow__28ec1c5981","statIcon":"ApigeeHomePage_statIcon__28ec1c5981","statTitle":"ApigeeHomePage_statTitle__28ec1c5981","sectionTitle":"ApigeeHomePage_sectionTitle__28ec1c5981","tiles":"ApigeeHomePage_tiles__28ec1c5981","tile":"ApigeeHomePage_tile__28ec1c5981","tileDisabled":"ApigeeHomePage_tileDisabled__28ec1c5981","tileTitleRow":"ApigeeHomePage_tileTitleRow__28ec1c5981","tileIcon":"ApigeeHomePage_tileIcon__28ec1c5981","tileArrow":"ApigeeHomePage_tileArrow__28ec1c5981","tileBadge":"ApigeeHomePage_tileBadge__28ec1c5981"};
|
|
5
|
+
styleInject(css_248z);
|
|
6
|
+
|
|
7
|
+
export { styles as default };
|
|
8
|
+
//# sourceMappingURL=ApigeeHomePage.module.css.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApigeeHomePage.module.css.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { Sidebar, SidebarItem, SidebarSubmenu, SidebarSubmenuItem } from '@backstage/core-components';
|
|
3
|
+
import { RiStackLine } from '@remixicon/react';
|
|
4
|
+
|
|
5
|
+
const ApigeeNavContent = ({ navItems }) => {
|
|
6
|
+
const allItems = navItems.rest();
|
|
7
|
+
const apigeeItems = allItems.filter(
|
|
8
|
+
(item) => item.node.spec.plugin.pluginId === "apigee"
|
|
9
|
+
);
|
|
10
|
+
const otherItems = allItems.filter(
|
|
11
|
+
(item) => item.node.spec.plugin.pluginId !== "apigee"
|
|
12
|
+
);
|
|
13
|
+
return /* @__PURE__ */ jsxs(Sidebar, { children: [
|
|
14
|
+
otherItems.map((item) => /* @__PURE__ */ jsx(
|
|
15
|
+
SidebarItem,
|
|
16
|
+
{
|
|
17
|
+
to: item.href,
|
|
18
|
+
icon: (_props) => item.icon,
|
|
19
|
+
text: item.title
|
|
20
|
+
},
|
|
21
|
+
item.node.spec.id
|
|
22
|
+
)),
|
|
23
|
+
apigeeItems.length > 0 && /* @__PURE__ */ jsx(SidebarItem, { icon: () => /* @__PURE__ */ jsx(RiStackLine, {}), text: "API Hub", to: "/api-hub", children: /* @__PURE__ */ jsx(SidebarSubmenu, { title: "API Hub", children: /* @__PURE__ */ jsx(SidebarSubmenuItem, { title: "API Catalog", to: "/api-hub/catalog" }) }) })
|
|
24
|
+
] });
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export { ApigeeNavContent };
|
|
28
|
+
//# sourceMappingURL=ApigeeNavContent.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApigeeNavContent.esm.js","sources":["../../src/components/ApigeeNavContent.tsx"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport {\n Sidebar,\n SidebarItem,\n SidebarSubmenu,\n SidebarSubmenuItem,\n} from '@backstage/core-components';\nimport type {\n NavContentComponent,\n NavContentNavItem,\n} from '@backstage/plugin-app-react';\nimport { RiStackLine } from '@remixicon/react';\nimport type { ReactElement } from 'react';\n\n/**\n * Renders the app nav bar with an expandable \"API Hub\" group for all apigee\n * plugin pages, while rendering any other plugins' pages as flat sidebar items.\n *\n * To activate, add `apigeeNavModule` to your app's `createApp({ features })`.\n */\nexport const ApigeeNavContent: NavContentComponent = ({ navItems }) => {\n const allItems = navItems.rest();\n const apigeeItems = allItems.filter(\n (item: NavContentNavItem) => item.node.spec.plugin.pluginId === 'apigee',\n );\n const otherItems = allItems.filter(\n (item: NavContentNavItem) => item.node.spec.plugin.pluginId !== 'apigee',\n );\n\n return (\n <Sidebar>\n {otherItems.map((item: NavContentNavItem) => (\n <SidebarItem\n key={item.node.spec.id}\n to={item.href}\n // item.icon is IconElement (JSX); SidebarItem.icon is IconComponent.\n // Wrapping in a render-prop is the documented pattern from AppNav.\n icon={(_props: object) => item.icon as ReactElement}\n text={item.title}\n />\n ))}\n {apigeeItems.length > 0 && (\n <SidebarItem icon={() => <RiStackLine />} text=\"API Hub\" to=\"/api-hub\">\n <SidebarSubmenu title=\"API Hub\">\n <SidebarSubmenuItem title=\"API Catalog\" to=\"/api-hub/catalog\" />\n </SidebarSubmenu>\n </SidebarItem>\n )}\n </Sidebar>\n );\n};\n"],"names":[],"mappings":";;;;AAoBO,MAAM,gBAAA,GAAwC,CAAC,EAAE,QAAA,EAAS,KAAM;AACrE,EAAA,MAAM,QAAA,GAAW,SAAS,IAAA,EAAK;AAC/B,EAAA,MAAM,cAAc,QAAA,CAAS,MAAA;AAAA,IAC3B,CAAC,IAAA,KAA4B,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,OAAO,QAAA,KAAa;AAAA,GAClE;AACA,EAAA,MAAM,aAAa,QAAA,CAAS,MAAA;AAAA,IAC1B,CAAC,IAAA,KAA4B,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,OAAO,QAAA,KAAa;AAAA,GAClE;AAEA,EAAA,4BACG,OAAA,EAAA,EACE,QAAA,EAAA;AAAA,IAAA,UAAA,CAAW,GAAA,CAAI,CAAC,IAAA,qBACf,GAAA;AAAA,MAAC,WAAA;AAAA,MAAA;AAAA,QAEC,IAAI,IAAA,CAAK,IAAA;AAAA,QAGT,IAAA,EAAM,CAAC,MAAA,KAAmB,IAAA,CAAK,IAAA;AAAA,QAC/B,MAAM,IAAA,CAAK;AAAA,OAAA;AAAA,MALN,IAAA,CAAK,KAAK,IAAA,CAAK;AAAA,KAOvB,CAAA;AAAA,IACA,WAAA,CAAY,MAAA,GAAS,CAAA,oBACpB,GAAA,CAAC,WAAA,EAAA,EAAY,MAAM,sBAAM,GAAA,CAAC,WAAA,EAAA,EAAY,CAAA,EAAI,IAAA,EAAK,SAAA,EAAU,IAAG,UAAA,EAC1D,QAAA,kBAAA,GAAA,CAAC,cAAA,EAAA,EAAe,KAAA,EAAM,SAAA,EACpB,QAAA,kBAAA,GAAA,CAAC,kBAAA,EAAA,EAAmB,KAAA,EAAM,aAAA,EAAc,EAAA,EAAG,kBAAA,EAAmB,CAAA,EAChE,CAAA,EACF;AAAA,GAAA,EAEJ,CAAA;AAEJ;;;;"}
|