zudoku 0.46.1 → 0.46.3
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/dist/config/validators/InputSidebarSchema.d.ts +3 -0
- package/dist/config/validators/InputSidebarSchema.js +1 -0
- package/dist/config/validators/InputSidebarSchema.js.map +1 -1
- package/dist/config/validators/SidebarSchema.d.ts +1 -1
- package/dist/config/validators/validate.d.ts +12 -12
- package/dist/config/validators/validate.js +3 -1
- package/dist/config/validators/validate.js.map +1 -1
- package/dist/lib/authentication/AuthenticationPlugin.d.ts +0 -5
- package/dist/lib/authentication/AuthenticationPlugin.js +0 -12
- package/dist/lib/authentication/AuthenticationPlugin.js.map +1 -1
- package/dist/lib/components/navigation/SidebarItem.js +2 -3
- package/dist/lib/components/navigation/SidebarItem.js.map +1 -1
- package/dist/lib/plugins/api-keys/SettingsApiKeys.js +110 -15
- package/dist/lib/plugins/api-keys/SettingsApiKeys.js.map +1 -1
- package/dist/lib/plugins/api-keys/index.d.ts +3 -3
- package/dist/lib/plugins/api-keys/index.js +14 -14
- package/dist/lib/plugins/api-keys/index.js.map +1 -1
- package/dist/lib/plugins/openapi/Sidecar.js +11 -91
- package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/BodyPanel.d.ts +5 -0
- package/dist/lib/plugins/openapi/playground/BodyPanel.js +22 -0
- package/dist/lib/plugins/openapi/playground/BodyPanel.js.map +1 -0
- package/dist/lib/plugins/openapi/playground/ExamplesDropdown.js +2 -1
- package/dist/lib/plugins/openapi/playground/ExamplesDropdown.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/Headers.js +25 -25
- package/dist/lib/plugins/openapi/playground/Headers.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/IdentitySelector.js +1 -1
- package/dist/lib/plugins/openapi/playground/IdentitySelector.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/ParamsGrid.js +2 -2
- package/dist/lib/plugins/openapi/playground/ParamsGrid.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/PathParams.js +1 -1
- package/dist/lib/plugins/openapi/playground/PathParams.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/Playground.d.ts +3 -8
- package/dist/lib/plugins/openapi/playground/Playground.js +70 -65
- package/dist/lib/plugins/openapi/playground/Playground.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/PlaygroundDialog.js +1 -1
- package/dist/lib/plugins/openapi/playground/PlaygroundDialog.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/fileUtils.d.ts +2 -0
- package/dist/lib/plugins/openapi/playground/fileUtils.js +22 -0
- package/dist/lib/plugins/openapi/playground/fileUtils.js.map +1 -0
- package/dist/lib/plugins/openapi/playground/result-panel/ResponseTab.d.ts +4 -1
- package/dist/lib/plugins/openapi/playground/result-panel/ResponseTab.js +29 -20
- package/dist/lib/plugins/openapi/playground/result-panel/ResponseTab.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/result-panel/ResultPanel.js +2 -2
- package/dist/lib/plugins/openapi/playground/result-panel/ResultPanel.js.map +1 -1
- package/dist/lib/plugins/openapi/playground/useRememberSkipLoginDialog.d.ts +16 -0
- package/dist/lib/plugins/openapi/playground/useRememberSkipLoginDialog.js +10 -0
- package/dist/lib/plugins/openapi/playground/useRememberSkipLoginDialog.js.map +1 -0
- package/dist/lib/plugins/openapi/util/createHttpSnippet.d.ts +11 -0
- package/dist/lib/plugins/openapi/util/createHttpSnippet.js +89 -0
- package/dist/lib/plugins/openapi/util/createHttpSnippet.js.map +1 -0
- package/dist/lib/shiki.d.ts +8 -12
- package/dist/lib/shiki.js +11 -13
- package/dist/lib/shiki.js.map +1 -1
- package/dist/lib/ui/CodeBlock.js +1 -1
- package/dist/lib/ui/CodeBlock.js.map +1 -1
- package/dist/lib/ui/Dialog.d.ts +3 -1
- package/dist/lib/ui/Dialog.js +2 -2
- package/dist/lib/ui/Dialog.js.map +1 -1
- package/dist/lib/util/humanFileSize.d.ts +6 -0
- package/dist/lib/util/humanFileSize.js +14 -0
- package/dist/lib/util/humanFileSize.js.map +1 -0
- package/dist/lib/util/humanFileSize.test.d.ts +1 -0
- package/dist/lib/util/humanFileSize.test.js +22 -0
- package/dist/lib/util/humanFileSize.test.js.map +1 -0
- package/dist/vite/build.js +1 -4
- package/dist/vite/build.js.map +1 -1
- package/lib/{Callout-BkgOUkoZ.js → Callout-CoVxYafP.js} +2 -2
- package/lib/{Callout-BkgOUkoZ.js.map → Callout-CoVxYafP.js.map} +1 -1
- package/lib/{Dialog-Du6WMcIA.js → Dialog-BxpuVLh9.js} +25 -25
- package/lib/Dialog-BxpuVLh9.js.map +1 -0
- package/lib/{Markdown-BRAyzyUJ.js → Markdown-D81l28Ib.js} +310 -306
- package/lib/{Markdown-BRAyzyUJ.js.map → Markdown-D81l28Ib.js.map} +1 -1
- package/lib/{MdxPage-B3v1BSKr.js → MdxPage-S_CxlNmX.js} +5 -5
- package/lib/{MdxPage-B3v1BSKr.js.map → MdxPage-S_CxlNmX.js.map} +1 -1
- package/lib/{OasProvider-5jrFuhVk.js → OasProvider-D5rt6WMb.js} +3 -3
- package/lib/{OasProvider-5jrFuhVk.js.map → OasProvider-D5rt6WMb.js.map} +1 -1
- package/lib/{OperationList-BmoMLQPO.js → OperationList-CNhg654C.js} +1139 -1131
- package/lib/OperationList-CNhg654C.js.map +1 -0
- package/lib/{Pagination-Cr0fWZS3.js → Pagination-CCxhL836.js} +2 -2
- package/lib/{Pagination-Cr0fWZS3.js.map → Pagination-CCxhL836.js.map} +1 -1
- package/lib/{RouteGuard-PrSVLbSr.js → RouteGuard-CZ_uLv3g.js} +6 -6
- package/lib/{RouteGuard-PrSVLbSr.js.map → RouteGuard-CZ_uLv3g.js.map} +1 -1
- package/lib/{SchemaList-B4riYLoP.js → SchemaList-BvzCrTYg.js} +6 -6
- package/lib/{SchemaList-B4riYLoP.js.map → SchemaList-BvzCrTYg.js.map} +1 -1
- package/lib/{SchemaView-CPZ6RgsF.js → SchemaView-Br1RupCp.js} +3 -3
- package/lib/{SchemaView-CPZ6RgsF.js.map → SchemaView-Br1RupCp.js.map} +1 -1
- package/lib/{SignUp-CWaiH0tY.js → SignUp-B-1Pvc-8.js} +3 -3
- package/lib/{SignUp-CWaiH0tY.js.map → SignUp-B-1Pvc-8.js.map} +1 -1
- package/lib/{Slot-Bo6K4tnb.js → Slot-T8NJUkm4.js} +11 -11
- package/lib/{Slot-Bo6K4tnb.js.map → Slot-T8NJUkm4.js.map} +1 -1
- package/lib/{SyntaxHighlight-DedRjJNr.js → SyntaxHighlight-Cz6Me7-F.js} +4474 -3323
- package/lib/SyntaxHighlight-Cz6Me7-F.js.map +1 -0
- package/lib/{Toc-lL3fzNkl.js → Toc-PA-j0gEu.js} +2 -2
- package/lib/{Toc-lL3fzNkl.js.map → Toc-PA-j0gEu.js.map} +1 -1
- package/lib/{chunk-BAXFHI7N-C9WnHsLV.js → chunk-DQRVZFIR-BblmKnHy.js} +697 -697
- package/lib/chunk-DQRVZFIR-BblmKnHy.js.map +1 -0
- package/lib/{circular-oB4auIIg.js → circular-5FeDWJOn.js} +1812 -1807
- package/lib/circular-5FeDWJOn.js.map +1 -0
- package/lib/{createServer-DCB82j2t.js → createServer-BC2RZgmW.js} +3648 -3493
- package/lib/createServer-BC2RZgmW.js.map +1 -0
- package/lib/{hook-DawSLaZr.js → hook-k7PfUIsj.js} +10 -10
- package/lib/{hook-DawSLaZr.js.map → hook-k7PfUIsj.js.map} +1 -1
- package/lib/{index-BXYvD5-7.js → index-CJZthJSj.js} +1053 -1095
- package/lib/index-CJZthJSj.js.map +1 -0
- package/lib/index.esm-Cp4wkyud.js +1236 -0
- package/lib/index.esm-Cp4wkyud.js.map +1 -0
- package/lib/{mutation-oxMvODNQ.js → mutation-BSeQ8pEK.js} +2 -2
- package/lib/{mutation-oxMvODNQ.js.map → mutation-BSeQ8pEK.js.map} +1 -1
- package/lib/react-nprogress.esm-C2MPXjiJ.js +389 -0
- package/lib/react-nprogress.esm-C2MPXjiJ.js.map +1 -0
- package/lib/ui/CodeBlock.js +17 -16
- package/lib/ui/CodeBlock.js.map +1 -1
- package/lib/ui/Command.js +1 -1
- package/lib/ui/Dialog.js +25 -25
- package/lib/ui/Dialog.js.map +1 -1
- package/lib/ui/Form.js +1 -1
- package/lib/ui/SyntaxHighlight.js +2 -2
- package/lib/{useExposedProps-DG8J6ewJ.js → useExposedProps-BZQkZneR.js} +2 -2
- package/lib/{useExposedProps-DG8J6ewJ.js.map → useExposedProps-BZQkZneR.js.map} +1 -1
- package/lib/{useMutation-C_j3dA_L.js → useMutation-CZSmsIGW.js} +3 -3
- package/lib/{useMutation-C_j3dA_L.js.map → useMutation-CZSmsIGW.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 +57 -66
- package/lib/zudoku.auth-openid.js.map +1 -1
- package/lib/zudoku.components.js +1698 -2082
- package/lib/zudoku.components.js.map +1 -1
- package/lib/zudoku.hooks.js +11 -11
- package/lib/zudoku.plugin-api-catalog.js +5 -5
- package/lib/zudoku.plugin-api-keys.js +473 -4970
- package/lib/zudoku.plugin-api-keys.js.map +1 -1
- 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 +5 -5
- package/package.json +33 -34
- package/src/lib/authentication/AuthenticationPlugin.tsx +0 -14
- package/src/lib/components/navigation/SidebarItem.tsx +2 -1
- package/src/lib/plugins/api-keys/SettingsApiKeys.tsx +193 -48
- package/src/lib/plugins/api-keys/index.tsx +25 -18
- package/src/lib/plugins/openapi/Sidecar.tsx +11 -97
- package/src/lib/plugins/openapi/playground/BodyPanel.tsx +46 -0
- package/src/lib/plugins/openapi/playground/ExamplesDropdown.tsx +4 -1
- package/src/lib/plugins/openapi/playground/Headers.tsx +110 -106
- package/src/lib/plugins/openapi/playground/IdentitySelector.tsx +13 -11
- package/src/lib/plugins/openapi/playground/ParamsGrid.tsx +2 -2
- package/src/lib/plugins/openapi/playground/PathParams.tsx +1 -1
- package/src/lib/plugins/openapi/playground/Playground.tsx +127 -211
- package/src/lib/plugins/openapi/playground/PlaygroundDialog.tsx +2 -1
- package/src/lib/plugins/openapi/playground/QueryParams.tsx +1 -1
- package/src/lib/plugins/openapi/playground/fileUtils.ts +32 -0
- package/src/lib/plugins/openapi/playground/result-panel/ResponseTab.tsx +74 -39
- package/src/lib/plugins/openapi/playground/result-panel/ResultPanel.tsx +4 -1
- package/src/lib/plugins/openapi/playground/useRememberSkipLoginDialog.tsx +20 -0
- package/src/lib/plugins/openapi/util/createHttpSnippet.ts +107 -0
- package/src/lib/shiki.ts +21 -22
- package/src/lib/ui/CodeBlock.tsx +1 -0
- package/src/lib/ui/Dialog.tsx +11 -7
- package/src/lib/util/humanFileSize.test.ts +24 -0
- package/src/lib/util/humanFileSize.ts +16 -0
- package/dist/lib/plugins/openapi/playground/SubmitButton.d.ts +0 -7
- package/dist/lib/plugins/openapi/playground/SubmitButton.js +0 -19
- package/dist/lib/plugins/openapi/playground/SubmitButton.js.map +0 -1
- package/lib/Dialog-Du6WMcIA.js.map +0 -1
- package/lib/OperationList-BmoMLQPO.js.map +0 -1
- package/lib/SyntaxHighlight-DedRjJNr.js.map +0 -1
- package/lib/chunk-BAXFHI7N-C9WnHsLV.js.map +0 -1
- package/lib/circular-oB4auIIg.js.map +0 -1
- package/lib/createServer-DCB82j2t.js.map +0 -1
- package/lib/index-BXYvD5-7.js.map +0 -1
- package/lib/index.esm-DSfX_eMP.js +0 -1216
- package/lib/index.esm-DSfX_eMP.js.map +0 -1
- package/src/lib/plugins/openapi/playground/SubmitButton.tsx +0 -70
|
@@ -1,10 +1,8 @@
|
|
|
1
|
+
import { useNProgress } from "@tanem/react-nprogress";
|
|
1
2
|
import { useMutation } from "@tanstack/react-query";
|
|
2
|
-
import { InfoIcon } from "lucide-react";
|
|
3
3
|
import { Fragment, useEffect, useRef, useState, useTransition } from "react";
|
|
4
4
|
import { FormProvider, useForm } from "react-hook-form";
|
|
5
|
-
import {
|
|
6
|
-
import { PathRenderer } from "../../../components/PathRenderer.js";
|
|
7
|
-
|
|
5
|
+
import { Button } from "zudoku/ui/Button.js";
|
|
8
6
|
import {
|
|
9
7
|
Select,
|
|
10
8
|
SelectContent,
|
|
@@ -12,17 +10,15 @@ import {
|
|
|
12
10
|
SelectTrigger,
|
|
13
11
|
SelectValue,
|
|
14
12
|
} from "zudoku/ui/Select.js";
|
|
15
|
-
import { Textarea } from "zudoku/ui/Textarea.js";
|
|
16
13
|
import { useApiIdentities } from "../../../components/context/ZudokuContext.js";
|
|
17
|
-
import {
|
|
18
|
-
import { cn } from "../../../util/cn.js";
|
|
19
|
-
import { objectEntries } from "../../../util/objectEntries.js";
|
|
14
|
+
import { PathRenderer } from "../../../components/PathRenderer.js";
|
|
20
15
|
import { useLatest } from "../../../util/useLatest.js";
|
|
21
16
|
import { ColorizedParam } from "../ColorizedParam.js";
|
|
22
17
|
import { type Content } from "../SidecarExamples.js";
|
|
23
18
|
import { useSelectedServer } from "../state.js";
|
|
19
|
+
import BodyPanel from "./BodyPanel.js";
|
|
24
20
|
import { createUrl } from "./createUrl.js";
|
|
25
|
-
import
|
|
21
|
+
import { extractFileName, isBinaryContentType } from "./fileUtils.js";
|
|
26
22
|
import { Headers } from "./Headers.js";
|
|
27
23
|
import { IdentityDialog } from "./IdentityDialog.js";
|
|
28
24
|
import IdentitySelector from "./IdentitySelector.js";
|
|
@@ -31,7 +27,7 @@ import { QueryParams } from "./QueryParams.js";
|
|
|
31
27
|
import { useIdentityStore } from "./rememberedIdentity.js";
|
|
32
28
|
import RequestLoginDialog from "./RequestLoginDialog.js";
|
|
33
29
|
import { ResultPanel } from "./result-panel/ResultPanel.js";
|
|
34
|
-
import
|
|
30
|
+
import { useRememberSkipLoginDialog } from "./useRememberSkipLoginDialog.js";
|
|
35
31
|
|
|
36
32
|
export const NO_IDENTITY = "__none";
|
|
37
33
|
|
|
@@ -59,17 +55,8 @@ export type PathParam = {
|
|
|
59
55
|
isRequired?: boolean;
|
|
60
56
|
};
|
|
61
57
|
|
|
62
|
-
const bodyContentTypeMap = {
|
|
63
|
-
Plain: "text/plain",
|
|
64
|
-
JSON: "application/json",
|
|
65
|
-
XML: "application/xml",
|
|
66
|
-
YAML: "application/yaml",
|
|
67
|
-
CSV: "text/csv",
|
|
68
|
-
} as const;
|
|
69
|
-
|
|
70
58
|
export type PlaygroundForm = {
|
|
71
59
|
body: string;
|
|
72
|
-
bodyContentType: keyof typeof bodyContentTypeMap;
|
|
73
60
|
queryParams: Array<{
|
|
74
61
|
name: string;
|
|
75
62
|
value: string;
|
|
@@ -95,6 +82,9 @@ export type PlaygroundResult = {
|
|
|
95
82
|
size: number;
|
|
96
83
|
body: string;
|
|
97
84
|
time: number;
|
|
85
|
+
isBinary?: boolean;
|
|
86
|
+
fileName?: string;
|
|
87
|
+
blob?: Blob;
|
|
98
88
|
request: {
|
|
99
89
|
method: string;
|
|
100
90
|
url: string;
|
|
@@ -139,7 +129,7 @@ export const Playground = ({
|
|
|
139
129
|
const identities = useApiIdentities();
|
|
140
130
|
const { setRememberedIdentity, getRememberedIdentity } = useIdentityStore();
|
|
141
131
|
const [, startTransition] = useTransition();
|
|
142
|
-
const
|
|
132
|
+
const { skipLogin, setSkipLogin } = useRememberSkipLoginDialog();
|
|
143
133
|
const [showLongRunningWarning, setShowLongRunningWarning] = useState(false);
|
|
144
134
|
const abortControllerRef = useRef<AbortController | undefined>(undefined);
|
|
145
135
|
const latestSetRememberedIdentity = useLatest(setRememberedIdentity);
|
|
@@ -148,46 +138,47 @@ export const Playground = ({
|
|
|
148
138
|
useForm<PlaygroundForm>({
|
|
149
139
|
defaultValues: {
|
|
150
140
|
body: defaultBody,
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
141
|
+
queryParams:
|
|
142
|
+
queryParams.length > 0
|
|
143
|
+
? queryParams.map((param) => ({
|
|
144
|
+
name: param.name,
|
|
145
|
+
value: param.defaultValue ?? "",
|
|
146
|
+
active: param.defaultActive ?? false,
|
|
147
|
+
enum: param.enum ?? [],
|
|
148
|
+
}))
|
|
149
|
+
: [
|
|
150
|
+
{
|
|
151
|
+
name: "",
|
|
152
|
+
value: "",
|
|
153
|
+
active: false,
|
|
154
|
+
enum: [],
|
|
155
|
+
},
|
|
156
|
+
],
|
|
167
157
|
pathParams: pathParams.map((param) => ({
|
|
168
158
|
name: param.name,
|
|
169
159
|
value: param.defaultValue ?? "",
|
|
170
160
|
})),
|
|
171
|
-
headers:
|
|
172
|
-
.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
161
|
+
headers:
|
|
162
|
+
headers.length > 0
|
|
163
|
+
? headers.map((header) => ({
|
|
164
|
+
name: header.name,
|
|
165
|
+
value: header.defaultValue ?? "",
|
|
166
|
+
active: header.defaultActive ?? false,
|
|
167
|
+
}))
|
|
168
|
+
: [
|
|
169
|
+
{
|
|
170
|
+
name: "",
|
|
171
|
+
value: "",
|
|
172
|
+
active: false,
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
identity: getRememberedIdentity([
|
|
176
|
+
NO_IDENTITY,
|
|
177
|
+
...(identities.data?.map((i) => i.id) ?? []),
|
|
178
|
+
]),
|
|
187
179
|
},
|
|
188
180
|
});
|
|
189
181
|
const formState = watch();
|
|
190
|
-
const formRef = useRef<HTMLFormElement>(null);
|
|
191
182
|
|
|
192
183
|
useEffect(() => {
|
|
193
184
|
if (formState.identity) {
|
|
@@ -200,17 +191,10 @@ export const Playground = ({
|
|
|
200
191
|
mutationFn: async (data: PlaygroundForm) => {
|
|
201
192
|
const start = performance.now();
|
|
202
193
|
|
|
203
|
-
const shouldSetContentType = !data.headers.some(
|
|
204
|
-
(h) => h.active && h.name.toLowerCase() === "content-type",
|
|
205
|
-
);
|
|
206
|
-
|
|
207
194
|
const headers = Object.fromEntries([
|
|
208
195
|
...data.headers
|
|
209
196
|
.filter((h) => h.name && h.active)
|
|
210
197
|
.map((header) => [header.name, header.value]),
|
|
211
|
-
...(shouldSetContentType
|
|
212
|
-
? [["content-type", bodyContentTypeMap[data.bodyContentType]]]
|
|
213
|
-
: []),
|
|
214
198
|
]);
|
|
215
199
|
|
|
216
200
|
const request = new Request(
|
|
@@ -244,15 +228,34 @@ export const Playground = ({
|
|
|
244
228
|
setShowLongRunningWarning(false);
|
|
245
229
|
|
|
246
230
|
const time = performance.now() - start;
|
|
247
|
-
const body = await response.text();
|
|
248
231
|
const url = new URL(request.url);
|
|
232
|
+
const responseHeaders = Array.from(response.headers.entries());
|
|
233
|
+
const contentType = response.headers.get("content-type") || "";
|
|
234
|
+
const isBinary = isBinaryContentType(contentType);
|
|
235
|
+
|
|
236
|
+
let body = "";
|
|
237
|
+
let blob: Blob | undefined;
|
|
238
|
+
let fileName: string | undefined;
|
|
239
|
+
|
|
240
|
+
if (isBinary) {
|
|
241
|
+
blob = await response.blob();
|
|
242
|
+
fileName = extractFileName(responseHeaders, request.url);
|
|
243
|
+
body = `Binary content (${contentType})`;
|
|
244
|
+
} else {
|
|
245
|
+
body = await response.text();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const responseSize = response.headers.get("content-length");
|
|
249
249
|
|
|
250
250
|
return {
|
|
251
251
|
status: response.status,
|
|
252
|
-
headers:
|
|
253
|
-
size: body.length,
|
|
252
|
+
headers: responseHeaders,
|
|
253
|
+
size: responseSize ? parseInt(responseSize) : body.length,
|
|
254
254
|
body,
|
|
255
255
|
time,
|
|
256
|
+
isBinary,
|
|
257
|
+
fileName,
|
|
258
|
+
blob,
|
|
256
259
|
request: {
|
|
257
260
|
method: request.method.toUpperCase(),
|
|
258
261
|
url: request.url,
|
|
@@ -278,6 +281,16 @@ export const Playground = ({
|
|
|
278
281
|
},
|
|
279
282
|
});
|
|
280
283
|
|
|
284
|
+
const isRequestAnimating = queryMutation.isPending;
|
|
285
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
286
|
+
|
|
287
|
+
useEffect(() => {
|
|
288
|
+
const timer = setTimeout(() => setIsAnimating(isRequestAnimating), 100);
|
|
289
|
+
return () => clearTimeout(timer);
|
|
290
|
+
}, [isRequestAnimating]);
|
|
291
|
+
|
|
292
|
+
const { isFinished, progress } = useNProgress({ isAnimating });
|
|
293
|
+
|
|
281
294
|
useEffect(() => {
|
|
282
295
|
return () => {
|
|
283
296
|
abortControllerRef.current?.abort();
|
|
@@ -362,7 +375,6 @@ export const Playground = ({
|
|
|
362
375
|
setShowSelectIdentity(true);
|
|
363
376
|
}
|
|
364
377
|
})}
|
|
365
|
-
ref={formRef}
|
|
366
378
|
className="relative"
|
|
367
379
|
>
|
|
368
380
|
<IdentityDialog
|
|
@@ -384,164 +396,68 @@ export const Playground = ({
|
|
|
384
396
|
onLogin={onLogin}
|
|
385
397
|
/>
|
|
386
398
|
|
|
387
|
-
<div className="grid grid-cols-
|
|
388
|
-
<div className="
|
|
399
|
+
<div className="grid grid-cols-[1fr_min-content_1fr] text-sm">
|
|
400
|
+
<div className="col-span-3 p-4 border-b">
|
|
389
401
|
<div className="flex gap-2 items-stretch">
|
|
390
|
-
<div className="flex flex-1 items-center w-full border rounded-md">
|
|
402
|
+
<div className="flex flex-1 items-center w-full border rounded-md relative overflow-hidden">
|
|
391
403
|
<div className="border-r p-2 bg-muted rounded-l-md self-stretch font-semibold font-mono flex items-center">
|
|
392
404
|
{method.toUpperCase()}
|
|
393
405
|
</div>
|
|
394
|
-
<div className="items-center px-2
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
406
|
+
<div className="items-center px-2 font-mono text-xs break-all leading-6 relative h-full w-full">
|
|
407
|
+
<div className="h-full py-1.5">
|
|
408
|
+
{serverSelect}
|
|
409
|
+
{path}
|
|
410
|
+
{urlQueryParams.length > 0 ? "?" : ""}
|
|
411
|
+
{urlQueryParams}
|
|
412
|
+
</div>
|
|
413
|
+
<div
|
|
414
|
+
className="h-[1px] bg-primary absolute left-0 -bottom-0 z-10 transition-all duration-300 ease-in-out"
|
|
415
|
+
style={{
|
|
416
|
+
opacity: isFinished ? 0 : 1,
|
|
417
|
+
width: isFinished ? 0 : `${progress * 100}%`,
|
|
418
|
+
}}
|
|
419
|
+
/>
|
|
399
420
|
</div>
|
|
400
421
|
</div>
|
|
401
422
|
|
|
402
|
-
<
|
|
403
|
-
|
|
404
|
-
formRef={formRef}
|
|
423
|
+
<Button
|
|
424
|
+
type="submit"
|
|
405
425
|
disabled={identities.isLoading || form.formState.isSubmitting}
|
|
406
|
-
|
|
426
|
+
>
|
|
427
|
+
Send
|
|
428
|
+
</Button>
|
|
407
429
|
</div>
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
Headers
|
|
420
|
-
{formState.headers.filter((h) => h.active).length > 0 && (
|
|
421
|
-
<div className="w-2 h-2 rounded-full bg-blue-400 ms-2" />
|
|
422
|
-
)}
|
|
423
|
-
</TabsTrigger>
|
|
424
|
-
<TabsTrigger value="auth">
|
|
425
|
-
Auth
|
|
426
|
-
{formState.identity !== NO_IDENTITY && (
|
|
427
|
-
<div className="w-2 h-2 rounded-full bg-blue-400 ms-2" />
|
|
428
|
-
)}
|
|
429
|
-
</TabsTrigger>
|
|
430
|
-
<TabsTrigger value="body">
|
|
431
|
-
Body
|
|
432
|
-
{formState.body && (
|
|
433
|
-
<div className="w-2 h-2 rounded-full bg-blue-400 ms-2" />
|
|
434
|
-
)}
|
|
435
|
-
</TabsTrigger>
|
|
436
|
-
</TabsList>
|
|
437
|
-
</div>
|
|
438
|
-
<TabsContent value="headers">
|
|
439
|
-
<Headers control={control} headers={headers} />
|
|
440
|
-
</TabsContent>
|
|
441
|
-
<TabsContent value="parameters">
|
|
442
|
-
{pathParams.length > 0 && (
|
|
443
|
-
<div className="flex flex-col gap-4 my-4">
|
|
444
|
-
<span className="font-semibold">Path Parameters</span>
|
|
445
|
-
<PathParams url={url} control={control} />
|
|
446
|
-
</div>
|
|
447
|
-
)}
|
|
448
|
-
<div className="flex flex-col gap-4 my-4">
|
|
449
|
-
<span className="font-semibold">Query Parameters</span>
|
|
450
|
-
<QueryParams control={control} queryParams={queryParams} />
|
|
451
|
-
</div>
|
|
452
|
-
</TabsContent>
|
|
453
|
-
<TabsContent value="body">
|
|
454
|
-
{!["POST", "PUT", "PATCH", "DELETE"].includes(
|
|
455
|
-
method.toUpperCase(),
|
|
456
|
-
) && (
|
|
457
|
-
<Alert className="mb-2">
|
|
458
|
-
<InfoIcon className="w-4 h-4" />
|
|
459
|
-
<AlertTitle>Body</AlertTitle>
|
|
460
|
-
<AlertDescription>
|
|
461
|
-
Body is only supported for POST, PUT, PATCH, and DELETE
|
|
462
|
-
requests
|
|
463
|
-
</AlertDescription>
|
|
464
|
-
</Alert>
|
|
465
|
-
)}
|
|
466
|
-
<Textarea
|
|
467
|
-
{...register("body")}
|
|
468
|
-
className={cn(
|
|
469
|
-
"border w-full rounded-lg bg-muted/40 p-2 h-64 font-mono text-[13px]",
|
|
470
|
-
!isBodySupported && "h-20 bg-muted",
|
|
471
|
-
)}
|
|
472
|
-
placeholder={
|
|
473
|
-
!isBodySupported
|
|
474
|
-
? "This request does not support a body"
|
|
475
|
-
: undefined
|
|
476
|
-
}
|
|
477
|
-
disabled={!isBodySupported}
|
|
478
|
-
/>
|
|
479
|
-
{isBodySupported && (
|
|
480
|
-
<div className="flex items-center gap-2 mt-2 justify-between">
|
|
481
|
-
<Select
|
|
482
|
-
value={formState.bodyContentType}
|
|
483
|
-
onValueChange={(value) =>
|
|
484
|
-
setValue(
|
|
485
|
-
"bodyContentType",
|
|
486
|
-
value as keyof typeof bodyContentTypeMap,
|
|
487
|
-
)
|
|
488
|
-
}
|
|
489
|
-
>
|
|
490
|
-
<SelectTrigger className="w-[100px]">
|
|
491
|
-
<SelectValue />
|
|
492
|
-
</SelectTrigger>
|
|
493
|
-
<SelectContent>
|
|
494
|
-
{Object.keys(bodyContentTypeMap).map((format) => (
|
|
495
|
-
<SelectItem key={format} value={format}>
|
|
496
|
-
{format}
|
|
497
|
-
</SelectItem>
|
|
498
|
-
))}
|
|
499
|
-
</SelectContent>
|
|
500
|
-
</Select>
|
|
501
|
-
{examples && examples.length > 0 && (
|
|
502
|
-
<ExamplesDropdown
|
|
503
|
-
examples={examples}
|
|
504
|
-
onSelect={(example, mediaType) => {
|
|
505
|
-
setValue(
|
|
506
|
-
"body",
|
|
507
|
-
JSON.stringify(example.value, null, 2),
|
|
508
|
-
);
|
|
509
|
-
|
|
510
|
-
const format = objectEntries(bodyContentTypeMap).find(
|
|
511
|
-
([_, contentType]) => contentType === mediaType,
|
|
512
|
-
)?.[0];
|
|
513
|
-
|
|
514
|
-
if (format) {
|
|
515
|
-
setValue("bodyContentType", format);
|
|
516
|
-
}
|
|
517
|
-
}}
|
|
518
|
-
/>
|
|
519
|
-
)}
|
|
520
|
-
</div>
|
|
521
|
-
)}
|
|
522
|
-
</TabsContent>
|
|
523
|
-
<TabsContent value="auth">
|
|
524
|
-
<div className="flex flex-col gap-4 my-4">
|
|
525
|
-
{identities.data?.length === 0 && (
|
|
526
|
-
<Alert>
|
|
527
|
-
<InfoIcon className="w-4 h-4" />
|
|
528
|
-
<AlertTitle>Authentication</AlertTitle>
|
|
529
|
-
<AlertDescription>
|
|
530
|
-
No identities found. Please create an identity first.
|
|
531
|
-
</AlertDescription>
|
|
532
|
-
</Alert>
|
|
533
|
-
)}
|
|
534
|
-
<div className="flex flex-col items-center gap-2">
|
|
535
|
-
<IdentitySelector
|
|
536
|
-
value={formState.identity}
|
|
537
|
-
identities={identities.data ?? []}
|
|
538
|
-
setValue={(value) => setValue("identity", value)}
|
|
539
|
-
/>
|
|
540
|
-
</div>
|
|
430
|
+
</div>
|
|
431
|
+
<div className="flex flex-col gap-5 p-4 after:bg-muted-foreground/20 relative overflow-y-auto h-[80vh]">
|
|
432
|
+
{identities.data?.length !== 0 && (
|
|
433
|
+
<div className="flex flex-col gap-2">
|
|
434
|
+
<div className="flex flex-col gap-2">
|
|
435
|
+
<span className="font-semibold">Authentication</span>
|
|
436
|
+
<IdentitySelector
|
|
437
|
+
value={formState.identity}
|
|
438
|
+
identities={identities.data ?? []}
|
|
439
|
+
setValue={(value) => setValue("identity", value)}
|
|
440
|
+
/>
|
|
541
441
|
</div>
|
|
542
|
-
</
|
|
543
|
-
|
|
442
|
+
</div>
|
|
443
|
+
)}
|
|
444
|
+
|
|
445
|
+
{pathParams.length > 0 && (
|
|
446
|
+
<div className="flex flex-col gap-2">
|
|
447
|
+
<span className="font-semibold">Path Parameters</span>
|
|
448
|
+
<PathParams url={url} control={control} />
|
|
449
|
+
</div>
|
|
450
|
+
)}
|
|
451
|
+
|
|
452
|
+
<div className="flex flex-col gap-2">
|
|
453
|
+
<span className="font-semibold">Query Parameters</span>
|
|
454
|
+
<QueryParams control={control} queryParams={queryParams} />
|
|
455
|
+
</div>
|
|
456
|
+
|
|
457
|
+
<Headers control={control} headers={headers} />
|
|
458
|
+
{isBodySupported && <BodyPanel examples={examples} />}
|
|
544
459
|
</div>
|
|
460
|
+
<div className="w-px bg-muted-foreground/20" />
|
|
545
461
|
<ResultPanel
|
|
546
462
|
queryMutation={queryMutation}
|
|
547
463
|
showPathParamsWarning={formState.pathParams.some(
|
|
@@ -50,8 +50,9 @@ const PlaygroundDialog = (props: PlaygroundDialogProps) => {
|
|
|
50
50
|
</DialogTrigger>
|
|
51
51
|
|
|
52
52
|
<DialogContent
|
|
53
|
-
className="max-w-screen-xl w-full
|
|
53
|
+
className="max-w-screen-xl w-full overflow-hidden p-0"
|
|
54
54
|
aria-describedby={undefined}
|
|
55
|
+
showCloseButton={false}
|
|
55
56
|
>
|
|
56
57
|
<VisuallyHidden>
|
|
57
58
|
<DialogTitle>Playground</DialogTitle>
|
|
@@ -28,7 +28,7 @@ export const QueryParams = ({
|
|
|
28
28
|
const requiredFields = queryParams.map((param) => Boolean(param.isRequired));
|
|
29
29
|
|
|
30
30
|
return (
|
|
31
|
-
<Card className="rounded-lg">
|
|
31
|
+
<Card className="rounded-lg overflow-hidden">
|
|
32
32
|
<div className="w-full ">
|
|
33
33
|
<ParamsGrid>
|
|
34
34
|
{fields.map((field, i) => {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function isBinaryContentType(contentType: string) {
|
|
2
|
+
return /^(application\/octet-stream|image\/|audio\/|video\/|font\/|application\/pdf|application\/zip|application\/x-protobuf|application\/x-binary)/i.test(
|
|
3
|
+
contentType,
|
|
4
|
+
);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const extractFileName = (
|
|
8
|
+
headers: Array<[string, string]>,
|
|
9
|
+
url: string,
|
|
10
|
+
): string => {
|
|
11
|
+
const contentDisposition = headers.find(
|
|
12
|
+
([key]) => key.toLowerCase() === "content-disposition",
|
|
13
|
+
)?.[1];
|
|
14
|
+
|
|
15
|
+
if (contentDisposition) {
|
|
16
|
+
const filenameMatch = contentDisposition.match(
|
|
17
|
+
/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/,
|
|
18
|
+
);
|
|
19
|
+
if (filenameMatch && filenameMatch[1]) {
|
|
20
|
+
return filenameMatch[1].replace(/['"]/g, "");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Extract filename from URL as fallback
|
|
25
|
+
try {
|
|
26
|
+
const urlPath = new URL(url).pathname;
|
|
27
|
+
const fileName = urlPath.split("/").pop() || "download";
|
|
28
|
+
return fileName.includes(".") ? fileName : "download";
|
|
29
|
+
} catch {
|
|
30
|
+
return "download";
|
|
31
|
+
}
|
|
32
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useQuery } from "@tanstack/react-query";
|
|
2
|
-
import { ChevronRightIcon } from "lucide-react";
|
|
2
|
+
import { ChevronRightIcon, DownloadIcon } from "lucide-react";
|
|
3
3
|
import { Fragment, useState } from "react";
|
|
4
|
+
import { Button } from "zudoku/ui/Button.js";
|
|
4
5
|
import { Callout } from "zudoku/ui/Callout.js";
|
|
5
6
|
import {
|
|
6
7
|
Collapsible,
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
} from "zudoku/ui/Select.js";
|
|
17
18
|
import { Card } from "../../../../ui/Card.js";
|
|
18
19
|
import { SyntaxHighlight } from "../../../../ui/SyntaxHighlight.js";
|
|
20
|
+
import { humanFileSize } from "../../../../util/humanFileSize.js";
|
|
19
21
|
import { convertToTypes } from "./convertToTypes.js";
|
|
20
22
|
|
|
21
23
|
const statusCodeMap: Record<number, string> = {
|
|
@@ -31,14 +33,6 @@ const statusCodeMap: Record<number, string> = {
|
|
|
31
33
|
500: "Internal Server Error",
|
|
32
34
|
};
|
|
33
35
|
|
|
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
|
-
|
|
42
36
|
const mimeTypeToLanguage = (mimeType: string) => {
|
|
43
37
|
const mimeTypeMapping = {
|
|
44
38
|
"application/json": "json",
|
|
@@ -101,6 +95,9 @@ export const ResponseTab = ({
|
|
|
101
95
|
time,
|
|
102
96
|
size,
|
|
103
97
|
url,
|
|
98
|
+
isBinary = false,
|
|
99
|
+
fileName,
|
|
100
|
+
blob,
|
|
104
101
|
}: {
|
|
105
102
|
body?: string;
|
|
106
103
|
headers: Array<[string, string]>;
|
|
@@ -108,6 +105,9 @@ export const ResponseTab = ({
|
|
|
108
105
|
time: number;
|
|
109
106
|
size: number;
|
|
110
107
|
url: string;
|
|
108
|
+
isBinary?: boolean;
|
|
109
|
+
fileName?: string;
|
|
110
|
+
blob?: Blob;
|
|
111
111
|
}) => {
|
|
112
112
|
const detectedLanguage = detectLanguage(headers);
|
|
113
113
|
const jsonContent = tryParseJson(body);
|
|
@@ -121,9 +121,22 @@ export const ResponseTab = ({
|
|
|
121
121
|
queryFn: async () => {
|
|
122
122
|
return convertToTypes(JSON.parse(beautifiedBody));
|
|
123
123
|
},
|
|
124
|
-
enabled: view === "types",
|
|
124
|
+
enabled: view === "types" && !isBinary,
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
+
const handleDownload = () => {
|
|
128
|
+
if (blob && fileName) {
|
|
129
|
+
const url = URL.createObjectURL(blob);
|
|
130
|
+
const link = document.createElement("a");
|
|
131
|
+
link.href = url;
|
|
132
|
+
link.download = fileName;
|
|
133
|
+
document.body.appendChild(link);
|
|
134
|
+
link.click();
|
|
135
|
+
document.body.removeChild(link);
|
|
136
|
+
URL.revokeObjectURL(url);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
127
140
|
const sortedHeaders = sortHeadersByRelevance([...headers]);
|
|
128
141
|
const shouldDisableHighlighting = size > SYNTAX_HIGHLIGHT_MAX_SIZE_THRESHOLD;
|
|
129
142
|
|
|
@@ -163,37 +176,59 @@ export const ResponseTab = ({
|
|
|
163
176
|
</Collapsible>
|
|
164
177
|
|
|
165
178
|
<Card className="shadow-none">
|
|
166
|
-
{
|
|
167
|
-
<
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
179
|
+
{isBinary ? (
|
|
180
|
+
<div className="p-4 text-center">
|
|
181
|
+
<div className="flex flex-col items-center gap-4">
|
|
182
|
+
<div className="text-lg font-semibold">Binary Content</div>
|
|
183
|
+
<div className="text-sm text-muted-foreground">
|
|
184
|
+
This response contains binary data that cannot be displayed as
|
|
185
|
+
text.
|
|
186
|
+
</div>
|
|
187
|
+
<Button
|
|
188
|
+
onClick={handleDownload}
|
|
189
|
+
className="flex items-center gap-2"
|
|
190
|
+
disabled={!blob}
|
|
191
|
+
>
|
|
192
|
+
<DownloadIcon className="h-4 w-4" />
|
|
193
|
+
Download {fileName || "file"} ({humanFileSize(size)})
|
|
194
|
+
</Button>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
) : (
|
|
198
|
+
<>
|
|
199
|
+
{shouldDisableHighlighting && (
|
|
200
|
+
<Callout type="info" className="my-0 p-2">
|
|
201
|
+
Code highlight is disabled for responses larger than{" "}
|
|
202
|
+
{humanFileSize(SYNTAX_HIGHLIGHT_MAX_SIZE_THRESHOLD)}
|
|
203
|
+
</Callout>
|
|
204
|
+
)}
|
|
205
|
+
<SyntaxHighlight
|
|
206
|
+
language={
|
|
207
|
+
view === "types"
|
|
208
|
+
? "typescript"
|
|
209
|
+
: view === "raw"
|
|
210
|
+
? jsonContent
|
|
211
|
+
? "plain"
|
|
212
|
+
: detectedLanguage
|
|
213
|
+
: "json"
|
|
214
|
+
}
|
|
215
|
+
showCopy="always"
|
|
216
|
+
disabled={shouldDisableHighlighting}
|
|
217
|
+
noBackground
|
|
218
|
+
className="overflow-x-auto p-4 text-xs max-h-[calc(83.333vh-180px)]"
|
|
219
|
+
code={
|
|
220
|
+
(view === "raw"
|
|
221
|
+
? body
|
|
222
|
+
: view === "types"
|
|
223
|
+
? types.data?.lines.join("\n")
|
|
224
|
+
: beautifiedBody) ?? ""
|
|
225
|
+
}
|
|
226
|
+
/>
|
|
227
|
+
</>
|
|
171
228
|
)}
|
|
172
|
-
<SyntaxHighlight
|
|
173
|
-
language={
|
|
174
|
-
view === "types"
|
|
175
|
-
? "typescript"
|
|
176
|
-
: view === "raw"
|
|
177
|
-
? jsonContent
|
|
178
|
-
? "plain"
|
|
179
|
-
: detectedLanguage
|
|
180
|
-
: "json"
|
|
181
|
-
}
|
|
182
|
-
showCopy="always"
|
|
183
|
-
disabled={shouldDisableHighlighting}
|
|
184
|
-
noBackground
|
|
185
|
-
className="overflow-x-auto p-4 text-xs max-h-[calc(83.333vh-180px)]"
|
|
186
|
-
code={
|
|
187
|
-
(view === "raw"
|
|
188
|
-
? body
|
|
189
|
-
: view === "types"
|
|
190
|
-
? types.data?.lines.join("\n")
|
|
191
|
-
: beautifiedBody) ?? ""
|
|
192
|
-
}
|
|
193
|
-
/>
|
|
194
229
|
</Card>
|
|
195
230
|
<div className="flex gap-2 justify-between items-center">
|
|
196
|
-
<div className="flex text-xs gap-
|
|
231
|
+
<div className="flex text-xs gap-5 border bg-muted rounded-md p-2 items-center h-8 font-mono">
|
|
197
232
|
<div>
|
|
198
233
|
<span className="text-muted-foreground">Status</span> {status}{" "}
|
|
199
234
|
{statusCodeMap[status] ?? ""}
|
|
@@ -207,7 +242,7 @@ export const ResponseTab = ({
|
|
|
207
242
|
{humanFileSize(size)}
|
|
208
243
|
</div>
|
|
209
244
|
</div>
|
|
210
|
-
{jsonContent && (
|
|
245
|
+
{jsonContent && !isBinary && (
|
|
211
246
|
<div>
|
|
212
247
|
<Select
|
|
213
248
|
value={view}
|