Upgrade to the new scalar client (#2539)

Co-authored-by: Samy Pessé <samypesse@gmail.com>
This commit is contained in:
Amrit Kahlon
2024-10-24 05:40:33 -07:00
committed by GitHub
parent 630c3e84dd
commit f89b31c940
11 changed files with 60 additions and 182 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@gitbook/react-openapi': patch
'gitbook': patch
---
Upgrade the scalar api client package
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -69,7 +69,7 @@
"devDependencies": {
"@argos-ci/playwright": "^2.0.0",
"@playwright/test": "^1.42.1",
"@cloudflare/next-on-pages": "^1.13.3",
"@cloudflare/next-on-pages": "^1.13.5",
"@cloudflare/workers-types": "^4.20240725.0",
"@types/js-cookie": "^3.0.6",
"@types/jsontoxml": "^1.0.5",
@@ -29,7 +29,7 @@ export async function OpenAPI(props: BlockProps<DocumentBlockSwagger>) {
async function OpenAPIBody(props: BlockProps<DocumentBlockSwagger>) {
const { block, context } = props;
const { data, error } = await fetchOpenAPIBlock(block, context.resolveContentRef);
const { data, specUrl, error } = await fetchOpenAPIBlock(block, context.resolveContentRef);
if (error) {
return (
@@ -41,7 +41,7 @@ async function OpenAPIBody(props: BlockProps<DocumentBlockSwagger>) {
);
}
if (!data) {
if (!data || !specUrl) {
return null;
}
@@ -49,6 +49,7 @@ async function OpenAPIBody(props: BlockProps<DocumentBlockSwagger>) {
<OpenAPIOperation
data={data}
context={{
specUrl,
icons: {
chevronDown: <Icon icon="chevron-down" />,
chevronRight: <Icon icon="chevron-right" />,
@@ -1,4 +1,7 @@
@import '@scalar/api-client-react/style.css';
.light .scalar-modal-layout,
.light .scalar-app,
.light .scalar {
--scalar-color-1: color-mix(
in srgb,
@@ -50,8 +53,13 @@
--scalar-button-1: rgb(49 53 56);
--scalar-button-1-color: #fff;
--scalar-button-1-hover: rgb(28 31 33);
--scalar-shadow-1: 0 1px 3px 0 rgba(0, 0, 0, 0.11);
--scalar-shadow-2: rgba(0, 0, 0, 0.08) 0px 13px 20px 0px, rgba(0, 0, 0, 0.08) 0px 3px 8px 0px,
#eeeeed 0px 0 0 1px;
}
.dark .scalar-modal-layout,
.dark .scalar-app,
.dark .scalar {
--scalar-color-1: color-mix(
in srgb,
@@ -102,8 +110,13 @@
--scalar-button-1: #f6f6f6;
--scalar-button-1-color: #000;
--scalar-button-1-hover: #e7e7e7;
--scalar-shadow-1: 0 1px 3px 0 rgb(0, 0, 0, 0.1);
--scalar-shadow-2: rgba(15, 15, 15, 0.2) 0px 3px 6px, rgba(15, 15, 15, 0.4) 0px 9px 24px,
0 0 0 1px rgba(255, 255, 255, 0.1);
}
.scalar-modal-layout,
.scalar-app,
.scalar {
--scalar-font: initial;
--scalar-font-code: var(--font-mono);
@@ -165,7 +178,7 @@
.scalar-api-client__close:hover {
cursor: pointer;
}
.scalar .scalar-app {
.scalar .scalar-app-layout {
background: var(--scalar-background-3);
height: calc(100dvh - 100px);
max-width: 1280px;
@@ -202,23 +215,6 @@
cursor: pointer;
animation: scalardrawerexitfadein 0.35s forwards;
}
.scalar .scalar-app-exit:before {
content: '\00d7';
font-family: sans-serif;
position: absolute;
top: 0;
right: 0;
font-size: 30px;
font-weight: 100;
line-height: 50px;
right: 12px;
text-align: center;
color: white;
opacity: 0.6;
}
.scalar .scalar-app-exit:hover:before {
opacity: 1;
}
@keyframes scalardrawerexitfadein {
from {
opacity: 0;
@@ -305,11 +301,6 @@
scrollbar-width: thin;
-webkit-overflow-scrolling: touch;
}
@supports (-moz-appearance: none) {
.scalar .custom-scroll {
padding-right: 12px;
}
}
.scalar .custom-scroll:hover {
scrollbar-color: rgba(0, 0, 0, 0.24) transparent;
}
@@ -347,3 +338,18 @@
padding-right: 12px;
}
}
.dark .scalar .client-wrapper-bg-color {
background: linear-gradient(
color-mix(in srgb, var(--tw-bg-base) 6%, transparent) 1%,
color-mix(in srgb, var(--scalar-background-1) 30%, black) 9%
);
}
.light .scalar .client-wrapper-bg-color {
background-color: var(--scalar-background-2) !important;
}
.scalar .gitbook-show {
display: block !important;
}
.scalar .gitbook-hidden {
display: none !important;
}
+4 -4
View File
@@ -19,12 +19,12 @@ export async function fetchOpenAPIBlock(
block: DocumentBlockSwagger,
resolveContentRef: (ref: ContentRef) => Promise<ResolvedContentRef | null>,
): Promise<
| { data: OpenAPIOperationData | null; error?: undefined }
| { error: OpenAPIFetchError; data?: undefined }
| { data: OpenAPIOperationData | null; specUrl: string | null; error?: undefined }
| { error: OpenAPIFetchError; data?: undefined; specUrl?: undefined }
> {
const resolved = block.data.ref ? await resolveContentRef(block.data.ref) : null;
if (!resolved || !block.data.path || !block.data.method) {
return { data: null };
return { data: null, specUrl: null };
}
try {
@@ -37,7 +37,7 @@ export async function fetchOpenAPIBlock(
fetcher,
);
return { data };
return { data, specUrl: resolved.href };
} catch (error) {
if (error instanceof OpenAPIFetchError) {
return { error };
+1 -2
View File
@@ -10,8 +10,7 @@
},
"version": "0.6.0",
"dependencies": {
"@scalar/api-client-react": "0.3.7",
"@scalar/oas-utils": "0.1.6",
"@scalar/api-client-react": "1.0.65",
"classnames": "^2.5.1",
"flatted": "^3.2.9",
"openapi-types": "^12.1.3",
@@ -1,7 +1,7 @@
import * as React from 'react';
import { CodeSampleInput, codeSampleGenerators } from './code-samples';
import { OpenAPIOperationData, toJSON } from './fetchOpenAPIOperation';
import { OpenAPIOperationData } from './fetchOpenAPIOperation';
import { generateMediaTypeExample, generateSchemaExample } from './generateSchemaExample';
import { InteractiveSection } from './InteractiveSection';
import { getServersURL } from './OpenAPIServerURL';
@@ -112,11 +112,6 @@ export function OpenAPICodeSample(props: {
return null;
}
async function fetchOperationData() {
'use server';
return toJSON(data);
}
return (
<InteractiveSection
header="Request"
@@ -124,7 +119,7 @@ export function OpenAPICodeSample(props: {
tabs={samples}
overlay={
data['x-hideTryItPanel'] || data.operation['x-hideTryItPanel'] ? null : (
<ScalarApiButton fetchOperationData={fetchOperationData} />
<ScalarApiButton />
)
}
/>
@@ -1,5 +1,6 @@
import * as React from 'react';
import classNames from 'classnames';
import { ApiClientModalProvider } from '@scalar/api-client-react';
import { OpenAPIOperationData, toJSON } from './fetchOpenAPIOperation';
import { Markdown } from './Markdown';
@@ -8,7 +9,6 @@ import { OpenAPIResponseExample } from './OpenAPIResponseExample';
import { OpenAPIServerURL } from './OpenAPIServerURL';
import { OpenAPISpec } from './OpenAPISpec';
import { OpenAPIClientContext, OpenAPIContextProps } from './types';
import { ScalarApiClient } from './ScalarApiButton';
/**
* Display an interactive OpenAPI operation.
@@ -28,7 +28,10 @@ export function OpenAPIOperation(props: {
};
return (
<ScalarApiClient>
<ApiClientModalProvider
configuration={{ spec: { url: context.specUrl } }}
initialRequest={{ path: data.path, method: data.method }}
>
<div className={classNames('openapi-operation', className)}>
<div className="openapi-intro">
<h2 className="openapi-summary" id={context.id}>
@@ -64,6 +67,6 @@ export function OpenAPIOperation(props: {
</div>
</div>
</div>
</ScalarApiClient>
</ApiClientModalProvider>
);
}
+5 -140
View File
@@ -1,44 +1,17 @@
'use client';
import {
Cookie,
getHarRequest,
getParametersFromOperation,
type TransformedOperation,
getRequestFromOperation,
Query,
Header,
RequestBody,
} from '@scalar/oas-utils';
import { useApiClientModal } from '@scalar/api-client-react';
import React from 'react';
import { OpenAPIOperationData, fromJSON } from './fetchOpenAPIOperation';
const ApiClientReact = React.lazy(async () => {
const mod = await import('@scalar/api-client-react');
return { default: mod.ApiClientReact };
});
const ScalarContext = React.createContext<
(fetchOperationData: () => Promise<OpenAPIOperationData>) => void
>(() => {});
/**
* Button which launches the Scalar API Client
*/
export function ScalarApiButton(props: {
fetchOperationData: () => Promise<OpenAPIOperationData>;
}) {
const { fetchOperationData } = props;
const open = React.useContext(ScalarContext);
export function ScalarApiButton() {
const client = useApiClientModal();
return (
<div className="scalar scalar-activate">
<button
className="scalar-activate-button"
onClick={() => {
open(fetchOperationData);
}}
>
<button className="scalar-activate-button" onClick={() => client?.open()}>
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="12" fill="none">
<path
stroke="currentColor"
@@ -51,111 +24,3 @@ export function ScalarApiButton(props: {
</div>
);
}
/**
* Wrap the rendering with a context to open the scalar modal.
*/
export function ScalarApiClient(props: { children: React.ReactNode }) {
const { children } = props;
const [active, setActive] = React.useState<null | {
operationData: OpenAPIOperationData | null;
}>(null);
const proxy = '/~scalar/proxy';
const open = React.useCallback(
async (fetchOperationData: () => Promise<OpenAPIOperationData>) => {
setActive({ operationData: null });
const operationData = fromJSON(await fetchOperationData());
setActive({ operationData });
},
[],
);
const onClose = React.useCallback(() => {
setActive(null);
}, []);
const request = React.useMemo(() => {
const operationData = active?.operationData;
if (!operationData) {
return null;
}
const operationId =
operationData.operation.operationId ?? operationData.method + operationData.path;
const operation = {
...operationData,
httpVerb: operationData.method,
pathParameters: operationData.operation.parameters,
} as TransformedOperation;
const variables = getParametersFromOperation(operation, 'path', false);
const request = getHarRequest(
{
url: operationData.path,
},
getRequestFromOperation(
{
...operation,
information: {
requestBody: operationData.operation.requestBody as RequestBody,
},
},
{ requiredOnly: false },
),
);
const data = {
id: operationId,
type: operationData.method,
path: operationData.path,
variables,
cookies: request.cookies.map((cookie: Cookie) => {
return { ...cookie, enabled: true };
}),
query: request.queryString.map((queryString: Query) => {
const query: typeof queryString & { required?: boolean } = queryString;
return { ...queryString, enabled: query.required ?? true };
}),
headers: request.headers.map((header: Header) => {
return { ...header, enabled: true };
}),
url: operationData.servers[0]?.url,
body: request.postData?.text,
};
return data;
}, [active]);
return (
<ScalarContext.Provider value={open}>
{children}
{active ? (
<div className="scalar">
<div className="scalar-container">
<div className="scalar-app">
<React.Suspense fallback={<ScalarLoading />}>
<ApiClientReact
close={onClose}
proxy={proxy}
isOpen={true}
request={request}
/>
</React.Suspense>
</div>
<div onClick={() => onClose()} className="scalar-app-exit"></div>
</div>
</div>
) : null}
</ScalarContext.Provider>
);
}
function ScalarLoading() {
return <div className="scalar-app-loading">Loading...</div>;
}
+3
View File
@@ -2,6 +2,9 @@ export type IconComponent = React.ComponentType<{ className?: string }>;
export interface OpenAPIContextProps extends OpenAPIClientContext {
CodeBlock: React.ComponentType<{ code: string; syntax: string }>;
/** Spec url for the Scalar Api Client */
specUrl: string;
}
export interface OpenAPIClientContext {