Lazy load all API definition widgets

Signed-off-by: Oliver Sand <oliver.sand@sda-se.com>
This commit is contained in:
Oliver Sand
2021-11-01 13:10:54 +01:00
parent cd646b55a9
commit 044c38e739
11 changed files with 375 additions and 250 deletions
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/plugin-api-docs': patch
---
Lazy load all API definition widgets. The widgets use libraries like
`swagger-ui`, `graphiql`, and `@asyncapi/react-component` which are quite heavy
weight. To improve initial load times, the widgets are only loaded once used.
+11 -7
View File
@@ -84,11 +84,13 @@ export const ApiTypeTitle: ({
apiEntity: ApiEntity;
}) => JSX.Element;
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "AsyncApiDefinitionWidgetProps" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "AsyncApiDefinitionWidget" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const AsyncApiDefinitionWidget: ({ definition }: Props_5) => JSX.Element;
export const AsyncApiDefinitionWidget: ({
definition,
}: AsyncApiDefinitionWidgetProps) => JSX.Element;
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "ConsumedApisCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -100,7 +102,7 @@ export const ConsumedApisCard: ({ variant }: Props_2) => JSX.Element;
// Warning: (ae-missing-release-tag) "ConsumingComponentsCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const ConsumingComponentsCard: ({ variant }: Props_6) => JSX.Element;
export const ConsumingComponentsCard: ({ variant }: Props_5) => JSX.Element;
// Warning: (ae-missing-release-tag) "defaultDefinitionWidgets" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -169,11 +171,13 @@ export const EntityProvidingComponentsCard: ({
// @public (undocumented)
export const HasApisCard: ({ variant }: Props_3) => JSX.Element;
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "OpenApiDefinitionWidgetProps" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "OpenApiDefinitionWidget" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const OpenApiDefinitionWidget: ({ definition }: Props_8) => JSX.Element;
export const OpenApiDefinitionWidget: ({
definition,
}: OpenApiDefinitionWidgetProps) => JSX.Element;
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "PlainApiDefinitionWidget" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -182,7 +186,7 @@ export const OpenApiDefinitionWidget: ({ definition }: Props_8) => JSX.Element;
export const PlainApiDefinitionWidget: ({
definition,
language,
}: Props_9) => JSX.Element;
}: Props_7) => JSX.Element;
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "ProvidedApisCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -194,5 +198,5 @@ export const ProvidedApisCard: ({ variant }: Props_4) => JSX.Element;
// Warning: (ae-missing-release-tag) "ProvidingComponentsCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const ProvidingComponentsCard: ({ variant }: Props_7) => JSX.Element;
export const ProvidingComponentsCard: ({ variant }: Props_6) => JSX.Element;
```
@@ -16,9 +16,9 @@
import { renderInTestApp } from '@backstage/test-utils';
import React from 'react';
import { AsyncApiDefinitionWidget } from './AsyncApiDefinitionWidget';
import { AsyncApiDefinition } from './AsyncApiDefinition';
describe('<AsyncApiDefinitionWidget />', () => {
describe('<AsyncApiDefinition />', () => {
it('renders asyncapi spec', async () => {
const definition = `
asyncapi: 2.0.0
@@ -40,7 +40,7 @@ components:
type: string
`;
const { getByText, getAllByText } = await renderInTestApp(
<AsyncApiDefinitionWidget definition={definition} />,
<AsyncApiDefinition definition={definition} />,
);
expect(getByText(/Account Service/i)).toBeInTheDocument();
@@ -51,7 +51,7 @@ components:
it('renders error if definition is missing', async () => {
const { getByText } = await renderInTestApp(
<AsyncApiDefinitionWidget definition="" />,
<AsyncApiDefinition definition="" />,
);
expect(getByText(/Error/i)).toBeInTheDocument();
expect(getByText(/Document can't be null or falsey/i)).toBeInTheDocument();
@@ -0,0 +1,145 @@
/*
* Copyright 2020 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 AsyncApi from '@asyncapi/react-component';
import '@asyncapi/react-component/lib/styles/fiori.css';
import { alpha, makeStyles } from '@material-ui/core/styles';
import React from 'react';
const useStyles = makeStyles(theme => ({
root: {
'& .asyncapi': {
'font-family': 'inherit',
background: 'none',
},
'& h2': {
...theme.typography.h6,
},
'& .text-teal': {
color: theme.palette.primary.main,
},
'& button': {
...theme.typography.button,
background: 'none',
boxSizing: 'border-box',
minWidth: 64,
borderRadius: theme.shape.borderRadius,
transition: theme.transitions.create(
['background-color', 'box-shadow', 'border'],
{
duration: theme.transitions.duration.short,
},
),
padding: '5px 15px',
color: theme.palette.primary.main,
border: `1px solid ${alpha(theme.palette.primary.main, 0.5)}`,
'&:hover': {
textDecoration: 'none',
'&.Mui-disabled': {
backgroundColor: 'transparent',
},
border: `1px solid ${theme.palette.primary.main}`,
backgroundColor: alpha(
theme.palette.primary.main,
theme.palette.action.hoverOpacity,
),
// Reset on touch devices, it doesn't add specificity
'@media (hover: none)': {
backgroundColor: 'transparent',
},
},
'&.Mui-disabled': {
color: theme.palette.action.disabled,
},
},
'& .asyncapi__collapse-button:hover': {
color: theme.palette.primary.main,
},
'& button.asyncapi__toggle-button': {
'min-width': 'inherit',
},
'& .asyncapi__info-list li': {
'border-color': theme.palette.primary.main,
'&:hover': {
color: theme.palette.text.primary,
'border-color': theme.palette.primary.main,
'background-color': theme.palette.primary.main,
},
},
'& .asyncapi__info-list li a': {
color: theme.palette.primary.main,
'&:hover': {
color: theme.palette.getContrastText(theme.palette.primary.main),
},
},
'& .asyncapi__enum': {
color: theme.palette.secondary.main,
},
'& .asyncapi__info, .asyncapi__channel, .asyncapi__channels > div, .asyncapi__schema, .asyncapi__channel-operations-list .asyncapi__messages-list-item .asyncapi__message, .asyncapi__message, .asyncapi__server, .asyncapi__servers > div, .asyncapi__messages > div, .asyncapi__schemas > div':
{
'background-color': 'inherit',
},
'& .asyncapi__channel-parameters-header, .asyncapi__channel-operations-header, .asyncapi__channel-operation-oneOf-subscribe-header, .asyncapi__channel-operation-oneOf-publish-header, .asyncapi__channel-operation-message-header, .asyncapi__message-header, .asyncapi__message-header-title, .asyncapi__message-header-title > h3, .asyncapi__bindings, .asyncapi__bindings-header, .asyncapi__bindings-header > h4':
{
'background-color': 'inherit',
color: theme.palette.text.primary,
},
'& .asyncapi__additional-properties-notice': {
color: theme.palette.text.hint,
},
'& .asyncapi__code, .asyncapi__code-pre': {
background: theme.palette.background.default,
},
'& .asyncapi__schema-example-header-title': {
color: theme.palette.text.secondary,
},
'& .asyncapi__message-headers-header, .asyncapi__message-payload-header, .asyncapi__server-variables-header, .asyncapi__server-security-header':
{
'background-color': 'inherit',
color: theme.palette.text.secondary,
},
'& .asyncapi__table-header': {
background: theme.palette.background.default,
},
'& .asyncapi__table-body': {
color: theme.palette.text.primary,
},
'& .asyncapi__server-security-flow': {
background: theme.palette.background.default,
border: 'none',
},
'& .asyncapi__server-security-flows-list a': {
color: theme.palette.primary.main,
},
'& .asyncapi__table-row--nested': {
color: theme.palette.text.secondary,
},
},
}));
type Props = {
definition: string;
};
export const AsyncApiDefinition = ({ definition }: Props) => {
const classes = useStyles();
return (
<div className={classes.root}>
<AsyncApi schema={definition} />
</div>
);
};
@@ -14,132 +14,27 @@
* limitations under the License.
*/
import AsyncApi from '@asyncapi/react-component';
import '@asyncapi/react-component/lib/styles/fiori.css';
import { fade, makeStyles } from '@material-ui/core/styles';
import React from 'react';
import { Progress } from '@backstage/core-components';
import React, { Suspense } from 'react';
const useStyles = makeStyles(theme => ({
root: {
'& .asyncapi': {
'font-family': 'inherit',
background: 'none',
},
'& h2': {
...theme.typography.h6,
},
'& .text-teal': {
color: theme.palette.primary.main,
},
'& button': {
...theme.typography.button,
background: 'none',
boxSizing: 'border-box',
minWidth: 64,
borderRadius: theme.shape.borderRadius,
transition: theme.transitions.create(
['background-color', 'box-shadow', 'border'],
{
duration: theme.transitions.duration.short,
},
),
padding: '5px 15px',
color: theme.palette.primary.main,
border: `1px solid ${fade(theme.palette.primary.main, 0.5)}`,
'&:hover': {
textDecoration: 'none',
'&.Mui-disabled': {
backgroundColor: 'transparent',
},
border: `1px solid ${theme.palette.primary.main}`,
backgroundColor: fade(
theme.palette.primary.main,
theme.palette.action.hoverOpacity,
),
// Reset on touch devices, it doesn't add specificity
'@media (hover: none)': {
backgroundColor: 'transparent',
},
},
'&.Mui-disabled': {
color: theme.palette.action.disabled,
},
},
'& .asyncapi__collapse-button:hover': {
color: theme.palette.primary.main,
},
'& button.asyncapi__toggle-button': {
'min-width': 'inherit',
},
'& .asyncapi__info-list li': {
'border-color': theme.palette.primary.main,
'&:hover': {
color: theme.palette.text.primary,
'border-color': theme.palette.primary.main,
'background-color': theme.palette.primary.main,
},
},
'& .asyncapi__info-list li a': {
color: theme.palette.primary.main,
'&:hover': {
color: theme.palette.getContrastText(theme.palette.primary.main),
},
},
'& .asyncapi__enum': {
color: theme.palette.secondary.main,
},
'& .asyncapi__info, .asyncapi__channel, .asyncapi__channels > div, .asyncapi__schema, .asyncapi__channel-operations-list .asyncapi__messages-list-item .asyncapi__message, .asyncapi__message, .asyncapi__server, .asyncapi__servers > div, .asyncapi__messages > div, .asyncapi__schemas > div':
{
'background-color': 'inherit',
},
'& .asyncapi__channel-parameters-header, .asyncapi__channel-operations-header, .asyncapi__channel-operation-oneOf-subscribe-header, .asyncapi__channel-operation-oneOf-publish-header, .asyncapi__channel-operation-message-header, .asyncapi__message-header, .asyncapi__message-header-title, .asyncapi__message-header-title > h3, .asyncapi__bindings, .asyncapi__bindings-header, .asyncapi__bindings-header > h4':
{
'background-color': 'inherit',
color: theme.palette.text.primary,
},
'& .asyncapi__additional-properties-notice': {
color: theme.palette.text.hint,
},
'& .asyncapi__code, .asyncapi__code-pre': {
background: theme.palette.background.default,
},
'& .asyncapi__schema-example-header-title': {
color: theme.palette.text.secondary,
},
'& .asyncapi__message-headers-header, .asyncapi__message-payload-header, .asyncapi__server-variables-header, .asyncapi__server-security-header':
{
'background-color': 'inherit',
color: theme.palette.text.secondary,
},
'& .asyncapi__table-header': {
background: theme.palette.background.default,
},
'& .asyncapi__table-body': {
color: theme.palette.text.primary,
},
'& .asyncapi__server-security-flow': {
background: theme.palette.background.default,
border: 'none',
},
'& .asyncapi__server-security-flows-list a': {
color: theme.palette.primary.main,
},
'& .asyncapi__table-row--nested': {
color: theme.palette.text.secondary,
},
},
}));
// The asyncapi component and related CSS has a significant size, only load it
// if the element is actually used.
const LazyAsyncApiDefinition = React.lazy(() =>
import('./AsyncApiDefinition').then(m => ({
default: m.AsyncApiDefinition,
})),
);
type Props = {
export type AsyncApiDefinitionWidgetProps = {
definition: string;
};
export const AsyncApiDefinitionWidget = ({ definition }: Props) => {
const classes = useStyles();
export const AsyncApiDefinitionWidget = ({
definition,
}: AsyncApiDefinitionWidgetProps) => {
return (
<div className={classes.root}>
<AsyncApi schema={definition} />
</div>
<Suspense fallback={<Progress />}>
<LazyAsyncApiDefinition definition={definition} />
</Suspense>
);
};
@@ -16,9 +16,9 @@
import { renderInTestApp } from '@backstage/test-utils';
import React from 'react';
import { GraphQlDefinitionWidget } from './GraphQlDefinitionWidget';
import { GraphQlDefinition } from './GraphQlDefinition';
describe('<GraphQlDefinitionWidget />', () => {
describe('<GraphQlDefinition />', () => {
it('renders graphql schema', async () => {
const definition = `
"""Hello World!"""
@@ -53,7 +53,7 @@ type Film {
};
const { getByText } = await renderInTestApp(
<GraphQlDefinitionWidget definition={definition} />,
<GraphQlDefinition definition={definition} />,
);
expect(getByText(/Film/i)).toBeInTheDocument();
@@ -0,0 +1,63 @@
/*
* Copyright 2020 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 { BackstageTheme } from '@backstage/theme';
import { makeStyles } from '@material-ui/core/styles';
import GraphiQL from 'graphiql';
import 'graphiql/graphiql.css';
import { buildSchema } from 'graphql';
import React from 'react';
const useStyles = makeStyles<BackstageTheme>(() => ({
root: {
height: '100%',
display: 'flex',
flexFlow: 'column nowrap',
},
graphiQlWrapper: {
flex: 1,
'@global': {
'.graphiql-container': {
boxSizing: 'initial',
height: '100%',
minHeight: '600px',
flex: '1 1 auto',
},
},
},
}));
type Props = {
definition: string;
};
export const GraphQlDefinition = ({ definition }: Props) => {
const classes = useStyles();
const schema = buildSchema(definition);
return (
<div className={classes.root}>
<div className={classes.graphiQlWrapper}>
<GraphiQL
fetcher={() => Promise.resolve(null) as any}
schema={schema}
docExplorerOpen
defaultSecondaryEditorOpen={false}
/>
</div>
</div>
);
};
@@ -14,54 +14,27 @@
* limitations under the License.
*/
import { BackstageTheme } from '@backstage/theme';
import { makeStyles } from '@material-ui/core/styles';
import 'graphiql/graphiql.css';
import { buildSchema } from 'graphql';
import React, { Suspense } from 'react';
import { Progress } from '@backstage/core-components';
import React, { Suspense } from 'react';
const GraphiQL = React.lazy(() => import('graphiql'));
// The graphql component, graphql and related CSS has a significant size, only
// load it if the element is actually used.
const LazyGraphQlDefinition = React.lazy(() =>
import('./GraphQlDefinition').then(m => ({
default: m.GraphQlDefinition,
})),
);
const useStyles = makeStyles<BackstageTheme>(() => ({
root: {
height: '100%',
display: 'flex',
flexFlow: 'column nowrap',
},
graphiQlWrapper: {
flex: 1,
'@global': {
'.graphiql-container': {
boxSizing: 'initial',
height: '100%',
minHeight: '600px',
flex: '1 1 auto',
},
},
},
}));
type Props = {
definition: any;
export type GraphQlDefinitionWidgetProps = {
definition: string;
};
export const GraphQlDefinitionWidget = ({ definition }: Props) => {
const classes = useStyles();
const schema = buildSchema(definition);
export const GraphQlDefinitionWidget = ({
definition,
}: GraphQlDefinitionWidgetProps) => {
return (
<Suspense fallback={<Progress />}>
<div className={classes.root}>
<div className={classes.graphiQlWrapper}>
<GraphiQL
fetcher={() => Promise.resolve(null) as any}
schema={schema}
docExplorerOpen
defaultSecondaryEditorOpen={false}
/>
</div>
</div>
<LazyGraphQlDefinition definition={definition} />
</Suspense>
);
};
@@ -17,9 +17,9 @@
import { renderInTestApp } from '@backstage/test-utils';
import { waitFor } from '@testing-library/react';
import React from 'react';
import { OpenApiDefinitionWidget } from './OpenApiDefinitionWidget';
import { OpenApiDefinition } from './OpenApiDefinition';
describe('<OpenApiDefinitionWidget />', () => {
describe('<OpenApiDefinition />', () => {
it('renders openapi spec', async () => {
const definition = `
openapi: "3.0.0"
@@ -39,7 +39,7 @@ paths:
description: Success
`;
const { getByText } = await renderInTestApp(
<OpenApiDefinitionWidget definition={definition} />,
<OpenApiDefinition definition={definition} />,
);
// swagger-ui loads the documentation asynchronously
@@ -51,7 +51,7 @@ paths:
it('renders error if definition is missing', async () => {
const { getByText } = await renderInTestApp(
<OpenApiDefinitionWidget definition="" />,
<OpenApiDefinition definition="" />,
);
expect(getByText(/No API definition provided/i)).toBeInTheDocument();
});
@@ -0,0 +1,92 @@
/*
* Copyright 2020 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 { makeStyles } from '@material-ui/core/styles';
import React, { useEffect, useState } from 'react';
import SwaggerUI from 'swagger-ui-react';
import 'swagger-ui-react/swagger-ui.css';
const useStyles = makeStyles(theme => ({
root: {
'& .swagger-ui, .info h1, .info h2, .info h3, .info h4, .info h': {
'font-family': 'inherit',
color: theme.palette.text.primary,
},
'& .scheme-container': {
'background-color': theme.palette.background.default,
},
'& .opblock-tag, .opblock-tag small, table thead tr td, table thead tr th':
{
color: theme.palette.text.primary,
'border-color': theme.palette.divider,
},
'& section.models, section.models.is-open h4': {
'border-color': theme.palette.divider,
},
'& .opblock .opblock-summary-description, .parameter__type, table.headers td, .model-title, .model .property.primitive, section h3':
{
color: theme.palette.text.secondary,
},
'& .opblock .opblock-summary-operation-id, .opblock .opblock-summary-path, .opblock .opblock-summary-path__deprecated, .opblock .opblock-section-header h4, .parameter__name, .response-col_status, .response-col_links, .responses-inner h4, .swagger-ui .responses-inner h5, .opblock-section-header .btn, .tab li, .info li, .info p, .info table, section.models h4, .info .title, table.model tr.description, .property-row':
{
color: theme.palette.text.primary,
},
'& .opblock .opblock-section-header, .model-box, section.models .model-container':
{
background: theme.palette.background.default,
},
'& .prop-format, .parameter__in': {
color: theme.palette.text.disabled,
},
'& ': {
color: theme.palette.text.primary,
'border-color': theme.palette.divider,
},
'& .opblock-description-wrapper p, .opblock-external-docs-wrapper p, .opblock-title_normal p, .response-control-media-type__accept-message, .opblock .opblock-section-header>label, .scheme-container .schemes>label, .info .base-url, .model':
{
color: theme.palette.text.hint,
},
'& .parameter__name.required:after': {
color: theme.palette.warning.dark,
},
'& .prop-type': {
color: theme.palette.primary.main,
},
},
}));
type Props = {
definition: string;
};
export const OpenApiDefinition = ({ definition }: Props) => {
const classes = useStyles();
// Due to a bug in the swagger-ui-react component, the component needs
// to be created without content first.
const [def, setDef] = useState('');
useEffect(() => {
const timer = setTimeout(() => setDef(definition), 0);
return () => clearTimeout(timer);
}, [definition, setDef]);
return (
<div className={classes.root}>
<SwaggerUI spec={def} deepLinking />
</div>
);
};
@@ -14,81 +14,27 @@
* limitations under the License.
*/
import { makeStyles } from '@material-ui/core/styles';
import React, { useEffect, useState } from 'react';
import SwaggerUI from 'swagger-ui-react';
import 'swagger-ui-react/swagger-ui.css';
import { Progress } from '@backstage/core-components';
import React, { Suspense } from 'react';
// TODO: Schemas
// The swagger-ui component and related CSS has a significant size, only load it
// if the element is actually used.
const LazyOpenApiDefinition = React.lazy(() =>
import('./OpenApiDefinition').then(m => ({
default: m.OpenApiDefinition,
})),
);
const useStyles = makeStyles(theme => ({
root: {
'& .swagger-ui, .info h1, .info h2, .info h3, .info h4, .info h': {
'font-family': 'inherit',
color: theme.palette.text.primary,
},
'& .scheme-container': {
'background-color': theme.palette.background.default,
},
'& .opblock-tag, .opblock-tag small, table thead tr td, table thead tr th':
{
color: theme.palette.text.primary,
'border-color': theme.palette.divider,
},
'& section.models, section.models.is-open h4': {
'border-color': theme.palette.divider,
},
'& .opblock .opblock-summary-description, .parameter__type, table.headers td, .model-title, .model .property.primitive, section h3':
{
color: theme.palette.text.secondary,
},
'& .opblock .opblock-summary-operation-id, .opblock .opblock-summary-path, .opblock .opblock-summary-path__deprecated, .opblock .opblock-section-header h4, .parameter__name, .response-col_status, .response-col_links, .responses-inner h4, .swagger-ui .responses-inner h5, .opblock-section-header .btn, .tab li, .info li, .info p, .info table, section.models h4, .info .title, table.model tr.description, .property-row':
{
color: theme.palette.text.primary,
},
'& .opblock .opblock-section-header, .model-box, section.models .model-container':
{
background: theme.palette.background.default,
},
'& .prop-format, .parameter__in': {
color: theme.palette.text.disabled,
},
'& ': {
color: theme.palette.text.primary,
'border-color': theme.palette.divider,
},
'& .opblock-description-wrapper p, .opblock-external-docs-wrapper p, .opblock-title_normal p, .response-control-media-type__accept-message, .opblock .opblock-section-header>label, .scheme-container .schemes>label, .info .base-url, .model':
{
color: theme.palette.text.hint,
},
'& .parameter__name.required:after': {
color: theme.palette.warning.dark,
},
'& .prop-type': {
color: theme.palette.primary.main,
},
},
}));
type Props = {
export type OpenApiDefinitionWidgetProps = {
definition: string;
};
export const OpenApiDefinitionWidget = ({ definition }: Props) => {
const classes = useStyles();
// Due to a bug in the swagger-ui-react component, the component needs
// to be created without content first.
const [def, setDef] = useState('');
useEffect(() => {
const timer = setTimeout(() => setDef(definition), 0);
return () => clearTimeout(timer);
}, [definition, setDef]);
export const OpenApiDefinitionWidget = ({
definition,
}: OpenApiDefinitionWidgetProps) => {
return (
<div className={classes.root}>
<SwaggerUI spec={def} deepLinking />
</div>
<Suspense fallback={<Progress />}>
<LazyOpenApiDefinition definition={definition} />
</Suspense>
);
};