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: () => ( + +