Update server choice based on selection

This commit is contained in:
Brett Jephson
2024-09-12 16:46:18 +01:00
parent b87ec84fd5
commit 4f59d18bfc
9 changed files with 81 additions and 49 deletions
@@ -49,6 +49,7 @@ async function OpenAPIBody(props: BlockProps<DocumentBlockSwagger>) {
context.searchParams && context.searchParams.block === block.key
? parseModifiers(data, context.searchParams)
: undefined;
return (
<OpenAPIOperation
data={data}
@@ -101,14 +102,14 @@ function parseModifiers(data: OpenAPIOperationData, params: Record<string, strin
if (!data) {
return;
}
const { servers } = params;
const { server: serverQueryParam } = params;
const serverIndex =
servers && !isNaN(Number(servers))
? Math.min(0, Math.max(Number(servers), servers.length - 1))
serverQueryParam && !isNaN(Number(serverQueryParam))
? Math.max(0, Math.min(Number(serverQueryParam), data.servers.length - 1))
: 0;
const server = data.servers[serverIndex];
if (server && server.variables) {
return Object.keys(server.variables).reduce<Record<string, number>>(
if (server) {
return Object.keys(server.variables ?? {}).reduce<Record<string, number>>(
(result, key) => {
const selection = Number(params[key]);
if (!isNaN(selection)) {
@@ -116,7 +117,7 @@ function parseModifiers(data: OpenAPIOperationData, params: Record<string, strin
}
return result;
},
{ servers: serverIndex },
{ server: serverIndex },
);
}
}
@@ -364,3 +364,7 @@
.openapi-markdown > *:last-child {
@apply mb-0;
}
.openapi-server-button {
@apply disabled:opacity-5;
}
@@ -51,7 +51,6 @@ export function OpenAPICodeSample(props: {
const requestBody = noReference(data.operation.requestBody);
const requestBodyContent = requestBody ? Object.entries(requestBody.content)[0] : undefined;
const input: CodeSampleInput = {
url:
getServersURL(data.servers, context.enumSelectors) +
@@ -28,9 +28,8 @@ export async function OpenAPIOperation(props: {
enumSelectors: context.enumSelectors,
};
const config = await getConfiguration(context);
return (
<ScalarApiClient>
<ScalarApiClient serverUrl={getServersURL(data.servers, context.enumSelectors)}>
<div className={classNames('openapi-operation', className)}>
<div className="openapi-intro">
<h2 className="openapi-summary" id={context.id}>
@@ -49,8 +48,7 @@ export async function OpenAPIOperation(props: {
{method.toUpperCase()}
</span>
<span className="openapi-url">
<OpenAPIServerURL servers={servers} context={clientContext} />
{path}
<OpenAPIServerURL servers={servers} context={clientContext} path={path} />
</span>
</div>
</div>
@@ -69,17 +67,3 @@ export async function OpenAPIOperation(props: {
</ScalarApiClient>
);
}
async function getConfiguration(context: OpenAPIContextProps) {
const response = await fetch(context.specUrl);
const doc = await response.json();
return {
spec: {
content: {
...doc,
servers: [{ url: getServersURL(doc.servers, context.enumSelectors) }],
},
},
};
}
@@ -3,6 +3,7 @@ import { OpenAPIV3 } from 'openapi-types';
import { OpenAPIServerURLVariable } from './OpenAPIServerURLVariable';
import { OpenAPIClientContext } from './types';
import { ServerURLForm } from './OpenAPIServerURLForm';
import { ServerSelector } from './ServerSelector';
/**
* Show the url of the server with variables replaced by their default values.
@@ -10,16 +11,17 @@ import { ServerURLForm } from './OpenAPIServerURLForm';
export function OpenAPIServerURL(props: {
servers: OpenAPIV3.ServerObject[];
context: OpenAPIClientContext;
path?: string;
}) {
const { servers, context } = props;
const serverIndex = context.enumSelectors?.servers ?? 0;
const { path, servers, context } = props;
const serverIndex = context.enumSelectors?.server ?? 0;
const server = servers[serverIndex];
const parts = parseServerURL(server?.url ?? '');
return (
<ServerURLForm context={context} server={server}>
<ServerURLForm context={context} servers={servers} serverIndex={serverIndex}>
{parts.map((part, i) => {
if (part.kind === 'text') {
if (part.kind === 'text') {
return <span key={i}>{part.text}</span>;
} else {
if (!server.variables?.[part.name]) {
@@ -35,7 +37,7 @@ export function OpenAPIServerURL(props: {
/>
);
}
})}
})}{path}
</ServerURLForm>
);
}
@@ -47,7 +49,8 @@ export function getServersURL(
servers: OpenAPIV3.ServerObject[],
selectors?: Record<string, number>,
): string {
const server = servers[0];
const serverIndex = selectors && !isNaN(selectors.server) ? Number(selectors.server) : 0;
const server = servers[serverIndex];
const parts = parseServerURL(server?.url ?? '');
return parts
@@ -4,25 +4,34 @@ import * as React from 'react';
import { useRouter } from 'next/navigation';
import { OpenAPIClientContext } from './types';
import { OpenAPIV3 } from 'openapi-types';
import { useApiClientModal } from '@scalar/api-client-react';
import { ServerSelector } from './ServerSelector';
export function ServerURLForm(props: {
children: React.ReactNode;
context: OpenAPIClientContext;
server: OpenAPIV3.ServerObject;
servers: OpenAPIV3.ServerObject[];
serverIndex: number;
}) {
const { children, context, server } = props;
const { children, context, servers, serverIndex } = props;
const router = useRouter();
const client = useApiClientModal();
const [isPending, startTransition] = React.useTransition();
const server = servers[serverIndex];
const formRef = React.useRef<HTMLFormElement>(null);
function updateServerUrl(formData: FormData) {
function switchServer(index: number) {
startTransition(() => {
if (!server.variables) {
return;
if (index !== serverIndex) {
let params = new URLSearchParams(`block=${context.blockKey}&server=${index ?? '0'}`);
router.push(`?${params}`, { scroll: false });
}
let params = new URLSearchParams(`block=${context.blockKey}`);
const variableKeys = Object.keys(server.variables);
});
}
function updateServerVariables(formData: FormData) {
startTransition(() => {
let params = new URLSearchParams(`block=${context.blockKey}&server=${formData.get('server') ?? '0'}`);
const variableKeys = Object.keys(server.variables ?? {});
for (const pair of formData.entries()) {
if (variableKeys.includes(pair[0]) && !isNaN(Number(pair[1]))) {
params.set(pair[0], `${pair[1]}`);
@@ -33,10 +42,11 @@ export function ServerURLForm(props: {
}
return (
<form action={updateServerUrl} className="contents">
<form ref={formRef} action={updateServerVariables} className="contents">
<fieldset disabled={isPending} className="contents">
<input type="hidden" name="block" value={context.blockKey} />
<span>{children}</span>
{children}
{ servers.length > 1 ? <ServerSelector servers={servers} currentIndex={serverIndex} onChange={switchServer} /> : null }
</fieldset>
</form>
);
@@ -55,8 +55,8 @@ export function ScalarApiButton(props: {
/**
* Wrap the rendering with a context to open the scalar modal.
*/
export function ScalarApiClient(props: { children: React.ReactNode }) {
const { children } = props;
export function ScalarApiClient(props: { children: React.ReactNode; serverUrl?: string }) {
const { children, serverUrl } = props;
const [active, setActive] = React.useState<null | {
operationData: OpenAPIOperationData | null;
@@ -125,12 +125,12 @@ export function ScalarApiClient(props: { children: React.ReactNode }) {
headers: request.headers.map((header: Header) => {
return { ...header, enabled: true };
}),
url: operationData.servers[0]?.url,
url: serverUrl ?? operationData.servers[0]?.url,
body: request.postData?.text,
};
return data;
}, [active]);
}, [active, serverUrl]);
return (
<ScalarContext.Provider value={open}>
@@ -0,0 +1,26 @@
'use client';
import * as React from 'react';
export function ServerSelector(props: { currentIndex: number; onChange: (value:number) => void; servers: any[] }) {
const { currentIndex, onChange, servers } = props;
const [index, setIndex] = React.useState(currentIndex);
React.useEffect(() => {
onChange(index);
}, [index]);
return <span className="inline-flex pl-4 gap-2">
<input type="hidden" value={`${index}`} name="server" />
<button className="openapi-server-button" disabled={index === 0} onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setIndex((index) => index - 1);
}} type="button" aria-label="Previous"></button>
<button className="openapi-server-button" disabled={index >= servers.length - 1} onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setIndex((index) => index + 1);
}} type="button" aria-label="Next"></button>
</span>;
}
+8 -3
View File
@@ -15,15 +15,20 @@ export interface OpenAPIClientContext {
* @default false
*/
defaultInteractiveOpened?: boolean;
/**
* The key of the block
*/
blockKey?: string;
/** Optional id attached to the OpenAPI Operation heading and used as an anchor */
/**
* Optional id attached to the OpenAPI Operation heading and used as an anchor
*/
id?: string;
blockKey?: string;
/**
* Selectors to update openapi enums, e.g. for server url variables
*/
enumSelectors?: Record<string, number>;
}