mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-16 15:45:13 +00:00
Refactor - naming and comments
This commit is contained in:
@@ -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<DocumentBlockSwagger>) {
|
||||
const { block, style } = props;
|
||||
const { style } = props;
|
||||
return (
|
||||
<div className={tcls('w-full', 'flex', 'flex-row', style, 'max-w-full')}>
|
||||
<React.Suspense fallback={<OpenAPIFallback />}>
|
||||
@@ -47,10 +47,12 @@ async function OpenAPIBody(props: BlockProps<DocumentBlockSwagger>) {
|
||||
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 (
|
||||
<OpenAPIContext block={block} data={data}>
|
||||
<OpenAPIClientStateContainer block={block} servers={data.servers}>
|
||||
<OpenAPIOperation
|
||||
data={data}
|
||||
context={{
|
||||
@@ -68,7 +70,7 @@ async function OpenAPIBody(props: BlockProps<DocumentBlockSwagger>) {
|
||||
}}
|
||||
className="openapi-block"
|
||||
/>
|
||||
</OpenAPIContext>
|
||||
</OpenAPIClientStateContainer>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+12
-7
@@ -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 (
|
||||
<OpenAPIContextProvider
|
||||
<OpenAPIClientState
|
||||
isPending={isPending}
|
||||
data={data}
|
||||
servers={servers}
|
||||
params={
|
||||
searchParams.get('block') === block.key
|
||||
? Object.fromEntries(searchParams.entries())
|
||||
@@ -33,6 +38,6 @@ export default function OpenAPIContext(props: {
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</OpenAPIContextProvider>
|
||||
</OpenAPIClientState>
|
||||
);
|
||||
}
|
||||
@@ -381,3 +381,7 @@
|
||||
.openapi-select-button {
|
||||
@apply leading-[1cap] disabled:opacity-5;
|
||||
}
|
||||
|
||||
.openapi-pending {
|
||||
@apply opacity-5;
|
||||
}
|
||||
|
||||
+35
-17
@@ -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<string, string> | null;
|
||||
/**
|
||||
* Callback for when the client state is updated
|
||||
*/
|
||||
onUpdate: (params: Record<string, string> | null) => void;
|
||||
};
|
||||
const OpenAPIContext = React.createContext<OpenAPIContextProps | null>(null);
|
||||
|
||||
export function useOpenAPIContext() {
|
||||
return React.useContext(OpenAPIContext);
|
||||
const OpenAPIClientStateContext = React.createContext<OpenAPIClientStateContextProps | null>(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<string, string>;
|
||||
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 (
|
||||
<OpenAPIContext.Provider
|
||||
<OpenAPIClientStateContext.Provider
|
||||
value={{
|
||||
isPending,
|
||||
state: clientState,
|
||||
@@ -43,20 +61,20 @@ export function OpenAPIContextProvider(props: {
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</OpenAPIContext.Provider>
|
||||
</OpenAPIClientStateContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function parseClientStateModifiers(data: OpenAPIOperationData, params: Record<string, string>) {
|
||||
if (!data) {
|
||||
function parseClientStateModifiers(servers: OpenAPIV3.ServerObject[], params: Record<string, string>) {
|
||||
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<Record<string, string>>(
|
||||
(result, key) => {
|
||||
@@ -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 (
|
||||
<ServerURLForm context={context} servers={servers} serverIndex={serverIndex}>
|
||||
{parts.map((part, i) => {
|
||||
if (part.kind === 'text') {
|
||||
return <span key={i}>{part.text}</span>;
|
||||
} else {
|
||||
if (!server.variables?.[part.name]) {
|
||||
return <span key={i}>{`{${part.name}}`}</span>;
|
||||
}
|
||||
<span className={classNames(stateContext?.isPending && "openapi-pending")}>
|
||||
{parts.map((part, i) => {
|
||||
if (part.kind === 'text') {
|
||||
return <span key={i}>{part.text}</span>;
|
||||
} else {
|
||||
if (!server.variables?.[part.name]) {
|
||||
return <span key={i}>{`{${part.name}}`}</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<OpenAPIServerURLVariable
|
||||
key={i}
|
||||
name={part.name}
|
||||
variable={server.variables[part.name]}
|
||||
selectionIndex={Number(ctx?.state?.[part.name])}
|
||||
selectable={Boolean(ctx?.state?.edit)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
{path}
|
||||
return (
|
||||
<OpenAPIServerURLVariable
|
||||
key={i}
|
||||
name={part.name}
|
||||
variable={server.variables[part.name]}
|
||||
selectionIndex={Number(stateContext?.state?.[part.name])}
|
||||
selectable={Boolean(stateContext?.state?.edit)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
{path}
|
||||
</span>
|
||||
</ServerURLForm>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<HTMLFormElement>(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 (
|
||||
<form
|
||||
ref={formRef}
|
||||
@@ -65,9 +68,9 @@ export function ServerURLForm(props: {
|
||||
<fieldset disabled={stateContext?.isPending} className="contents">
|
||||
<input type="hidden" name="block" value={context.blockKey} />
|
||||
{children}
|
||||
{stateContext?.state?.edit && servers.length > 1 ? (
|
||||
{isEditing && servers.length > 1 ? (
|
||||
<ServerSelector
|
||||
servers={servers}
|
||||
lastIndex={servers.length - 1}
|
||||
currentIndex={serverIndex}
|
||||
onChange={switchServer}
|
||||
/>
|
||||
@@ -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}
|
||||
</button>
|
||||
) : null}
|
||||
</fieldset>
|
||||
|
||||
@@ -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<null | {
|
||||
operationData: OpenAPIOperationData | null;
|
||||
@@ -129,12 +128,12 @@ export function ScalarApiClient(props: { children: React.ReactNode }) {
|
||||
headers: request.headers.map((header: Header) => {
|
||||
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 (
|
||||
<ScalarContext.Provider value={open}>
|
||||
|
||||
@@ -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: {
|
||||
</button>
|
||||
<button
|
||||
className="openapi-select-button"
|
||||
disabled={index >= servers.length - 1}
|
||||
disabled={index >= lastIndex}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
'use client';
|
||||
export { OpenAPIContextProvider } from './OpenAPIContextProvider';
|
||||
export { OpenAPIClientState } from './OpenAPIClientStateContext';
|
||||
|
||||
Reference in New Issue
Block a user