Skip to content

Commit

Permalink
Update Scalar Api Client to new version (#2406)
Browse files Browse the repository at this point in the history
Co-authored-by: marclave <[email protected]>
Co-authored-by: Samy Pessé <[email protected]>
  • Loading branch information
3 people authored Jul 25, 2024
1 parent 24b785c commit 709f1a1
Show file tree
Hide file tree
Showing 10 changed files with 59 additions and 179 deletions.
5 changes: 5 additions & 0 deletions .changeset/hip-moons-collect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@gitbook/react-openapi': minor
---

Update Scalar to the latest version, with faster performances and an improved experience
Binary file modified bun.lockb
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,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 (
Expand All @@ -46,7 +46,7 @@ async function OpenAPIBody(props: BlockProps<DocumentBlockSwagger>) {
);
}

if (!data) {
if (!data || !specUrl) {
return null;
}

Expand All @@ -60,6 +60,7 @@ async function OpenAPIBody(props: BlockProps<DocumentBlockSwagger>) {
},
CodeBlock: PlainCodeBlock,
defaultInteractiveOpened: context.mode === 'print',
specUrl,
}}
className="openapi-block"
/>
Expand Down
52 changes: 29 additions & 23 deletions packages/gitbook/src/components/DocumentView/OpenAPI/scalar.css
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
8 changes: 4 additions & 4 deletions packages/gitbook/src/lib/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -37,7 +37,7 @@ export async function fetchOpenAPIBlock(
fetcher,
);

return { data };
return { data, specUrl: resolved.href };
} catch (error) {
if (error instanceof OpenAPIFetchError) {
return { error };
Expand Down
3 changes: 1 addition & 2 deletions packages/react-openapi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
},
"version": "0.5.0",
"dependencies": {
"@scalar/api-client-react": "^0.3.7",
"@scalar/oas-utils": "0.1.6",
"@scalar/api-client-react": "1.0.5",
"classnames": "^2.5.1",
"flatted": "^3.2.9",
"openapi-types": "^12.1.3",
Expand Down
9 changes: 2 additions & 7 deletions packages/react-openapi/src/OpenAPICodeSample.tsx
Original file line number Diff line number Diff line change
@@ -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 } from './generateSchemaExample';
import { InteractiveSection } from './InteractiveSection';
import { getServersURL } from './OpenAPIServerURL';
Expand Down Expand Up @@ -66,19 +66,14 @@ export function OpenAPICodeSample(props: {
return null;
}

async function fetchOperationData() {
'use server';
return toJSON(data);
}

return (
<InteractiveSection
header="Request"
className="openapi-codesample"
tabs={samples}
overlay={
data['x-hideTryItPanel'] || data.operation['x-hideTryItPanel'] ? null : (
<ScalarApiButton fetchOperationData={fetchOperationData} />
<ScalarApiButton />
)
}
/>
Expand Down
10 changes: 7 additions & 3 deletions packages/react-openapi/src/OpenAPIOperation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { OpenAPICodeSample } from './OpenAPICodeSample';
import { OpenAPIResponseExample } from './OpenAPIResponseExample';
import { OpenAPIServerURL } from './OpenAPIServerURL';
import { OpenAPISpec } from './OpenAPISpec';
import { ScalarApiClient } from './ScalarApiButton';
import { OpenAPIClientContext, OpenAPIContextProps } from './types';
import { ApiClientModalProvider } from '@scalar/api-client-react';

/**
* Display an interactive OpenAPI operation.
Expand All @@ -23,11 +23,15 @@ export function OpenAPIOperation(props: {

const clientContext: OpenAPIClientContext = {
defaultInteractiveOpened: context.defaultInteractiveOpened,
specUrl: context.specUrl,
icons: context.icons,
};

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">{operation.summary}</h2>
Expand Down Expand Up @@ -61,6 +65,6 @@ export function OpenAPIOperation(props: {
</div>
</div>
</div>
</ScalarApiClient>
</ApiClientModalProvider>
);
}
143 changes: 5 additions & 138 deletions packages/react-openapi/src/ScalarApiButton.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,17 @@
'use client';
import {
Cookie,
getHarRequest,
getParametersFromOperation,
type TransformedOperation,
getRequestFromOperation,
Query,
Header,
RequestBody,
} from '@scalar/oas-utils';
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
>(() => {});
import { useApiClientModal } from '@scalar/api-client-react';
import React from 'react';

/**
* 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"
Expand All @@ -51,109 +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 },
),
);

return {
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,
};
}, [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>;
}
Loading

0 comments on commit 709f1a1

Please sign in to comment.