zudoku 0.0.0-f3786a4 → 0.0.0-f3f6db5

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 (209) hide show
  1. package/dist/app/main.js +1 -2
  2. package/dist/app/main.js.map +1 -1
  3. package/dist/config/validators/InputSidebarSchema.d.ts +2 -2
  4. package/dist/lib/components/PathRenderer.d.ts +11 -0
  5. package/dist/lib/components/PathRenderer.js +25 -0
  6. package/dist/lib/components/PathRenderer.js.map +1 -0
  7. package/dist/lib/components/ThemeSwitch.js +4 -4
  8. package/dist/lib/components/ThemeSwitch.js.map +1 -1
  9. package/dist/lib/components/index.d.ts +1 -0
  10. package/dist/lib/components/index.js +4 -2
  11. package/dist/lib/components/index.js.map +1 -1
  12. package/dist/lib/components/navigation/SidebarCategory.js +17 -15
  13. package/dist/lib/components/navigation/SidebarCategory.js.map +1 -1
  14. package/dist/lib/oas/graphql/circular.js +17 -6
  15. package/dist/lib/oas/graphql/circular.js.map +1 -1
  16. package/dist/lib/oas/graphql/index.d.ts +1 -0
  17. package/dist/lib/oas/graphql/index.js +41 -23
  18. package/dist/lib/oas/graphql/index.js.map +1 -1
  19. package/dist/lib/plugins/openapi/ColorizedParam.js +3 -1
  20. package/dist/lib/plugins/openapi/ColorizedParam.js.map +1 -1
  21. package/dist/lib/plugins/openapi/Endpoint.js +2 -2
  22. package/dist/lib/plugins/openapi/Endpoint.js.map +1 -1
  23. package/dist/lib/plugins/openapi/{Route.d.ts → OpenApiRoute.d.ts} +2 -1
  24. package/dist/lib/plugins/openapi/{Route.js → OpenApiRoute.js} +3 -4
  25. package/dist/lib/plugins/openapi/OpenApiRoute.js.map +1 -0
  26. package/dist/lib/plugins/openapi/OperationList.d.ts +4 -1
  27. package/dist/lib/plugins/openapi/OperationList.js +20 -14
  28. package/dist/lib/plugins/openapi/OperationList.js.map +1 -1
  29. package/dist/lib/plugins/openapi/OperationListItem.js +1 -1
  30. package/dist/lib/plugins/openapi/OperationListItem.js.map +1 -1
  31. package/dist/lib/plugins/openapi/ParameterListItem.js +2 -2
  32. package/dist/lib/plugins/openapi/ParameterListItem.js.map +1 -1
  33. package/dist/lib/plugins/openapi/RequestBodySidecarBox.d.ts +1 -1
  34. package/dist/lib/plugins/openapi/RequestBodySidecarBox.js +2 -0
  35. package/dist/lib/plugins/openapi/RequestBodySidecarBox.js.map +1 -1
  36. package/dist/lib/plugins/openapi/Sidecar.js +6 -11
  37. package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
  38. package/dist/lib/plugins/openapi/SidecarExamples.js +17 -14
  39. package/dist/lib/plugins/openapi/SidecarExamples.js.map +1 -1
  40. package/dist/lib/plugins/openapi/graphql/gql.d.ts +6 -2
  41. package/dist/lib/plugins/openapi/graphql/gql.js +3 -2
  42. package/dist/lib/plugins/openapi/graphql/gql.js.map +1 -1
  43. package/dist/lib/plugins/openapi/graphql/graphql.d.ts +47 -26
  44. package/dist/lib/plugins/openapi/graphql/graphql.js +20 -16
  45. package/dist/lib/plugins/openapi/graphql/graphql.js.map +1 -1
  46. package/dist/lib/plugins/openapi/index.js +97 -54
  47. package/dist/lib/plugins/openapi/index.js.map +1 -1
  48. package/dist/lib/plugins/openapi/interfaces.d.ts +1 -0
  49. package/dist/lib/plugins/openapi/playground/ExamplesDropdown.js +5 -5
  50. package/dist/lib/plugins/openapi/playground/ExamplesDropdown.js.map +1 -1
  51. package/dist/lib/plugins/openapi/playground/Headers.js +17 -16
  52. package/dist/lib/plugins/openapi/playground/Headers.js.map +1 -1
  53. package/dist/lib/plugins/openapi/playground/ParamsGrid.d.ts +5 -0
  54. package/dist/lib/plugins/openapi/playground/ParamsGrid.js +4 -0
  55. package/dist/lib/plugins/openapi/playground/ParamsGrid.js.map +1 -0
  56. package/dist/lib/plugins/openapi/playground/PathParams.js +4 -12
  57. package/dist/lib/plugins/openapi/playground/PathParams.js.map +1 -1
  58. package/dist/lib/plugins/openapi/playground/Playground.d.ts +13 -0
  59. package/dist/lib/plugins/openapi/playground/Playground.js +25 -45
  60. package/dist/lib/plugins/openapi/playground/Playground.js.map +1 -1
  61. package/dist/lib/plugins/openapi/playground/PlaygroundDialog.js +1 -1
  62. package/dist/lib/plugins/openapi/playground/PlaygroundDialog.js.map +1 -1
  63. package/dist/lib/plugins/openapi/playground/QueryParams.js +4 -3
  64. package/dist/lib/plugins/openapi/playground/QueryParams.js.map +1 -1
  65. package/dist/lib/plugins/openapi/playground/SubmitButton.d.ts +7 -0
  66. package/dist/lib/plugins/openapi/playground/SubmitButton.js +22 -0
  67. package/dist/lib/plugins/openapi/playground/SubmitButton.js.map +1 -0
  68. package/dist/lib/plugins/openapi/playground/result-panel/RequestTab.d.ts +7 -0
  69. package/dist/lib/plugins/openapi/playground/result-panel/RequestTab.js +11 -0
  70. package/dist/lib/plugins/openapi/playground/result-panel/RequestTab.js.map +1 -0
  71. package/dist/lib/plugins/openapi/playground/result-panel/ResponseTab.d.ts +8 -0
  72. package/dist/lib/plugins/openapi/playground/result-panel/ResponseTab.js +95 -0
  73. package/dist/lib/plugins/openapi/playground/result-panel/ResponseTab.js.map +1 -0
  74. package/dist/lib/plugins/openapi/playground/result-panel/ResultPanel.d.ts +7 -0
  75. package/dist/lib/plugins/openapi/playground/result-panel/ResultPanel.js +16 -0
  76. package/dist/lib/plugins/openapi/playground/result-panel/ResultPanel.js.map +1 -0
  77. package/dist/lib/plugins/openapi/playground/result-panel/convertToTypes.d.ts +10 -0
  78. package/dist/lib/plugins/openapi/playground/result-panel/convertToTypes.js +32 -0
  79. package/dist/lib/plugins/openapi/playground/result-panel/convertToTypes.js.map +1 -0
  80. package/dist/lib/plugins/openapi/playground/result-panel/convertToTypes.test.d.ts +1 -0
  81. package/dist/lib/plugins/openapi/playground/result-panel/convertToTypes.test.js +56 -0
  82. package/dist/lib/plugins/openapi/playground/result-panel/convertToTypes.test.js.map +1 -0
  83. package/dist/lib/plugins/openapi/schema/{SchemaComponents.js → SchemaPropertyItem.js} +10 -8
  84. package/dist/lib/plugins/openapi/schema/SchemaPropertyItem.js.map +1 -0
  85. package/dist/lib/plugins/openapi/schema/SchemaView.js +1 -1
  86. package/dist/lib/plugins/openapi/schema/SchemaView.js.map +1 -1
  87. package/dist/lib/plugins/openapi/schema/utils.d.ts +1 -0
  88. package/dist/lib/plugins/openapi/schema/utils.js +2 -0
  89. package/dist/lib/plugins/openapi/schema/utils.js.map +1 -1
  90. package/dist/lib/ui/Command.js +1 -1
  91. package/dist/lib/ui/Command.js.map +1 -1
  92. package/dist/lib/ui/Select.js +2 -2
  93. package/dist/lib/ui/Select.js.map +1 -1
  94. package/dist/lib/util/joinUrl.js +1 -1
  95. package/dist/lib/util/joinUrl.js.map +1 -1
  96. package/dist/lib/util/joinUrl.test.d.ts +1 -0
  97. package/dist/lib/util/joinUrl.test.js +43 -0
  98. package/dist/lib/util/joinUrl.test.js.map +1 -0
  99. package/dist/vite/plugin-api.js +9 -1
  100. package/dist/vite/plugin-api.js.map +1 -1
  101. package/dist/vite/prerender.js +0 -1
  102. package/dist/vite/prerender.js.map +1 -1
  103. package/lib/{AuthenticationPlugin-CvwlQWjV.js → AuthenticationPlugin-Du8cLBSr.js} +2 -2
  104. package/lib/{AuthenticationPlugin-CvwlQWjV.js.map → AuthenticationPlugin-Du8cLBSr.js.map} +1 -1
  105. package/lib/{Markdown-B8o9Qz4q.js → Markdown-Cyrx_JrO.js} +8 -9
  106. package/lib/{Markdown-B8o9Qz4q.js.map → Markdown-Cyrx_JrO.js.map} +1 -1
  107. package/lib/{MdxPage-DiWukCrZ.js → MdxPage-BuG8Tuwc.js} +5 -5
  108. package/lib/{MdxPage-DiWukCrZ.js.map → MdxPage-BuG8Tuwc.js.map} +1 -1
  109. package/lib/OpenApiRoute-UrC_t0e5.js +36 -0
  110. package/lib/OpenApiRoute-UrC_t0e5.js.map +1 -0
  111. package/lib/{OperationList-CyNwMHMV.js → OperationList-CDt1xdc4.js} +1925 -1902
  112. package/lib/OperationList-CDt1xdc4.js.map +1 -0
  113. package/lib/{Select-4kIgJ1bc.js → Select-CnCZ4WhS.js} +61 -61
  114. package/lib/Select-CnCZ4WhS.js.map +1 -0
  115. package/lib/{SlotletProvider-CtIp8rP3.js → SlotletProvider-mQiPDQIH.js} +2 -2
  116. package/lib/{SlotletProvider-CtIp8rP3.js.map → SlotletProvider-mQiPDQIH.js.map} +1 -1
  117. package/lib/{SyntaxHighlight-C1w1QPdY.js → SyntaxHighlight-B0L4SC_N.js} +11 -5
  118. package/lib/SyntaxHighlight-B0L4SC_N.js.map +1 -0
  119. package/lib/{ZudokuContext-Bp2uQx1d.js → ZudokuContext-BTUJPpQl.js} +36 -35
  120. package/lib/{ZudokuContext-Bp2uQx1d.js.map → ZudokuContext-BTUJPpQl.js.map} +1 -1
  121. package/lib/{circular-Dgpd6AN-.js → circular-DxaIIlWD.js} +251 -239
  122. package/lib/{circular-Dgpd6AN-.js.map → circular-DxaIIlWD.js.map} +1 -1
  123. package/lib/{createServer-BV0tHzLK.js → createServer-CjNktZzL.js} +821 -808
  124. package/lib/{createServer-BV0tHzLK.js.map → createServer-CjNktZzL.js.map} +1 -1
  125. package/lib/{hook-BDmay56y.js → hook-FT3SJLe_.js} +2 -2
  126. package/lib/{hook-BDmay56y.js.map → hook-FT3SJLe_.js.map} +1 -1
  127. package/lib/{index-LNp6rxyU.js → index-CjJS0l4l.js} +2 -2
  128. package/lib/{index-LNp6rxyU.js.map → index-CjJS0l4l.js.map} +1 -1
  129. package/lib/index-Eb1oiHbM.js +1997 -0
  130. package/lib/index-Eb1oiHbM.js.map +1 -0
  131. package/lib/{joinUrl-BTy9bvoK.js → joinUrl-nLx9pD-Z.js} +2 -2
  132. package/lib/joinUrl-nLx9pD-Z.js.map +1 -0
  133. package/lib/ui/Command.js +27 -27
  134. package/lib/ui/Command.js.map +1 -1
  135. package/lib/ui/Select.js +2 -2
  136. package/lib/ui/Select.js.map +1 -1
  137. package/lib/{useScrollToAnchor-S-NbG5Za.js → useScrollToAnchor-BZsGmBng.js} +86 -90
  138. package/lib/useScrollToAnchor-BZsGmBng.js.map +1 -0
  139. package/lib/zudoku.auth-clerk.js +1 -1
  140. package/lib/zudoku.auth-openid.js +3 -3
  141. package/lib/zudoku.components.js +364 -348
  142. package/lib/zudoku.components.js.map +1 -1
  143. package/lib/zudoku.plugin-api-catalog.js +3 -3
  144. package/lib/zudoku.plugin-api-keys.js +4 -4
  145. package/lib/zudoku.plugin-custom-pages.js +1 -1
  146. package/lib/zudoku.plugin-markdown.js +1 -1
  147. package/lib/zudoku.plugin-openapi.js +6 -5
  148. package/lib/zudoku.plugin-openapi.js.map +1 -1
  149. package/package.json +1 -1
  150. package/src/app/main.tsx +1 -2
  151. package/src/lib/components/PathRenderer.tsx +59 -0
  152. package/src/lib/components/ThemeSwitch.tsx +15 -14
  153. package/src/lib/components/index.ts +7 -5
  154. package/src/lib/components/navigation/SidebarCategory.tsx +44 -41
  155. package/src/lib/oas/graphql/circular.ts +27 -6
  156. package/src/lib/oas/graphql/index.ts +63 -35
  157. package/src/lib/plugins/openapi/ColorizedParam.tsx +3 -3
  158. package/src/lib/plugins/openapi/Endpoint.tsx +2 -2
  159. package/src/lib/plugins/openapi/{Route.tsx → OpenApiRoute.tsx} +3 -3
  160. package/src/lib/plugins/openapi/OperationList.tsx +34 -12
  161. package/src/lib/plugins/openapi/OperationListItem.tsx +6 -1
  162. package/src/lib/plugins/openapi/ParameterListItem.tsx +2 -1
  163. package/src/lib/plugins/openapi/RequestBodySidecarBox.tsx +2 -0
  164. package/src/lib/plugins/openapi/Sidecar.tsx +18 -27
  165. package/src/lib/plugins/openapi/SidecarExamples.tsx +24 -24
  166. package/src/lib/plugins/openapi/graphql/gql.ts +12 -4
  167. package/src/lib/plugins/openapi/graphql/graphql.ts +66 -43
  168. package/src/lib/plugins/openapi/index.tsx +125 -67
  169. package/src/lib/plugins/openapi/interfaces.ts +1 -0
  170. package/src/lib/plugins/openapi/playground/ExamplesDropdown.tsx +30 -27
  171. package/src/lib/plugins/openapi/playground/Headers.tsx +65 -65
  172. package/src/lib/plugins/openapi/playground/ParamsGrid.tsx +8 -0
  173. package/src/lib/plugins/openapi/playground/PathParams.tsx +34 -74
  174. package/src/lib/plugins/openapi/playground/Playground.tsx +86 -148
  175. package/src/lib/plugins/openapi/playground/PlaygroundDialog.tsx +1 -1
  176. package/src/lib/plugins/openapi/playground/QueryParams.tsx +46 -45
  177. package/src/lib/plugins/openapi/playground/SubmitButton.tsx +75 -0
  178. package/src/lib/plugins/openapi/playground/result-panel/RequestTab.tsx +73 -0
  179. package/src/lib/plugins/openapi/playground/result-panel/ResponseTab.tsx +210 -0
  180. package/src/lib/plugins/openapi/playground/result-panel/ResultPanel.tsx +101 -0
  181. package/src/lib/plugins/openapi/playground/result-panel/convertToTypes.test.ts +64 -0
  182. package/src/lib/plugins/openapi/playground/result-panel/convertToTypes.ts +36 -0
  183. package/src/lib/plugins/openapi/schema/{SchemaComponents.tsx → SchemaPropertyItem.tsx} +10 -6
  184. package/src/lib/plugins/openapi/schema/SchemaView.tsx +4 -1
  185. package/src/lib/plugins/openapi/schema/utils.ts +4 -0
  186. package/src/lib/ui/Command.tsx +1 -1
  187. package/src/lib/ui/Select.tsx +1 -1
  188. package/src/lib/util/joinUrl.test.ts +62 -0
  189. package/src/lib/util/joinUrl.ts +1 -1
  190. package/dist/lib/plugins/openapi/Route.js.map +0 -1
  191. package/dist/lib/plugins/openapi/playground/ResponseTab.d.ts +0 -4
  192. package/dist/lib/plugins/openapi/playground/ResponseTab.js +0 -42
  193. package/dist/lib/plugins/openapi/playground/ResponseTab.js.map +0 -1
  194. package/dist/lib/plugins/openapi/schema/SchemaComponents.js.map +0 -1
  195. package/lib/OperationList-CyNwMHMV.js.map +0 -1
  196. package/lib/Route-DH64hIrR.js +0 -35
  197. package/lib/Route-DH64hIrR.js.map +0 -1
  198. package/lib/Select-4kIgJ1bc.js.map +0 -1
  199. package/lib/StaggeredRender-DgsamH_G.js +0 -17
  200. package/lib/StaggeredRender-DgsamH_G.js.map +0 -1
  201. package/lib/SyntaxHighlight-C1w1QPdY.js.map +0 -1
  202. package/lib/index-Bn6Lc9tq.js +0 -9
  203. package/lib/index-Bn6Lc9tq.js.map +0 -1
  204. package/lib/index-BtNudmKQ.js +0 -1744
  205. package/lib/index-BtNudmKQ.js.map +0 -1
  206. package/lib/joinUrl-BTy9bvoK.js.map +0 -1
  207. package/lib/useScrollToAnchor-S-NbG5Za.js.map +0 -1
  208. package/src/lib/plugins/openapi/playground/ResponseTab.tsx +0 -76
  209. /package/dist/lib/plugins/openapi/schema/{SchemaComponents.d.ts → SchemaPropertyItem.d.ts} +0 -0
@@ -3,6 +3,8 @@ 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
+ import { PathRenderer } from "../../../components/PathRenderer.js";
7
+
6
8
  import { Label } from "zudoku/ui/Label.js";
7
9
  import { RadioGroup, RadioGroupItem } from "zudoku/ui/RadioGroup.js";
8
10
  import {
@@ -15,10 +17,7 @@ import {
15
17
  import { Textarea } from "zudoku/ui/Textarea.js";
16
18
  import { useSelectedServerStore } from "../../../authentication/state.js";
17
19
  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";
20
+ import { Card } from "../../../ui/Card.js";
22
21
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../../ui/Tabs.js";
23
22
  import { cn } from "../../../util/cn.js";
24
23
  import { ColorizedParam } from "../ColorizedParam.js";
@@ -28,28 +27,17 @@ import ExamplesDropdown from "./ExamplesDropdown.js";
28
27
  import { Headers } from "./Headers.js";
29
28
  import { PathParams } from "./PathParams.js";
30
29
  import { QueryParams } from "./QueryParams.js";
31
- import { ResponseTab } from "./ResponseTab.js";
30
+ import { ResultPanel } from "./result-panel/ResultPanel.js";
31
+ import SubmitButton from "./SubmitButton.js";
32
32
 
33
33
  export const NO_IDENTITY = "__none";
34
34
 
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
35
  export type Header = {
49
36
  name: string;
50
37
  defaultValue?: string;
51
38
  defaultActive?: boolean;
52
39
  };
40
+
53
41
  export type QueryParam = {
54
42
  name: string;
55
43
  defaultValue?: string;
@@ -81,6 +69,20 @@ export type PlaygroundForm = {
81
69
  identity?: string;
82
70
  };
83
71
 
72
+ export type PlaygroundResult = {
73
+ status: number;
74
+ headers: Array<[string, string]>;
75
+ size: number;
76
+ body: string;
77
+ time: number;
78
+ request: {
79
+ method: string;
80
+ url: string;
81
+ headers: Array<[string, string]>;
82
+ body?: string;
83
+ };
84
+ };
85
+
84
86
  export type PlaygroundContentProps = {
85
87
  server: string;
86
88
  servers?: string[];
@@ -158,20 +160,23 @@ export const Playground = ({
158
160
  }
159
161
  }, [setValue, identities.data]);
160
162
 
163
+ const formRef = useRef<HTMLFormElement>(null);
164
+
161
165
  const queryMutation = useMutation({
162
166
  mutationFn: async (data: PlaygroundForm) => {
163
- const requestUrl = createUrl(selectedServer ?? server, url, data);
164
167
  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
- });
168
+ const request = new Request(
169
+ createUrl(selectedServer ?? server, url, data),
170
+ {
171
+ method: method.toUpperCase(),
172
+ headers: Object.fromEntries(
173
+ data.headers
174
+ .filter((h) => h.name && h.active)
175
+ .map((header) => [header.name, header.value]),
176
+ ),
177
+ body: data.body ? data.body : undefined,
178
+ },
179
+ );
175
180
 
176
181
  if (data.identity !== NO_IDENTITY) {
177
182
  identities.data
@@ -187,13 +192,25 @@ export const Playground = ({
187
192
 
188
193
  const body = await response.text();
189
194
 
195
+ const url = new URL(request.url);
196
+
190
197
  return {
191
198
  status: response.status,
192
- headers: response.headers,
199
+ headers: Array.from(response.headers.entries()),
193
200
  size: body.length,
194
201
  body,
195
202
  time,
196
- };
203
+ request: {
204
+ method: request.method.toUpperCase(),
205
+ url: request.url,
206
+ headers: [
207
+ ["Host", url.host],
208
+ ["User-Agent", "Zudoku Playground"],
209
+ ...Array.from(request.headers.entries()),
210
+ ],
211
+ body: data.body ? data.body : undefined,
212
+ },
213
+ } satisfies PlaygroundResult;
197
214
  } catch (error) {
198
215
  if (error instanceof TypeError) {
199
216
  throw new Error(
@@ -206,38 +223,27 @@ export const Playground = ({
206
223
  },
207
224
  });
208
225
 
209
- const path = url.split("/").map((part, i, arr) => {
210
- const isPathParam =
211
- (part.startsWith("{") && part.endsWith("}")) || part.startsWith(":");
212
- const replaced = part.replace(/[:{}]/g, "");
213
- const value = formState.pathParams.find((p) => p.name === replaced)?.value;
214
-
215
- const pathParamValue = (
216
- <ColorizedParam
217
- backgroundOpacity="25%"
218
- name={part}
219
- slug={part}
220
- title={
221
- !value
222
- ? `Missing value for path parameter \`${replaced}\``
223
- : undefined
224
- }
225
- >
226
- {value ? encodeURIComponent(value) : part}
227
- </ColorizedParam>
228
- );
229
-
230
- return (
231
- // eslint-disable-next-line react/no-array-index-key
232
- <Fragment key={part + i}>
233
- {isPathParam ? pathParamValue : part}
234
- {i < arr.length - 1 && "/"}
235
- <wbr />
236
- </Fragment>
237
- );
238
- });
226
+ const path = (
227
+ <PathRenderer
228
+ path={url}
229
+ renderParam={({ name, originalValue, index }) => {
230
+ const formValue = formState.pathParams.find(
231
+ (param) => param.name === name,
232
+ )?.value;
239
233
 
240
- const headerEntries = Array.from(queryMutation.data?.headers.entries() ?? []);
234
+ return (
235
+ <ColorizedParam
236
+ name={name}
237
+ backgroundOpacity="0"
238
+ slug={name}
239
+ onClick={() => form.setFocus(`pathParams.${index}.value`)}
240
+ >
241
+ {formValue || originalValue}
242
+ </ColorizedParam>
243
+ );
244
+ }}
245
+ />
246
+ );
241
247
 
242
248
  const urlQueryParams = formState.queryParams
243
249
  .filter((p) => p.active)
@@ -254,9 +260,7 @@ export const Playground = ({
254
260
  {servers && servers.length > 1 ? (
255
261
  <Select
256
262
  onValueChange={(value) => {
257
- startTransition(() => {
258
- setSelectedServer(value);
259
- });
263
+ startTransition(() => setSelectedServer(value));
260
264
  }}
261
265
  value={selectedServer}
262
266
  defaultValue={selectedServer}
@@ -282,24 +286,30 @@ export const Playground = ({
282
286
  <FormProvider
283
287
  {...{ register, control, handleSubmit, watch, setValue, ...form }}
284
288
  >
285
- <form onSubmit={handleSubmit((data) => queryMutation.mutateAsync(data))}>
286
- <div className="grid grid-cols-[8fr_7fr] text-sm h-full">
289
+ <form
290
+ onSubmit={handleSubmit((data) => queryMutation.mutateAsync(data))}
291
+ ref={formRef}
292
+ >
293
+ <div className="grid grid-cols-2 text-sm h-full">
287
294
  <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
295
  <div className="flex gap-2 items-stretch">
289
296
  <div className="flex flex-1 items-center w-full border rounded-md">
290
297
  <div className="border-r p-2 bg-muted rounded-l-md self-stretch font-semibold font-mono flex items-center">
291
298
  {method.toUpperCase()}
292
299
  </div>
293
- <div className="flex items-center flex-wrap p-2 font-mono text-xs">
300
+ <div className="items-center p-2 font-mono text-xs break-words">
294
301
  {serverSelect}
295
302
  {path}
296
303
  {urlQueryParams.length > 0 ? "?" : ""}
297
304
  {urlQueryParams}
298
305
  </div>
299
306
  </div>
300
- <Button type="submit" className="h-auto flex gap-1">
301
- Send
302
- </Button>
307
+
308
+ <SubmitButton
309
+ identities={identities.data ?? []}
310
+ formRef={formRef}
311
+ disabled={form.formState.isSubmitting}
312
+ />
303
313
  </div>
304
314
  <Tabs defaultValue="parameters">
305
315
  <div className="flex flex-wrap gap-1 justify-between">
@@ -438,84 +448,12 @@ export const Playground = ({
438
448
  </TabsContent>
439
449
  </Tabs>
440
450
  </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>
451
+ <ResultPanel
452
+ queryMutation={queryMutation}
453
+ showPathParamsWarning={formState.pathParams.some(
454
+ (p) => p.value === "",
517
455
  )}
518
- </div>
456
+ />
519
457
  </div>
520
458
  </form>
521
459
  </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;