@djangocfg/ui-tools 2.1.91

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 (174) hide show
  1. package/dist/LottiePlayer.client-LBEC2JKY.mjs +161 -0
  2. package/dist/LottiePlayer.client-LBEC2JKY.mjs.map +1 -0
  3. package/dist/LottiePlayer.client-WFMG2OOW.cjs +168 -0
  4. package/dist/LottiePlayer.client-WFMG2OOW.cjs.map +1 -0
  5. package/dist/Mermaid.client-4TU2TSH3.mjs +477 -0
  6. package/dist/Mermaid.client-4TU2TSH3.mjs.map +1 -0
  7. package/dist/Mermaid.client-SBYY364Q.cjs +483 -0
  8. package/dist/Mermaid.client-SBYY364Q.cjs.map +1 -0
  9. package/dist/PlaygroundLayout-3YVSAEAF.cjs +1003 -0
  10. package/dist/PlaygroundLayout-3YVSAEAF.cjs.map +1 -0
  11. package/dist/PlaygroundLayout-4DYBORAS.mjs +996 -0
  12. package/dist/PlaygroundLayout-4DYBORAS.mjs.map +1 -0
  13. package/dist/PrettyCode.client-LCBPPTIX.mjs +152 -0
  14. package/dist/PrettyCode.client-LCBPPTIX.mjs.map +1 -0
  15. package/dist/PrettyCode.client-PNPLXRH6.cjs +154 -0
  16. package/dist/PrettyCode.client-PNPLXRH6.cjs.map +1 -0
  17. package/dist/chunk-37ZI6VD4.mjs +12 -0
  18. package/dist/chunk-37ZI6VD4.mjs.map +1 -0
  19. package/dist/chunk-3HK2OE62.cjs +81 -0
  20. package/dist/chunk-3HK2OE62.cjs.map +1 -0
  21. package/dist/chunk-7DGDQVQW.cjs +591 -0
  22. package/dist/chunk-7DGDQVQW.cjs.map +1 -0
  23. package/dist/chunk-M6P2FU7L.mjs +572 -0
  24. package/dist/chunk-M6P2FU7L.mjs.map +1 -0
  25. package/dist/chunk-UQ3XI5MY.cjs +15 -0
  26. package/dist/chunk-UQ3XI5MY.cjs.map +1 -0
  27. package/dist/chunk-YFRNE2IR.mjs +79 -0
  28. package/dist/chunk-YFRNE2IR.mjs.map +1 -0
  29. package/dist/index.cjs +5042 -0
  30. package/dist/index.cjs.map +1 -0
  31. package/dist/index.d.cts +1591 -0
  32. package/dist/index.d.ts +1591 -0
  33. package/dist/index.mjs +4941 -0
  34. package/dist/index.mjs.map +1 -0
  35. package/package.json +86 -0
  36. package/src/components/markdown/MarkdownMessage.tsx +340 -0
  37. package/src/components/markdown/index.ts +5 -0
  38. package/src/index.ts +26 -0
  39. package/src/stores/index.ts +9 -0
  40. package/src/stores/mediaCache.ts +534 -0
  41. package/src/tools/AudioPlayer/README.md +206 -0
  42. package/src/tools/AudioPlayer/components/HybridAudioPlayer.tsx +216 -0
  43. package/src/tools/AudioPlayer/components/HybridSimplePlayer.tsx +280 -0
  44. package/src/tools/AudioPlayer/components/HybridWaveform.tsx +279 -0
  45. package/src/tools/AudioPlayer/components/ReactiveCover/AudioReactiveCover.tsx +149 -0
  46. package/src/tools/AudioPlayer/components/ReactiveCover/effects/GlowEffect.tsx +110 -0
  47. package/src/tools/AudioPlayer/components/ReactiveCover/effects/MeshEffect.tsx +58 -0
  48. package/src/tools/AudioPlayer/components/ReactiveCover/effects/OrbsEffect.tsx +45 -0
  49. package/src/tools/AudioPlayer/components/ReactiveCover/effects/SpotlightEffect.tsx +82 -0
  50. package/src/tools/AudioPlayer/components/ReactiveCover/effects/index.ts +8 -0
  51. package/src/tools/AudioPlayer/components/ReactiveCover/index.ts +6 -0
  52. package/src/tools/AudioPlayer/components/index.ts +22 -0
  53. package/src/tools/AudioPlayer/context/HybridAudioProvider.tsx +158 -0
  54. package/src/tools/AudioPlayer/context/index.ts +16 -0
  55. package/src/tools/AudioPlayer/effects/index.ts +412 -0
  56. package/src/tools/AudioPlayer/hooks/index.ts +35 -0
  57. package/src/tools/AudioPlayer/hooks/useHybridAudio.ts +387 -0
  58. package/src/tools/AudioPlayer/hooks/useHybridAudioAnalysis.ts +95 -0
  59. package/src/tools/AudioPlayer/hooks/useVisualization.tsx +207 -0
  60. package/src/tools/AudioPlayer/index.ts +133 -0
  61. package/src/tools/AudioPlayer/types/effects.ts +73 -0
  62. package/src/tools/AudioPlayer/types/index.ts +27 -0
  63. package/src/tools/AudioPlayer/utils/debug.ts +14 -0
  64. package/src/tools/AudioPlayer/utils/formatTime.ts +10 -0
  65. package/src/tools/AudioPlayer/utils/index.ts +6 -0
  66. package/src/tools/ImageViewer/@refactoring/00-PLAN.md +71 -0
  67. package/src/tools/ImageViewer/@refactoring/01-TYPES.md +121 -0
  68. package/src/tools/ImageViewer/@refactoring/02-UTILS.md +143 -0
  69. package/src/tools/ImageViewer/@refactoring/03-HOOKS.md +261 -0
  70. package/src/tools/ImageViewer/@refactoring/04-COMPONENTS.md +427 -0
  71. package/src/tools/ImageViewer/@refactoring/05-EXECUTION-CHECKLIST.md +126 -0
  72. package/src/tools/ImageViewer/README.md +200 -0
  73. package/src/tools/ImageViewer/components/ImageInfo.tsx +44 -0
  74. package/src/tools/ImageViewer/components/ImageToolbar.tsx +145 -0
  75. package/src/tools/ImageViewer/components/ImageViewer.tsx +241 -0
  76. package/src/tools/ImageViewer/components/index.ts +7 -0
  77. package/src/tools/ImageViewer/hooks/index.ts +9 -0
  78. package/src/tools/ImageViewer/hooks/useImageLoading.ts +204 -0
  79. package/src/tools/ImageViewer/hooks/useImageTransform.ts +101 -0
  80. package/src/tools/ImageViewer/index.ts +60 -0
  81. package/src/tools/ImageViewer/types.ts +81 -0
  82. package/src/tools/ImageViewer/utils/constants.ts +59 -0
  83. package/src/tools/ImageViewer/utils/debug.ts +14 -0
  84. package/src/tools/ImageViewer/utils/index.ts +17 -0
  85. package/src/tools/ImageViewer/utils/lqip.ts +47 -0
  86. package/src/tools/JsonForm/JsonSchemaForm.tsx +197 -0
  87. package/src/tools/JsonForm/examples/BotConfigExample.tsx +249 -0
  88. package/src/tools/JsonForm/examples/RealBotConfigExample.tsx +161 -0
  89. package/src/tools/JsonForm/index.ts +46 -0
  90. package/src/tools/JsonForm/templates/ArrayFieldItemTemplate.tsx +47 -0
  91. package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +74 -0
  92. package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +107 -0
  93. package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +35 -0
  94. package/src/tools/JsonForm/templates/FieldTemplate.tsx +62 -0
  95. package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +116 -0
  96. package/src/tools/JsonForm/templates/index.ts +12 -0
  97. package/src/tools/JsonForm/types.ts +83 -0
  98. package/src/tools/JsonForm/utils.ts +213 -0
  99. package/src/tools/JsonForm/widgets/CheckboxWidget.tsx +37 -0
  100. package/src/tools/JsonForm/widgets/ColorWidget.tsx +219 -0
  101. package/src/tools/JsonForm/widgets/NumberWidget.tsx +89 -0
  102. package/src/tools/JsonForm/widgets/SelectWidget.tsx +97 -0
  103. package/src/tools/JsonForm/widgets/SliderWidget.tsx +148 -0
  104. package/src/tools/JsonForm/widgets/SwitchWidget.tsx +35 -0
  105. package/src/tools/JsonForm/widgets/TextWidget.tsx +96 -0
  106. package/src/tools/JsonForm/widgets/index.ts +14 -0
  107. package/src/tools/JsonTree/index.tsx +243 -0
  108. package/src/tools/LottiePlayer/LottiePlayer.client.tsx +213 -0
  109. package/src/tools/LottiePlayer/index.tsx +56 -0
  110. package/src/tools/LottiePlayer/types.ts +108 -0
  111. package/src/tools/LottiePlayer/useLottie.ts +164 -0
  112. package/src/tools/Mermaid/Mermaid.client.tsx +82 -0
  113. package/src/tools/Mermaid/components/MermaidCodeViewer.tsx +95 -0
  114. package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +103 -0
  115. package/src/tools/Mermaid/hooks/index.ts +4 -0
  116. package/src/tools/Mermaid/hooks/useMermaidCleanup.ts +73 -0
  117. package/src/tools/Mermaid/hooks/useMermaidFullscreen.ts +46 -0
  118. package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +226 -0
  119. package/src/tools/Mermaid/hooks/useMermaidValidation.ts +29 -0
  120. package/src/tools/Mermaid/index.tsx +44 -0
  121. package/src/tools/Mermaid/utils/mermaid-helpers.ts +33 -0
  122. package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +149 -0
  123. package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +263 -0
  124. package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +125 -0
  125. package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +100 -0
  126. package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +157 -0
  127. package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +253 -0
  128. package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +173 -0
  129. package/src/tools/OpenapiViewer/components/VersionSelector.tsx +68 -0
  130. package/src/tools/OpenapiViewer/components/index.ts +14 -0
  131. package/src/tools/OpenapiViewer/constants.ts +39 -0
  132. package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +337 -0
  133. package/src/tools/OpenapiViewer/hooks/index.ts +8 -0
  134. package/src/tools/OpenapiViewer/hooks/useMobile.ts +10 -0
  135. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +199 -0
  136. package/src/tools/OpenapiViewer/index.tsx +37 -0
  137. package/src/tools/OpenapiViewer/types.ts +151 -0
  138. package/src/tools/OpenapiViewer/utils/apiKeyManager.ts +149 -0
  139. package/src/tools/OpenapiViewer/utils/formatters.ts +71 -0
  140. package/src/tools/OpenapiViewer/utils/index.ts +9 -0
  141. package/src/tools/OpenapiViewer/utils/versionManager.ts +161 -0
  142. package/src/tools/PrettyCode/PrettyCode.client.tsx +208 -0
  143. package/src/tools/PrettyCode/index.tsx +47 -0
  144. package/src/tools/VideoPlayer/@refactoring/00-PLAN.md +91 -0
  145. package/src/tools/VideoPlayer/@refactoring/01-TYPES.md +284 -0
  146. package/src/tools/VideoPlayer/@refactoring/02-UTILS.md +141 -0
  147. package/src/tools/VideoPlayer/@refactoring/03-HOOKS.md +178 -0
  148. package/src/tools/VideoPlayer/@refactoring/04-COMPONENTS.md +95 -0
  149. package/src/tools/VideoPlayer/@refactoring/05-EXECUTION-CHECKLIST.md +139 -0
  150. package/src/tools/VideoPlayer/README.md +264 -0
  151. package/src/tools/VideoPlayer/components/VideoControls.tsx +138 -0
  152. package/src/tools/VideoPlayer/components/VideoErrorFallback.tsx +172 -0
  153. package/src/tools/VideoPlayer/components/VideoPlayer.tsx +201 -0
  154. package/src/tools/VideoPlayer/components/index.ts +14 -0
  155. package/src/tools/VideoPlayer/context/VideoPlayerContext.tsx +52 -0
  156. package/src/tools/VideoPlayer/context/index.ts +8 -0
  157. package/src/tools/VideoPlayer/hooks/index.ts +12 -0
  158. package/src/tools/VideoPlayer/hooks/useVideoPlayerSettings.ts +70 -0
  159. package/src/tools/VideoPlayer/hooks/useVideoPositionCache.ts +116 -0
  160. package/src/tools/VideoPlayer/index.ts +77 -0
  161. package/src/tools/VideoPlayer/providers/NativeProvider.tsx +284 -0
  162. package/src/tools/VideoPlayer/providers/StreamProvider.tsx +505 -0
  163. package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +400 -0
  164. package/src/tools/VideoPlayer/providers/index.ts +8 -0
  165. package/src/tools/VideoPlayer/types/index.ts +38 -0
  166. package/src/tools/VideoPlayer/types/player.ts +116 -0
  167. package/src/tools/VideoPlayer/types/provider.ts +93 -0
  168. package/src/tools/VideoPlayer/types/sources.ts +97 -0
  169. package/src/tools/VideoPlayer/utils/debug.ts +14 -0
  170. package/src/tools/VideoPlayer/utils/fileSource.ts +78 -0
  171. package/src/tools/VideoPlayer/utils/index.ts +12 -0
  172. package/src/tools/VideoPlayer/utils/resolvers.ts +75 -0
  173. package/src/tools/_shared.ts +29 -0
  174. package/src/tools/index.ts +172 -0
@@ -0,0 +1,337 @@
1
+ 'use client';
2
+
3
+ import consola from 'consola';
4
+ import React, {
5
+ createContext, ReactNode, useCallback, useContext, useEffect, useState
6
+ } from 'react';
7
+
8
+ import type {
9
+ ApiEndpoint, ApiResponse, PlaygroundConfig, PlaygroundContextType, PlaygroundState,
10
+ PlaygroundStep
11
+ } from '../types';
12
+ import { parseRequestHeaders, substituteUrlParameters } from '../utils';
13
+ import { getDefaultVersion } from '../utils/versionManager';
14
+
15
+ const createInitialState = (): PlaygroundState => ({
16
+ // Step management
17
+ currentStep: 'endpoints',
18
+ steps: ['endpoints', 'request', 'response'],
19
+
20
+ // Endpoint selection
21
+ selectedEndpoint: null,
22
+ selectedCategory: 'All',
23
+ searchTerm: '',
24
+ selectedVersion: getDefaultVersion().id,
25
+
26
+ // Request configuration
27
+ requestUrl: '',
28
+ requestMethod: 'GET',
29
+ requestHeaders: '{\n "Content-Type": "application/json"\n}',
30
+ requestBody: '',
31
+ selectedApiKey: null,
32
+ manualApiToken: '',
33
+ parameters: {},
34
+
35
+ // Response
36
+ response: null,
37
+ loading: false,
38
+
39
+ // UI state
40
+ sidebarOpen: false,
41
+ });
42
+
43
+ const PlaygroundContext = createContext<PlaygroundContextType | undefined>(undefined);
44
+
45
+ export const usePlaygroundContext = () => {
46
+ const context = useContext(PlaygroundContext);
47
+ if (!context) {
48
+ throw new Error('usePlaygroundContext must be used within a PlaygroundProvider');
49
+ }
50
+ return context;
51
+ };
52
+
53
+ interface PlaygroundProviderProps {
54
+ children: ReactNode;
55
+ config: PlaygroundConfig;
56
+ }
57
+
58
+ export const PlaygroundProvider: React.FC<PlaygroundProviderProps> = ({ children, config }) => {
59
+ const [state, setState] = useState<PlaygroundState>(() => createInitialState());
60
+
61
+ // TODO: Get API keys from CFG context - temporarily disabled
62
+ // const { apiKeys: apiKeysResponse, isLoadingApiKeys } = useApiKeysContext();
63
+ // const apiKeys = (apiKeysResponse && apiKeysResponse.results) ? apiKeysResponse.results : [];
64
+ const apiKeys = React.useMemo(() => [], []);
65
+ const isLoadingApiKeys = false;
66
+
67
+ const updateState = (updates: Partial<PlaygroundState>) => {
68
+ setState((prev) => ({ ...prev, ...updates }));
69
+ };
70
+
71
+ // Auto-select first API key when available
72
+ useEffect(() => {
73
+ if (!isLoadingApiKeys && apiKeys.length > 0 && !state.selectedApiKey) {
74
+ updateState({ selectedApiKey: apiKeys[0]?.id || null });
75
+ }
76
+ }, [apiKeys, isLoadingApiKeys, state.selectedApiKey]);
77
+
78
+ // Update headers when API key changes
79
+ useEffect(() => {
80
+ try {
81
+ setState(prev => {
82
+ const headers = parseRequestHeaders(prev.requestHeaders);
83
+ let hasChanged = false;
84
+
85
+ if (prev.selectedApiKey) {
86
+ const apiKey = apiKeys.find(k => k.id === prev.selectedApiKey);
87
+
88
+ if (apiKey) {
89
+ // Add API key to headers only if it changed
90
+ if (headers['X-API-Key'] !== apiKey.id) {
91
+ headers['X-API-Key'] = apiKey.id;
92
+ hasChanged = true;
93
+ }
94
+ } else {
95
+ // Selected API key no longer exists, clear selection
96
+ return { ...prev, selectedApiKey: null };
97
+ }
98
+ } else {
99
+ // Remove API key from headers if no key is selected
100
+ if (headers['X-API-Key']) {
101
+ delete headers['X-API-Key'];
102
+ hasChanged = true;
103
+ }
104
+ }
105
+
106
+ // Only update if headers actually changed
107
+ if (hasChanged) {
108
+ const updatedHeaders = JSON.stringify(headers, null, 2);
109
+ return { ...prev, requestHeaders: updatedHeaders };
110
+ }
111
+
112
+ return prev;
113
+ });
114
+ } catch (error) {
115
+ consola.error('Error updating headers:', error);
116
+ }
117
+ }, [state.selectedApiKey, apiKeys]); // Removed state.requestHeaders dependency
118
+
119
+ // Update URL when parameters change
120
+ useEffect(() => {
121
+ if (state.selectedEndpoint && state.parameters) {
122
+ // Path is already a full URL from the endpoint
123
+ const updatedUrl = substituteUrlParameters(state.selectedEndpoint.path, state.parameters);
124
+
125
+ // Only update if URL actually changed to avoid infinite loop
126
+ if (updatedUrl !== state.requestUrl) {
127
+ updateState({ requestUrl: updatedUrl });
128
+ }
129
+ }
130
+ }, [state.parameters, state.selectedEndpoint, state.requestUrl]);
131
+
132
+ // Step management
133
+ const setCurrentStep = (step: PlaygroundStep) => {
134
+ updateState({ currentStep: step });
135
+ };
136
+
137
+ const goToNextStep = () => {
138
+ const currentIndex = state.steps.indexOf(state.currentStep);
139
+ if (currentIndex < state.steps.length - 1) {
140
+ updateState({ currentStep: state.steps[currentIndex + 1] });
141
+ }
142
+ };
143
+
144
+ const goToPreviousStep = () => {
145
+ const currentIndex = state.steps.indexOf(state.currentStep);
146
+ if (currentIndex > 0) {
147
+ updateState({ currentStep: state.steps[currentIndex - 1] });
148
+ }
149
+ };
150
+
151
+ // Endpoint management
152
+ const setSelectedEndpoint = (endpoint: ApiEndpoint | null) => {
153
+ if (endpoint) {
154
+ // All endpoints are GET only
155
+ // Path is already a full URL from the endpoint
156
+ updateState({
157
+ selectedEndpoint: endpoint,
158
+ requestMethod: 'GET',
159
+ requestUrl: endpoint.path,
160
+ parameters: {}, // Reset parameters when endpoint changes
161
+ currentStep: 'request'
162
+ });
163
+ } else {
164
+ updateState({ selectedEndpoint: endpoint });
165
+ }
166
+ };
167
+
168
+ const setSelectedCategory = (category: string) => {
169
+ updateState({ selectedCategory: category });
170
+ };
171
+
172
+ const setSearchTerm = (term: string) => {
173
+ updateState({ searchTerm: term });
174
+ };
175
+
176
+ const setSelectedVersion = (version: string) => {
177
+ updateState({ selectedVersion: version });
178
+ };
179
+
180
+ // Request management
181
+ const setRequestUrl = (url: string) => {
182
+ updateState({ requestUrl: url });
183
+ };
184
+
185
+ const setRequestMethod = (method: string) => {
186
+ updateState({ requestMethod: method });
187
+ };
188
+
189
+ const setRequestHeaders = (headers: string) => {
190
+ updateState({ requestHeaders: headers });
191
+ };
192
+
193
+ const setRequestBody = (body: string) => {
194
+ updateState({ requestBody: body });
195
+ };
196
+
197
+ const setSelectedApiKey = (apiKeyId: string | null) => {
198
+ updateState({ selectedApiKey: apiKeyId });
199
+ };
200
+
201
+ const setManualApiToken = (manualApiToken: string) => {
202
+ updateState({ manualApiToken });
203
+ };
204
+
205
+ const setParameters = (parameters: Record<string, string>) => {
206
+ updateState({ parameters });
207
+ };
208
+
209
+ // Response management
210
+ const setResponse = (response: ApiResponse | null) => {
211
+ updateState({ response });
212
+ };
213
+
214
+ const setLoading = (loading: boolean) => {
215
+ updateState({ loading });
216
+ };
217
+
218
+ // UI management
219
+ const setSidebarOpen = (sidebarOpen: boolean) => {
220
+ updateState({ sidebarOpen });
221
+ };
222
+
223
+ // Actions
224
+ const clearAll = useCallback(() => {
225
+ setState(createInitialState());
226
+ }, []);
227
+
228
+ const sendRequest = useCallback(async () => {
229
+ if (!state.requestUrl) {
230
+ consola.error('No URL provided');
231
+ return;
232
+ }
233
+
234
+ setLoading(true);
235
+ setResponse(null);
236
+
237
+ try {
238
+ const headers = parseRequestHeaders(state.requestHeaders);
239
+
240
+ // Bearer token priority: manual token → JWT token from localStorage
241
+ let bearerToken: string | null = null;
242
+
243
+ if (state.manualApiToken) {
244
+ // Use manual token if provided
245
+ bearerToken = state.manualApiToken;
246
+ } else {
247
+ // Try to get JWT token from localStorage
248
+ if (typeof window !== 'undefined') {
249
+ bearerToken = window.localStorage.getItem('auth_token');
250
+ }
251
+ }
252
+
253
+ if (bearerToken) {
254
+ headers['Authorization'] = `Bearer ${bearerToken}`;
255
+ }
256
+
257
+ const requestOptions: RequestInit = {
258
+ method: state.requestMethod,
259
+ headers,
260
+ };
261
+
262
+ if (state.requestBody && state.requestMethod !== 'GET') {
263
+ requestOptions.body = state.requestBody;
264
+ }
265
+
266
+ const response = await fetch(state.requestUrl, requestOptions);
267
+ const responseText = await response.text();
268
+
269
+ let responseData;
270
+ try {
271
+ responseData = JSON.parse(responseText);
272
+ } catch {
273
+ responseData = responseText;
274
+ }
275
+
276
+ setResponse({
277
+ status: response.status,
278
+ statusText: response.statusText,
279
+ headers: Object.fromEntries(response.headers.entries()),
280
+ data: responseData,
281
+ });
282
+
283
+ consola.success(`Request successful: ${state.requestMethod} ${state.requestUrl}`);
284
+
285
+ // Auto-advance to response step
286
+ updateState({ currentStep: 'response' });
287
+ } catch (error) {
288
+ consola.error('Request failed:', error);
289
+ setResponse({
290
+ error: error instanceof Error ? error.message : 'Request failed',
291
+ });
292
+ } finally {
293
+ setLoading(false);
294
+ }
295
+ }, [state, setLoading, setResponse]);
296
+
297
+ const contextValue: PlaygroundContextType = {
298
+ // State
299
+ state,
300
+ config,
301
+ apiKeys,
302
+ apiKeysLoading: isLoadingApiKeys,
303
+
304
+ // Step management
305
+ setCurrentStep,
306
+ goToNextStep,
307
+ goToPreviousStep,
308
+
309
+ // Endpoint management
310
+ setSelectedEndpoint,
311
+ setSelectedCategory,
312
+ setSearchTerm,
313
+ setSelectedVersion,
314
+
315
+ // Request management
316
+ setRequestUrl,
317
+ setRequestMethod,
318
+ setRequestHeaders,
319
+ setRequestBody,
320
+ setSelectedApiKey,
321
+ setManualApiToken,
322
+ setParameters,
323
+
324
+ // Response management
325
+ setResponse,
326
+ setLoading,
327
+
328
+ // UI management
329
+ setSidebarOpen,
330
+
331
+ // Actions
332
+ clearAll,
333
+ sendRequest,
334
+ };
335
+
336
+ return <PlaygroundContext.Provider value={contextValue}>{children}</PlaygroundContext.Provider>;
337
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Playground Hooks
3
+ *
4
+ * Centralized exports for all playground hooks
5
+ */
6
+
7
+ export { default as useOpenApiSchema } from './useOpenApiSchema';
8
+ export { useMobile } from './useMobile';
@@ -0,0 +1,10 @@
1
+ import { useIsMobile } from '@djangocfg/ui-core/hooks';
2
+
3
+ export const useMobile = () => {
4
+ const isMobile = useIsMobile();
5
+
6
+ return {
7
+ isMobile,
8
+ isDesktop: !isMobile,
9
+ };
10
+ };
@@ -0,0 +1,199 @@
1
+ 'use client';
2
+
3
+ import consola from 'consola';
4
+ import { useCallback, useEffect, useMemo, useState } from 'react';
5
+
6
+ import { ApiEndpoint, OpenApiSchema, SchemaSource, UseOpenApiSchemaReturn } from '../types';
7
+
8
+ // Extract endpoints from OpenAPI schema (GET only)
9
+ const extractEndpoints = (schema: OpenApiSchema): ApiEndpoint[] => {
10
+ const endpointMap = new Map<string, ApiEndpoint>();
11
+
12
+ if (!schema.paths) return [];
13
+
14
+ // Get base URL from servers
15
+ const baseUrl = schema.servers && schema.servers.length > 0 ? schema.servers[0].url : '';
16
+
17
+ for (const [path, methods] of Object.entries(schema.paths)) {
18
+ // Only process GET methods
19
+ const getOperation = (methods as any).get;
20
+ if (!getOperation) continue;
21
+
22
+ const op = getOperation as any;
23
+ const description = op.description || op.summary || `GET ${path}`;
24
+ const category = op.tags?.[0] || 'Other';
25
+
26
+ const parameters: Array<{
27
+ name: string;
28
+ type: string;
29
+ required: boolean;
30
+ description?: string;
31
+ }> = [];
32
+
33
+ // Collect parameters
34
+ if (op.parameters) {
35
+ for (const param of op.parameters) {
36
+ parameters.push({
37
+ name: param.name,
38
+ type: param.schema?.type || 'string',
39
+ required: param.required || false,
40
+ description: param.description,
41
+ });
42
+ }
43
+ }
44
+
45
+ // Collect responses
46
+ const responses: Array<{
47
+ code: string;
48
+ description: string;
49
+ }> = [];
50
+
51
+ if (op.responses) {
52
+ for (const [code, response] of Object.entries(op.responses)) {
53
+ responses.push({
54
+ code,
55
+ description: (response as any).description || `Response ${code}`,
56
+ });
57
+ }
58
+ }
59
+
60
+ // Create endpoint (GET only) with full URL
61
+ const endpoint: ApiEndpoint = {
62
+ name: path.split('/').pop() || path,
63
+ method: 'GET',
64
+ path: baseUrl + path, // Combine baseUrl with path
65
+ description,
66
+ category,
67
+ parameters: parameters.length > 0 ? parameters : undefined,
68
+ requestBody: undefined, // GET requests don't have request body
69
+ responses: responses.length > 0 ? responses : undefined,
70
+ };
71
+
72
+ endpointMap.set(path, endpoint);
73
+ }
74
+
75
+ return Array.from(endpointMap.values());
76
+ };
77
+
78
+ // Get unique categories from endpoints
79
+ const getCategories = (endpoints: ApiEndpoint[]): string[] => {
80
+ const categories = new Set<string>();
81
+ endpoints.forEach((endpoint) => categories.add(endpoint.category));
82
+ return Array.from(categories).sort();
83
+ };
84
+
85
+ // Fetch schema from URL
86
+ const fetchSchema = async (url: string): Promise<OpenApiSchema> => {
87
+ const response = await fetch(url, {
88
+ headers: {
89
+ 'Accept': 'application/json',
90
+ },
91
+ });
92
+ if (!response.ok) {
93
+ throw new Error(`Failed to fetch schema: ${response.statusText}`);
94
+ }
95
+ return response.json();
96
+ };
97
+
98
+ interface UseOpenApiSchemaProps {
99
+ schemas: SchemaSource[];
100
+ defaultSchemaId?: string;
101
+ }
102
+
103
+ export default function useOpenApiSchema({
104
+ schemas,
105
+ defaultSchemaId,
106
+ }: UseOpenApiSchemaProps): UseOpenApiSchemaReturn {
107
+ const [loading, setLoading] = useState(true);
108
+ const [error, setError] = useState<string | null>(null);
109
+ const [currentSchemaId, setCurrentSchemaId] = useState<string>(
110
+ defaultSchemaId || schemas[0]?.id
111
+ );
112
+ const [loadedSchemas, setLoadedSchemas] = useState<Map<string, OpenApiSchema>>(
113
+ new Map()
114
+ );
115
+
116
+ const currentSchema = useMemo(
117
+ () => schemas.find((s) => s.id === currentSchemaId) || null,
118
+ [schemas, currentSchemaId]
119
+ );
120
+
121
+ const currentOpenApiSchema = useMemo(
122
+ () => (currentSchemaId ? loadedSchemas.get(currentSchemaId) : null),
123
+ [loadedSchemas, currentSchemaId]
124
+ );
125
+
126
+ const endpoints = useMemo(
127
+ () => (currentOpenApiSchema ? extractEndpoints(currentOpenApiSchema) : []),
128
+ [currentOpenApiSchema]
129
+ );
130
+
131
+ const categories = useMemo(() => getCategories(endpoints), [endpoints]);
132
+
133
+ // Load schema when current schema changes
134
+ useEffect(() => {
135
+ if (!currentSchema) return;
136
+
137
+ // Skip if already loaded
138
+ if (loadedSchemas.has(currentSchema.id)) {
139
+ setLoading(false);
140
+ return;
141
+ }
142
+
143
+ setLoading(true);
144
+ setError(null);
145
+
146
+ fetchSchema(currentSchema.url)
147
+ .then((schema) => {
148
+ setLoadedSchemas((prev) => new Map(prev).set(currentSchema.id, schema));
149
+ consola.success(`Schema loaded: ${currentSchema.name}`);
150
+ setLoading(false);
151
+ })
152
+ .catch((err) => {
153
+ consola.error(`Error loading schema from ${currentSchema.url}:`, err);
154
+ setError(err instanceof Error ? err.message : 'Failed to load schema');
155
+ setLoading(false);
156
+ });
157
+ }, [currentSchema, loadedSchemas]);
158
+
159
+ const setCurrentSchema = useCallback((schemaId: string) => {
160
+ setCurrentSchemaId(schemaId);
161
+ }, []);
162
+
163
+ const refresh = useCallback(() => {
164
+ if (!currentSchema) return;
165
+
166
+ setLoading(true);
167
+ setError(null);
168
+
169
+ // Remove from cache to force reload
170
+ setLoadedSchemas((prev) => {
171
+ const next = new Map(prev);
172
+ next.delete(currentSchema.id);
173
+ return next;
174
+ });
175
+
176
+ fetchSchema(currentSchema.url)
177
+ .then((schema) => {
178
+ setLoadedSchemas((prev) => new Map(prev).set(currentSchema.id, schema));
179
+ consola.success(`Schema refreshed: ${currentSchema.name}`);
180
+ setLoading(false);
181
+ })
182
+ .catch((err) => {
183
+ consola.error(`Error refreshing schema from ${currentSchema.url}:`, err);
184
+ setError(err instanceof Error ? err.message : 'Failed to refresh schema');
185
+ setLoading(false);
186
+ });
187
+ }, [currentSchema]);
188
+
189
+ return {
190
+ loading,
191
+ error,
192
+ endpoints,
193
+ categories,
194
+ schemas,
195
+ currentSchema,
196
+ setCurrentSchema,
197
+ refresh,
198
+ };
199
+ }
@@ -0,0 +1,37 @@
1
+ 'use client';
2
+
3
+ import React, { lazy, Suspense } from 'react';
4
+ import { PlaygroundProvider } from './context/PlaygroundContext';
5
+ import type { PlaygroundConfig } from './types';
6
+
7
+ // Lazy load the PlaygroundLayout component
8
+ const PlaygroundLayout = lazy(() =>
9
+ import('./components/PlaygroundLayout').then((mod) => ({ default: mod.PlaygroundLayout }))
10
+ );
11
+
12
+ // Loading fallback component
13
+ const LoadingFallback = () => (
14
+ <div className="flex items-center justify-center min-h-[400px]">
15
+ <div className="text-muted-foreground">Loading API Playground...</div>
16
+ </div>
17
+ );
18
+
19
+ export interface PlaygroundProps {
20
+ config: PlaygroundConfig;
21
+ }
22
+
23
+ export const Playground: React.FC<PlaygroundProps> = ({ config }) => {
24
+ return (
25
+ <PlaygroundProvider config={config}>
26
+ <Suspense fallback={<LoadingFallback />}>
27
+ <PlaygroundLayout />
28
+ </Suspense>
29
+ </PlaygroundProvider>
30
+ );
31
+ };
32
+
33
+ // Re-export types for convenience
34
+ export type { PlaygroundConfig, SchemaSource } from './types';
35
+
36
+ // Default export for dynamic import
37
+ export default Playground;