From 95e8772eabcb8c187ceed73495280fcc944c2984 Mon Sep 17 00:00:00 2001 From: Brett Jephson Date: Wed, 2 Oct 2024 11:44:10 +0100 Subject: [PATCH] Refactor - naming and comments --- .../DocumentView/OpenAPI/OpenAPI.tsx | 12 +++-- ...xt.tsx => OpenAPIClientStateContainer.tsx} | 19 ++++--- .../components/DocumentView/OpenAPI/style.css | 4 ++ ...ider.tsx => OpenAPIClientStateContext.tsx} | 52 +++++++++++++------ .../react-openapi/src/OpenAPIServerURL.tsx | 51 ++++++++++-------- .../src/OpenAPIServerURLForm.tsx | 21 ++++---- .../react-openapi/src/ScalarApiButton.tsx | 9 ++-- packages/react-openapi/src/ServerSelector.tsx | 6 +-- packages/react-openapi/src/client.ts | 2 +- 9 files changed, 107 insertions(+), 69 deletions(-) rename packages/gitbook/src/components/DocumentView/OpenAPI/{OpenAPIContext.tsx => OpenAPIClientStateContainer.tsx} (66%) rename packages/react-openapi/src/{OpenAPIContextProvider.tsx => OpenAPIClientStateContext.tsx} (50%) diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx index 08fe0be58..a70d0f1f4 100644 --- a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx @@ -7,7 +7,7 @@ import { LoadingPane } from '@/components/primitives'; import { fetchOpenAPIBlock } from '@/lib/openapi'; import { tcls } from '@/lib/tailwind'; -import OpenAPIContext from './OpenAPIContext'; +import OpenAPIClientStateContainer from './OpenAPIClientStateContainer'; import { serverUrlCache } from './ServerUrlCache'; import { BlockProps } from '../Block'; import { PlainCodeBlock } from '../CodeBlock'; @@ -19,7 +19,7 @@ import './scalar.css'; * Render an OpenAPI block. */ export async function OpenAPI(props: BlockProps) { - const { block, style } = props; + const { style } = props; return (
}> @@ -47,10 +47,12 @@ async function OpenAPIBody(props: BlockProps) { return null; } - const serverUrl = serverUrlCache.get('serverUrl'); + // To update the code sample we need to re-render the server component + // so reading the cached value from search params + const serverUrl = serverUrlCache.get('serverUrl'); return ( - + ) { }} className="openapi-block" /> - + ); } diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIContext.tsx b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIClientStateContainer.tsx similarity index 66% rename from packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIContext.tsx rename to packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIClientStateContainer.tsx index de4b2d840..743fb79e1 100644 --- a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIContext.tsx +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIClientStateContainer.tsx @@ -2,24 +2,29 @@ import { DocumentBlock } from '@gitbook/api'; import { OpenAPIOperationData } from '@gitbook/react-openapi'; -import { OpenAPIContextProvider } from '@gitbook/react-openapi/client'; +import { OpenAPIClientState } from '@gitbook/react-openapi/client'; import { useRouter, useSearchParams } from 'next/navigation'; +import { OpenAPIV3 } from 'openapi-types'; import * as React from 'react'; -export default function OpenAPIContext(props: { +/** + * Client component that wraps `OpenAPIClientState` so we can + * use some hooks (e.g. useRouter) in the `onUpdate` callback. + */ +export default function OpenAPIClientStateContainer(props: { children: React.ReactNode; block: DocumentBlock; - data: OpenAPIOperationData; + servers: OpenAPIV3.ServerObject[]; }) { - const { block, children, data } = props; + const { block, children, servers } = props; const [isPending, startTransition] = React.useTransition(); const router = useRouter(); const searchParams = useSearchParams(); return ( - {children} - + ); } diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/style.css b/packages/gitbook/src/components/DocumentView/OpenAPI/style.css index 54ef1adaf..56e2295bb 100644 --- a/packages/gitbook/src/components/DocumentView/OpenAPI/style.css +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/style.css @@ -381,3 +381,7 @@ .openapi-select-button { @apply leading-[1cap] disabled:opacity-5; } + +.openapi-pending { + @apply opacity-5; +} diff --git a/packages/react-openapi/src/OpenAPIContextProvider.tsx b/packages/react-openapi/src/OpenAPIClientStateContext.tsx similarity index 50% rename from packages/react-openapi/src/OpenAPIContextProvider.tsx rename to packages/react-openapi/src/OpenAPIClientStateContext.tsx index b09ee2933..e5d213dd0 100644 --- a/packages/react-openapi/src/OpenAPIContextProvider.tsx +++ b/packages/react-openapi/src/OpenAPIClientStateContext.tsx @@ -3,38 +3,56 @@ import * as React from 'react'; import { OpenAPIOperationData } from './fetchOpenAPIOperation'; import { getServersURL } from './utils'; +import { OpenAPIV3 } from 'openapi-types'; -type OpenAPIContextProps = { +type OpenAPIClientStateContextProps = { + /** + * Whether client state updates are in a pending state, + * i.e. is a transition in progress. + */ isPending?: boolean; + /** + * The server url + */ serverUrl?: string; + /** + * The current state + */ state?: Record | null; + /** + * Callback for when the client state is updated + */ onUpdate: (params: Record | null) => void; }; -const OpenAPIContext = React.createContext(null); -export function useOpenAPIContext() { - return React.useContext(OpenAPIContext); +const OpenAPIClientStateContext = React.createContext(null); + +export function useOpenAPIClientState() { + return React.useContext(OpenAPIClientStateContext); } -export function OpenAPIContextProvider(props: { +/** + * Control client state for an OpenAPI operation + */ +export function OpenAPIClientState(props: { children: React.ReactNode; - data: OpenAPIOperationData; + servers: OpenAPIV3.ServerObject[]; isPending?: boolean; params?: Record; - onUpdate: OpenAPIContextProps['onUpdate']; + onUpdate: OpenAPIClientStateContextProps['onUpdate']; }) { - const { children, data, isPending, params, onUpdate } = props; + const { children, servers, isPending, params, onUpdate } = props; const clientState = React.useMemo(() => { if (!params) { return null; } - return parseClientStateModifiers(data, params); - }, [data, params]); - const serverUrl = getServersURL(data.servers, clientState ?? undefined); + return parseClientStateModifiers(servers, params); + }, [servers, params]); + const serverUrl = getServersURL(servers, clientState ?? undefined); return ( - {children} - + ); } -function parseClientStateModifiers(data: OpenAPIOperationData, params: Record) { - if (!data) { +function parseClientStateModifiers(servers: OpenAPIV3.ServerObject[], params: Record) { + if (!servers) { return null; } const serverQueryParam = params['server']; const serverIndex = serverQueryParam && !isNaN(Number(serverQueryParam)) - ? Math.max(0, Math.min(Number(serverQueryParam), data.servers.length - 1)) + ? Math.max(0, Math.min(Number(serverQueryParam), servers.length - 1)) : 0; - const server = data.servers[serverIndex]; + const server = servers[serverIndex]; return server ? Object.keys(server.variables ?? {}).reduce>( (result, key) => { diff --git a/packages/react-openapi/src/OpenAPIServerURL.tsx b/packages/react-openapi/src/OpenAPIServerURL.tsx index d0f8e9d53..786f49120 100644 --- a/packages/react-openapi/src/OpenAPIServerURL.tsx +++ b/packages/react-openapi/src/OpenAPIServerURL.tsx @@ -2,10 +2,12 @@ import * as React from 'react'; import { OpenAPIV3 } from 'openapi-types'; +import classNames from 'classnames'; + import { OpenAPIServerURLVariable } from './OpenAPIServerURLVariable'; import { OpenAPIClientContext } from './types'; import { ServerURLForm } from './OpenAPIServerURLForm'; -import { useOpenAPIContext } from './OpenAPIContextProvider'; +import { useOpenAPIClientState } from './OpenAPIClientStateContext'; import { parseServerURL } from './utils'; /** @@ -17,33 +19,38 @@ export function OpenAPIServerURL(props: { path?: string; }) { const { path, servers, context } = props; - const ctx = useOpenAPIContext(); - const serverIndex = !isNaN(Number(ctx?.state?.server)) ? Number(ctx?.state?.server) : 0; + const stateContext = useOpenAPIClientState(); + + const serverIndex = !isNaN(Number(stateContext?.state?.server)) ? Number(stateContext?.state?.server) : 0; const server = servers[serverIndex]; const parts = parseServerURL(server?.url ?? ''); + if (!server) { return null; } + return ( - {parts.map((part, i) => { - if (part.kind === 'text') { - return {part.text}; - } else { - if (!server.variables?.[part.name]) { - return {`{${part.name}}`}; - } + + {parts.map((part, i) => { + if (part.kind === 'text') { + return {part.text}; + } else { + if (!server.variables?.[part.name]) { + return {`{${part.name}}`}; + } - return ( - - ); - } - })} - {path} + return ( + + ); + } + })} + {path} + ); } diff --git a/packages/react-openapi/src/OpenAPIServerURLForm.tsx b/packages/react-openapi/src/OpenAPIServerURLForm.tsx index 5abfad838..9df610dd5 100644 --- a/packages/react-openapi/src/OpenAPIServerURLForm.tsx +++ b/packages/react-openapi/src/OpenAPIServerURLForm.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { OpenAPIClientContext } from './types'; import { OpenAPIV3 } from 'openapi-types'; import { ServerSelector } from './ServerSelector'; -import { useOpenAPIContext } from './OpenAPIContextProvider'; +import { useOpenAPIClientState } from './OpenAPIClientStateContext'; import { getServersURL } from './utils'; export function ServerURLForm(props: { @@ -14,7 +14,7 @@ export function ServerURLForm(props: { serverIndex: number; }) { const { children, context, servers, serverIndex } = props; - const stateContext = useOpenAPIContext(); + const stateContext = useOpenAPIClientState(); const server = servers[serverIndex]; const formRef = React.useRef(null); @@ -52,7 +52,10 @@ export function ServerURLForm(props: { }); } - const isEditable = stateContext?.onUpdate && (servers.length > 1 || server.variables); + // Only make the server url editable if there is some onUpdate callback + // and if there are variations on the server url (e.g. an array of servers or url variables). + const isEditable = stateContext?.onUpdate && (servers.length > 1 || server.variables); + const isEditing = isEditable && stateContext?.state?.edit; return (
{children} - {stateContext?.state?.edit && servers.length > 1 ? ( + {isEditing && servers.length > 1 ? ( @@ -81,17 +84,17 @@ export function ServerURLForm(props: { update({ server: `${serverIndex}`, ...state, - ...(stateContext?.state?.edit + ...(isEditing ? { serverUrl: getServersURL(servers, state) } : { edit: 'true' }), }); }} title={ - stateContext?.state?.edit ? undefined : 'Try different server options' + isEditing ? undefined : 'Try different server options' } - aria-label={stateContext?.state?.edit ? 'Clear' : 'Edit'} + aria-label={isEditing ? 'Clear' : 'Edit'} > - {stateContext?.state?.edit ? context.icons.editDone : context.icons.edit} + {isEditing ? context.icons.editDone : context.icons.edit} ) : null} diff --git a/packages/react-openapi/src/ScalarApiButton.tsx b/packages/react-openapi/src/ScalarApiButton.tsx index af6257fca..ca0a9869c 100644 --- a/packages/react-openapi/src/ScalarApiButton.tsx +++ b/packages/react-openapi/src/ScalarApiButton.tsx @@ -12,8 +12,7 @@ import { import React from 'react'; import { OpenAPIOperationData, fromJSON } from './fetchOpenAPIOperation'; -import { useOpenAPIContext } from './OpenAPIContextProvider'; -import { getServersURL } from './utils'; +import { useOpenAPIClientState } from './OpenAPIClientStateContext'; const ApiClientReact = React.lazy(async () => { const mod = await import('@scalar/api-client-react'); @@ -60,7 +59,7 @@ export function ScalarApiButton(props: { export function ScalarApiClient(props: { children: React.ReactNode }) { const { children } = props; - const ctx = useOpenAPIContext(); + const stateCtx = useOpenAPIClientState(); const [active, setActive] = React.useState { return { ...header, enabled: true }; }), - url: ctx?.serverUrl ?? operationData.servers[0]?.url, + url: stateCtx?.serverUrl ?? operationData.servers[0]?.url, body: request.postData?.text, }; return data; - }, [active, ctx?.state?.serverUrl]); + }, [active, stateCtx?.serverUrl]); return ( diff --git a/packages/react-openapi/src/ServerSelector.tsx b/packages/react-openapi/src/ServerSelector.tsx index bd2997619..17037ffbf 100644 --- a/packages/react-openapi/src/ServerSelector.tsx +++ b/packages/react-openapi/src/ServerSelector.tsx @@ -4,10 +4,10 @@ import * as React from 'react'; export function ServerSelector(props: { currentIndex: number; + lastIndex: number; onChange: (value: number) => void; - servers: any[]; }) { - const { currentIndex, onChange, servers } = props; + const { currentIndex, onChange, lastIndex } = props; const [index, setIndex] = React.useState(currentIndex); React.useEffect(() => { @@ -32,7 +32,7 @@ export function ServerSelector(props: {