Skip to content

Commit 050c29a

Browse files
v50553490-cyberVladislav Abrosimov
andauthored
'multipart/form-data' body: Add posibility to render file array and form object example via LiveEditor (#1266)
Co-authored-by: Vladislav Abrosimov <[email protected]>
1 parent 036819b commit 050c29a

File tree

6 files changed

+301
-115
lines changed

6 files changed

+301
-115
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/* ============================================================================
2+
* Copyright (c) Palo Alto Networks
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
* ========================================================================== */
7+
8+
import React, { useState } from "react";
9+
import FormFileUpload from "@theme/ApiExplorer/FormFileUpload";
10+
import { useTypedDispatch } from "@theme/ApiItem/hooks";
11+
import { FileContent, setFileArrayFormBody } from "../slice";
12+
13+
interface FileArrayFormItemProps {
14+
id: string;
15+
description: string | undefined;
16+
}
17+
18+
export default function FileArrayFormBodyItem({
19+
id,
20+
description,
21+
}: FileArrayFormItemProps): React.JSX.Element {
22+
const dispatch = useTypedDispatch();
23+
const [fileItems, setFileItems] = useState<
24+
Map<number, FileContent["value"] | undefined>
25+
>(new Map([[0, undefined]]));
26+
27+
const handleFileChange = (index: number, file: any) => {
28+
const newItems = new Map(fileItems);
29+
30+
if (file === undefined) {
31+
newItems.delete(index);
32+
33+
setFileItems(newItems);
34+
35+
dispatch(
36+
setFileArrayFormBody({
37+
key: id,
38+
value: [...newItems.values()].filter((item) => item !== undefined),
39+
})
40+
);
41+
return;
42+
}
43+
44+
let maxIndex = 0;
45+
46+
newItems.keys().forEach((item) => {
47+
maxIndex = item > maxIndex ? item : maxIndex;
48+
});
49+
newItems.set(index, {
50+
src: `/path/to/${file.name}`,
51+
content: file,
52+
});
53+
newItems.set(index + 1, undefined);
54+
55+
setFileItems(newItems);
56+
57+
dispatch(
58+
setFileArrayFormBody({
59+
key: id,
60+
value: [...newItems.values()].filter((item) => item !== undefined),
61+
})
62+
);
63+
};
64+
65+
return (
66+
<div>
67+
{[...fileItems.keys()].map((index) => (
68+
<div key={index}>
69+
<FormFileUpload
70+
placeholder={description || id}
71+
onChange={(file: any) => handleFileChange(index, file)}
72+
/>
73+
</div>
74+
))}
75+
</div>
76+
);
77+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/* ============================================================================
2+
* Copyright (c) Palo Alto Networks
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
* ========================================================================== */
7+
8+
import React from "react";
9+
import FormFileUpload from "@theme/ApiExplorer/FormFileUpload";
10+
import FormSelect from "@theme/ApiExplorer/FormSelect";
11+
import FormTextInput from "@theme/ApiExplorer/FormTextInput";
12+
import LiveApp from "@theme/ApiExplorer/LiveEditor";
13+
import { useTypedDispatch } from "@theme/ApiItem/hooks";
14+
import { SchemaObject } from "docusaurus-plugin-openapi-docs/src/openapi/types";
15+
import { clearFormBodyKey, setFileFormBody, setStringFormBody } from "../slice";
16+
import FileArrayFormBodyItem from "../FileArrayFormBodyItem";
17+
18+
interface FormBodyItemProps {
19+
schemaObject: SchemaObject;
20+
id: string;
21+
schema: SchemaObject;
22+
}
23+
24+
export default function FormBodyItem({
25+
schemaObject,
26+
id,
27+
schema,
28+
}: FormBodyItemProps): React.JSX.Element {
29+
const dispatch = useTypedDispatch();
30+
31+
if (
32+
schemaObject.type === "array" &&
33+
schemaObject.items?.format === "binary"
34+
) {
35+
return (
36+
<FileArrayFormBodyItem id={id} description={schemaObject.description} />
37+
);
38+
}
39+
40+
if (schemaObject.format === "binary") {
41+
return (
42+
<FormFileUpload
43+
placeholder={schemaObject.description || id}
44+
onChange={(file: any) => {
45+
if (file === undefined) {
46+
dispatch(clearFormBodyKey(id));
47+
return;
48+
}
49+
dispatch(
50+
setFileFormBody({
51+
key: id,
52+
value: {
53+
src: `/path/to/${file.name}`,
54+
content: file,
55+
},
56+
})
57+
);
58+
}}
59+
/>
60+
);
61+
}
62+
63+
if (
64+
schemaObject.type === "object" &&
65+
(schemaObject.example || schemaObject.examples)
66+
) {
67+
const objectExample = JSON.stringify(
68+
schemaObject.example ?? schemaObject.examples[0],
69+
null,
70+
2
71+
);
72+
73+
return (
74+
<LiveApp
75+
action={(code: string) =>
76+
dispatch(setStringFormBody({ key: id, value: code }))
77+
}
78+
>
79+
{objectExample}
80+
</LiveApp>
81+
);
82+
}
83+
84+
if (
85+
schemaObject.enum &&
86+
schemaObject.enum.every((value) => typeof value === "string")
87+
) {
88+
return (
89+
<FormSelect
90+
options={["---", ...schemaObject.enum]}
91+
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
92+
const val = e.target.value;
93+
if (val === "---") {
94+
dispatch(clearFormBodyKey(id));
95+
} else {
96+
dispatch(
97+
setStringFormBody({
98+
key: id,
99+
value: val,
100+
})
101+
);
102+
}
103+
}}
104+
/>
105+
);
106+
}
107+
// TODO: support all the other types.
108+
return (
109+
<FormTextInput
110+
paramName={id}
111+
isRequired={
112+
Array.isArray(schema.required) && schema.required.includes(id)
113+
}
114+
placeholder={schemaObject.description || id}
115+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
116+
dispatch(setStringFormBody({ key: id, value: e.target.value }));
117+
}}
118+
/>
119+
);
120+
}

packages/docusaurus-theme-openapi-docs/src/theme/ApiExplorer/Body/index.tsx

Lines changed: 39 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ import { translate } from "@docusaurus/Translate";
1212
import json2xml from "@theme/ApiExplorer/Body/json2xml";
1313
import FormFileUpload from "@theme/ApiExplorer/FormFileUpload";
1414
import FormItem from "@theme/ApiExplorer/FormItem";
15-
import FormSelect from "@theme/ApiExplorer/FormSelect";
16-
import FormTextInput from "@theme/ApiExplorer/FormTextInput";
1715
import LiveApp from "@theme/ApiExplorer/LiveEditor";
1816
import { useTypedDispatch, useTypedSelector } from "@theme/ApiItem/hooks";
1917
import Markdown from "@theme/Markdown";
@@ -23,13 +21,8 @@ import { OPENAPI_BODY, OPENAPI_REQUEST } from "@theme/translationIds";
2321
import { RequestBodyObject } from "docusaurus-plugin-openapi-docs/src/openapi/types";
2422
import format from "xml-formatter";
2523

26-
import {
27-
clearFormBodyKey,
28-
clearRawBody,
29-
setFileFormBody,
30-
setFileRawBody,
31-
setStringFormBody,
32-
} from "./slice";
24+
import { clearRawBody, setFileRawBody, setStringRawBody } from "./slice";
25+
import FormBodyItem from "./FormBodyItem";
3326

3427
export interface Props {
3528
jsonRequestBodyExample: string;
@@ -130,96 +123,23 @@ function Body({
130123
) {
131124
return (
132125
<FormItem className="openapi-explorer__form-item-body-container">
133-
<div>
134-
{Object.entries(schema.properties ?? {}).map(([key, val]: any) => {
135-
if (val.format === "binary") {
136-
return (
137-
<FormItem
138-
key={key}
139-
label={key}
140-
required={
141-
Array.isArray(schema.required) &&
142-
schema.required.includes(key)
143-
}
144-
>
145-
<FormFileUpload
146-
placeholder={val.description || key}
147-
onChange={(file: any) => {
148-
if (file === undefined) {
149-
dispatch(clearFormBodyKey(key));
150-
return;
151-
}
152-
dispatch(
153-
setFileFormBody({
154-
key: key,
155-
value: {
156-
src: `/path/to/${file.name}`,
157-
content: file,
158-
},
159-
})
160-
);
161-
}}
162-
/>
163-
</FormItem>
164-
);
165-
}
166-
167-
if (val.enum) {
168-
return (
169-
<FormItem
170-
key={key}
171-
label={key}
172-
required={
173-
Array.isArray(schema.required) &&
174-
schema.required.includes(key)
175-
}
176-
>
177-
<FormSelect
178-
options={["---", ...val.enum]}
179-
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
180-
const val = e.target.value;
181-
if (val === "---") {
182-
dispatch(clearFormBodyKey(key));
183-
} else {
184-
dispatch(
185-
setStringFormBody({
186-
key: key,
187-
value: val,
188-
})
189-
);
190-
}
191-
}}
192-
/>
193-
</FormItem>
194-
);
195-
}
196-
// TODO: support all the other types.
197-
return (
198-
<FormItem
199-
key={key}
200-
label={key}
201-
required={
202-
Array.isArray(schema.required) &&
203-
schema.required.includes(key)
204-
}
205-
>
206-
<FormTextInput
207-
paramName={key}
208-
isRequired={
209-
Array.isArray(schema.required) &&
210-
schema.required.includes(key)
211-
}
212-
placeholder={val.description || key}
213-
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
214-
dispatch(
215-
setStringFormBody({ key: key, value: e.target.value })
216-
);
217-
}}
218-
/>
219-
</FormItem>
220-
);
221-
})}
222-
</div>
126+
{Object.entries(schema.properties ?? {}).map(([key, val]: any) => {
127+
return (
128+
<FormItem
129+
key={key}
130+
label={key}
131+
required={
132+
Array.isArray(schema.required) && schema.required.includes(key)
133+
}
134+
>
135+
<FormBodyItem
136+
schemaObject={val}
137+
id={key}
138+
schema={schema}
139+
></FormBodyItem>
140+
</FormItem>
141+
);
142+
})}
223143
</FormItem>
224144
);
225145
}
@@ -348,7 +268,11 @@ function Body({
348268
value="Example (from schema)"
349269
default
350270
>
351-
<LiveApp action={dispatch} language={language} required={required}>
271+
<LiveApp
272+
action={(code: string) => dispatch(setStringRawBody(code))}
273+
language={language}
274+
required={required}
275+
>
352276
{defaultBody}
353277
</LiveApp>
354278
</TabItem>
@@ -357,7 +281,7 @@ function Body({
357281
{example.summary && <Markdown>{example.summary}</Markdown>}
358282
{exampleBody && (
359283
<LiveApp
360-
action={dispatch}
284+
action={(code: string) => dispatch(setStringRawBody(code))}
361285
language={language}
362286
required={required}
363287
>
@@ -383,7 +307,11 @@ function Body({
383307
value="Example (from schema)"
384308
default
385309
>
386-
<LiveApp action={dispatch} language={language} required={required}>
310+
<LiveApp
311+
action={(code: string) => dispatch(setStringRawBody(code))}
312+
language={language}
313+
required={required}
314+
>
387315
{defaultBody}
388316
</LiveApp>
389317
</TabItem>
@@ -397,7 +325,10 @@ function Body({
397325
>
398326
{example.summary && <Markdown>{example.summary}</Markdown>}
399327
{example.body && (
400-
<LiveApp action={dispatch} language={language}>
328+
<LiveApp
329+
action={(code: string) => dispatch(setStringRawBody(code))}
330+
language={language}
331+
>
401332
{example.body}
402333
</LiveApp>
403334
)}
@@ -411,7 +342,11 @@ function Body({
411342

412343
return (
413344
<FormItem>
414-
<LiveApp action={dispatch} language={language} required={required}>
345+
<LiveApp
346+
action={(code: string) => dispatch(setStringRawBody(code))}
347+
language={language}
348+
required={required}
349+
>
415350
{defaultBody}
416351
</LiveApp>
417352
</FormItem>

0 commit comments

Comments
 (0)