zudoku 0.33.2-local.4 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +121 -0
- package/dist/config/validators/common.d.ts +346 -346
- package/dist/config/validators/validate.d.ts +165 -165
- package/dist/lib/components/AnchorLink.d.ts +2 -2
- package/dist/lib/components/AnchorLink.js +4 -4
- package/dist/lib/components/AnchorLink.js.map +1 -1
- package/dist/lib/components/Heading.d.ts +1 -1
- package/dist/lib/components/context/ZudokuContext.d.ts +1 -1
- package/dist/lib/components/navigation/SidebarItem.js +6 -5
- package/dist/lib/components/navigation/SidebarItem.js.map +1 -1
- package/dist/lib/core/ZudokuContext.d.ts +4 -0
- package/dist/lib/core/ZudokuContext.js.map +1 -1
- package/dist/lib/plugins/openapi/OperationList.js +4 -1
- package/dist/lib/plugins/openapi/OperationList.js.map +1 -1
- package/dist/lib/plugins/openapi/OperationListItem.d.ts +1 -1
- package/dist/lib/plugins/openapi/OperationListItem.js +5 -3
- package/dist/lib/plugins/openapi/OperationListItem.js.map +1 -1
- package/dist/lib/plugins/openapi/graphql/gql.d.ts +1 -1
- package/dist/lib/plugins/openapi/graphql/gql.js +1 -1
- package/dist/lib/plugins/openapi/graphql/gql.js.map +1 -1
- package/dist/lib/plugins/openapi/graphql/graphql.d.ts +1 -0
- package/dist/lib/plugins/openapi/graphql/graphql.js +2 -0
- package/dist/lib/plugins/openapi/graphql/graphql.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/ExamplesDropdown.d.ts +2 -2
- package/dist/lib/plugins/openapi/playground/ExamplesDropdown.js +1 -5
- package/dist/lib/plugins/openapi/playground/ExamplesDropdown.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/Headers.js +1 -1
- package/dist/lib/plugins/openapi/playground/Headers.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/IdentityDialog.d.ts +11 -0
- package/dist/lib/plugins/openapi/playground/IdentityDialog.js +14 -0
- package/dist/lib/plugins/openapi/playground/IdentityDialog.js.map +1 -0
- package/dist/lib/plugins/openapi/playground/IdentitySelector.d.ts +7 -0
- package/dist/lib/plugins/openapi/playground/IdentitySelector.js +10 -0
- package/dist/lib/plugins/openapi/playground/IdentitySelector.js.map +1 -0
- package/dist/lib/plugins/openapi/playground/Playground.d.ts +9 -1
- package/dist/lib/plugins/openapi/playground/Playground.js +75 -24
- package/dist/lib/plugins/openapi/playground/Playground.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/QueryParams.js +1 -1
- package/dist/lib/plugins/openapi/playground/QueryParams.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/RequestLoginDialog.d.ts +7 -0
- package/dist/lib/plugins/openapi/playground/RequestLoginDialog.js +8 -0
- package/dist/lib/plugins/openapi/playground/RequestLoginDialog.js.map +1 -0
- package/dist/lib/plugins/openapi/playground/rememberedIdentity.d.ts +17 -0
- package/dist/lib/plugins/openapi/playground/rememberedIdentity.js +11 -0
- package/dist/lib/plugins/openapi/playground/rememberedIdentity.js.map +1 -0
- package/dist/lib/plugins/openapi/playground/result-panel/ResponseTab.js +19 -13
- package/dist/lib/plugins/openapi/playground/result-panel/ResponseTab.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/result-panel/ResultPanel.d.ts +6 -4
- package/dist/lib/plugins/openapi/playground/result-panel/ResultPanel.js +4 -3
- package/dist/lib/plugins/openapi/playground/result-panel/ResultPanel.js.map +1 -1
- package/dist/lib/ui/Checkbox.d.ts +2 -8
- package/dist/lib/ui/Checkbox.js +1 -13
- package/dist/lib/ui/Checkbox.js.map +1 -1
- package/dist/lib/ui/Command.d.ts +6 -6
- package/dist/lib/ui/Select.js +1 -1
- package/dist/lib/ui/Select.js.map +1 -1
- package/dist/lib/ui/SyntaxHighlight.d.ts +2 -1
- package/dist/lib/ui/SyntaxHighlight.js +19 -15
- package/dist/lib/ui/SyntaxHighlight.js.map +1 -1
- package/dist/lib/util/MdxComponents.d.ts +1 -1
- package/dist/lib/util/MdxComponents.js +2 -2
- package/dist/lib/util/MdxComponents.js.map +1 -1
- package/lib/{AuthenticationPlugin-BCYuduZ9.js → AuthenticationPlugin-4ip08maU.js} +3 -3
- package/lib/{AuthenticationPlugin-BCYuduZ9.js.map → AuthenticationPlugin-4ip08maU.js.map} +1 -1
- package/lib/Callout-B_sEhkYd.js +211 -0
- package/lib/Callout-B_sEhkYd.js.map +1 -0
- package/lib/{Dialog-mi6BrnrM.js → Dialog-sbgekbjb.js} +48 -33
- package/lib/{Dialog-mi6BrnrM.js.map → Dialog-sbgekbjb.js.map} +1 -1
- package/lib/{Markdown-DofXBcqg.js → Markdown-DZXjQjpH.js} +4099 -3848
- package/lib/Markdown-DZXjQjpH.js.map +1 -0
- package/lib/{MdxPage-KJcNWIgt.js → MdxPage-52vRwa_7.js} +13 -13
- package/lib/{MdxPage-KJcNWIgt.js.map → MdxPage-52vRwa_7.js.map} +1 -1
- package/lib/{OasProvider-HcqBeC4H.js → OasProvider-CR2nG1Eg.js} +4 -4
- package/lib/{OasProvider-HcqBeC4H.js.map → OasProvider-CR2nG1Eg.js.map} +1 -1
- package/lib/{OperationList-C3wnbFxp.js → OperationList-DndcCJUG.js} +1097 -1052
- package/lib/{OperationList-C3wnbFxp.js.map → OperationList-DndcCJUG.js.map} +1 -1
- package/lib/{Select-Co6MuS4j.js → Select-FAYHOYTy.js} +35 -35
- package/lib/{Select-Co6MuS4j.js.map → Select-FAYHOYTy.js.map} +1 -1
- package/lib/{SlotletProvider-CYFNHuok.js → SlotletProvider-TydSHROc.js} +4 -4
- package/lib/{SlotletProvider-CYFNHuok.js.map → SlotletProvider-TydSHROc.js.map} +1 -1
- package/lib/{chunk-IR6S3I6Y-CRDBmIgK.js → chunk-HA7DTUK3-ZGg2W6yV.js} +276 -276
- package/lib/chunk-HA7DTUK3-ZGg2W6yV.js.map +1 -0
- package/lib/{hook-LTe5qHSc.js → hook-CfCFKZ-2.js} +10 -7
- package/lib/{hook-LTe5qHSc.js.map → hook-CfCFKZ-2.js.map} +1 -1
- package/lib/index-DK7IuUyR.js +2201 -0
- package/lib/index-DK7IuUyR.js.map +1 -0
- package/lib/index.esm-CltAN0Tf.js +711 -0
- package/lib/index.esm-CltAN0Tf.js.map +1 -0
- package/lib/objectEntries-BS7aAgOm.js +12 -0
- package/lib/objectEntries-BS7aAgOm.js.map +1 -0
- package/lib/ui/Checkbox.js +15 -25
- package/lib/ui/Checkbox.js.map +1 -1
- package/lib/ui/Command.js +1 -1
- package/lib/ui/Select.js +1 -1
- package/lib/ui/Select.js.map +1 -1
- package/lib/ui/SyntaxHighlight.js +483 -502
- package/lib/ui/SyntaxHighlight.js.map +1 -1
- package/lib/{useExposedProps-D76yras4.js → useExposedProps-BslIn-FE.js} +2 -2
- package/lib/{useExposedProps-D76yras4.js.map → useExposedProps-BslIn-FE.js.map} +1 -1
- package/lib/zudoku.auth-auth0.js +1 -1
- package/lib/zudoku.auth-clerk.js +2 -2
- package/lib/zudoku.auth-openid.js +3 -3
- package/lib/zudoku.components.js +1390 -32
- package/lib/zudoku.components.js.map +1 -1
- package/lib/zudoku.hooks.js +1 -1
- package/lib/zudoku.plugin-api-catalog.js +5 -5
- package/lib/zudoku.plugin-api-keys.js +4 -4
- package/lib/zudoku.plugin-custom-pages.js +2 -2
- package/lib/zudoku.plugin-markdown.js +1 -1
- package/lib/zudoku.plugin-openapi.js +3 -3
- package/lib/zudoku.plugin-redirect.js +1 -1
- package/lib/zudoku.plugin-search-pagefind.js +84 -154
- package/lib/zudoku.plugin-search-pagefind.js.map +1 -1
- package/package.json +3 -3
- package/src/lib/components/AnchorLink.tsx +7 -7
- package/src/lib/components/navigation/SidebarItem.tsx +8 -23
- package/src/lib/core/ZudokuContext.ts +4 -0
- package/src/lib/plugins/openapi/OperationList.tsx +73 -33
- package/src/lib/plugins/openapi/OperationListItem.tsx +105 -92
- package/src/lib/plugins/openapi/graphql/gql.ts +3 -3
- package/src/lib/plugins/openapi/graphql/graphql.ts +3 -0
- package/src/lib/plugins/openapi/playground/ExamplesDropdown.tsx +30 -32
- package/src/lib/plugins/openapi/playground/Headers.tsx +0 -1
- package/src/lib/plugins/openapi/playground/IdentityDialog.tsx +74 -0
- package/src/lib/plugins/openapi/playground/IdentitySelector.tsx +54 -0
- package/src/lib/plugins/openapi/playground/Playground.tsx +164 -133
- package/src/lib/plugins/openapi/playground/QueryParams.tsx +0 -1
- package/src/lib/plugins/openapi/playground/RequestLoginDialog.tsx +51 -0
- package/src/lib/plugins/openapi/playground/rememberedIdentity.ts +26 -0
- package/src/lib/plugins/openapi/playground/result-panel/ResponseTab.tsx +24 -4
- package/src/lib/plugins/openapi/playground/result-panel/ResultPanel.tsx +66 -45
- package/src/lib/ui/Checkbox.tsx +8 -24
- package/src/lib/ui/Select.tsx +1 -1
- package/src/lib/ui/SyntaxHighlight.tsx +94 -96
- package/src/lib/util/MdxComponents.tsx +2 -2
- package/lib/Command-CrTA1FX0.js +0 -140
- package/lib/Command-CrTA1FX0.js.map +0 -1
- package/lib/Markdown-DofXBcqg.js.map +0 -1
- package/lib/chunk-IR6S3I6Y-CRDBmIgK.js.map +0 -1
- package/lib/index-CtkRMvMw.js +0 -2052
- package/lib/index-CtkRMvMw.js.map +0 -1
- package/lib/index-vn5bsvmU.js +0 -1399
- package/lib/index-vn5bsvmU.js.map +0 -1
- package/lib/useScrollToAnchor-DKyrbZoy.js +0 -977
- package/lib/useScrollToAnchor-DKyrbZoy.js.map +0 -1
|
@@ -5,9 +5,6 @@ import { FormProvider, useForm } from "react-hook-form";
|
|
|
5
5
|
import { Alert, AlertDescription, AlertTitle } from "zudoku/ui/Alert.js";
|
|
6
6
|
import { PathRenderer } from "../../../components/PathRenderer.js";
|
|
7
7
|
|
|
8
|
-
import { Button } from "zudoku/ui/Button.js";
|
|
9
|
-
import { Label } from "zudoku/ui/Label.js";
|
|
10
|
-
import { RadioGroup, RadioGroupItem } from "zudoku/ui/RadioGroup.js";
|
|
11
8
|
import {
|
|
12
9
|
Select,
|
|
13
10
|
SelectContent,
|
|
@@ -18,16 +15,21 @@ import {
|
|
|
18
15
|
import { Textarea } from "zudoku/ui/Textarea.js";
|
|
19
16
|
import { useSelectedServer } from "../../../authentication/state.js";
|
|
20
17
|
import { useApiIdentities } from "../../../components/context/ZudokuContext.js";
|
|
21
|
-
import { Card } from "../../../ui/Card.js";
|
|
22
18
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../../ui/Tabs.js";
|
|
23
19
|
import { cn } from "../../../util/cn.js";
|
|
20
|
+
import { objectEntries } from "../../../util/objectEntries.js";
|
|
21
|
+
import { useLatest } from "../../../util/useLatest.js";
|
|
24
22
|
import { ColorizedParam } from "../ColorizedParam.js";
|
|
25
|
-
import { Content } from "../SidecarExamples.js";
|
|
23
|
+
import { type Content } from "../SidecarExamples.js";
|
|
26
24
|
import { createUrl } from "./createUrl.js";
|
|
27
25
|
import ExamplesDropdown from "./ExamplesDropdown.js";
|
|
28
26
|
import { Headers } from "./Headers.js";
|
|
27
|
+
import { IdentityDialog } from "./IdentityDialog.js";
|
|
28
|
+
import IdentitySelector from "./IdentitySelector.js";
|
|
29
29
|
import { PathParams } from "./PathParams.js";
|
|
30
30
|
import { QueryParams } from "./QueryParams.js";
|
|
31
|
+
import { useIdentityStore } from "./rememberedIdentity.js";
|
|
32
|
+
import RequestLoginDialog from "./RequestLoginDialog.js";
|
|
31
33
|
import { ResultPanel } from "./result-panel/ResultPanel.js";
|
|
32
34
|
import SubmitButton from "./SubmitButton.js";
|
|
33
35
|
|
|
@@ -56,8 +58,17 @@ export type PathParam = {
|
|
|
56
58
|
isRequired?: boolean;
|
|
57
59
|
};
|
|
58
60
|
|
|
61
|
+
const bodyContentTypeMap = {
|
|
62
|
+
Plain: "text/plain",
|
|
63
|
+
JSON: "application/json",
|
|
64
|
+
XML: "application/xml",
|
|
65
|
+
YAML: "application/yaml",
|
|
66
|
+
CSV: "text/csv",
|
|
67
|
+
} as const;
|
|
68
|
+
|
|
59
69
|
export type PlaygroundForm = {
|
|
60
70
|
body: string;
|
|
71
|
+
bodyContentType: keyof typeof bodyContentTypeMap;
|
|
61
72
|
queryParams: Array<{
|
|
62
73
|
name: string;
|
|
63
74
|
value: string;
|
|
@@ -120,12 +131,20 @@ export const Playground = ({
|
|
|
120
131
|
const { selectedServer, setSelectedServer } = useSelectedServer(
|
|
121
132
|
servers.map((url) => ({ url })),
|
|
122
133
|
);
|
|
134
|
+
const [showSelectIdentity, setShowSelectIdentity] = useState(false);
|
|
135
|
+
const identities = useApiIdentities();
|
|
136
|
+
const { setRememberedIdentity, getRememberedIdentity } = useIdentityStore();
|
|
123
137
|
const [, startTransition] = useTransition();
|
|
124
138
|
const [skipLogin, setSkipLogin] = useState(false);
|
|
139
|
+
const [showLongRunningWarning, setShowLongRunningWarning] = useState(false);
|
|
140
|
+
const abortControllerRef = useRef<AbortController | undefined>(undefined);
|
|
141
|
+
const latestSetRememberedIdentity = useLatest(setRememberedIdentity);
|
|
142
|
+
|
|
125
143
|
const { register, control, handleSubmit, watch, setValue, ...form } =
|
|
126
144
|
useForm<PlaygroundForm>({
|
|
127
145
|
defaultValues: {
|
|
128
146
|
body: defaultBody,
|
|
147
|
+
bodyContentType: "JSON",
|
|
129
148
|
queryParams: queryParams
|
|
130
149
|
.map((param) => ({
|
|
131
150
|
name: param.name,
|
|
@@ -158,36 +177,42 @@ export const Playground = ({
|
|
|
158
177
|
active: false,
|
|
159
178
|
},
|
|
160
179
|
]),
|
|
161
|
-
identity:
|
|
180
|
+
identity: getRememberedIdentity(
|
|
181
|
+
identities.data?.map((i) => i.id) ?? [],
|
|
182
|
+
),
|
|
162
183
|
},
|
|
163
184
|
});
|
|
164
185
|
const formState = watch();
|
|
165
|
-
const
|
|
186
|
+
const formRef = useRef<HTMLFormElement>(null);
|
|
166
187
|
|
|
167
|
-
const setOnce = useRef(false);
|
|
168
188
|
useEffect(() => {
|
|
169
|
-
if (
|
|
170
|
-
|
|
171
|
-
if (firstIdentity) {
|
|
172
|
-
setValue("identity", firstIdentity.id);
|
|
173
|
-
setOnce.current = true;
|
|
189
|
+
if (formState.identity) {
|
|
190
|
+
latestSetRememberedIdentity.current(formState.identity);
|
|
174
191
|
}
|
|
175
|
-
}, [
|
|
176
|
-
|
|
177
|
-
const formRef = useRef<HTMLFormElement>(null);
|
|
192
|
+
}, [latestSetRememberedIdentity, formState.identity]);
|
|
178
193
|
|
|
179
194
|
const queryMutation = useMutation({
|
|
180
195
|
mutationFn: async (data: PlaygroundForm) => {
|
|
181
196
|
const start = performance.now();
|
|
197
|
+
|
|
198
|
+
const shouldSetContentType = !data.headers.some(
|
|
199
|
+
(h) => h.active && h.name.toLowerCase() === "content-type",
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const headers = Object.fromEntries([
|
|
203
|
+
...data.headers
|
|
204
|
+
.filter((h) => h.name && h.active)
|
|
205
|
+
.map((header) => [header.name, header.value]),
|
|
206
|
+
...(shouldSetContentType
|
|
207
|
+
? [["content-type", bodyContentTypeMap[data.bodyContentType]]]
|
|
208
|
+
: []),
|
|
209
|
+
]);
|
|
210
|
+
|
|
182
211
|
const request = new Request(
|
|
183
212
|
createUrl(server ?? selectedServer, url, data),
|
|
184
213
|
{
|
|
185
214
|
method: method.toUpperCase(),
|
|
186
|
-
headers
|
|
187
|
-
data.headers
|
|
188
|
-
.filter((h) => h.name && h.active)
|
|
189
|
-
.map((header) => [header.name, header.value]),
|
|
190
|
-
),
|
|
215
|
+
headers,
|
|
191
216
|
body: data.body ? data.body : undefined,
|
|
192
217
|
},
|
|
193
218
|
);
|
|
@@ -197,15 +222,23 @@ export const Playground = ({
|
|
|
197
222
|
?.find((i) => i.id === data.identity)
|
|
198
223
|
?.authorizeRequest(request);
|
|
199
224
|
}
|
|
225
|
+
|
|
226
|
+
const warningTimeout = setTimeout(
|
|
227
|
+
() => setShowLongRunningWarning(true),
|
|
228
|
+
3210,
|
|
229
|
+
);
|
|
230
|
+
abortControllerRef.current = new AbortController();
|
|
231
|
+
|
|
200
232
|
try {
|
|
201
233
|
const response = await fetch(request, {
|
|
202
|
-
signal:
|
|
234
|
+
signal: abortControllerRef.current.signal,
|
|
203
235
|
});
|
|
204
236
|
|
|
205
|
-
|
|
237
|
+
clearTimeout(warningTimeout);
|
|
238
|
+
setShowLongRunningWarning(false);
|
|
206
239
|
|
|
240
|
+
const time = performance.now() - start;
|
|
207
241
|
const body = await response.text();
|
|
208
|
-
|
|
209
242
|
const url = new URL(request.url);
|
|
210
243
|
|
|
211
244
|
return {
|
|
@@ -226,6 +259,8 @@ export const Playground = ({
|
|
|
226
259
|
},
|
|
227
260
|
} satisfies PlaygroundResult;
|
|
228
261
|
} catch (error) {
|
|
262
|
+
clearTimeout(warningTimeout);
|
|
263
|
+
setShowLongRunningWarning(false);
|
|
229
264
|
if (error instanceof TypeError) {
|
|
230
265
|
throw new Error(
|
|
231
266
|
"The request failed, possibly due to network issues or CORS policy.",
|
|
@@ -237,6 +272,12 @@ export const Playground = ({
|
|
|
237
272
|
},
|
|
238
273
|
});
|
|
239
274
|
|
|
275
|
+
useEffect(() => {
|
|
276
|
+
return () => {
|
|
277
|
+
abortControllerRef.current?.abort();
|
|
278
|
+
};
|
|
279
|
+
}, []);
|
|
280
|
+
|
|
240
281
|
const path = (
|
|
241
282
|
<PathRenderer
|
|
242
283
|
path={url}
|
|
@@ -270,9 +311,9 @@ export const Playground = ({
|
|
|
270
311
|
));
|
|
271
312
|
|
|
272
313
|
const serverSelect = (
|
|
273
|
-
<div className="inline-block opacity-50 hover:opacity-100 transition
|
|
314
|
+
<div className="inline-block opacity-50 hover:opacity-100 transition">
|
|
274
315
|
{server ? (
|
|
275
|
-
<span>{server.replace(/^https?:\/\//, "")}</span>
|
|
316
|
+
<span>{server.replace(/^https?:\/\//, "").replace(/\/$/, "")}</span>
|
|
276
317
|
) : (
|
|
277
318
|
servers.length > 1 && (
|
|
278
319
|
<Select
|
|
@@ -282,13 +323,13 @@ export const Playground = ({
|
|
|
282
323
|
value={selectedServer}
|
|
283
324
|
defaultValue={selectedServer}
|
|
284
325
|
>
|
|
285
|
-
<SelectTrigger className="p-0 border-none flex-row-reverse bg-transparent text-xs gap-0.5 h-auto">
|
|
326
|
+
<SelectTrigger className="p-0 border-none flex-row-reverse bg-transparent text-xs gap-0.5 h-auto translate-y-[4px]">
|
|
286
327
|
<SelectValue />
|
|
287
328
|
</SelectTrigger>
|
|
288
329
|
<SelectContent>
|
|
289
330
|
{servers.map((s) => (
|
|
290
331
|
<SelectItem key={s} value={s}>
|
|
291
|
-
{s.replace(/^https?:\/\//, "")}
|
|
332
|
+
{s.replace(/^https?:\/\//, "").replace(/\/$/, "")}
|
|
292
333
|
</SelectItem>
|
|
293
334
|
))}
|
|
294
335
|
</SelectContent>
|
|
@@ -299,62 +340,45 @@ export const Playground = ({
|
|
|
299
340
|
);
|
|
300
341
|
|
|
301
342
|
const showLogin = requiresLogin && !skipLogin;
|
|
343
|
+
const isBodySupported = ["POST", "PUT", "PATCH", "DELETE"].includes(
|
|
344
|
+
method.toUpperCase(),
|
|
345
|
+
);
|
|
302
346
|
|
|
303
347
|
return (
|
|
304
348
|
<FormProvider
|
|
305
349
|
{...{ register, control, handleSubmit, watch, setValue, ...form }}
|
|
306
350
|
>
|
|
307
351
|
<form
|
|
308
|
-
onSubmit={handleSubmit((data) =>
|
|
352
|
+
onSubmit={handleSubmit((data) => {
|
|
353
|
+
if (identities.data?.length === 0 || data.identity) {
|
|
354
|
+
queryMutation.mutate(data);
|
|
355
|
+
} else {
|
|
356
|
+
setShowSelectIdentity(true);
|
|
357
|
+
}
|
|
358
|
+
})}
|
|
309
359
|
ref={formRef}
|
|
310
360
|
className="relative"
|
|
311
361
|
>
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
{onSignUp && (
|
|
333
|
-
<Button
|
|
334
|
-
type="button"
|
|
335
|
-
variant="outline"
|
|
336
|
-
onClick={onSignUp}
|
|
337
|
-
>
|
|
338
|
-
Sign Up
|
|
339
|
-
</Button>
|
|
340
|
-
)}
|
|
341
|
-
{onLogin && (
|
|
342
|
-
<Button type="button" variant="default" onClick={onLogin}>
|
|
343
|
-
Login
|
|
344
|
-
</Button>
|
|
345
|
-
)}
|
|
346
|
-
</div>
|
|
347
|
-
</div>
|
|
348
|
-
</AlertDescription>
|
|
349
|
-
</Alert>
|
|
350
|
-
</div>
|
|
351
|
-
)}
|
|
352
|
-
<div
|
|
353
|
-
className={cn(
|
|
354
|
-
"grid grid-cols-2 text-sm h-full",
|
|
355
|
-
showLogin && "opacity-30 pointer-events-none",
|
|
356
|
-
)}
|
|
357
|
-
>
|
|
362
|
+
<IdentityDialog
|
|
363
|
+
identities={identities.data ?? []}
|
|
364
|
+
open={showSelectIdentity}
|
|
365
|
+
onOpenChange={setShowSelectIdentity}
|
|
366
|
+
onSubmit={({ rememberedIdentity, identity }) => {
|
|
367
|
+
if (rememberedIdentity) {
|
|
368
|
+
setValue("identity", identity ?? NO_IDENTITY);
|
|
369
|
+
}
|
|
370
|
+
setShowSelectIdentity(false);
|
|
371
|
+
queryMutation.mutate({ ...formState, identity });
|
|
372
|
+
}}
|
|
373
|
+
/>
|
|
374
|
+
<RequestLoginDialog
|
|
375
|
+
open={showLogin}
|
|
376
|
+
setOpen={(open) => setSkipLogin(!open)}
|
|
377
|
+
onSignUp={onSignUp}
|
|
378
|
+
onLogin={onLogin}
|
|
379
|
+
/>
|
|
380
|
+
|
|
381
|
+
<div className="grid grid-cols-2 text-sm h-full">
|
|
358
382
|
<div className="flex flex-col gap-4 p-4 after:bg-muted-foreground/20 relative after:absolute after:w-px after:inset-0 after:left-auto">
|
|
359
383
|
<div className="flex gap-2 items-stretch">
|
|
360
384
|
<div className="flex flex-1 items-center w-full border rounded-md">
|
|
@@ -372,7 +396,7 @@ export const Playground = ({
|
|
|
372
396
|
<SubmitButton
|
|
373
397
|
identities={identities.data ?? []}
|
|
374
398
|
formRef={formRef}
|
|
375
|
-
disabled={form.formState.isSubmitting}
|
|
399
|
+
disabled={identities.isLoading || form.formState.isSubmitting}
|
|
376
400
|
/>
|
|
377
401
|
</div>
|
|
378
402
|
<Tabs defaultValue="parameters">
|
|
@@ -397,7 +421,12 @@ export const Playground = ({
|
|
|
397
421
|
<div className="w-2 h-2 rounded-full bg-blue-400 ml-2" />
|
|
398
422
|
)}
|
|
399
423
|
</TabsTrigger>
|
|
400
|
-
<TabsTrigger value="body">
|
|
424
|
+
<TabsTrigger value="body">
|
|
425
|
+
Body
|
|
426
|
+
{formState.body && (
|
|
427
|
+
<div className="w-2 h-2 rounded-full bg-blue-400 ml-2" />
|
|
428
|
+
)}
|
|
429
|
+
</TabsTrigger>
|
|
401
430
|
</TabsList>
|
|
402
431
|
</div>
|
|
403
432
|
<TabsContent value="headers">
|
|
@@ -431,31 +460,58 @@ export const Playground = ({
|
|
|
431
460
|
<Textarea
|
|
432
461
|
{...register("body")}
|
|
433
462
|
className={cn(
|
|
434
|
-
"border w-full rounded-lg p-2
|
|
435
|
-
!
|
|
436
|
-
method.toUpperCase(),
|
|
437
|
-
) && "h-20",
|
|
463
|
+
"border w-full rounded-lg bg-muted/40 p-2 h-64 font-mono text-[13px]",
|
|
464
|
+
!isBodySupported && "h-20 bg-muted",
|
|
438
465
|
)}
|
|
439
466
|
placeholder={
|
|
440
|
-
!
|
|
441
|
-
method.toUpperCase(),
|
|
442
|
-
)
|
|
467
|
+
!isBodySupported
|
|
443
468
|
? "This request does not support a body"
|
|
444
469
|
: undefined
|
|
445
470
|
}
|
|
446
|
-
disabled={
|
|
447
|
-
!["POST", "PUT", "PATCH", "DELETE"].includes(
|
|
448
|
-
method.toUpperCase(),
|
|
449
|
-
)
|
|
450
|
-
}
|
|
471
|
+
disabled={!isBodySupported}
|
|
451
472
|
/>
|
|
452
|
-
{
|
|
453
|
-
<
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
473
|
+
{isBodySupported && (
|
|
474
|
+
<div className="flex items-center gap-2 mt-2 justify-between">
|
|
475
|
+
<Select
|
|
476
|
+
value={formState.bodyContentType}
|
|
477
|
+
onValueChange={(value) =>
|
|
478
|
+
setValue(
|
|
479
|
+
"bodyContentType",
|
|
480
|
+
value as keyof typeof bodyContentTypeMap,
|
|
481
|
+
)
|
|
482
|
+
}
|
|
483
|
+
>
|
|
484
|
+
<SelectTrigger className="w-[100px]">
|
|
485
|
+
<SelectValue />
|
|
486
|
+
</SelectTrigger>
|
|
487
|
+
<SelectContent>
|
|
488
|
+
{Object.keys(bodyContentTypeMap).map((format) => (
|
|
489
|
+
<SelectItem key={format} value={format}>
|
|
490
|
+
{format}
|
|
491
|
+
</SelectItem>
|
|
492
|
+
))}
|
|
493
|
+
</SelectContent>
|
|
494
|
+
</Select>
|
|
495
|
+
{examples && examples.length > 0 && (
|
|
496
|
+
<ExamplesDropdown
|
|
497
|
+
examples={examples}
|
|
498
|
+
onSelect={(example, mediaType) => {
|
|
499
|
+
setValue(
|
|
500
|
+
"body",
|
|
501
|
+
JSON.stringify(example.value, null, 2),
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
const format = objectEntries(bodyContentTypeMap).find(
|
|
505
|
+
([_, contentType]) => contentType === mediaType,
|
|
506
|
+
)?.[0];
|
|
507
|
+
|
|
508
|
+
if (format) {
|
|
509
|
+
setValue("bodyContentType", format);
|
|
510
|
+
}
|
|
511
|
+
}}
|
|
512
|
+
/>
|
|
513
|
+
)}
|
|
514
|
+
</div>
|
|
459
515
|
)}
|
|
460
516
|
</TabsContent>
|
|
461
517
|
<TabsContent value="auth">
|
|
@@ -470,43 +526,11 @@ export const Playground = ({
|
|
|
470
526
|
</Alert>
|
|
471
527
|
)}
|
|
472
528
|
<div className="flex flex-col items-center gap-2">
|
|
473
|
-
<
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
className="gap-0"
|
|
479
|
-
disabled={identities.data?.length === 0}
|
|
480
|
-
>
|
|
481
|
-
<Label
|
|
482
|
-
className="h-12 border-b items-center flex p-4 cursor-pointer hover:bg-accent"
|
|
483
|
-
htmlFor="none"
|
|
484
|
-
>
|
|
485
|
-
<RadioGroupItem value={NO_IDENTITY} id="none">
|
|
486
|
-
None
|
|
487
|
-
</RadioGroupItem>
|
|
488
|
-
<Label htmlFor="none" className="ml-2">
|
|
489
|
-
None
|
|
490
|
-
</Label>
|
|
491
|
-
</Label>
|
|
492
|
-
{identities.data?.map((identity) => (
|
|
493
|
-
<Label
|
|
494
|
-
key={identity.id}
|
|
495
|
-
className="h-12 border-b items-center flex p-4 cursor-pointer hover:bg-accent"
|
|
496
|
-
>
|
|
497
|
-
<RadioGroupItem
|
|
498
|
-
value={identity.id}
|
|
499
|
-
id={identity.id}
|
|
500
|
-
>
|
|
501
|
-
{identity.label}
|
|
502
|
-
</RadioGroupItem>
|
|
503
|
-
<Label htmlFor={identity.id} className="ml-2">
|
|
504
|
-
{identity.label}
|
|
505
|
-
</Label>
|
|
506
|
-
</Label>
|
|
507
|
-
))}
|
|
508
|
-
</RadioGroup>
|
|
509
|
-
</Card>
|
|
529
|
+
<IdentitySelector
|
|
530
|
+
value={formState.identity}
|
|
531
|
+
identities={identities.data ?? []}
|
|
532
|
+
setValue={(value) => setValue("identity", value)}
|
|
533
|
+
/>
|
|
510
534
|
</div>
|
|
511
535
|
</div>
|
|
512
536
|
</TabsContent>
|
|
@@ -517,6 +541,13 @@ export const Playground = ({
|
|
|
517
541
|
showPathParamsWarning={formState.pathParams.some(
|
|
518
542
|
(p) => p.value === "",
|
|
519
543
|
)}
|
|
544
|
+
showLongRunningWarning={showLongRunningWarning}
|
|
545
|
+
onCancel={() => {
|
|
546
|
+
abortControllerRef.current?.abort(
|
|
547
|
+
"Request cancelled by the user",
|
|
548
|
+
);
|
|
549
|
+
setShowLongRunningWarning(false);
|
|
550
|
+
}}
|
|
520
551
|
/>
|
|
521
552
|
</div>
|
|
522
553
|
</form>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Button } from "zudoku/ui/Button.js";
|
|
2
|
+
import {
|
|
3
|
+
Dialog,
|
|
4
|
+
DialogContent,
|
|
5
|
+
DialogDescription,
|
|
6
|
+
DialogFooter,
|
|
7
|
+
DialogTitle,
|
|
8
|
+
} from "zudoku/ui/Dialog.js";
|
|
9
|
+
|
|
10
|
+
const RequestLoginDialog = ({
|
|
11
|
+
open,
|
|
12
|
+
setOpen,
|
|
13
|
+
onSignUp,
|
|
14
|
+
onLogin,
|
|
15
|
+
}: {
|
|
16
|
+
open: boolean;
|
|
17
|
+
onSignUp?: () => void;
|
|
18
|
+
onLogin?: () => void;
|
|
19
|
+
setOpen: (open: boolean) => void;
|
|
20
|
+
}) => {
|
|
21
|
+
return (
|
|
22
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
23
|
+
<DialogContent>
|
|
24
|
+
<DialogTitle>Welcome to the Playground!</DialogTitle>
|
|
25
|
+
<DialogDescription>
|
|
26
|
+
The Playground is a tool for developers to test and explore our APIs.
|
|
27
|
+
To use the Playground, you need to login.
|
|
28
|
+
</DialogDescription>
|
|
29
|
+
<DialogFooter className="flex gap-2 sm:justify-between">
|
|
30
|
+
<Button type="button" variant="ghost" onClick={() => setOpen(false)}>
|
|
31
|
+
Skip
|
|
32
|
+
</Button>
|
|
33
|
+
<div className="flex gap-2">
|
|
34
|
+
{onSignUp && (
|
|
35
|
+
<Button type="button" variant="outline" onClick={onSignUp}>
|
|
36
|
+
Sign Up
|
|
37
|
+
</Button>
|
|
38
|
+
)}
|
|
39
|
+
{onLogin && (
|
|
40
|
+
<Button type="button" variant="default" onClick={onLogin}>
|
|
41
|
+
Login
|
|
42
|
+
</Button>
|
|
43
|
+
)}
|
|
44
|
+
</div>
|
|
45
|
+
</DialogFooter>
|
|
46
|
+
</DialogContent>
|
|
47
|
+
</Dialog>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default RequestLoginDialog;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { create } from "zustand";
|
|
2
|
+
import { createJSONStorage, persist } from "zustand/middleware";
|
|
3
|
+
|
|
4
|
+
interface IdentityState {
|
|
5
|
+
rememberedIdentity: string | null;
|
|
6
|
+
setRememberedIdentity: (identity: string | null) => void;
|
|
7
|
+
getRememberedIdentity: (availableIdentities: string[]) => string | undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const useIdentityStore = create<IdentityState>()(
|
|
11
|
+
persist(
|
|
12
|
+
(set, get) => ({
|
|
13
|
+
rememberedIdentity: null,
|
|
14
|
+
setRememberedIdentity: (identity: string | null) =>
|
|
15
|
+
set({ rememberedIdentity: identity }),
|
|
16
|
+
getRememberedIdentity: (availableIdentities: string[]) =>
|
|
17
|
+
availableIdentities.find(
|
|
18
|
+
(identity) => identity === get().rememberedIdentity,
|
|
19
|
+
),
|
|
20
|
+
}),
|
|
21
|
+
{
|
|
22
|
+
name: "identity-storage",
|
|
23
|
+
storage: createJSONStorage(() => sessionStorage),
|
|
24
|
+
},
|
|
25
|
+
),
|
|
26
|
+
);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useQuery } from "@tanstack/react-query";
|
|
2
2
|
import { ChevronRightIcon } from "lucide-react";
|
|
3
3
|
import { Fragment, useState } from "react";
|
|
4
|
+
import { Callout } from "zudoku/ui/Callout.js";
|
|
4
5
|
import {
|
|
5
6
|
Collapsible,
|
|
6
7
|
CollapsibleContent,
|
|
@@ -30,6 +31,14 @@ const statusCodeMap: Record<number, string> = {
|
|
|
30
31
|
500: "Internal Server Error",
|
|
31
32
|
};
|
|
32
33
|
|
|
34
|
+
const humanFileSize = (bytes: number) => {
|
|
35
|
+
const exponent = Math.floor(Math.log(bytes) / Math.log(1000.0));
|
|
36
|
+
const decimal = (bytes / Math.pow(1000.0, exponent)).toFixed(
|
|
37
|
+
exponent ? 2 : 0,
|
|
38
|
+
);
|
|
39
|
+
return `${decimal} ${exponent ? `${"kMGTPEZY"[exponent - 1]}B` : "B"}`;
|
|
40
|
+
};
|
|
41
|
+
|
|
33
42
|
const mimeTypeToLanguage = (mimeType: string) => {
|
|
34
43
|
const mimeTypeMapping = {
|
|
35
44
|
"application/json": "json",
|
|
@@ -83,6 +92,8 @@ const sortHeadersByRelevance = (
|
|
|
83
92
|
});
|
|
84
93
|
};
|
|
85
94
|
|
|
95
|
+
const SYNTAX_HIGHLIGHT_MAX_SIZE_THRESHOLD = 64_000;
|
|
96
|
+
|
|
86
97
|
export const ResponseTab = ({
|
|
87
98
|
body = "",
|
|
88
99
|
headers,
|
|
@@ -114,9 +125,10 @@ export const ResponseTab = ({
|
|
|
114
125
|
});
|
|
115
126
|
|
|
116
127
|
const sortedHeaders = sortHeadersByRelevance([...headers]);
|
|
128
|
+
const shouldDisableHighlighting = size > SYNTAX_HIGHLIGHT_MAX_SIZE_THRESHOLD;
|
|
117
129
|
|
|
118
130
|
return (
|
|
119
|
-
<div className="flex flex-col gap-2 h-full overflow-
|
|
131
|
+
<div className="flex flex-col gap-2 h-full overflow-auto max-h-[calc(100vh-220px)] ">
|
|
120
132
|
<Collapsible defaultOpen>
|
|
121
133
|
<CollapsibleTrigger className="flex items-center gap-2 hover:text-primary group">
|
|
122
134
|
<ChevronRightIcon className="h-4 w-4 transition-transform duration-200 group-data-[state=open]:rotate-[90deg]" />
|
|
@@ -151,6 +163,12 @@ export const ResponseTab = ({
|
|
|
151
163
|
</Collapsible>
|
|
152
164
|
|
|
153
165
|
<Card className="shadow-none">
|
|
166
|
+
{shouldDisableHighlighting && (
|
|
167
|
+
<Callout type="info" className="my-0 p-2">
|
|
168
|
+
Code highlight is disabled for responses larger than{" "}
|
|
169
|
+
{humanFileSize(SYNTAX_HIGHLIGHT_MAX_SIZE_THRESHOLD)}
|
|
170
|
+
</Callout>
|
|
171
|
+
)}
|
|
154
172
|
<SyntaxHighlight
|
|
155
173
|
language={
|
|
156
174
|
view === "types"
|
|
@@ -161,8 +179,9 @@ export const ResponseTab = ({
|
|
|
161
179
|
: detectedLanguage
|
|
162
180
|
: "json"
|
|
163
181
|
}
|
|
182
|
+
showCopy="always"
|
|
183
|
+
disabled={shouldDisableHighlighting}
|
|
164
184
|
noBackground
|
|
165
|
-
// playground dialog has h-5/6 ≈ 83.333vh
|
|
166
185
|
className="overflow-x-auto p-4 text-xs max-h-[calc(83.333vh-180px)]"
|
|
167
186
|
code={
|
|
168
187
|
(view === "raw"
|
|
@@ -173,7 +192,7 @@ export const ResponseTab = ({
|
|
|
173
192
|
}
|
|
174
193
|
/>
|
|
175
194
|
</Card>
|
|
176
|
-
<div className="flex gap-2 justify-between">
|
|
195
|
+
<div className="flex gap-2 justify-between items-center">
|
|
177
196
|
<div className="flex text-xs gap-2 border bg-muted rounded-md p-2 items-center h-8 font-mono divide-x">
|
|
178
197
|
<div>
|
|
179
198
|
<span className="text-muted-foreground">Status</span> {status}{" "}
|
|
@@ -184,7 +203,8 @@ export const ResponseTab = ({
|
|
|
184
203
|
{time.toFixed(0)}ms
|
|
185
204
|
</div>
|
|
186
205
|
<div>
|
|
187
|
-
<span className="text-muted-foreground">Size</span>
|
|
206
|
+
<span className="text-muted-foreground">Size</span>{" "}
|
|
207
|
+
{humanFileSize(size)}
|
|
188
208
|
</div>
|
|
189
209
|
</div>
|
|
190
210
|
{jsonContent && (
|