diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json
index 17f4b1bef3..f377be2122 100644
--- a/packages/frontend-app-api/package.json
+++ b/packages/frontend-app-api/package.json
@@ -35,10 +35,12 @@
"dependencies": {
"@backstage/config": "workspace:^",
"@backstage/core-app-api": "workspace:^",
+ "@backstage/core-components": "workspace:^",
"@backstage/core-plugin-api": "workspace:^",
"@backstage/frontend-plugin-api": "workspace:^",
"@backstage/plugin-graphiql": "workspace:^",
"@backstage/types": "workspace:^",
+ "@material-ui/core": "^4.12.4",
"lodash": "^4.17.21"
},
"peerDependencies": {
diff --git a/packages/frontend-app-api/src/createApp.tsx b/packages/frontend-app-api/src/createApp.tsx
index 19ca7b367f..4882191206 100644
--- a/packages/frontend-app-api/src/createApp.tsx
+++ b/packages/frontend-app-api/src/createApp.tsx
@@ -20,8 +20,10 @@ import {
BackstagePlugin,
coreExtensionData,
} from '@backstage/frontend-plugin-api';
-import { CoreRouter } from './extensions/CoreRouter';
import { Core } from './extensions/Core';
+import { CoreRoutes } from './extensions/CoreRoutes';
+import { CoreLayout } from './extensions/CoreLayout';
+import { CoreNav } from './extensions/CoreNav';
import {
createExtensionInstance,
ExtensionInstance,
@@ -43,6 +45,7 @@ import {
IconComponent,
RouteRef,
BackstagePlugin as LegacyBackstagePlugin,
+ featureFlagsApiRef,
} from '@backstage/core-plugin-api';
import { getAvailablePlugins } from './wiring/discovery';
import {
@@ -58,6 +61,8 @@ import { AppThemeProvider } from '../../core-app-api/src/app/AppThemeProvider';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { AppContextProvider } from '../../core-app-api/src/app/AppContext';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
+import { LocalStorageFeatureFlags } from '../../core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags';
+// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { defaultConfigLoaderSync } from '../../core-app-api/src/app/defaultConfigLoader';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { overrideBaseUrlConfigs } from '../../core-app-api/src/app/overrideBaseUrlConfigs';
@@ -68,6 +73,7 @@ import {
icons as defaultIcons,
themes as defaultThemes,
} from '../../app-defaults/src/defaults';
+import { BrowserRouter } from 'react-router-dom';
/** @public */
export function createApp(options: {
@@ -80,7 +86,7 @@ export function createApp(options: {
options?.config ??
ConfigReader.fromConfigs(overrideBaseUrlConfigs(defaultConfigLoaderSync()));
- const builtinExtensions = [CoreRouter, Core];
+ const builtinExtensions = [Core, CoreRoutes, CoreNav, CoreLayout];
const discoveredPlugins = getAvailablePlugins();
const allPlugins = [...discoveredPlugins, ...options.plugins];
@@ -180,9 +186,12 @@ export function createApp(options: {
- {rootComponents.map((Component, i) => (
-
- ))}
+ {/* TODO: set base path using the logic from AppRouter */}
+
+ {rootComponents.map((Component, i) => (
+
+ ))}
+
@@ -257,6 +266,13 @@ function createApiHolder(
factoryRegistry.register('default', factory);
}
+ // TODO: properly discovery feature flags, maybe rework the whole thing
+ factoryRegistry.register('default', {
+ api: featureFlagsApiRef,
+ deps: {},
+ factory: () => new LocalStorageFeatureFlags(),
+ });
+
factoryRegistry.register('static', {
api: appThemeApiRef,
deps: {},
diff --git a/packages/frontend-app-api/src/extensions/CoreLayout.tsx b/packages/frontend-app-api/src/extensions/CoreLayout.tsx
new file mode 100644
index 0000000000..845bc8cf5b
--- /dev/null
+++ b/packages/frontend-app-api/src/extensions/CoreLayout.tsx
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2023 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import {
+ createExtension,
+ coreExtensionData,
+} from '@backstage/frontend-plugin-api';
+import { SidebarPage } from '@backstage/core-components';
+
+export const CoreLayout = createExtension({
+ id: 'core.layout',
+ at: 'root',
+ inputs: {
+ nav: {
+ extensionData: {
+ component: coreExtensionData.reactComponent,
+ },
+ },
+ content: {
+ extensionData: {
+ component: coreExtensionData.reactComponent,
+ },
+ },
+ },
+ output: {
+ component: coreExtensionData.reactComponent,
+ },
+ factory({ bind, inputs }) {
+ // TODO: Support this as part of the core system
+ if (inputs.nav.length !== 1) {
+ throw Error(
+ `Extension 'core.layout' did not receive exactly one 'nav' input, got ${inputs.nav.length}`,
+ );
+ }
+ const Nav = inputs.nav[0].component;
+
+ if (inputs.content.length !== 1) {
+ throw Error(
+ `Extension 'core.layout' did not receive exactly one 'content' input, got ${inputs.content.length}`,
+ );
+ }
+ const Content = inputs.content[0].component;
+
+ bind({
+ // TODO: set base path using the logic from AppRouter
+ component: () => (
+
+
+
+
+ ),
+ });
+ },
+});
diff --git a/packages/frontend-app-api/src/extensions/CoreNav.tsx b/packages/frontend-app-api/src/extensions/CoreNav.tsx
new file mode 100644
index 0000000000..57f458cec7
--- /dev/null
+++ b/packages/frontend-app-api/src/extensions/CoreNav.tsx
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2023 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import {
+ createExtension,
+ coreExtensionData,
+} from '@backstage/frontend-plugin-api';
+import { makeStyles } from '@material-ui/core';
+import {
+ Sidebar,
+ useSidebarOpenState,
+ Link,
+ sidebarConfig,
+ SidebarDivider,
+ SidebarItem,
+} from '@backstage/core-components';
+import { GraphiQLIcon } from '@backstage/plugin-graphiql';
+// eslint-disable-next-line @backstage/no-relative-monorepo-imports
+import LogoIcon from '../../../app/src/components/Root/LogoIcon';
+// eslint-disable-next-line @backstage/no-relative-monorepo-imports
+import LogoFull from '../../../app/src/components/Root/LogoFull';
+
+const useSidebarLogoStyles = makeStyles({
+ root: {
+ width: sidebarConfig.drawerWidthClosed,
+ height: 3 * sidebarConfig.logoHeight,
+ display: 'flex',
+ flexFlow: 'row nowrap',
+ alignItems: 'center',
+ marginBottom: -14,
+ },
+ link: {
+ width: sidebarConfig.drawerWidthClosed,
+ marginLeft: 24,
+ },
+});
+
+const SidebarLogo = () => {
+ const classes = useSidebarLogoStyles();
+ const { isOpen } = useSidebarOpenState();
+
+ return (
+
+
+ {isOpen ? : }
+
+
+ );
+};
+
+export const CoreNav = createExtension({
+ id: 'core.nav',
+ at: 'core.layout/nav',
+ inputs: {},
+ output: {
+ component: coreExtensionData.reactComponent,
+ },
+ factory({ bind }) {
+ bind({
+ // TODO: set base path using the logic from AppRouter
+ component: () => (
+
+
+
+
+
+ ),
+ });
+ },
+});
diff --git a/packages/frontend-app-api/src/extensions/CoreRouter.tsx b/packages/frontend-app-api/src/extensions/CoreRoutes.tsx
similarity index 80%
rename from packages/frontend-app-api/src/extensions/CoreRouter.tsx
rename to packages/frontend-app-api/src/extensions/CoreRoutes.tsx
index a148931b0f..5b48149fbf 100644
--- a/packages/frontend-app-api/src/extensions/CoreRouter.tsx
+++ b/packages/frontend-app-api/src/extensions/CoreRoutes.tsx
@@ -19,11 +19,11 @@ import {
createExtension,
coreExtensionData,
} from '@backstage/frontend-plugin-api';
-import { BrowserRouter, useRoutes } from 'react-router-dom';
+import { useRoutes } from 'react-router-dom';
-export const CoreRouter = createExtension({
- id: 'core.router',
- at: 'root',
+export const CoreRoutes = createExtension({
+ id: 'core.routes',
+ at: 'core.layout/content',
inputs: {
routes: {
extensionData: {
@@ -48,12 +48,7 @@ export const CoreRouter = createExtension({
return element;
};
bind({
- // TODO: set base path using the logic from AppRouter
- component: () => (
-
-
-
- ),
+ component: () => ,
});
},
});
diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx
index db389ae4b1..71f6c87374 100644
--- a/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx
+++ b/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx
@@ -35,7 +35,7 @@ describe('createPageExtension', () => {
).toEqual({
$$type: 'extension',
id: 'test',
- at: 'core.router/routes',
+ at: 'core.routes/routes',
configSchema: expect.anything(),
disabled: false,
inputs: {},
@@ -88,7 +88,7 @@ describe('createPageExtension', () => {
).toEqual({
$$type: 'extension',
id: 'test',
- at: 'core.router/routes',
+ at: 'core.routes/routes',
configSchema: expect.anything(),
disabled: false,
inputs: {},
diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx
index 167dd397dd..9a82aa3f37 100644
--- a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx
+++ b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx
@@ -63,7 +63,7 @@ export function createPageExtension<
return createExtension({
id: options.id,
- at: options.at ?? 'core.router/routes',
+ at: options.at ?? 'core.routes/routes',
disabled: options.disabled,
output: {
component: coreExtensionData.reactComponent,
diff --git a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts b/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts
index 724dad5143..534a92ffe8 100644
--- a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts
+++ b/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts
@@ -143,7 +143,7 @@ describe('createPlugin', () => {
render(
createTestAppRoot({
plugins: [plugin],
- config: { app: { extensions: [{ 'core.router': false }] } },
+ config: { app: { extensions: [{ 'core.routes': false }] } },
}),
);
@@ -169,7 +169,7 @@ describe('createPlugin', () => {
config: {
app: {
extensions: [
- { 'core.router': false },
+ { 'core.routes': false },
{
'plugin.catalog.page': {
config: { name: 'CatalogRenamed' },
diff --git a/yarn.lock b/yarn.lock
index 4a9b30d532..198965bab6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4318,10 +4318,12 @@ __metadata:
"@backstage/cli": "workspace:^"
"@backstage/config": "workspace:^"
"@backstage/core-app-api": "workspace:^"
+ "@backstage/core-components": "workspace:^"
"@backstage/core-plugin-api": "workspace:^"
"@backstage/frontend-plugin-api": "workspace:^"
"@backstage/plugin-graphiql": "workspace:^"
"@backstage/types": "workspace:^"
+ "@material-ui/core": ^4.12.4
"@testing-library/jest-dom": ^5.10.1
lodash: ^4.17.21
peerDependencies: