chore: move things around a little bit to the app plugin
Co-authored-by: Patrik Oldsberg <poldsberg@gmail.com> Co-authored-by: Fredrik Adelöw <freben@users.noreply.github.com> Co-authored-by: Johan Haals <johan@haals.se> Co-authored-by: Camila Belo <camilaibs@users.noreply.github.com> Co-authored-by: Camila Belo <camilaibs@gmail.com> Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 {
|
||||
ExtensionBoundary,
|
||||
coreExtensionData,
|
||||
createExtension,
|
||||
createExtensionInput,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
|
||||
export const App = createExtension({
|
||||
namespace: 'app',
|
||||
attachTo: { id: 'root', input: 'app' },
|
||||
inputs: {
|
||||
root: createExtensionInput([coreExtensionData.reactElement], {
|
||||
singleton: true,
|
||||
}),
|
||||
},
|
||||
output: [coreExtensionData.reactElement],
|
||||
factory: ({ node, inputs }) => [
|
||||
coreExtensionData.reactElement(
|
||||
<ExtensionBoundary node={node}>
|
||||
{inputs.root.get(coreExtensionData.reactElement)}
|
||||
</ExtensionBoundary>,
|
||||
),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { AppLanguageSelector } from '../../../../packages/core-app-api/src/apis/implementations/AppLanguageApi';
|
||||
import { appLanguageApiRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { ApiBlueprint, createApiFactory } from '@backstage/frontend-plugin-api';
|
||||
|
||||
export const AppLanguageApi = ApiBlueprint.make({
|
||||
name: 'app-language',
|
||||
params: {
|
||||
factory: createApiFactory(
|
||||
appLanguageApiRef,
|
||||
AppLanguageSelector.createWithStorage(),
|
||||
),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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,
|
||||
createExtensionInput,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { SidebarPage } from '@backstage/core-components';
|
||||
|
||||
export const AppLayout = createExtension({
|
||||
namespace: 'app',
|
||||
name: 'layout',
|
||||
attachTo: { id: 'app/root', input: 'children' },
|
||||
inputs: {
|
||||
nav: createExtensionInput([coreExtensionData.reactElement], {
|
||||
singleton: true,
|
||||
}),
|
||||
content: createExtensionInput([coreExtensionData.reactElement], {
|
||||
singleton: true,
|
||||
}),
|
||||
},
|
||||
output: [coreExtensionData.reactElement],
|
||||
factory: ({ inputs }) => [
|
||||
coreExtensionData.reactElement(
|
||||
<SidebarPage>
|
||||
{inputs.nav.get(coreExtensionData.reactElement)}
|
||||
{inputs.content.get(coreExtensionData.reactElement)}
|
||||
</SidebarPage>,
|
||||
),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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,
|
||||
createExtensionInput,
|
||||
useRouteRef,
|
||||
NavItemBlueprint,
|
||||
NavLogoBlueprint,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import {
|
||||
Sidebar,
|
||||
useSidebarOpenState,
|
||||
Link,
|
||||
sidebarConfig,
|
||||
SidebarDivider,
|
||||
SidebarItem,
|
||||
} from '@backstage/core-components';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import LogoIcon from '../../../../packages/app/src/components/Root/LogoIcon';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import LogoFull from '../../../../packages/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 = (
|
||||
props: (typeof NavLogoBlueprint.dataRefs.logoElements)['T'],
|
||||
) => {
|
||||
const classes = useSidebarLogoStyles();
|
||||
const { isOpen } = useSidebarOpenState();
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Link to="/" underline="none" className={classes.link} aria-label="Home">
|
||||
{isOpen
|
||||
? props?.logoFull ?? <LogoFull />
|
||||
: props?.logoIcon ?? <LogoIcon />}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SidebarNavItem = (
|
||||
props: (typeof NavItemBlueprint.dataRefs.target)['T'],
|
||||
) => {
|
||||
const { icon: Icon, title, routeRef } = props;
|
||||
const link = useRouteRef(routeRef);
|
||||
if (!link) {
|
||||
return null;
|
||||
}
|
||||
// TODO: Support opening modal, for example, the search one
|
||||
return <SidebarItem to={link()} icon={Icon} text={title} />;
|
||||
};
|
||||
|
||||
export const AppNav = createExtension({
|
||||
namespace: 'app',
|
||||
name: 'nav',
|
||||
attachTo: { id: 'app/layout', input: 'nav' },
|
||||
inputs: {
|
||||
items: createExtensionInput([NavItemBlueprint.dataRefs.target]),
|
||||
logos: createExtensionInput([NavLogoBlueprint.dataRefs.logoElements], {
|
||||
singleton: true,
|
||||
optional: true,
|
||||
}),
|
||||
},
|
||||
output: [coreExtensionData.reactElement],
|
||||
factory: ({ inputs }) => [
|
||||
coreExtensionData.reactElement(
|
||||
<Sidebar>
|
||||
<SidebarLogo
|
||||
{...inputs.logos?.get(NavLogoBlueprint.dataRefs.logoElements)}
|
||||
/>
|
||||
<SidebarDivider />
|
||||
{inputs.items.map((item, index) => (
|
||||
<SidebarNavItem
|
||||
{...item.get(NavItemBlueprint.dataRefs.target)}
|
||||
key={index}
|
||||
/>
|
||||
))}
|
||||
</Sidebar>,
|
||||
),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* 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, {
|
||||
ComponentType,
|
||||
Fragment,
|
||||
PropsWithChildren,
|
||||
ReactNode,
|
||||
useContext,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {
|
||||
AppRootWrapperBlueprint,
|
||||
RouterBlueprint,
|
||||
SignInPageBlueprint,
|
||||
coreExtensionData,
|
||||
createExtension,
|
||||
createExtensionInput,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import {
|
||||
IdentityApi,
|
||||
SignInPageProps,
|
||||
configApiRef,
|
||||
useApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { InternalAppContext } from '../../../../packages/frontend-app-api/src/wiring/InternalAppContext';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { AppIdentityProxy } from '../../../../packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { RouteTracker } from '../../../../packages/frontend-app-api/src/routing/RouteTracker';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { getBasePath } from '../../../../packages/frontend-app-api/src/routing/getBasePath';
|
||||
|
||||
export const AppRoot = createExtension({
|
||||
namespace: 'app',
|
||||
name: 'root',
|
||||
attachTo: { id: 'app', input: 'root' },
|
||||
inputs: {
|
||||
router: createExtensionInput([RouterBlueprint.dataRefs.component], {
|
||||
singleton: true,
|
||||
optional: true,
|
||||
}),
|
||||
signInPage: createExtensionInput([SignInPageBlueprint.dataRefs.component], {
|
||||
singleton: true,
|
||||
optional: true,
|
||||
}),
|
||||
children: createExtensionInput([coreExtensionData.reactElement], {
|
||||
singleton: true,
|
||||
}),
|
||||
elements: createExtensionInput([coreExtensionData.reactElement]),
|
||||
wrappers: createExtensionInput([
|
||||
AppRootWrapperBlueprint.dataRefs.component,
|
||||
]),
|
||||
},
|
||||
output: [coreExtensionData.reactElement],
|
||||
factory({ inputs }) {
|
||||
let content: React.ReactNode = (
|
||||
<>
|
||||
{inputs.elements.map(el => (
|
||||
<Fragment key={el.node.spec.id}>
|
||||
{el.get(coreExtensionData.reactElement)}
|
||||
</Fragment>
|
||||
))}
|
||||
{inputs.children.get(coreExtensionData.reactElement)}
|
||||
</>
|
||||
);
|
||||
|
||||
for (const wrapper of inputs.wrappers) {
|
||||
const Component = wrapper.get(AppRootWrapperBlueprint.dataRefs.component);
|
||||
content = <Component>{content}</Component>;
|
||||
}
|
||||
|
||||
return [
|
||||
coreExtensionData.reactElement(
|
||||
<AppRouter
|
||||
SignInPageComponent={inputs.signInPage?.get(
|
||||
SignInPageBlueprint.dataRefs.component,
|
||||
)}
|
||||
RouterComponent={inputs.router?.get(
|
||||
RouterBlueprint.dataRefs.component,
|
||||
)}
|
||||
>
|
||||
{content}
|
||||
</AppRouter>,
|
||||
),
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
// This wraps the sign-in page and waits for sign-in to be completed before rendering the app
|
||||
function SignInPageWrapper({
|
||||
component: Component,
|
||||
appIdentityProxy,
|
||||
children,
|
||||
}: {
|
||||
component: ComponentType<SignInPageProps>;
|
||||
appIdentityProxy: AppIdentityProxy;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const [identityApi, setIdentityApi] = useState<IdentityApi>();
|
||||
const configApi = useApi(configApiRef);
|
||||
const basePath = getBasePath(configApi);
|
||||
|
||||
if (!identityApi) {
|
||||
return <Component onSignInSuccess={setIdentityApi} />;
|
||||
}
|
||||
|
||||
appIdentityProxy.setTarget(identityApi, {
|
||||
signOutTargetUrl: basePath || '/',
|
||||
});
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for the {@link AppRouter} component.
|
||||
* @public
|
||||
*/
|
||||
export interface AppRouterProps {
|
||||
children?: ReactNode;
|
||||
SignInPageComponent?: ComponentType<SignInPageProps>;
|
||||
RouterComponent?: ComponentType<PropsWithChildren<{}>>;
|
||||
}
|
||||
|
||||
function DefaultRouter(props: PropsWithChildren<{}>) {
|
||||
const configApi = useApi(configApiRef);
|
||||
const basePath = getBasePath(configApi);
|
||||
return <BrowserRouter basename={basePath}>{props.children}</BrowserRouter>;
|
||||
}
|
||||
|
||||
/**
|
||||
* App router and sign-in page wrapper.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* The AppRouter provides the routing context and renders the sign-in page.
|
||||
* Until the user has successfully signed in, this component will render
|
||||
* the sign-in page. Once the user has signed-in, it will instead render
|
||||
* the app, while providing routing and route tracking for the app.
|
||||
*/
|
||||
export function AppRouter(props: AppRouterProps) {
|
||||
const {
|
||||
children,
|
||||
SignInPageComponent,
|
||||
RouterComponent = DefaultRouter,
|
||||
} = props;
|
||||
|
||||
const configApi = useApi(configApiRef);
|
||||
const basePath = getBasePath(configApi);
|
||||
const internalAppContext = useContext(InternalAppContext);
|
||||
if (!internalAppContext) {
|
||||
throw new Error('AppRouter must be rendered within the AppProvider');
|
||||
}
|
||||
const { routeObjects, appIdentityProxy } = internalAppContext;
|
||||
|
||||
// If the app hasn't configured a sign-in page, we just continue as guest.
|
||||
if (!SignInPageComponent) {
|
||||
appIdentityProxy.setTarget(
|
||||
{
|
||||
getUserId: () => 'guest',
|
||||
getIdToken: async () => undefined,
|
||||
getProfile: () => ({
|
||||
email: 'guest@example.com',
|
||||
displayName: 'Guest',
|
||||
}),
|
||||
getProfileInfo: async () => ({
|
||||
email: 'guest@example.com',
|
||||
displayName: 'Guest',
|
||||
}),
|
||||
getBackstageIdentity: async () => ({
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/guest',
|
||||
ownershipEntityRefs: ['user:default/guest'],
|
||||
}),
|
||||
getCredentials: async () => ({}),
|
||||
signOut: async () => {},
|
||||
},
|
||||
{ signOutTargetUrl: basePath || '/' },
|
||||
);
|
||||
|
||||
return (
|
||||
<RouterComponent>
|
||||
<RouteTracker routeObjects={routeObjects} />
|
||||
{children}
|
||||
</RouterComponent>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RouterComponent>
|
||||
<RouteTracker routeObjects={routeObjects} />
|
||||
<SignInPageWrapper
|
||||
component={SignInPageComponent}
|
||||
appIdentityProxy={appIdentityProxy}
|
||||
>
|
||||
{children}
|
||||
</SignInPageWrapper>
|
||||
</RouterComponent>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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,
|
||||
createExtensionInput,
|
||||
coreComponentRefs,
|
||||
useComponentRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { useRoutes } from 'react-router-dom';
|
||||
|
||||
export const AppRoutes = createExtension({
|
||||
namespace: 'app',
|
||||
name: 'routes',
|
||||
attachTo: { id: 'app/layout', input: 'content' },
|
||||
inputs: {
|
||||
routes: createExtensionInput([
|
||||
coreExtensionData.routePath,
|
||||
coreExtensionData.routeRef.optional(),
|
||||
coreExtensionData.reactElement,
|
||||
]),
|
||||
},
|
||||
output: [coreExtensionData.reactElement],
|
||||
factory({ inputs }) {
|
||||
const Routes = () => {
|
||||
const NotFoundErrorPage = useComponentRef(
|
||||
coreComponentRefs.notFoundErrorPage,
|
||||
);
|
||||
|
||||
const element = useRoutes([
|
||||
...inputs.routes.map(route => ({
|
||||
path: `${route.get(coreExtensionData.routePath)}/*`,
|
||||
element: route.get(coreExtensionData.reactElement),
|
||||
})),
|
||||
{
|
||||
path: '*',
|
||||
element: <NotFoundErrorPage />,
|
||||
},
|
||||
]);
|
||||
|
||||
return element;
|
||||
};
|
||||
|
||||
return [coreExtensionData.reactElement(<Routes />)];
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2024 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 {
|
||||
UnifiedThemeProvider,
|
||||
themes as builtinThemes,
|
||||
} from '@backstage/theme';
|
||||
import DarkIcon from '@material-ui/icons/Brightness2';
|
||||
import LightIcon from '@material-ui/icons/WbSunny';
|
||||
import {
|
||||
createExtensionInput,
|
||||
ThemeBlueprint,
|
||||
ApiBlueprint,
|
||||
createApiFactory,
|
||||
appThemeApiRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { AppThemeSelector } from '@backstage/core-app-api';
|
||||
|
||||
/**
|
||||
* Contains the themes installed into the app.
|
||||
*/
|
||||
export const AppThemeApi = ApiBlueprint.makeWithOverrides({
|
||||
name: 'app-theme',
|
||||
inputs: {
|
||||
themes: createExtensionInput([ThemeBlueprint.dataRefs.theme], {
|
||||
replaces: [{ id: 'app', input: 'themes' }],
|
||||
}),
|
||||
},
|
||||
factory: (originalFactory, { inputs }) => {
|
||||
return originalFactory({
|
||||
factory: createApiFactory(
|
||||
appThemeApiRef,
|
||||
AppThemeSelector.createWithStorage(
|
||||
inputs.themes.map(i => i.get(ThemeBlueprint.dataRefs.theme)),
|
||||
),
|
||||
),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const LightTheme = ThemeBlueprint.make({
|
||||
name: 'light',
|
||||
params: {
|
||||
theme: {
|
||||
id: 'light',
|
||||
title: 'Light Theme',
|
||||
variant: 'light',
|
||||
icon: <LightIcon />,
|
||||
Provider: ({ children }) => (
|
||||
<UnifiedThemeProvider theme={builtinThemes.light} children={children} />
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const DarkTheme = ThemeBlueprint.make({
|
||||
name: 'dark',
|
||||
params: {
|
||||
theme: {
|
||||
id: 'dark',
|
||||
title: 'Dark Theme',
|
||||
variant: 'dark',
|
||||
icon: <DarkIcon />,
|
||||
Provider: ({ children }) => (
|
||||
<UnifiedThemeProvider theme={builtinThemes.dark} children={children} />
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2024 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 {
|
||||
createComponentExtension,
|
||||
createExtensionInput,
|
||||
ApiBlueprint,
|
||||
createApiFactory,
|
||||
componentsApiRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { DefaultComponentsApi } from '../../../../packages/frontend-app-api/src/apis/implementations/ComponentsApi';
|
||||
|
||||
/**
|
||||
* Contains the shareable components installed into the app.
|
||||
*/
|
||||
export const ComponentsApi = ApiBlueprint.makeWithOverrides({
|
||||
name: 'components',
|
||||
inputs: {
|
||||
components: createExtensionInput(
|
||||
[createComponentExtension.componentDataRef],
|
||||
{ replaces: [{ id: 'app', input: 'components' }] },
|
||||
),
|
||||
},
|
||||
factory: (originalFactory, { inputs }) => {
|
||||
return originalFactory({
|
||||
factory: createApiFactory(
|
||||
componentsApiRef,
|
||||
DefaultComponentsApi.fromComponents(
|
||||
inputs.components.map(i =>
|
||||
i.get(createComponentExtension.componentDataRef),
|
||||
),
|
||||
),
|
||||
),
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2024 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 {
|
||||
ApiBlueprint,
|
||||
createApiFactory,
|
||||
featureFlagsApiRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { LocalStorageFeatureFlags } from '../../../../packages/core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags';
|
||||
|
||||
/**
|
||||
* Contains the shareable icons installed into the app.
|
||||
*/
|
||||
export const FeatureFlagsApi = ApiBlueprint.make({
|
||||
name: 'feature-flags',
|
||||
params: {
|
||||
// TODO: properly discovery feature flags, maybe rework the whole thing
|
||||
factory: createApiFactory({
|
||||
api: featureFlagsApiRef,
|
||||
deps: {},
|
||||
factory: () => new LocalStorageFeatureFlags(),
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2024 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 {
|
||||
createExtensionInput,
|
||||
IconBundleBlueprint,
|
||||
ApiBlueprint,
|
||||
createApiFactory,
|
||||
iconsApiRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { DefaultIconsApi } from '../../../../packages/frontend-app-api/src/apis/implementations/IconsApi';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { icons as defaultIcons } from '../../../../packages/app-defaults/src/defaults';
|
||||
|
||||
/**
|
||||
* Contains the shareable icons installed into the app.
|
||||
*/
|
||||
export const IconsApi = ApiBlueprint.makeWithOverrides({
|
||||
name: 'icons',
|
||||
inputs: {
|
||||
icons: createExtensionInput([IconBundleBlueprint.dataRefs.icons], {
|
||||
replaces: [{ id: 'app', input: 'icons' }],
|
||||
}),
|
||||
},
|
||||
factory: (originalFactory, { inputs }) => {
|
||||
return originalFactory({
|
||||
factory: createApiFactory(
|
||||
iconsApiRef,
|
||||
new DefaultIconsApi(
|
||||
inputs.icons
|
||||
.map(i => i.get(IconBundleBlueprint.dataRefs.icons))
|
||||
.reduce((acc, bundle) => ({ ...acc, ...bundle }), defaultIcons),
|
||||
),
|
||||
),
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2024 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 {
|
||||
ApiBlueprint,
|
||||
TranslationBlueprint,
|
||||
createApiFactory,
|
||||
createExtensionInput,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import {
|
||||
appLanguageApiRef,
|
||||
translationApiRef,
|
||||
} from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { I18nextTranslationApi } from '../../../../packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi';
|
||||
|
||||
/**
|
||||
* Contains translations that are installed in the app.
|
||||
*/
|
||||
export const TranslationsApi = ApiBlueprint.makeWithOverrides({
|
||||
name: 'translations',
|
||||
inputs: {
|
||||
translations: createExtensionInput(
|
||||
[TranslationBlueprint.dataRefs.translation],
|
||||
{ replaces: [{ id: 'app', input: 'translations' }] },
|
||||
),
|
||||
},
|
||||
factory: (originalFactory, { inputs }) => {
|
||||
return originalFactory({
|
||||
factory: createApiFactory({
|
||||
api: translationApiRef,
|
||||
deps: { languageApi: appLanguageApiRef },
|
||||
factory: ({ languageApi }) =>
|
||||
I18nextTranslationApi.create({
|
||||
languageApi,
|
||||
resources: inputs.translations.map(i =>
|
||||
i.get(TranslationBlueprint.dataRefs.translation),
|
||||
),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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';
|
||||
// TODO: Dependency on MUI should be removed from core packages
|
||||
import Button from '@material-ui/core/Button';
|
||||
|
||||
import {
|
||||
createComponentExtension,
|
||||
coreComponentRefs,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { ErrorPanel } from '@backstage/core-components';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { components as defaultComponents } from '../../../../packages/app-defaults/src/defaults';
|
||||
|
||||
export const DefaultProgressComponent = createComponentExtension({
|
||||
ref: coreComponentRefs.progress,
|
||||
loader: { sync: () => defaultComponents.Progress },
|
||||
});
|
||||
|
||||
export const DefaultNotFoundErrorPageComponent = createComponentExtension({
|
||||
ref: coreComponentRefs.notFoundErrorPage,
|
||||
loader: { sync: () => defaultComponents.NotFoundErrorPage },
|
||||
});
|
||||
|
||||
export const DefaultErrorBoundaryComponent = createComponentExtension({
|
||||
ref: coreComponentRefs.errorBoundaryFallback,
|
||||
loader: {
|
||||
sync: () => props => {
|
||||
const { plugin, error, resetError } = props;
|
||||
const title = `Error in ${plugin?.id}`;
|
||||
|
||||
return (
|
||||
<ErrorPanel title={title} error={error} defaultExpanded>
|
||||
<Button variant="outlined" onClick={resetError}>
|
||||
Retry
|
||||
</Button>
|
||||
</ErrorPanel>
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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 { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components';
|
||||
import { AppRootElementBlueprint } from '@backstage/frontend-plugin-api';
|
||||
import React from 'react';
|
||||
|
||||
export const oauthRequestDialogAppRootElement = AppRootElementBlueprint.make({
|
||||
namespace: 'app',
|
||||
name: 'oauth-request-dialog',
|
||||
params: {
|
||||
element: <OAuthRequestDialog />,
|
||||
},
|
||||
});
|
||||
|
||||
export const alertDisplayAppRootElement =
|
||||
AppRootElementBlueprint.makeWithOverrides({
|
||||
namespace: 'app',
|
||||
name: 'alert-display',
|
||||
config: {
|
||||
schema: {
|
||||
transientTimeoutMs: z => z.number().default(5000),
|
||||
anchorOrigin: z =>
|
||||
z
|
||||
.object({
|
||||
vertical: z.enum(['top', 'bottom']).default('top'),
|
||||
horizontal: z.enum(['left', 'center', 'right']).default('center'),
|
||||
})
|
||||
.default({}),
|
||||
},
|
||||
},
|
||||
factory: (originalFactory, { config }) => {
|
||||
return originalFactory({
|
||||
element: () => <AlertDisplay {...config} />,
|
||||
});
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user