Skip to content
This repository was archived by the owner on May 12, 2025. It is now read-only.

Commit adb372a

Browse files
committed
request body
1 parent ffeb450 commit adb372a

File tree

4 files changed

+337
-34
lines changed

4 files changed

+337
-34
lines changed

openapi-ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"version": "0.1.0",
44
"private": true,
55
"dependencies": {
6+
"@apidevtools/swagger-parser": "^9.0.1",
67
"@docusaurus/core": "^2.0.0-alpha.40",
78
"@docusaurus/theme-classic": "^2.0.0-alpha.37",
89
"@monaco-editor/react": "^3.3.1",

openapi-ui/src/App.js

Lines changed: 151 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import React, { useState } from 'react';
1+
import React, { useState, useEffect, useRef } from 'react';
22

33
import { BrowserRouter as Router, Route } from 'react-router-dom';
44

55
import MagicDropzone from 'react-magic-dropzone';
66
// import Editor from 'react-monaco-editor';
77
import Editor, { monaco } from '@monaco-editor/react';
88

9+
import SwaggerParser from '@apidevtools/swagger-parser';
10+
911
import queryString from 'query-string';
1012

1113
import DocSidebar from './DocSidebar';
@@ -14,6 +16,8 @@ import styles from './App.module.css';
1416

1517
import spec from './spec.json';
1618

19+
import { sampleFromSchema } from './x-utils';
20+
1721
import './default-dark.css';
1822

1923
monaco
@@ -25,10 +29,10 @@ monaco
2529
inherit: false,
2630
rules: [
2731
{ token: '', foreground: '7f7f7f' },
28-
// { token: 'string.key.json', foreground: 'd4d4d4' },
2932
{ token: 'string.key.json', foreground: 'f5f6f7' },
3033
{ token: 'string.value.json', foreground: '85d996' },
3134
{ token: 'number', foreground: 'a4cdfe' },
35+
{ token: 'keyword.json', foreground: 'a4cdfe' },
3236
],
3337
colors: {
3438
// 'editor.background': '#393939',
@@ -127,28 +131,74 @@ function organizeSpec(spec) {
127131
});
128132
}
129133

130-
function Curl({ item, path, query, header, cookie, accept }) {
131-
const multiline = accept !== undefined;
132-
134+
function Curl({
135+
theref,
136+
item,
137+
path,
138+
query,
139+
header,
140+
cookie,
141+
accept,
142+
body,
143+
contentType,
144+
}) {
133145
const qs = queryString.stringify(query);
134146

147+
let bodyString;
148+
try {
149+
bodyString = JSON.stringify(JSON.stringify(JSON.parse(body)));
150+
} catch {
151+
bodyString = '"{}"';
152+
}
153+
135154
return (
136-
<>
137-
<div>
155+
<code ref={theref} className={styles.curlything}>
156+
<span>
138157
curl -X <span>{item.method.toUpperCase()}</span> "
139158
{window.location.origin}
140159
{item.path.replace(/{([a-z0-9-_]+)}/gi, (_, p1) => {
141160
return path[p1] || `:${p1}`;
142161
})}
143162
{qs && '?'}
144-
{qs}" {multiline && '\\'}
145-
</div>
163+
{qs}"
164+
</span>
165+
146166
{accept && (
147-
<div>
148-
{' '}-H <span style={{ color: '#85d996' }}>"accept: {accept}"</span>
149-
</div>
167+
<>
168+
{' \\'}
169+
<br />
170+
<span>
171+
{' '}
172+
-H <span style={{ color: '#85d996' }}>"Accept: {accept}"</span>
173+
</span>
174+
</>
150175
)}
151-
</>
176+
177+
{contentType && (
178+
<>
179+
{' \\'}
180+
<br />
181+
<span>
182+
{' '}
183+
-H{' '}
184+
<span style={{ color: '#85d996' }}>
185+
"Content-Type: {contentType}"
186+
</span>
187+
</span>
188+
</>
189+
)}
190+
191+
{body && (
192+
<>
193+
{' \\'}
194+
<br />
195+
<span>
196+
{' '}
197+
-d <span style={{ color: '#85d996' }}>{bodyString}</span>
198+
</span>
199+
</>
200+
)}
201+
</code>
152202
);
153203
}
154204

@@ -249,6 +299,9 @@ function TryItOut({ item }) {
249299
];
250300

251301
const [accept, setAccept] = useState(acceptArray[0]);
302+
const [contentType, setContentType] = useState(
303+
Object.keys(item.requestBody?.content || {})[0]
304+
);
252305
const [path, setPath] = useState({});
253306
const [query, setQuery] = useState({});
254307
const [header, setHeader] = useState({});
@@ -259,6 +312,11 @@ function TryItOut({ item }) {
259312

260313
const [copyText, setCopyText] = useState('Copy');
261314

315+
const [body, setBody] = useState(undefined);
316+
317+
const curlRef = useRef(null);
318+
// console.log(curlRef);
319+
262320
const requiredParams = item?.parameters?.filter((param) => param.required);
263321

264322
let finishedRequest = true;
@@ -310,20 +368,16 @@ function TryItOut({ item }) {
310368
setAccept(e.target.value);
311369
};
312370

313-
const handleCurlCopy = (item, path, query, header, cookie, accept) => (e) => {
371+
const handleContentTypeChange = (e) => {
372+
setContentType(e.target.value);
373+
};
374+
375+
const handleCurlCopy = () => {
314376
setCopyText('Copied');
315377
setTimeout(() => {
316378
setCopyText('Copy');
317379
}, 2000);
318-
const qs = queryString.stringify(query);
319-
320-
const x = `curl -X ${item.method.toUpperCase()} "${
321-
window.location.origin
322-
}${item.path.replace(/{([a-z0-9-_]+)}/gi, (_, p1) => {
323-
return path[p1] || `:${p1}`;
324-
})}${qs && '?'}${qs}"${accept && ` -H "accept: ${accept}"`}`;
325-
326-
navigator.clipboard.writeText(x);
380+
navigator.clipboard.writeText(curlRef.current.innerText);
327381
};
328382

329383
async function buildAndExecute(item, path, query, header, cookie, accept) {
@@ -395,8 +449,27 @@ function TryItOut({ item }) {
395449

396450
{/* TODO: Optional params */}
397451

452+
{/* TODO: Content-Type dropdown */}
453+
{item.requestBody?.content &&
454+
Object.keys(item.requestBody?.content).length > 0 && (
455+
<div className={styles.formItem}>
456+
<code>Content-Type</code>
457+
<div>
458+
<select
459+
className={styles.selectInput}
460+
value={contentType}
461+
onChange={handleContentTypeChange}
462+
>
463+
{Object.keys(item.requestBody?.content).map((type) => {
464+
return <option value={type}>{type}</option>;
465+
})}
466+
</select>
467+
</div>
468+
</div>
469+
)}
470+
398471
<>
399-
{item.requestBody?.content && (
472+
{item.requestBody?.content?.['application/json'] && (
400473
<div className={styles.formItem}>
401474
<code>Body</code>
402475
<div
@@ -408,10 +481,16 @@ function TryItOut({ item }) {
408481
>
409482
{/* monaco */}
410483
<Editor
411-
value={JSON.stringify({ example: 'thing' }, null, 2)}
484+
value={JSON.stringify(
485+
sampleFromSchema(
486+
item.requestBody?.content?.['application/json']?.schema
487+
),
488+
null,
489+
2
490+
)}
412491
language="json"
413492
theme="myCustomTheme"
414-
height="200px"
493+
// height="200px"
415494
options={{
416495
contentLeft: 0,
417496
lineNumbers: 'off',
@@ -427,16 +506,47 @@ function TryItOut({ item }) {
427506
lineDecorationsWidth: 0,
428507
contextmenu: false,
429508
}}
430-
editorDidMount={(_, editor) => {
509+
editorDidMount={(_valueGetter, editor) => {
431510
editor.onDidFocusEditorText(() => {
432511
setEditorFocused(true);
433512
});
434513
editor.onDidBlurEditorText(() => {
435514
setEditorFocused(false);
436515
});
516+
editor.onDidChangeModelDecorations(() => {
517+
updateEditorHeight(); // typing
518+
requestAnimationFrame(updateEditorHeight); // folding
519+
setBody(editor.getValue());
520+
});
521+
522+
let prevHeight = 0;
523+
524+
const updateEditorHeight = () => {
525+
const editorElement = editor.getDomNode();
526+
527+
if (!editorElement) {
528+
return;
529+
}
530+
531+
const lineHeight = 22;
532+
const lineCount =
533+
editor.getModel()?.getLineCount() || 1;
534+
const height =
535+
editor.getTopForLineNumber(lineCount + 1) +
536+
lineHeight;
537+
538+
const clippedHeight = Math.min(height, 500);
539+
540+
if (prevHeight !== clippedHeight) {
541+
prevHeight = clippedHeight;
542+
editorElement.style.height = `${clippedHeight}px`;
543+
editor.layout();
544+
}
545+
};
437546
}}
438547
/>
439548

549+
{/* schema: string + binary */}
440550
{/* <MagicDropzone className={styles.dropzone} onDrop={() => {}}>
441551
<div className={styles.dropzoneContent}>
442552
{item.requestBody.description || 'file upload'}
@@ -466,23 +576,23 @@ function TryItOut({ item }) {
466576
</div>
467577
)}
468578
<div className={styles.floatingButton}>
469-
<button
470-
onClick={handleCurlCopy(item, path, query, header, cookie, accept)}
471-
>
472-
{copyText}
473-
</button>
579+
<button onClick={handleCurlCopy}>{copyText}</button>
474580
<pre
475581
style={{
476582
background: '#242526',
583+
paddingRight: '60px',
477584
}}
478585
>
479586
<Curl
587+
theref={curlRef}
480588
item={item}
481589
path={path}
482590
query={query}
483591
header={header}
484592
cookie={cookie}
485593
accept={accept}
594+
contentType={contentType}
595+
body={body}
486596
/>
487597
</pre>
488598
</div>
@@ -515,7 +625,15 @@ function TryItOut({ item }) {
515625
}
516626

517627
function App() {
518-
const order = organizeSpec(spec);
628+
const [order, setOrder] = useState(organizeSpec(spec));
629+
630+
useEffect(() => {
631+
SwaggerParser.dereference(spec).then((api) => {
632+
console.log(api);
633+
// TODO: This ruins our variable names...
634+
setOrder(organizeSpec(api));
635+
});
636+
}, []);
519637

520638
const docsSidebars = {
521639
default: order.map((x) => {

0 commit comments

Comments
 (0)