zudoku 0.26.1 → 0.27.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.
Files changed (214) hide show
  1. package/dist/app/main.d.ts +1 -1
  2. package/dist/app/main.js +19 -7
  3. package/dist/app/main.js.map +1 -1
  4. package/dist/config/loader.js +1 -1
  5. package/dist/config/loader.js.map +1 -1
  6. package/dist/config/validators/InputSidebarSchema.d.ts +2 -2
  7. package/dist/config/validators/common.d.ts +67 -0
  8. package/dist/config/validators/common.js +5 -0
  9. package/dist/config/validators/common.js.map +1 -1
  10. package/dist/config/validators/validate.d.ts +29 -0
  11. package/dist/lib/components/AnchorLink.js +5 -2
  12. package/dist/lib/components/AnchorLink.js.map +1 -1
  13. package/dist/lib/components/Header.js +1 -1
  14. package/dist/lib/components/Header.js.map +1 -1
  15. package/dist/lib/components/Heading.d.ts +1 -1
  16. package/dist/lib/components/Markdown.d.ts +2 -2
  17. package/dist/lib/components/Markdown.js +3 -1
  18. package/dist/lib/components/Markdown.js.map +1 -1
  19. package/dist/lib/components/StatusPage.d.ts +7 -0
  20. package/dist/lib/components/StatusPage.js +71 -0
  21. package/dist/lib/components/StatusPage.js.map +1 -0
  22. package/dist/lib/components/SyntaxHighlight.d.ts +2 -1
  23. package/dist/lib/components/SyntaxHighlight.js +2 -2
  24. package/dist/lib/components/SyntaxHighlight.js.map +1 -1
  25. package/dist/lib/components/ThemeSwitch.js +4 -4
  26. package/dist/lib/components/ThemeSwitch.js.map +1 -1
  27. package/dist/lib/components/cache.d.ts +6 -0
  28. package/dist/lib/components/cache.js +13 -0
  29. package/dist/lib/components/cache.js.map +1 -0
  30. package/dist/lib/components/context/ViewportAnchorContext.js +16 -4
  31. package/dist/lib/components/context/ViewportAnchorContext.js.map +1 -1
  32. package/dist/lib/components/context/ZudokuContext.js +2 -1
  33. package/dist/lib/components/context/ZudokuContext.js.map +1 -1
  34. package/dist/lib/components/index.d.ts +9 -2
  35. package/dist/lib/components/index.js +3 -0
  36. package/dist/lib/components/index.js.map +1 -1
  37. package/dist/lib/core/RouteGuard.d.ts +1 -0
  38. package/dist/lib/core/RouteGuard.js +28 -0
  39. package/dist/lib/core/RouteGuard.js.map +1 -0
  40. package/dist/lib/core/ZudokuContext.d.ts +4 -2
  41. package/dist/lib/core/ZudokuContext.js +9 -7
  42. package/dist/lib/core/ZudokuContext.js.map +1 -1
  43. package/dist/lib/oas/graphql/circular.d.ts +3 -0
  44. package/dist/lib/oas/graphql/circular.js +27 -0
  45. package/dist/lib/oas/graphql/circular.js.map +1 -0
  46. package/dist/lib/oas/graphql/index.js +5 -6
  47. package/dist/lib/oas/graphql/index.js.map +1 -1
  48. package/dist/lib/oas/parser/dereference/index.d.ts +0 -1
  49. package/dist/lib/oas/parser/dereference/index.js +1 -1
  50. package/dist/lib/oas/parser/dereference/index.js.map +1 -1
  51. package/dist/lib/plugins/openapi/OperationListItem.js +1 -1
  52. package/dist/lib/plugins/openapi/OperationListItem.js.map +1 -1
  53. package/dist/lib/plugins/openapi/ParameterListItem.js +1 -1
  54. package/dist/lib/plugins/openapi/ParameterListItem.js.map +1 -1
  55. package/dist/lib/plugins/openapi/Sidecar.js +2 -2
  56. package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
  57. package/dist/lib/plugins/openapi/index.js +4 -11
  58. package/dist/lib/plugins/openapi/index.js.map +1 -1
  59. package/dist/lib/plugins/openapi/interfaces.d.ts +7 -2
  60. package/dist/lib/plugins/openapi/playground/ExamplesDropdown.js +5 -5
  61. package/dist/lib/plugins/openapi/playground/ExamplesDropdown.js.map +1 -1
  62. package/dist/lib/plugins/openapi/playground/Headers.js +17 -16
  63. package/dist/lib/plugins/openapi/playground/Headers.js.map +1 -1
  64. package/dist/lib/plugins/openapi/playground/ParamsGrid.d.ts +5 -0
  65. package/dist/lib/plugins/openapi/playground/ParamsGrid.js +4 -0
  66. package/dist/lib/plugins/openapi/playground/ParamsGrid.js.map +1 -0
  67. package/dist/lib/plugins/openapi/playground/PathParams.js +4 -12
  68. package/dist/lib/plugins/openapi/playground/PathParams.js.map +1 -1
  69. package/dist/lib/plugins/openapi/playground/Playground.d.ts +13 -0
  70. package/dist/lib/plugins/openapi/playground/Playground.js +19 -31
  71. package/dist/lib/plugins/openapi/playground/Playground.js.map +1 -1
  72. package/dist/lib/plugins/openapi/playground/PlaygroundDialog.js +1 -1
  73. package/dist/lib/plugins/openapi/playground/PlaygroundDialog.js.map +1 -1
  74. package/dist/lib/plugins/openapi/playground/QueryParams.js +4 -3
  75. package/dist/lib/plugins/openapi/playground/QueryParams.js.map +1 -1
  76. package/dist/lib/plugins/openapi/playground/SubmitButton.d.ts +7 -0
  77. package/dist/lib/plugins/openapi/playground/SubmitButton.js +22 -0
  78. package/dist/lib/plugins/openapi/playground/SubmitButton.js.map +1 -0
  79. package/dist/lib/plugins/openapi/playground/result-panel/RequestTab.d.ts +7 -0
  80. package/dist/lib/plugins/openapi/playground/result-panel/RequestTab.js +11 -0
  81. package/dist/lib/plugins/openapi/playground/result-panel/RequestTab.js.map +1 -0
  82. package/dist/lib/plugins/openapi/playground/result-panel/ResponseTab.d.ts +8 -0
  83. package/dist/lib/plugins/openapi/playground/result-panel/ResponseTab.js +95 -0
  84. package/dist/lib/plugins/openapi/playground/result-panel/ResponseTab.js.map +1 -0
  85. package/dist/lib/plugins/openapi/playground/result-panel/ResultPanel.d.ts +7 -0
  86. package/dist/lib/plugins/openapi/playground/result-panel/ResultPanel.js +16 -0
  87. package/dist/lib/plugins/openapi/playground/result-panel/ResultPanel.js.map +1 -0
  88. package/dist/lib/plugins/openapi/playground/result-panel/convertToTypes.d.ts +10 -0
  89. package/dist/lib/plugins/openapi/playground/result-panel/convertToTypes.js +32 -0
  90. package/dist/lib/plugins/openapi/playground/result-panel/convertToTypes.js.map +1 -0
  91. package/dist/lib/plugins/openapi/playground/result-panel/convertToTypes.test.d.ts +1 -0
  92. package/dist/lib/plugins/openapi/playground/result-panel/convertToTypes.test.js +56 -0
  93. package/dist/lib/plugins/openapi/playground/result-panel/convertToTypes.test.js.map +1 -0
  94. package/dist/lib/plugins/openapi/schema/SchemaComponents.js +1 -1
  95. package/dist/lib/plugins/openapi/schema/SchemaComponents.js.map +1 -1
  96. package/dist/lib/ui/Command.js +1 -1
  97. package/dist/lib/ui/Command.js.map +1 -1
  98. package/dist/lib/ui/Select.js +2 -2
  99. package/dist/lib/ui/Select.js.map +1 -1
  100. package/dist/lib/util/MdxComponents.js +2 -2
  101. package/dist/lib/util/MdxComponents.js.map +1 -1
  102. package/dist/lib/util/useScrollToAnchor.d.ts +1 -0
  103. package/dist/lib/util/useScrollToAnchor.js +26 -15
  104. package/dist/lib/util/useScrollToAnchor.js.map +1 -1
  105. package/dist/vite/plugin-api.js +9 -3
  106. package/dist/vite/plugin-api.js.map +1 -1
  107. package/dist/vite/prerender.js +1 -0
  108. package/dist/vite/prerender.js.map +1 -1
  109. package/dist/zuplo/enrich-with-zuplo.js +1 -1
  110. package/dist/zuplo/enrich-with-zuplo.js.map +1 -1
  111. package/dist/zuplo/with-zuplo.d.ts +2 -1
  112. package/dist/zuplo/with-zuplo.js +3 -1
  113. package/dist/zuplo/with-zuplo.js.map +1 -1
  114. package/lib/{AuthenticationPlugin-C9SwOxkc.js → AuthenticationPlugin-CO_YCd2x.js} +3 -3
  115. package/lib/{AuthenticationPlugin-C9SwOxkc.js.map → AuthenticationPlugin-CO_YCd2x.js.map} +1 -1
  116. package/lib/{Markdown-DFN6p0J-.js → Markdown-B8o9Qz4q.js} +1197 -1186
  117. package/lib/{Markdown-DFN6p0J-.js.map → Markdown-B8o9Qz4q.js.map} +1 -1
  118. package/lib/{MdxPage-D9c4z09Q.js → MdxPage-BxRt3Ly7.js} +5 -5
  119. package/lib/{MdxPage-D9c4z09Q.js.map → MdxPage-BxRt3Ly7.js.map} +1 -1
  120. package/lib/OperationList-DH-zIgtq.js +5160 -0
  121. package/lib/OperationList-DH-zIgtq.js.map +1 -0
  122. package/lib/{Route-VdmEyOD0.js → Route-DJ0ZlVq1.js} +3 -3
  123. package/lib/{Route-VdmEyOD0.js.map → Route-DJ0ZlVq1.js.map} +1 -1
  124. package/lib/{Select-D3O7wISy.js → Select-B7UXR0SB.js} +61 -61
  125. package/lib/Select-B7UXR0SB.js.map +1 -0
  126. package/lib/{SlotletProvider-_3zzX_g_.js → SlotletProvider-CtIp8rP3.js} +4 -4
  127. package/lib/{SlotletProvider-_3zzX_g_.js.map → SlotletProvider-CtIp8rP3.js.map} +1 -1
  128. package/lib/{SyntaxHighlight-CJCSPG1F.js → SyntaxHighlight-C1w1QPdY.js} +300 -295
  129. package/lib/{SyntaxHighlight-CJCSPG1F.js.map → SyntaxHighlight-C1w1QPdY.js.map} +1 -1
  130. package/lib/{ZudokuContext-DeQZEp-x.js → ZudokuContext-8jts0fF3.js} +259 -248
  131. package/lib/ZudokuContext-8jts0fF3.js.map +1 -0
  132. package/lib/{chunk-SYFQ2XB5-BF5IDYrB.js → chunk-SYFQ2XB5-BPvC-soB.js} +5 -5
  133. package/lib/{chunk-SYFQ2XB5-BF5IDYrB.js.map → chunk-SYFQ2XB5-BPvC-soB.js.map} +1 -1
  134. package/lib/circular-Dgpd6AN-.js +15397 -0
  135. package/lib/circular-Dgpd6AN-.js.map +1 -0
  136. package/lib/{createServer-BcaswoFO.js → createServer-BV0tHzLK.js} +3450 -5577
  137. package/lib/createServer-BV0tHzLK.js.map +1 -0
  138. package/lib/{hook-BRQEDRbn.js → hook-BG02esyv.js} +2 -2
  139. package/lib/{hook-BRQEDRbn.js.map → hook-BG02esyv.js.map} +1 -1
  140. package/lib/index-DmqsUPcm.js +1915 -0
  141. package/lib/index-DmqsUPcm.js.map +1 -0
  142. package/lib/ui/Command.js +27 -27
  143. package/lib/ui/Command.js.map +1 -1
  144. package/lib/ui/Select.js +2 -2
  145. package/lib/ui/Select.js.map +1 -1
  146. package/lib/{useExposedProps-CetwhZpP.js → useExposedProps-BLKFBylA.js} +2 -2
  147. package/lib/{useExposedProps-CetwhZpP.js.map → useExposedProps-BLKFBylA.js.map} +1 -1
  148. package/lib/useScrollToAnchor-Bl6mz9_x.js +288 -0
  149. package/lib/useScrollToAnchor-Bl6mz9_x.js.map +1 -0
  150. package/lib/zudoku.auth-clerk.js +1 -1
  151. package/lib/zudoku.auth-openid.js +3 -3
  152. package/lib/zudoku.components.js +753 -991
  153. package/lib/zudoku.components.js.map +1 -1
  154. package/lib/zudoku.plugin-api-catalog.js +3 -3
  155. package/lib/zudoku.plugin-api-keys.js +5 -5
  156. package/lib/zudoku.plugin-custom-pages.js +2 -2
  157. package/lib/zudoku.plugin-markdown.js +1 -1
  158. package/lib/zudoku.plugin-openapi.js +4 -4
  159. package/lib/zudoku.plugin-redirect.js +1 -1
  160. package/package.json +2 -2
  161. package/src/app/main.tsx +26 -7
  162. package/src/lib/components/AnchorLink.tsx +5 -2
  163. package/src/lib/components/Header.tsx +1 -1
  164. package/src/lib/components/Markdown.tsx +14 -15
  165. package/src/lib/components/StatusPage.tsx +91 -0
  166. package/src/lib/components/SyntaxHighlight.tsx +14 -0
  167. package/src/lib/components/ThemeSwitch.tsx +14 -15
  168. package/src/lib/components/cache.ts +15 -0
  169. package/src/lib/components/context/ViewportAnchorContext.tsx +20 -6
  170. package/src/lib/components/context/ZudokuContext.ts +3 -1
  171. package/src/lib/components/index.ts +7 -0
  172. package/src/lib/core/RouteGuard.tsx +35 -0
  173. package/src/lib/core/ZudokuContext.ts +9 -8
  174. package/src/lib/oas/graphql/circular.ts +29 -0
  175. package/src/lib/oas/graphql/index.ts +9 -9
  176. package/src/lib/oas/parser/dereference/index.ts +1 -2
  177. package/src/lib/plugins/openapi/OperationListItem.tsx +0 -2
  178. package/src/lib/plugins/openapi/ParameterListItem.tsx +1 -0
  179. package/src/lib/plugins/openapi/Sidecar.tsx +3 -2
  180. package/src/lib/plugins/openapi/index.tsx +9 -15
  181. package/src/lib/plugins/openapi/interfaces.ts +10 -2
  182. package/src/lib/plugins/openapi/playground/ExamplesDropdown.tsx +30 -27
  183. package/src/lib/plugins/openapi/playground/Headers.tsx +65 -65
  184. package/src/lib/plugins/openapi/playground/ParamsGrid.tsx +8 -0
  185. package/src/lib/plugins/openapi/playground/PathParams.tsx +34 -74
  186. package/src/lib/plugins/openapi/playground/Playground.tsx +64 -116
  187. package/src/lib/plugins/openapi/playground/PlaygroundDialog.tsx +1 -1
  188. package/src/lib/plugins/openapi/playground/QueryParams.tsx +46 -45
  189. package/src/lib/plugins/openapi/playground/SubmitButton.tsx +75 -0
  190. package/src/lib/plugins/openapi/playground/result-panel/RequestTab.tsx +73 -0
  191. package/src/lib/plugins/openapi/playground/result-panel/ResponseTab.tsx +210 -0
  192. package/src/lib/plugins/openapi/playground/result-panel/ResultPanel.tsx +101 -0
  193. package/src/lib/plugins/openapi/playground/result-panel/convertToTypes.test.ts +64 -0
  194. package/src/lib/plugins/openapi/playground/result-panel/convertToTypes.ts +36 -0
  195. package/src/lib/plugins/openapi/schema/SchemaComponents.tsx +1 -1
  196. package/src/lib/ui/Command.tsx +1 -1
  197. package/src/lib/ui/Select.tsx +1 -1
  198. package/src/lib/util/MdxComponents.tsx +2 -1
  199. package/src/lib/util/useScrollToAnchor.ts +32 -15
  200. package/dist/lib/plugins/openapi/playground/ResponseTab.d.ts +0 -4
  201. package/dist/lib/plugins/openapi/playground/ResponseTab.js +0 -42
  202. package/dist/lib/plugins/openapi/playground/ResponseTab.js.map +0 -1
  203. package/lib/AnchorLink-bObQitZv.js +0 -34
  204. package/lib/AnchorLink-bObQitZv.js.map +0 -1
  205. package/lib/OperationList-DGJWDx1G.js +0 -5148
  206. package/lib/OperationList-DGJWDx1G.js.map +0 -1
  207. package/lib/Select-D3O7wISy.js.map +0 -1
  208. package/lib/ZudokuContext-DeQZEp-x.js.map +0 -1
  209. package/lib/createServer-BcaswoFO.js.map +0 -1
  210. package/lib/index-CXRrqOIl.js +0 -1750
  211. package/lib/index-CXRrqOIl.js.map +0 -1
  212. package/lib/index-TaRXY2w1.js +0 -43
  213. package/lib/index-TaRXY2w1.js.map +0 -1
  214. package/src/lib/plugins/openapi/playground/ResponseTab.tsx +0 -76
@@ -3,6 +3,7 @@ import { InfoIcon } from "lucide-react";
3
3
  import { Fragment, useEffect, useRef, useTransition } from "react";
4
4
  import { FormProvider, useForm } from "react-hook-form";
5
5
  import { Alert, AlertDescription, AlertTitle } from "zudoku/ui/Alert.js";
6
+
6
7
  import { Label } from "zudoku/ui/Label.js";
7
8
  import { RadioGroup, RadioGroupItem } from "zudoku/ui/RadioGroup.js";
8
9
  import {
@@ -15,10 +16,7 @@ import {
15
16
  import { Textarea } from "zudoku/ui/Textarea.js";
16
17
  import { useSelectedServerStore } from "../../../authentication/state.js";
17
18
  import { useApiIdentities } from "../../../components/context/ZudokuContext.js";
18
- import { Spinner } from "../../../components/Spinner.js";
19
- import { Button } from "../../../ui/Button.js";
20
- import { Callout } from "../../../ui/Callout.js";
21
- import { Card, CardContent, CardHeader, CardTitle } from "../../../ui/Card.js";
19
+ import { Card } from "../../../ui/Card.js";
22
20
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../../ui/Tabs.js";
23
21
  import { cn } from "../../../util/cn.js";
24
22
  import { ColorizedParam } from "../ColorizedParam.js";
@@ -28,28 +26,17 @@ import ExamplesDropdown from "./ExamplesDropdown.js";
28
26
  import { Headers } from "./Headers.js";
29
27
  import { PathParams } from "./PathParams.js";
30
28
  import { QueryParams } from "./QueryParams.js";
31
- import { ResponseTab } from "./ResponseTab.js";
29
+ import { ResultPanel } from "./result-panel/ResultPanel.js";
30
+ import SubmitButton from "./SubmitButton.js";
32
31
 
33
32
  export const NO_IDENTITY = "__none";
34
33
 
35
- const statusCodeMap: Record<number, string> = {
36
- 200: "OK",
37
- 201: "Created",
38
- 202: "Accepted",
39
- 204: "No Content",
40
- 400: "Bad Request",
41
- 401: "Unauthorized",
42
- 403: "Forbidden",
43
- 404: "Not Found",
44
- 405: "Method Not Allowed",
45
- 500: "Internal Server Error",
46
- };
47
-
48
34
  export type Header = {
49
35
  name: string;
50
36
  defaultValue?: string;
51
37
  defaultActive?: boolean;
52
38
  };
39
+
53
40
  export type QueryParam = {
54
41
  name: string;
55
42
  defaultValue?: string;
@@ -81,6 +68,20 @@ export type PlaygroundForm = {
81
68
  identity?: string;
82
69
  };
83
70
 
71
+ export type PlaygroundResult = {
72
+ status: number;
73
+ headers: Array<[string, string]>;
74
+ size: number;
75
+ body: string;
76
+ time: number;
77
+ request: {
78
+ method: string;
79
+ url: string;
80
+ headers: Array<[string, string]>;
81
+ body?: string;
82
+ };
83
+ };
84
+
84
85
  export type PlaygroundContentProps = {
85
86
  server: string;
86
87
  servers?: string[];
@@ -158,20 +159,23 @@ export const Playground = ({
158
159
  }
159
160
  }, [setValue, identities.data]);
160
161
 
162
+ const formRef = useRef<HTMLFormElement>(null);
163
+
161
164
  const queryMutation = useMutation({
162
165
  mutationFn: async (data: PlaygroundForm) => {
163
- const requestUrl = createUrl(selectedServer ?? server, url, data);
164
166
  const start = performance.now();
165
-
166
- const request = new Request(requestUrl, {
167
- method: method.toUpperCase(),
168
- headers: Object.fromEntries(
169
- data.headers
170
- .filter((h) => h.name && h.active)
171
- .map((header) => [header.name, header.value]),
172
- ),
173
- body: data.body ? data.body : undefined,
174
- });
167
+ const request = new Request(
168
+ createUrl(selectedServer ?? server, url, data),
169
+ {
170
+ method: method.toUpperCase(),
171
+ headers: Object.fromEntries(
172
+ data.headers
173
+ .filter((h) => h.name && h.active)
174
+ .map((header) => [header.name, header.value]),
175
+ ),
176
+ body: data.body ? data.body : undefined,
177
+ },
178
+ );
175
179
 
176
180
  if (data.identity !== NO_IDENTITY) {
177
181
  identities.data
@@ -187,13 +191,25 @@ export const Playground = ({
187
191
 
188
192
  const body = await response.text();
189
193
 
194
+ const url = new URL(request.url);
195
+
190
196
  return {
191
197
  status: response.status,
192
- headers: response.headers,
198
+ headers: Array.from(response.headers.entries()),
193
199
  size: body.length,
194
200
  body,
195
201
  time,
196
- };
202
+ request: {
203
+ method: request.method.toUpperCase(),
204
+ url: request.url,
205
+ headers: [
206
+ ["Host", url.host],
207
+ ["User-Agent", "Zudoku Playground"],
208
+ ...Array.from(request.headers.entries()),
209
+ ],
210
+ body: data.body ? data.body : undefined,
211
+ },
212
+ } satisfies PlaygroundResult;
197
213
  } catch (error) {
198
214
  if (error instanceof TypeError) {
199
215
  throw new Error(
@@ -237,8 +253,6 @@ export const Playground = ({
237
253
  );
238
254
  });
239
255
 
240
- const headerEntries = Array.from(queryMutation.data?.headers.entries() ?? []);
241
-
242
256
  const urlQueryParams = formState.queryParams
243
257
  .filter((p) => p.active)
244
258
  .map((p, i, arr) => (
@@ -282,24 +296,30 @@ export const Playground = ({
282
296
  <FormProvider
283
297
  {...{ register, control, handleSubmit, watch, setValue, ...form }}
284
298
  >
285
- <form onSubmit={handleSubmit((data) => queryMutation.mutateAsync(data))}>
286
- <div className="grid grid-cols-[8fr_7fr] text-sm h-full">
299
+ <form
300
+ onSubmit={handleSubmit((data) => queryMutation.mutateAsync(data))}
301
+ ref={formRef}
302
+ >
303
+ <div className="grid grid-cols-2 text-sm h-full">
287
304
  <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">
288
305
  <div className="flex gap-2 items-stretch">
289
306
  <div className="flex flex-1 items-center w-full border rounded-md">
290
307
  <div className="border-r p-2 bg-muted rounded-l-md self-stretch font-semibold font-mono flex items-center">
291
308
  {method.toUpperCase()}
292
309
  </div>
293
- <div className="flex items-center flex-wrap p-2 font-mono text-xs">
310
+ <div className="flex items-center flex-wrap p-2 font-mono text-xs break-all">
294
311
  {serverSelect}
295
312
  {path}
296
313
  {urlQueryParams.length > 0 ? "?" : ""}
297
314
  {urlQueryParams}
298
315
  </div>
299
316
  </div>
300
- <Button type="submit" className="h-auto flex gap-1">
301
- Send
302
- </Button>
317
+
318
+ <SubmitButton
319
+ identities={identities.data ?? []}
320
+ formRef={formRef}
321
+ disabled={form.formState.isSubmitting}
322
+ />
303
323
  </div>
304
324
  <Tabs defaultValue="parameters">
305
325
  <div className="flex flex-wrap gap-1 justify-between">
@@ -438,84 +458,12 @@ export const Playground = ({
438
458
  </TabsContent>
439
459
  </Tabs>
440
460
  </div>
441
- <div className="min-w-0 p-8 bg-muted/70">
442
- {queryMutation.error ? (
443
- <div className="flex flex-col gap-2">
444
- {formState.pathParams.some((p) => p.value === "") && (
445
- <Callout type="caution">
446
- Some path parameters are missing values. Please fill them in
447
- to ensure the request is sent correctly.
448
- </Callout>
449
- )}
450
- <Card>
451
- <CardHeader>
452
- <CardTitle>Request failed</CardTitle>
453
- </CardHeader>
454
- <CardContent>
455
- Error:{" "}
456
- {queryMutation.error.message ||
457
- String(queryMutation.error) ||
458
- "Unexpected error"}
459
- </CardContent>
460
- </Card>
461
- </div>
462
- ) : queryMutation.data ? (
463
- <div className="flex flex-col gap-2">
464
- <div className="flex gap-2">
465
- <div className="flex text-xs gap-6">
466
- <div>
467
- Status: {queryMutation.data.status}{" "}
468
- {statusCodeMap[queryMutation.data.status] ?? ""}
469
- </div>
470
- <div>Time: {queryMutation.data.time.toFixed(0)}ms</div>
471
- <div>Size: {queryMutation.data.size} B</div>
472
- </div>
473
- </div>
474
- <Tabs defaultValue="response">
475
- <TabsList>
476
- <TabsTrigger value="response">Response</TabsTrigger>
477
- <TabsTrigger value="headers">
478
- {headerEntries.length
479
- ? `Headers (${headerEntries.length})`
480
- : "No headers"}
481
- </TabsTrigger>
482
- </TabsList>
483
-
484
- <TabsContent value="response">
485
- <ResponseTab
486
- headers={queryMutation.data.headers}
487
- body={queryMutation.data.body}
488
- />
489
- </TabsContent>
490
- <TabsContent value="headers">
491
- <Card
492
- // playground dialog has h-5/6 ≈ 83.333vh
493
- className="max-h-[calc(83.333vh-140px)] overflow-y-auto grid grid-cols-2 w-full gap-2.5 font-mono text-xs shadow-none p-4"
494
- >
495
- <div className="font-semibold">Key</div>
496
- <div className="font-semibold">Value</div>
497
- {headerEntries.map(([key, value]) => (
498
- <Fragment key={key}>
499
- <div>{key}</div>
500
- <div className="break-words">{value}</div>
501
- </Fragment>
502
- ))}
503
- </Card>
504
- </TabsContent>
505
- </Tabs>
506
- </div>
507
- ) : (
508
- <div className="grid place-items-center h-full">
509
- <span className="text-[16px] font-semibold text-muted-foreground">
510
- {queryMutation.isPending ? (
511
- <Spinner />
512
- ) : (
513
- "Send a request first to see the response here"
514
- )}
515
- </span>
516
- </div>
461
+ <ResultPanel
462
+ queryMutation={queryMutation}
463
+ showPathParamsWarning={formState.pathParams.some(
464
+ (p) => p.value === "",
517
465
  )}
518
- </div>
466
+ />
519
467
  </div>
520
468
  </form>
521
469
  </FormProvider>
@@ -47,7 +47,7 @@ const PlaygroundDialog = (props: PlaygroundDialogProps) => {
47
47
  </DialogTrigger>
48
48
 
49
49
  <DialogContent
50
- className="max-w-screen-xl w-full h-5/6 overflow-auto p-0"
50
+ className="max-w-screen-xl w-full h-5/6 overflow-hidden p-0"
51
51
  aria-describedby={undefined}
52
52
  >
53
53
  <VisuallyHidden>
@@ -9,6 +9,7 @@ import { Checkbox } from "zudoku/ui/Checkbox.js";
9
9
  import { Autocomplete } from "../../../components/Autocomplete.js";
10
10
  import { Input } from "../../../ui/Input.js";
11
11
  import { InlineInput } from "./InlineInput.js";
12
+ import ParamsGrid from "./ParamsGrid.js";
12
13
  import { type PlaygroundForm, type QueryParam } from "./Playground.js";
13
14
 
14
15
  export const QueryParams = ({
@@ -34,51 +35,51 @@ export const QueryParams = ({
34
35
  (param) => param.name === field.name,
35
36
  );
36
37
  return (
37
- <div
38
- key={field.id}
39
- className="hover:bg-accent/40 grid grid-cols-[min-content_1fr_1fr] gap-2 items-center px-3"
40
- >
41
- <Controller
42
- control={control}
43
- name={`queryParams.${i}.active`}
44
- render={({ field }) => (
45
- <Checkbox
46
- variant="outline"
47
- id={`queryParams.${i}.active`}
48
- className="mr-2"
49
- checked={field.value}
50
- onCheckedChange={field.onChange}
51
- />
52
- )}
53
- />
54
- <Controller
55
- control={control}
56
- render={({ field }) =>
57
- !requiredFields[i] ? (
58
- <Autocomplete
59
- value={field.value}
60
- options={queryParams.map((param) => param.name)}
61
- onChange={(e) => {
62
- field.onChange(e);
63
- }}
64
- className="border-0 font-mono text-xs bg-transparent hover:bg-transparent"
38
+ <ParamsGrid key={field.id}>
39
+ <div className="flex items-center">
40
+ <Controller
41
+ control={control}
42
+ name={`queryParams.${i}.active`}
43
+ render={({ field }) => (
44
+ <Checkbox
45
+ variant="outline"
46
+ id={`queryParams.${i}.active`}
47
+ className="mr-2"
48
+ checked={field.value}
49
+ onCheckedChange={field.onChange}
65
50
  />
66
- ) : (
67
- <InlineInput asChild>
68
- <label
69
- className="flex items-center cursor-pointer gap-1"
70
- htmlFor={`queryParams.${i}.active`}
71
- title={requiredFields[i] ? "Required field" : undefined}
72
- >
73
- {field.value}
74
- {requiredFields[i] && <sup>&nbsp;*</sup>}
75
- </label>
76
- </InlineInput>
77
- )
78
- }
79
- name={`queryParams.${i}.name`}
80
- />
81
-
51
+ )}
52
+ />
53
+ <Controller
54
+ control={control}
55
+ render={({ field }) =>
56
+ !requiredFields[i] ? (
57
+ <Autocomplete
58
+ value={field.value}
59
+ options={queryParams.map((param) => param.name)}
60
+ onChange={(e) => {
61
+ field.onChange(e);
62
+ }}
63
+ className="border-0 font-mono text-xs bg-transparent hover:bg-transparent"
64
+ />
65
+ ) : (
66
+ <InlineInput asChild>
67
+ <label
68
+ className="flex items-center cursor-pointer gap-1"
69
+ htmlFor={`queryParams.${i}.active`}
70
+ title={
71
+ requiredFields[i] ? "Required field" : undefined
72
+ }
73
+ >
74
+ {field.value}
75
+ {requiredFields[i] && <sup>&nbsp;*</sup>}
76
+ </label>
77
+ </InlineInput>
78
+ )
79
+ }
80
+ name={`queryParams.${i}.name`}
81
+ />
82
+ </div>
82
83
  <div className="flex justify-between items-center">
83
84
  <Controller
84
85
  control={control}
@@ -117,7 +118,7 @@ export const QueryParams = ({
117
118
  name={`queryParams.${i}.value`}
118
119
  />
119
120
  </div>
120
- </div>
121
+ </ParamsGrid>
121
122
  );
122
123
  })}
123
124
  </div>
@@ -0,0 +1,75 @@
1
+ import { ChevronDownIcon } from "lucide-react";
2
+ import { useState } from "react";
3
+ import { useFormContext } from "react-hook-form";
4
+ import { Button } from "zudoku/ui/Button.js";
5
+ import {
6
+ DropdownMenu,
7
+ DropdownMenuContent,
8
+ DropdownMenuItem,
9
+ DropdownMenuTrigger,
10
+ } from "zudoku/ui/DropdownMenu.js";
11
+ import { RadioGroup, RadioGroupItem } from "zudoku/ui/RadioGroup.js";
12
+ import { ApiIdentity } from "../../../core/ZudokuContext.js";
13
+ import { NO_IDENTITY } from "./Playground.js";
14
+
15
+ const SubmitButton = ({
16
+ identities,
17
+ formRef,
18
+ disabled,
19
+ }: {
20
+ identities: ApiIdentity[];
21
+ formRef?: React.RefObject<HTMLFormElement | null>;
22
+ disabled?: boolean;
23
+ }) => {
24
+ const { setValue } = useFormContext();
25
+ const [dropdownValue, setDropdownValue] = useState<string | undefined>();
26
+ if (identities.length === 0) {
27
+ return <Button disabled={disabled}>Send</Button>;
28
+ }
29
+ return (
30
+ <div className="flex">
31
+ <Button
32
+ className="rounded-r-none inset-shadow-sm"
33
+ disabled={disabled}
34
+ onClick={() => formRef?.current?.requestSubmit()}
35
+ >
36
+ Send
37
+ </Button>
38
+ <DropdownMenu>
39
+ <DropdownMenuTrigger asChild>
40
+ <Button
41
+ disabled={disabled}
42
+ className="rounded-l-none border-l border-border/40 inset-shadow-sm w-6"
43
+ size="icon"
44
+ >
45
+ <ChevronDownIcon className="w-4 h-4" />
46
+ </Button>
47
+ </DropdownMenuTrigger>
48
+ <RadioGroup value={dropdownValue}>
49
+ <DropdownMenuContent className="w-56" align="end" alignOffset={-150}>
50
+ {[{ id: NO_IDENTITY, label: "None" }, ...identities].map(
51
+ (identity) => (
52
+ <DropdownMenuItem
53
+ key={identity.id}
54
+ onClick={() => {
55
+ setDropdownValue(identity.id);
56
+ setValue("identity", identity.id);
57
+ formRef?.current?.requestSubmit();
58
+ }}
59
+ onMouseEnter={() => setDropdownValue(identity.id)}
60
+ onMouseLeave={() => setDropdownValue(undefined)}
61
+ >
62
+ <RadioGroupItem value={identity.id} className="mr-2" />
63
+
64
+ {identity.label}
65
+ </DropdownMenuItem>
66
+ ),
67
+ )}
68
+ </DropdownMenuContent>
69
+ </RadioGroup>
70
+ </DropdownMenu>
71
+ </div>
72
+ );
73
+ };
74
+
75
+ export default SubmitButton;
@@ -0,0 +1,73 @@
1
+ import { ChevronRightIcon } from "lucide-react";
2
+ import { Fragment } from "react";
3
+ import {
4
+ Collapsible,
5
+ CollapsibleContent,
6
+ CollapsibleTrigger,
7
+ } from "../../../../ui/Collapsible.js";
8
+ import { cn } from "../../../../util/cn.js";
9
+ import { methodForColor } from "../../util/methodToColor.js";
10
+
11
+ export const RequestTab = ({
12
+ method,
13
+ url,
14
+ headers,
15
+ body,
16
+ }: {
17
+ method: string;
18
+ url: string;
19
+ headers: Array<[string, string]>;
20
+ body?: string;
21
+ }) => {
22
+ return (
23
+ <div className="flex flex-col gap-2 font-mono text-xs">
24
+ <div className="gap-2 p-2 bg-muted rounded-md">
25
+ <span className={cn(methodForColor(method), "font-semibold")}>
26
+ {method}
27
+ </span>
28
+ &nbsp;
29
+ <span className="break-all">{url}</span>&nbsp;
30
+ <span className="text-muted-foreground">HTTP/1.1</span>
31
+ </div>
32
+ <div className="mx-1.5 flex flex-col gap-3">
33
+ <Collapsible defaultOpen>
34
+ <CollapsibleTrigger className="flex items-center gap-2 hover:text-primary group">
35
+ <ChevronRightIcon className="h-4 w-4 transition-transform duration-200 group-data-[state=open]:rotate-[90deg]" />
36
+ <span className="font-semibold">Headers</span>
37
+ </CollapsibleTrigger>
38
+ <CollapsibleContent>
39
+ <div className="grid grid-cols-[auto,1fr] gap-x-8 gap-y-1 pl-1.5 pt-2">
40
+ {headers.map(([key, value]) => (
41
+ <Fragment key={key}>
42
+ <div className="text-primary">{key}</div>
43
+ <div className="break-words">{value}</div>
44
+ </Fragment>
45
+ ))}
46
+ </div>
47
+ </CollapsibleContent>
48
+ </Collapsible>
49
+
50
+ <Collapsible defaultOpen>
51
+ <CollapsibleTrigger className="flex items-center gap-2 hover:text-primary group">
52
+ <ChevronRightIcon className="h-4 w-4 transition-transform duration-200 group-data-[state=open]:rotate-[90deg]" />
53
+ <span className="font-semibold">Body</span>
54
+ </CollapsibleTrigger>
55
+ <CollapsibleContent>
56
+ <div className="pl-0 pt-2">
57
+ <div
58
+ className={cn(
59
+ "whitespace-pre-wrap break-all bg-muted p-2 rounded-md",
60
+ !body && "text-muted-foreground",
61
+ )}
62
+ >
63
+ {body ?? "Empty body"}
64
+ </div>
65
+ </div>
66
+ </CollapsibleContent>
67
+ </Collapsible>
68
+ </div>
69
+ </div>
70
+ );
71
+ };
72
+
73
+ export default RequestTab;