@djangocfg/ui-tools 2.1.287 → 2.1.290
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -3
- package/dist/DocsLayout-IKH7BLSU.cjs +3464 -0
- package/dist/DocsLayout-IKH7BLSU.cjs.map +1 -0
- package/dist/DocsLayout-JPXFUKAR.mjs +3457 -0
- package/dist/DocsLayout-JPXFUKAR.mjs.map +1 -0
- package/dist/{PrettyCode.client-5GABIN2I.cjs → PrettyCode.client-RPDIE5CH.cjs} +104 -3
- package/dist/PrettyCode.client-RPDIE5CH.cjs.map +1 -0
- package/dist/{PrettyCode.client-IZTXXYHG.mjs → PrettyCode.client-SPMTQEG4.mjs} +106 -5
- package/dist/PrettyCode.client-SPMTQEG4.mjs.map +1 -0
- package/dist/{chunk-IULI4XII.cjs → chunk-5Q4UMSWB.cjs} +355 -9
- package/dist/chunk-5Q4UMSWB.cjs.map +1 -0
- package/dist/{chunk-VZGQC3NG.mjs → chunk-EFWOJPA6.mjs} +349 -9
- package/dist/chunk-EFWOJPA6.mjs.map +1 -0
- package/dist/index.cjs +10 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.mjs +5 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +21 -14
- package/src/components/markdown/MarkdownMessage.tsx +46 -0
- package/src/tools/OpenapiViewer/OpenapiViewer.story.tsx +93 -157
- package/src/tools/OpenapiViewer/README.md +114 -6
- package/src/tools/OpenapiViewer/components/DocsLayout/ApiIntroSection.tsx +20 -6
- package/src/tools/OpenapiViewer/components/DocsLayout/DocsView.tsx +331 -53
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/LanguageTabs.tsx +36 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/index.tsx +56 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/useCodeSnippet.ts +77 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MetaActions.tsx +146 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MethodBadge.tsx +6 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/PathDisplay.tsx +26 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/index.tsx +87 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/ParamGroup.tsx +30 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/ParamRow.tsx +36 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/index.tsx +22 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/RequestBody/index.tsx +33 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/ResponseBody.tsx +76 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/ResponseRow.tsx +80 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/StatusTag.tsx +32 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/index.tsx +21 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +106 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +127 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/index.tsx +31 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/types.ts +28 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/SectionHeader.tsx +87 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/defaults.ts +27 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/index.tsx +45 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/context.tsx +56 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/hooks/useSectionHash.ts +63 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/index.tsx +96 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/store/index.ts +133 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/store/selectors.ts +40 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/types.ts +17 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/SchemaCopyMenu.tsx +40 -11
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/BrandHeader.tsx +48 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/CategoryBlock.tsx +33 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/EndpointRow.tsx +73 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/MethodChips.tsx +43 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SchemaSection.tsx +27 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SearchInput.tsx +45 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SidebarBody.tsx +50 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/Toolbar.tsx +64 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/buildVM.ts +126 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/index.tsx +112 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/types.ts +42 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/useDebouncedValue.ts +14 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/SlideInPlayground.tsx +10 -7
- package/src/tools/OpenapiViewer/components/DocsLayout/TryItSheet.tsx +9 -6
- package/src/tools/OpenapiViewer/components/DocsLayout/anchor.ts +19 -2
- package/src/tools/OpenapiViewer/components/DocsLayout/grouping.ts +38 -21
- package/src/tools/OpenapiViewer/components/DocsLayout/index.tsx +168 -50
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/PrettyView.tsx +55 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/PreviewView.tsx +115 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/RawView.tsx +24 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/StatusBar.tsx +63 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/ViewTabs.tsx +45 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/detectContent.ts +97 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/index.tsx +93 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/types.ts +26 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/useResponseView.ts +62 -0
- package/src/tools/OpenapiViewer/hooks/index.ts +3 -1
- package/src/tools/OpenapiViewer/hooks/useDocsUrlSync.ts +119 -0
- package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +164 -74
- package/src/tools/OpenapiViewer/types.ts +46 -1
- package/src/tools/OpenapiViewer/utils/codeSamples.ts +287 -0
- package/src/tools/OpenapiViewer/utils/index.ts +3 -0
- package/src/tools/OpenapiViewer/utils/operationToHar.ts +119 -0
- package/src/tools/OpenapiViewer/utils/sampler.ts +72 -0
- package/src/tools/OpenapiViewer/utils/scrollParent.ts +68 -0
- package/src/tools/PrettyCode/PrettyCode.client.tsx +88 -1
- package/src/tools/PrettyCode/PrettyCode.story.tsx +114 -361
- package/src/tools/PrettyCode/index.tsx +13 -0
- package/src/tools/PrettyCode/lazy.tsx +5 -0
- package/src/tools/PrettyCode/registerPrismLanguages.ts +111 -0
- package/dist/DocsLayout-BCVU6TTX.cjs +0 -2027
- package/dist/DocsLayout-BCVU6TTX.cjs.map +0 -1
- package/dist/DocsLayout-ERETJLLV.mjs +0 -2020
- package/dist/DocsLayout-ERETJLLV.mjs.map +0 -1
- package/dist/PrettyCode.client-5GABIN2I.cjs.map +0 -1
- package/dist/PrettyCode.client-IZTXXYHG.mjs.map +0 -1
- package/dist/chunk-IULI4XII.cjs.map +0 -1
- package/dist/chunk-VZGQC3NG.mjs.map +0 -1
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc.tsx +0 -268
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar.tsx +0 -211
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel.tsx +0 -127
|
@@ -3,78 +3,32 @@
|
|
|
3
3
|
import consola from 'consola';
|
|
4
4
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
5
5
|
|
|
6
|
-
import { ApiEndpoint, OpenApiInfo, OpenApiSchema, SchemaSource, UseOpenApiSchemaReturn } from '../types';
|
|
6
|
+
import { ApiEndpoint, LoadedSchemaEntry, OpenApiInfo, OpenApiSchema, SchemaSource, UseOpenApiSchemaReturn } from '../types';
|
|
7
|
+
import { sampleSchemaJson } from '../utils/sampler';
|
|
7
8
|
import { dereferenceSchema } from '../utils/schemaExport';
|
|
8
9
|
import { joinUrl, resolveBaseUrl } from '../utils/url';
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
type JsonSchemaNode = Record<string, unknown> & {
|
|
13
|
-
type?: string;
|
|
14
|
-
properties?: Record<string, JsonSchemaNode>;
|
|
15
|
-
required?: string[];
|
|
16
|
-
items?: JsonSchemaNode;
|
|
17
|
-
enum?: unknown[];
|
|
18
|
-
example?: unknown;
|
|
19
|
-
default?: unknown;
|
|
20
|
-
format?: string;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/** Walk a JSON Schema and build a realistic-looking example value. */
|
|
24
|
-
function exampleFromSchema(schema: JsonSchemaNode | undefined, depth = 0): unknown {
|
|
25
|
-
if (!schema || depth > 8) return null;
|
|
26
|
-
|
|
27
|
-
// Respect schema-provided examples first — no need to invent a value
|
|
28
|
-
// when the spec author already did the work.
|
|
29
|
-
if (schema.example !== undefined) return schema.example;
|
|
30
|
-
if (schema.default !== undefined) return schema.default;
|
|
31
|
-
if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
|
|
32
|
-
|
|
33
|
-
switch (schema.type) {
|
|
34
|
-
case 'object': {
|
|
35
|
-
const out: Record<string, unknown> = {};
|
|
36
|
-
const props = schema.properties ?? {};
|
|
37
|
-
for (const [k, v] of Object.entries(props)) {
|
|
38
|
-
out[k] = exampleFromSchema(v, depth + 1);
|
|
39
|
-
}
|
|
40
|
-
return out;
|
|
41
|
-
}
|
|
42
|
-
case 'array':
|
|
43
|
-
return [exampleFromSchema(schema.items, depth + 1)];
|
|
44
|
-
case 'integer':
|
|
45
|
-
case 'number':
|
|
46
|
-
return 0;
|
|
47
|
-
case 'boolean':
|
|
48
|
-
return false;
|
|
49
|
-
case 'string':
|
|
50
|
-
if (schema.format === 'date-time') return new Date().toISOString();
|
|
51
|
-
if (schema.format === 'date') return new Date().toISOString().slice(0, 10);
|
|
52
|
-
if (schema.format === 'email') return 'user@example.com';
|
|
53
|
-
if (schema.format === 'uri' || schema.format === 'url') return 'https://example.com';
|
|
54
|
-
if (schema.format === 'uuid') return '00000000-0000-0000-0000-000000000000';
|
|
55
|
-
return '';
|
|
56
|
-
default:
|
|
57
|
-
// No type (or composed schema like allOf/oneOf we don't unpack) —
|
|
58
|
-
// fall back to an empty object rather than ``null`` so the resulting
|
|
59
|
-
// JSON is still valid-looking.
|
|
60
|
-
if (schema.properties) {
|
|
61
|
-
const out: Record<string, unknown> = {};
|
|
62
|
-
for (const [k, v] of Object.entries(schema.properties)) {
|
|
63
|
-
out[k] = exampleFromSchema(v, depth + 1);
|
|
64
|
-
}
|
|
65
|
-
return out;
|
|
66
|
-
}
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
11
|
+
type JsonSchemaNode = Record<string, unknown>;
|
|
70
12
|
|
|
71
13
|
// HTTP methods to extract from OpenAPI schema
|
|
72
14
|
const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete'] as const;
|
|
73
15
|
|
|
74
16
|
// Extract endpoints from OpenAPI schema (all methods). ``baseUrl`` is
|
|
75
17
|
// resolved by the caller via ``resolveBaseUrl`` — we just paste it onto
|
|
76
|
-
// the front of each path here.
|
|
77
|
-
|
|
18
|
+
// the front of each path here. ``schemaId`` is tagged onto every
|
|
19
|
+
// endpoint so downstream consumers (sections-mode sidebar, anchors, URL
|
|
20
|
+
// sync) can correlate endpoints back to their source schema.
|
|
21
|
+
//
|
|
22
|
+
// ``specRoot`` is the raw, un-dereferenced schema — passed to
|
|
23
|
+
// ``openapi-sampler`` so it can resolve any ``$ref`` nodes our own
|
|
24
|
+
// ``dereferenceSchema`` left behind (depth-limited, external, or
|
|
25
|
+
// circular). Without it the sampler throws on deep ref chains.
|
|
26
|
+
const extractEndpoints = (
|
|
27
|
+
schema: OpenApiSchema,
|
|
28
|
+
baseUrl: string,
|
|
29
|
+
schemaId?: string,
|
|
30
|
+
specRoot?: OpenApiSchema,
|
|
31
|
+
): ApiEndpoint[] => {
|
|
78
32
|
const endpoints: ApiEndpoint[] = [];
|
|
79
33
|
|
|
80
34
|
if (!schema.paths) return [];
|
|
@@ -107,17 +61,32 @@ const extractEndpoints = (schema: OpenApiSchema, baseUrl: string): ApiEndpoint[]
|
|
|
107
61
|
});
|
|
108
62
|
}
|
|
109
63
|
|
|
110
|
-
// Collect responses
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
64
|
+
// Collect responses. We also extract the ``application/json``
|
|
65
|
+
// schema (preferring it, falling back to whatever media type is
|
|
66
|
+
// present) and generate a sampled example — the docs layout
|
|
67
|
+
// renders it as a collapsible "Example response" block under the
|
|
68
|
+
// status-code table. ``writeOnly`` fields are skipped so secrets
|
|
69
|
+
// declared ``writeOnly: true`` don't leak into response samples.
|
|
70
|
+
const responses: NonNullable<ApiEndpoint['responses']> = [];
|
|
115
71
|
|
|
116
72
|
if (op.responses) {
|
|
117
73
|
for (const [code, response] of Object.entries(op.responses)) {
|
|
74
|
+
const respContent = (response as any).content as Record<string, any> | undefined;
|
|
75
|
+
const contentKeys = respContent ? Object.keys(respContent) : [];
|
|
76
|
+
const chosenContentType = respContent?.['application/json']
|
|
77
|
+
? 'application/json'
|
|
78
|
+
: contentKeys[0];
|
|
79
|
+
const chosen = chosenContentType ? respContent?.[chosenContentType] : undefined;
|
|
80
|
+
const respSchema = chosen?.schema as JsonSchemaNode | undefined;
|
|
81
|
+
|
|
118
82
|
responses.push({
|
|
119
83
|
code,
|
|
120
84
|
description: (response as any).description || `Response ${code}`,
|
|
85
|
+
contentType: chosenContentType,
|
|
86
|
+
schema: respSchema,
|
|
87
|
+
example: respSchema
|
|
88
|
+
? sampleSchemaJson(respSchema, { skipWriteOnly: true }, specRoot)
|
|
89
|
+
: undefined,
|
|
121
90
|
});
|
|
122
91
|
}
|
|
123
92
|
}
|
|
@@ -125,17 +94,20 @@ const extractEndpoints = (schema: OpenApiSchema, baseUrl: string): ApiEndpoint[]
|
|
|
125
94
|
// Extract request body info — keep the dereferenced schema so
|
|
126
95
|
// downstream UI can render a fields table and generate a starter
|
|
127
96
|
// example instead of showing an opaque ``object`` / ``array`` tag.
|
|
97
|
+
// ``readOnly`` fields are skipped: the body is what the client
|
|
98
|
+
// *sends*, so server-owned fields (id, created_at, …) must not
|
|
99
|
+
// pre-fill the editor.
|
|
128
100
|
let requestBody: ApiEndpoint['requestBody'];
|
|
129
101
|
if (op.requestBody) {
|
|
130
102
|
const content = op.requestBody.content;
|
|
131
103
|
const mediaType = content?.['application/json'] || content?.[Object.keys(content || {})[0]];
|
|
132
104
|
const rawSchema = mediaType?.schema as JsonSchemaNode | undefined;
|
|
133
105
|
requestBody = {
|
|
134
|
-
type: rawSchema?.type || 'object',
|
|
106
|
+
type: (rawSchema?.type as string | undefined) || 'object',
|
|
135
107
|
description: op.requestBody.description,
|
|
136
108
|
schema: rawSchema,
|
|
137
109
|
example: rawSchema
|
|
138
|
-
?
|
|
110
|
+
? sampleSchemaJson(rawSchema, { skipReadOnly: true }, specRoot)
|
|
139
111
|
: undefined,
|
|
140
112
|
};
|
|
141
113
|
}
|
|
@@ -150,6 +122,7 @@ const extractEndpoints = (schema: OpenApiSchema, baseUrl: string): ApiEndpoint[]
|
|
|
150
122
|
parameters: parameters.length > 0 ? parameters : undefined,
|
|
151
123
|
requestBody,
|
|
152
124
|
responses: responses.length > 0 ? responses : undefined,
|
|
125
|
+
schemaId,
|
|
153
126
|
};
|
|
154
127
|
|
|
155
128
|
endpoints.push(endpoint);
|
|
@@ -185,12 +158,24 @@ interface UseOpenApiSchemaProps {
|
|
|
185
158
|
/** Global base URL override from ``PlaygroundConfig.baseUrl``.
|
|
186
159
|
* Per-schema ``SchemaSource.baseUrl`` takes precedence over this. */
|
|
187
160
|
baseUrl?: string;
|
|
161
|
+
/** When ``true`` the hook fetches every schema in ``schemas`` (not just
|
|
162
|
+
* the active one) and exposes them via ``schemasData``. Used by the
|
|
163
|
+
* ``sections`` grouping mode — the docs column concatenates endpoints
|
|
164
|
+
* from every schema, so they all need to be on the client. Default
|
|
165
|
+
* is ``false`` to preserve the original lazy behaviour. */
|
|
166
|
+
preloadAll?: boolean;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
interface SchemaLoadState {
|
|
170
|
+
loading: boolean;
|
|
171
|
+
error: string | null;
|
|
188
172
|
}
|
|
189
173
|
|
|
190
174
|
export default function useOpenApiSchema({
|
|
191
175
|
schemas,
|
|
192
176
|
defaultSchemaId,
|
|
193
177
|
baseUrl: configBaseUrl,
|
|
178
|
+
preloadAll = false,
|
|
194
179
|
}: UseOpenApiSchemaProps): UseOpenApiSchemaReturn {
|
|
195
180
|
const [loading, setLoading] = useState(true);
|
|
196
181
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -200,6 +185,10 @@ export default function useOpenApiSchema({
|
|
|
200
185
|
const [loadedSchemas, setLoadedSchemas] = useState<Map<string, OpenApiSchema>>(
|
|
201
186
|
new Map()
|
|
202
187
|
);
|
|
188
|
+
// Per-schema loading/error state for ``preloadAll`` — each schema may
|
|
189
|
+
// succeed or fail independently, and the UI wants to render partial
|
|
190
|
+
// results while slow/broken ones are still resolving.
|
|
191
|
+
const [loadStates, setLoadStates] = useState<Map<string, SchemaLoadState>>(new Map());
|
|
203
192
|
|
|
204
193
|
const currentSchema = useMemo(
|
|
205
194
|
() => schemas.find((s) => s.id === currentSchemaId) || null,
|
|
@@ -233,8 +222,11 @@ export default function useOpenApiSchema({
|
|
|
233
222
|
);
|
|
234
223
|
|
|
235
224
|
const endpoints = useMemo(
|
|
236
|
-
() =>
|
|
237
|
-
|
|
225
|
+
() =>
|
|
226
|
+
dereferencedSchema
|
|
227
|
+
? extractEndpoints(dereferencedSchema, resolvedBaseUrl, currentSchemaId, currentOpenApiSchema ?? undefined)
|
|
228
|
+
: [],
|
|
229
|
+
[dereferencedSchema, resolvedBaseUrl, currentSchemaId, currentOpenApiSchema]
|
|
238
230
|
);
|
|
239
231
|
|
|
240
232
|
const categories = useMemo(() => getCategories(endpoints), [endpoints]);
|
|
@@ -250,8 +242,9 @@ export default function useOpenApiSchema({
|
|
|
250
242
|
};
|
|
251
243
|
}, [currentOpenApiSchema]);
|
|
252
244
|
|
|
253
|
-
// Load schema when current schema changes
|
|
245
|
+
// Load schema when current schema changes (single-schema mode)
|
|
254
246
|
useEffect(() => {
|
|
247
|
+
if (preloadAll) return;
|
|
255
248
|
if (!currentSchema) return;
|
|
256
249
|
|
|
257
250
|
// Skip if already loaded
|
|
@@ -274,7 +267,103 @@ export default function useOpenApiSchema({
|
|
|
274
267
|
setError(err instanceof Error ? err.message : 'Failed to load schema');
|
|
275
268
|
setLoading(false);
|
|
276
269
|
});
|
|
277
|
-
}, [currentSchema, loadedSchemas]);
|
|
270
|
+
}, [currentSchema, loadedSchemas, preloadAll]);
|
|
271
|
+
|
|
272
|
+
// Preload every schema (sections-grouping mode). Each schema is fetched
|
|
273
|
+
// independently — a slow or broken source doesn't block the rest.
|
|
274
|
+
useEffect(() => {
|
|
275
|
+
if (!preloadAll) return;
|
|
276
|
+
if (schemas.length === 0) {
|
|
277
|
+
setLoading(false);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
let cancelled = false;
|
|
282
|
+
const pending = schemas.filter((s) => !loadedSchemas.has(s.id));
|
|
283
|
+
if (pending.length === 0) {
|
|
284
|
+
setLoading(false);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
setLoading(true);
|
|
289
|
+
setLoadStates((prev) => {
|
|
290
|
+
const next = new Map(prev);
|
|
291
|
+
for (const s of pending) next.set(s.id, { loading: true, error: null });
|
|
292
|
+
return next;
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
Promise.allSettled(
|
|
296
|
+
pending.map((s) =>
|
|
297
|
+
fetchSchema(s.url).then((schema) => ({ id: s.id, name: s.name, schema })),
|
|
298
|
+
),
|
|
299
|
+
).then((results) => {
|
|
300
|
+
if (cancelled) return;
|
|
301
|
+
|
|
302
|
+
setLoadedSchemas((prev) => {
|
|
303
|
+
const next = new Map(prev);
|
|
304
|
+
for (const r of results) {
|
|
305
|
+
if (r.status === 'fulfilled') {
|
|
306
|
+
next.set(r.value.id, r.value.schema);
|
|
307
|
+
consola.success(`Schema loaded: ${r.value.name}`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return next;
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
setLoadStates((prev) => {
|
|
314
|
+
const next = new Map(prev);
|
|
315
|
+
results.forEach((r, i) => {
|
|
316
|
+
const src = pending[i]!;
|
|
317
|
+
if (r.status === 'fulfilled') {
|
|
318
|
+
next.set(src.id, { loading: false, error: null });
|
|
319
|
+
} else {
|
|
320
|
+
const msg = r.reason instanceof Error ? r.reason.message : 'Failed to load schema';
|
|
321
|
+
consola.error(`Error loading schema from ${src.url}:`, r.reason);
|
|
322
|
+
next.set(src.id, { loading: false, error: msg });
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
return next;
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
setLoading(false);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
return () => {
|
|
332
|
+
cancelled = true;
|
|
333
|
+
};
|
|
334
|
+
}, [preloadAll, schemas, loadedSchemas]);
|
|
335
|
+
|
|
336
|
+
const schemasData = useMemo<LoadedSchemaEntry[]>(() => {
|
|
337
|
+
if (!preloadAll) return [];
|
|
338
|
+
return schemas.map((src) => {
|
|
339
|
+
const raw = loadedSchemas.get(src.id) ?? null;
|
|
340
|
+
const deref = raw ? dereferenceSchema(raw) : null;
|
|
341
|
+
const resolved = resolveBaseUrl({
|
|
342
|
+
schemaSource: src.baseUrl,
|
|
343
|
+
config: configBaseUrl,
|
|
344
|
+
fromServers: raw?.servers?.[0]?.url,
|
|
345
|
+
});
|
|
346
|
+
const info: OpenApiInfo | null = raw?.info
|
|
347
|
+
? {
|
|
348
|
+
title: raw.info.title,
|
|
349
|
+
version: raw.info.version,
|
|
350
|
+
description: raw.info.description,
|
|
351
|
+
servers: raw.servers,
|
|
352
|
+
}
|
|
353
|
+
: null;
|
|
354
|
+
const eps = deref ? extractEndpoints(deref, resolved, src.id, raw ?? undefined) : [];
|
|
355
|
+
const state = loadStates.get(src.id) ?? { loading: !raw, error: null };
|
|
356
|
+
return {
|
|
357
|
+
source: src,
|
|
358
|
+
info,
|
|
359
|
+
rawSchema: raw,
|
|
360
|
+
endpoints: eps,
|
|
361
|
+
resolvedBaseUrl: resolved || undefined,
|
|
362
|
+
loading: state.loading,
|
|
363
|
+
error: state.error,
|
|
364
|
+
};
|
|
365
|
+
});
|
|
366
|
+
}, [preloadAll, schemas, loadedSchemas, loadStates, configBaseUrl]);
|
|
278
367
|
|
|
279
368
|
const setCurrentSchema = useCallback((schemaId: string) => {
|
|
280
369
|
setCurrentSchemaId(schemaId);
|
|
@@ -321,5 +410,6 @@ export default function useOpenApiSchema({
|
|
|
321
410
|
resolvedBaseUrl: resolvedBaseUrl || undefined,
|
|
322
411
|
setCurrentSchema,
|
|
323
412
|
refresh,
|
|
413
|
+
schemasData,
|
|
324
414
|
};
|
|
325
415
|
}
|
|
@@ -19,6 +19,10 @@ export interface ApiEndpoint {
|
|
|
19
19
|
name: string;
|
|
20
20
|
method: string;
|
|
21
21
|
path: string;
|
|
22
|
+
/** ID of the schema this endpoint was extracted from. Populated whenever
|
|
23
|
+
* endpoints from multiple schemas coexist (``sections`` grouping), so
|
|
24
|
+
* sidebar grouping, anchors and URL sync can all tell them apart. */
|
|
25
|
+
schemaId?: string;
|
|
22
26
|
/** Short human label from OpenAPI ``operation.summary``. Empty when
|
|
23
27
|
* the spec provides none. Prefer this for sidebar rows and breadcrumbs. */
|
|
24
28
|
summary: string;
|
|
@@ -43,6 +47,16 @@ export interface ApiEndpoint {
|
|
|
43
47
|
responses?: Array<{
|
|
44
48
|
code: string;
|
|
45
49
|
description: string;
|
|
50
|
+
/** Media type the schema is sourced from (e.g. ``application/json``).
|
|
51
|
+
* ``undefined`` when the response advertises no body. */
|
|
52
|
+
contentType?: string;
|
|
53
|
+
/** Dereferenced JSON Schema for the response body. Used by the docs
|
|
54
|
+
* layout to render a sampled example under the status-code table. */
|
|
55
|
+
schema?: Record<string, unknown>;
|
|
56
|
+
/** Pre-generated example JSON (sampled via ``openapi-sampler``).
|
|
57
|
+
* ``undefined`` when the response has no schema or sampling failed
|
|
58
|
+
* — the UI conditionally hides the "Example" block in that case. */
|
|
59
|
+
example?: string;
|
|
46
60
|
}>;
|
|
47
61
|
}
|
|
48
62
|
|
|
@@ -92,6 +106,20 @@ export interface PlaygroundConfig {
|
|
|
92
106
|
* a spinner instead of "no keys yet". */
|
|
93
107
|
apiKeys?: ApiKey[];
|
|
94
108
|
apiKeysLoading?: boolean;
|
|
109
|
+
/** How multiple schemas are presented in the sidebar.
|
|
110
|
+
* - ``'selector'`` (default): a Combobox switches between schemas, the
|
|
111
|
+
* docs column shows endpoints of the active schema only.
|
|
112
|
+
* - ``'sections'``: the Combobox is hidden and every schema becomes a
|
|
113
|
+
* top-level heading in the sidebar, with endpoints of all schemas
|
|
114
|
+
* rendered back-to-back in the docs column. Scrollspy picks the
|
|
115
|
+
* active schema based on what's visible. */
|
|
116
|
+
schemaGrouping?: 'selector' | 'sections';
|
|
117
|
+
/** Optional URL-hash sync. When enabled, the viewer reads/writes
|
|
118
|
+
* ``#<schemaId>/<anchor>`` on the browser location. Falsy value (the
|
|
119
|
+
* default) keeps the viewer hash-free. Set to an object with
|
|
120
|
+
* ``{ enabled: true }`` to opt in; future fields (e.g. a custom
|
|
121
|
+
* adapter) stay backwards compatible. */
|
|
122
|
+
urlSync?: boolean | { enabled: boolean };
|
|
95
123
|
}
|
|
96
124
|
|
|
97
125
|
// Playground state types
|
|
@@ -190,6 +218,19 @@ export interface OpenApiInfo {
|
|
|
190
218
|
servers?: Array<{ url: string; description?: string }>;
|
|
191
219
|
}
|
|
192
220
|
|
|
221
|
+
/** Per-schema snapshot used by the ``sections`` grouping mode. Mirrors the
|
|
222
|
+
* fields that ``UseOpenApiSchemaReturn`` exposes for the active schema,
|
|
223
|
+
* but repeated for every loaded schema. */
|
|
224
|
+
export interface LoadedSchemaEntry {
|
|
225
|
+
source: SchemaSource;
|
|
226
|
+
info: OpenApiInfo | null;
|
|
227
|
+
rawSchema: OpenApiSchema | null;
|
|
228
|
+
endpoints: ApiEndpoint[];
|
|
229
|
+
resolvedBaseUrl: string | undefined;
|
|
230
|
+
loading: boolean;
|
|
231
|
+
error: string | null;
|
|
232
|
+
}
|
|
233
|
+
|
|
193
234
|
// Hook return types
|
|
194
235
|
export interface UseOpenApiSchemaReturn {
|
|
195
236
|
loading: boolean;
|
|
@@ -208,4 +249,8 @@ export interface UseOpenApiSchemaReturn {
|
|
|
208
249
|
resolvedBaseUrl: string | undefined;
|
|
209
250
|
setCurrentSchema: (schemaId: string) => void;
|
|
210
251
|
refresh: () => void;
|
|
211
|
-
|
|
252
|
+
/** Populated only when the hook was called with ``preloadAll: true``
|
|
253
|
+
* (``sections`` grouping mode). One entry per schema source, in the
|
|
254
|
+
* same order as ``schemas``. */
|
|
255
|
+
schemasData: LoadedSchemaEntry[];
|
|
256
|
+
}
|