@jpmorganchase/elemental 1.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. package/__fixtures__/api-descriptions/Instagram.d.ts +1547 -0
  2. package/__fixtures__/api-descriptions/badgesForSchema.d.ts +1 -0
  3. package/__fixtures__/api-descriptions/simpleApiWithInternalOperations.d.ts +224 -0
  4. package/__fixtures__/api-descriptions/simpleApiWithoutDescription.d.ts +212 -0
  5. package/__fixtures__/api-descriptions/todosApiBundled.d.ts +1 -0
  6. package/__fixtures__/api-descriptions/zoomApiYaml.d.ts +1 -0
  7. package/components/API/APIWithSidebarLayout.d.ts +17 -0
  8. package/components/API/APIWithStackedLayout.d.ts +15 -0
  9. package/components/API/utils.d.ts +18 -0
  10. package/containers/API.d.ts +23 -0
  11. package/containers/API.spec.d.ts +3 -0
  12. package/containers/API.stories.d.ts +57 -0
  13. package/hooks/useExportDocumentProps.d.ts +11 -0
  14. package/hooks/useExportDocumentProps.spec.d.ts +1 -0
  15. package/index.esm.js +516 -0
  16. package/index.js +549 -0
  17. package/index.mjs +516 -0
  18. package/package.json +17 -76
  19. package/styles.min.css +1 -0
  20. package/utils/oas/index.d.ts +3 -0
  21. package/utils/oas/oas2.d.ts +2 -0
  22. package/utils/oas/oas3.d.ts +2 -0
  23. package/utils/oas/types.d.ts +30 -0
  24. package/web-components/components.d.ts +1 -0
  25. package/web-components/index.d.ts +1 -0
  26. package/web-components.min.js +2 -0
  27. package/web-components.min.js.LICENSE.txt +189 -0
  28. package/.storybook/main.js +0 -1
  29. package/.storybook/manager.js +0 -1
  30. package/.storybook/preview.jsx +0 -3
  31. package/jest.config.js +0 -7
  32. package/src/__fixtures__/api-descriptions/Instagram.ts +0 -1859
  33. package/src/__fixtures__/api-descriptions/badgesForSchema.ts +0 -36
  34. package/src/__fixtures__/api-descriptions/simpleApiWithInternalOperations.ts +0 -253
  35. package/src/__fixtures__/api-descriptions/simpleApiWithoutDescription.ts +0 -243
  36. package/src/__fixtures__/api-descriptions/todosApiBundled.ts +0 -430
  37. package/src/__fixtures__/api-descriptions/zoomApiYaml.ts +0 -6083
  38. package/src/components/API/APIWithSidebarLayout.tsx +0 -111
  39. package/src/components/API/APIWithStackedLayout.tsx +0 -220
  40. package/src/components/API/__tests__/utils.test.ts +0 -848
  41. package/src/components/API/utils.ts +0 -174
  42. package/src/containers/API.spec.tsx +0 -131
  43. package/src/containers/API.stories.tsx +0 -99
  44. package/src/containers/API.tsx +0 -200
  45. package/src/hooks/useExportDocumentProps.spec.tsx +0 -68
  46. package/src/hooks/useExportDocumentProps.tsx +0 -48
  47. package/src/styles.css +0 -1
  48. package/src/utils/oas/__tests__/oas.spec.ts +0 -272
  49. package/src/utils/oas/index.ts +0 -150
  50. package/src/utils/oas/oas2.ts +0 -31
  51. package/src/utils/oas/oas3.ts +0 -37
  52. package/src/utils/oas/types.ts +0 -31
  53. package/src/web-components/__stories__/Api.stories.tsx +0 -63
  54. package/src/web-components/components.ts +0 -20
  55. package/src/web-components/index.ts +0 -3
  56. package/tsconfig.build.json +0 -18
  57. package/tsconfig.json +0 -7
  58. package/web-components.config.js +0 -1
  59. /package/{src/index.ts → index.d.ts} +0 -0
@@ -0,0 +1,57 @@
1
+ import { Story } from '@storybook/react';
2
+ import * as React from 'react';
3
+ import { APIProps } from './API';
4
+ declare const _default: {
5
+ title: string;
6
+ component: React.FC<APIProps>;
7
+ argTypes: {
8
+ apiDescriptionDocument: {
9
+ control: string;
10
+ type: {
11
+ required: boolean;
12
+ };
13
+ table: {
14
+ category: string;
15
+ };
16
+ };
17
+ apiDescriptionUrl: {
18
+ control: string;
19
+ table: {
20
+ category: string;
21
+ };
22
+ };
23
+ layout: {
24
+ control: {
25
+ type: string;
26
+ };
27
+ table: {
28
+ category: string;
29
+ };
30
+ };
31
+ basePath: {
32
+ table: {
33
+ category: string;
34
+ };
35
+ };
36
+ router: {
37
+ table: {
38
+ category: string;
39
+ };
40
+ };
41
+ };
42
+ args: {
43
+ router: string;
44
+ };
45
+ };
46
+ export default _default;
47
+ export declare const APIWithYamlProvidedDirectly: Story<APIProps>;
48
+ export declare const APIWithJSONProvidedDirectly: Story<APIProps>;
49
+ export declare const APIWithoutDescription: Story<APIProps>;
50
+ export declare const APIWithInternalOperations: Story<APIProps>;
51
+ export declare const OpenApi3Schema: Story<APIProps>;
52
+ export declare const BadgesForSchema: Story<APIProps>;
53
+ export declare const StackedLayout: Story<APIProps>;
54
+ export declare const Box: Story<APIProps>;
55
+ export declare const DigitalOcean: Story<APIProps>;
56
+ export declare const Github: Story<APIProps>;
57
+ export declare const Instagram: Story<APIProps>;
@@ -0,0 +1,11 @@
1
+ export declare function useExportDocumentProps({ originalDocument, bundledDocument, }: {
2
+ originalDocument: string | object;
3
+ bundledDocument: unknown;
4
+ }): {
5
+ original: {
6
+ onPress: () => void;
7
+ };
8
+ bundled: {
9
+ onPress: () => void;
10
+ };
11
+ };
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';
package/index.esm.js ADDED
@@ -0,0 +1,516 @@
1
+ import { isHttpOperation, isHttpService, Logo, TableOfContents, PoweredByLink, SidebarLayout, ParsedDocs, HttpMethodColors, DeprecatedBadge, TryItWithRequestSamples, Docs, slugify, withRouter, withStyles, withPersistenceBoundary, withMosaicProvider, withQueryClientProvider, useParsedValue, useBundleRefsIntoDocument, NonIdealState, InlineRefResolverProvider } from '@jpmorganchase/elemental-core';
2
+ import { Flex, Heading, Box, Icon, Tabs, TabList, Tab, TabPanels, TabPanel } from '@stoplight/mosaic';
3
+ import flow from 'lodash/flow.js';
4
+ import * as React from 'react';
5
+ import { useQuery } from 'react-query';
6
+ import { NodeType } from '@stoplight/types';
7
+ import { useLocation, Redirect, Link } from 'react-router-dom';
8
+ import defaults from 'lodash/defaults.js';
9
+ import cn from 'classnames';
10
+ import { safeStringify } from '@stoplight/yaml';
11
+ import saver from 'file-saver';
12
+ import { transformOas2Service, transformOas2Operation } from '@stoplight/http-spec/oas2';
13
+ import { transformOas3Service, transformOas3Operation } from '@stoplight/http-spec/oas3';
14
+ import { encodePointerFragment, pointerToPath } from '@stoplight/json';
15
+ import get from 'lodash/get.js';
16
+ import isObject from 'lodash/isObject.js';
17
+ import last from 'lodash/last.js';
18
+
19
+ const computeTagGroups = (serviceNode) => {
20
+ const groupsByTagId = {};
21
+ const ungrouped = [];
22
+ const lowerCaseServiceTags = serviceNode.tags.map(tn => tn.toLowerCase());
23
+ for (const node of serviceNode.children) {
24
+ if (node.type !== NodeType.HttpOperation)
25
+ continue;
26
+ const tagName = node.tags[0];
27
+ if (tagName) {
28
+ const tagId = tagName.toLowerCase();
29
+ if (groupsByTagId[tagId]) {
30
+ groupsByTagId[tagId].items.push(node);
31
+ }
32
+ else {
33
+ const serviceTagIndex = lowerCaseServiceTags.findIndex(tn => tn === tagId);
34
+ const serviceTagName = serviceNode.tags[serviceTagIndex];
35
+ groupsByTagId[tagId] = {
36
+ title: serviceTagName || tagName,
37
+ items: [node],
38
+ };
39
+ }
40
+ }
41
+ else {
42
+ ungrouped.push(node);
43
+ }
44
+ }
45
+ const orderedTagGroups = Object.entries(groupsByTagId)
46
+ .sort(([g1], [g2]) => {
47
+ const g1LC = g1.toLowerCase();
48
+ const g2LC = g2.toLowerCase();
49
+ const g1Idx = lowerCaseServiceTags.findIndex(tn => tn === g1LC);
50
+ const g2Idx = lowerCaseServiceTags.findIndex(tn => tn === g2LC);
51
+ if (g1Idx < 0 && g2Idx < 0)
52
+ return 0;
53
+ if (g1Idx < 0)
54
+ return 1;
55
+ if (g2Idx < 0)
56
+ return -1;
57
+ return g1Idx - g2Idx;
58
+ })
59
+ .map(([, tagGroup]) => tagGroup);
60
+ return { groups: orderedTagGroups, ungrouped };
61
+ };
62
+ const defaultComputerAPITreeConfig = {
63
+ hideSchemas: false,
64
+ hideInternal: false,
65
+ };
66
+ const computeAPITree = (serviceNode, config = {}) => {
67
+ const mergedConfig = defaults(config, defaultComputerAPITreeConfig);
68
+ const tree = [];
69
+ tree.push({
70
+ id: '/',
71
+ slug: '/',
72
+ title: 'Overview',
73
+ type: 'overview',
74
+ meta: '',
75
+ });
76
+ const operationNodes = serviceNode.children.filter(node => node.type === NodeType.HttpOperation);
77
+ if (operationNodes.length) {
78
+ tree.push({
79
+ title: 'Endpoints',
80
+ });
81
+ const { groups, ungrouped } = computeTagGroups(serviceNode);
82
+ ungrouped.forEach(operationNode => {
83
+ if (mergedConfig.hideInternal && operationNode.data.internal) {
84
+ return;
85
+ }
86
+ tree.push({
87
+ id: operationNode.uri,
88
+ slug: operationNode.uri,
89
+ title: operationNode.name,
90
+ type: operationNode.type,
91
+ meta: operationNode.data.method,
92
+ });
93
+ });
94
+ groups.forEach(group => {
95
+ const items = group.items.flatMap(operationNode => {
96
+ if (mergedConfig.hideInternal && operationNode.data.internal) {
97
+ return [];
98
+ }
99
+ return {
100
+ id: operationNode.uri,
101
+ slug: operationNode.uri,
102
+ title: operationNode.name,
103
+ type: operationNode.type,
104
+ meta: operationNode.data.method,
105
+ };
106
+ });
107
+ if (items.length > 0) {
108
+ tree.push({
109
+ title: group.title,
110
+ items,
111
+ });
112
+ }
113
+ });
114
+ }
115
+ let schemaNodes = serviceNode.children.filter(node => node.type === NodeType.Model);
116
+ if (mergedConfig.hideInternal) {
117
+ schemaNodes = schemaNodes.filter(node => !node.data['x-internal']);
118
+ }
119
+ if (!mergedConfig.hideSchemas && schemaNodes.length) {
120
+ tree.push({
121
+ title: 'Schemas',
122
+ });
123
+ schemaNodes.forEach(node => {
124
+ tree.push({
125
+ id: node.uri,
126
+ slug: node.uri,
127
+ title: node.name,
128
+ type: node.type,
129
+ meta: '',
130
+ });
131
+ });
132
+ }
133
+ return tree;
134
+ };
135
+ const findFirstNodeSlug = (tree) => {
136
+ for (const item of tree) {
137
+ if ('slug' in item) {
138
+ return item.slug;
139
+ }
140
+ if ('items' in item) {
141
+ const slug = findFirstNodeSlug(item.items);
142
+ if (slug) {
143
+ return slug;
144
+ }
145
+ }
146
+ }
147
+ return;
148
+ };
149
+ const isInternal = (node) => {
150
+ const data = node.data;
151
+ if (isHttpOperation(data)) {
152
+ return !!data.internal;
153
+ }
154
+ if (isHttpService(data)) {
155
+ return false;
156
+ }
157
+ return !!data['x-internal'];
158
+ };
159
+
160
+ const APIWithSidebarLayout = ({ serviceNode, logo, hideTryIt, hideSchemas, hideInternal, hideExport, exportProps, tryItCredentialsPolicy, tryItCorsProxy, tryItOutDefaultServer, }) => {
161
+ const container = React.useRef(null);
162
+ const tree = React.useMemo(() => computeAPITree(serviceNode, { hideSchemas, hideInternal }), [serviceNode, hideSchemas, hideInternal]);
163
+ const location = useLocation();
164
+ const { pathname } = location;
165
+ const isRootPath = !pathname || pathname === '/';
166
+ const node = isRootPath ? serviceNode : serviceNode.children.find(child => child.uri === pathname);
167
+ const layoutOptions = React.useMemo(() => ({ hideTryIt: hideTryIt, hideExport: hideExport || (node === null || node === void 0 ? void 0 : node.type) !== NodeType.HttpService }), [hideTryIt, hideExport, node]);
168
+ if (!node) {
169
+ const firstSlug = findFirstNodeSlug(tree);
170
+ if (firstSlug) {
171
+ return React.createElement(Redirect, { to: firstSlug });
172
+ }
173
+ }
174
+ if (hideInternal && node && isInternal(node)) {
175
+ return React.createElement(Redirect, { to: "/" });
176
+ }
177
+ const handleTocClick = () => {
178
+ if (container.current) {
179
+ container.current.scrollIntoView();
180
+ }
181
+ };
182
+ const sidebar = (React.createElement(React.Fragment, null,
183
+ React.createElement(Flex, { ml: 4, mb: 5, alignItems: "center" },
184
+ logo ? (React.createElement(Logo, { logo: { url: logo, altText: 'logo' } })) : (serviceNode.data.logo && React.createElement(Logo, { logo: serviceNode.data.logo })),
185
+ React.createElement(Heading, { size: 4 }, serviceNode.name)),
186
+ React.createElement(Flex, { flexGrow: true, flexShrink: true, overflowY: "auto", direction: "col" },
187
+ React.createElement(TableOfContents, { tree: tree, activeId: pathname, Link: Link, onLinkClick: handleTocClick })),
188
+ React.createElement(PoweredByLink, { source: serviceNode.name, pathname: pathname, packageType: "elements" })));
189
+ return (React.createElement(SidebarLayout, { ref: container, sidebar: sidebar }, node && (React.createElement(ParsedDocs, { key: pathname, uri: pathname, node: node, nodeTitle: node.name, layoutOptions: layoutOptions, location: location, exportProps: exportProps, tryItCredentialsPolicy: tryItCredentialsPolicy, tryItCorsProxy: tryItCorsProxy, tryItOutDefaultServer: tryItOutDefaultServer }))));
190
+ };
191
+
192
+ const itemMatchesHash = (hash, item) => {
193
+ return hash.substr(1) === `${item.name}-${item.data.method}`;
194
+ };
195
+ const TryItContext = React.createContext({
196
+ hideTryIt: false,
197
+ tryItCredentialsPolicy: 'omit',
198
+ });
199
+ TryItContext.displayName = 'TryItContext';
200
+ const APIWithStackedLayout = ({ serviceNode, hideTryIt, hideExport, exportProps, tryItCredentialsPolicy, tryItCorsProxy, tryItOutDefaultServer, }) => {
201
+ const location = useLocation();
202
+ const { groups } = computeTagGroups(serviceNode);
203
+ return (React.createElement(TryItContext.Provider, { value: { hideTryIt, tryItCredentialsPolicy, corsProxy: tryItCorsProxy } },
204
+ React.createElement(Flex, { w: "full", flexDirection: "col", m: "auto", className: "sl-max-w-4xl" },
205
+ React.createElement(Box, { w: "full", borderB: true },
206
+ React.createElement(Docs, { className: "sl-mx-auto", nodeData: serviceNode.data, nodeTitle: serviceNode.name, nodeType: NodeType.HttpService, location: location, layoutOptions: { showPoweredByLink: true, hideExport }, exportProps: exportProps, tryItCredentialsPolicy: tryItCredentialsPolicy, tryItOutDefaultServer: tryItOutDefaultServer })),
207
+ groups.map(group => (React.createElement(Group, { key: group.title, group: group }))))));
208
+ };
209
+ const Group = React.memo(({ group }) => {
210
+ const [isExpanded, setIsExpanded] = React.useState(false);
211
+ const { hash } = useLocation();
212
+ const scrollRef = React.useRef(null);
213
+ const urlHashMatches = hash.substr(1) === group.title;
214
+ const onClick = React.useCallback(() => setIsExpanded(!isExpanded), [isExpanded]);
215
+ const shouldExpand = React.useMemo(() => {
216
+ return urlHashMatches || group.items.some(item => itemMatchesHash(hash, item));
217
+ }, [group, hash, urlHashMatches]);
218
+ React.useEffect(() => {
219
+ var _a;
220
+ if (shouldExpand) {
221
+ setIsExpanded(true);
222
+ if (urlHashMatches && ((_a = scrollRef === null || scrollRef === void 0 ? void 0 : scrollRef.current) === null || _a === void 0 ? void 0 : _a.offsetTop)) {
223
+ window.scrollTo(0, scrollRef.current.offsetTop);
224
+ }
225
+ }
226
+ }, [shouldExpand, urlHashMatches, group, hash]);
227
+ return (React.createElement(Box, null,
228
+ React.createElement(Flex, { ref: scrollRef, onClick: onClick, mx: "auto", justifyContent: "between", alignItems: "center", borderB: true, px: 2, py: 4, cursor: "pointer", color: { default: 'current', hover: 'muted' } },
229
+ React.createElement(Box, { fontSize: "lg", fontWeight: "medium" }, group.title),
230
+ React.createElement(Icon, { className: "sl-mr-2", icon: isExpanded ? 'chevron-down' : 'chevron-right', size: "sm" })),
231
+ React.createElement(Collapse, { isOpen: isExpanded }, group.items.map(item => {
232
+ return React.createElement(Item, { key: item.uri, item: item });
233
+ }))));
234
+ });
235
+ const Item = React.memo(({ item }) => {
236
+ const location = useLocation();
237
+ const { hash } = location;
238
+ const [isExpanded, setIsExpanded] = React.useState(false);
239
+ const scrollRef = React.useRef(null);
240
+ const color = HttpMethodColors[item.data.method] || 'gray';
241
+ const isDeprecated = !!item.data.deprecated;
242
+ const { hideTryIt, tryItCredentialsPolicy, corsProxy } = React.useContext(TryItContext);
243
+ const onClick = React.useCallback(() => setIsExpanded(!isExpanded), [isExpanded]);
244
+ React.useEffect(() => {
245
+ var _a;
246
+ if (itemMatchesHash(hash, item)) {
247
+ setIsExpanded(true);
248
+ if ((_a = scrollRef === null || scrollRef === void 0 ? void 0 : scrollRef.current) === null || _a === void 0 ? void 0 : _a.offsetTop) {
249
+ window.scrollTo(0, scrollRef.current.offsetTop);
250
+ }
251
+ }
252
+ }, [hash, item]);
253
+ return (React.createElement(Box, { ref: scrollRef, w: "full", my: 2, border: true, borderColor: { default: isExpanded ? 'light' : 'transparent', hover: 'light' }, bg: { default: isExpanded ? 'code' : 'transparent', hover: 'code' } },
254
+ React.createElement(Flex, { mx: "auto", alignItems: "center", cursor: "pointer", fontSize: "lg", p: 2, onClick: onClick, color: "current" },
255
+ React.createElement(Box, { w: 24, textTransform: "uppercase", textAlign: "center", fontWeight: "semibold", border: true, rounded: true, px: 2, bg: "canvas", className: cn(`sl-mr-5 sl-text-base`, `sl-text-${color}`, `sl-border-${color}`) }, item.data.method || 'UNKNOWN'),
256
+ React.createElement(Box, { flex: 1, fontWeight: "medium", wordBreak: "all" }, item.name),
257
+ isDeprecated && React.createElement(DeprecatedBadge, null)),
258
+ React.createElement(Collapse, { isOpen: isExpanded }, hideTryIt ? (React.createElement(Box, { as: ParsedDocs, layoutOptions: { noHeading: true, hideTryItPanel: true }, node: item, p: 4 })) : (React.createElement(Tabs, { appearance: "line" },
259
+ React.createElement(TabList, null,
260
+ React.createElement(Tab, null, "Docs"),
261
+ React.createElement(Tab, null, "TryIt")),
262
+ React.createElement(TabPanels, null,
263
+ React.createElement(TabPanel, null,
264
+ React.createElement(ParsedDocs, { className: "sl-px-4", node: item, location: location, layoutOptions: { noHeading: true, hideTryItPanel: true } })),
265
+ React.createElement(TabPanel, null,
266
+ React.createElement(TryItWithRequestSamples, { httpOperation: item.data, tryItCredentialsPolicy: tryItCredentialsPolicy, corsProxy: corsProxy }))))))));
267
+ });
268
+ const Collapse = ({ isOpen, children }) => {
269
+ if (!isOpen)
270
+ return null;
271
+ return React.createElement(Box, null, children);
272
+ };
273
+
274
+ var NodeTypes;
275
+ (function (NodeTypes) {
276
+ NodeTypes["Paths"] = "paths";
277
+ NodeTypes["Path"] = "path";
278
+ NodeTypes["Operation"] = "operation";
279
+ NodeTypes["Components"] = "components";
280
+ NodeTypes["Models"] = "models";
281
+ NodeTypes["Model"] = "model";
282
+ })(NodeTypes || (NodeTypes = {}));
283
+
284
+ const oas2SourceMap = [
285
+ {
286
+ match: 'paths',
287
+ type: NodeTypes.Paths,
288
+ children: [
289
+ {
290
+ notMatch: '^x-',
291
+ type: NodeTypes.Path,
292
+ children: [
293
+ {
294
+ match: 'get|post|put|delete|options|head|patch|trace',
295
+ type: NodeTypes.Operation,
296
+ },
297
+ ],
298
+ },
299
+ ],
300
+ },
301
+ {
302
+ match: 'definitions',
303
+ type: NodeTypes.Models,
304
+ children: [
305
+ {
306
+ notMatch: '^x-',
307
+ type: NodeTypes.Model,
308
+ },
309
+ ],
310
+ },
311
+ ];
312
+
313
+ const oas3SourceMap = [
314
+ {
315
+ match: 'paths',
316
+ type: NodeTypes.Paths,
317
+ children: [
318
+ {
319
+ notMatch: '^x-',
320
+ type: NodeTypes.Path,
321
+ children: [
322
+ {
323
+ match: 'get|post|put|delete|options|head|patch|trace',
324
+ type: NodeTypes.Operation,
325
+ },
326
+ ],
327
+ },
328
+ ],
329
+ },
330
+ {
331
+ match: 'components',
332
+ type: NodeTypes.Components,
333
+ children: [
334
+ {
335
+ match: 'schemas',
336
+ type: NodeTypes.Models,
337
+ children: [
338
+ {
339
+ notMatch: '^x-',
340
+ type: NodeTypes.Model,
341
+ },
342
+ ],
343
+ },
344
+ ],
345
+ },
346
+ ];
347
+
348
+ const isOas2 = (parsed) => isObject(parsed) &&
349
+ 'swagger' in parsed &&
350
+ Number.parseInt(String(parsed.swagger)) === 2;
351
+ const isOas3 = (parsed) => isObject(parsed) &&
352
+ 'openapi' in parsed &&
353
+ Number.parseFloat(String(parsed.openapi)) >= 3;
354
+ const isOas31 = (parsed) => isObject(parsed) &&
355
+ 'openapi' in parsed &&
356
+ Number.parseFloat(String(parsed.openapi)) === 3.1;
357
+ const OAS_MODEL_REGEXP = /((definitions|components)\/?(schemas)?)\//;
358
+ function transformOasToServiceNode(apiDescriptionDocument) {
359
+ if (isOas31(apiDescriptionDocument)) {
360
+ return computeServiceNode(Object.assign(Object.assign({}, apiDescriptionDocument), { jsonSchemaDialect: 'http://json-schema.org/draft-07/schema#' }), oas3SourceMap, transformOas3Service, transformOas3Operation);
361
+ }
362
+ if (isOas3(apiDescriptionDocument)) {
363
+ return computeServiceNode(apiDescriptionDocument, oas3SourceMap, transformOas3Service, transformOas3Operation);
364
+ }
365
+ else if (isOas2(apiDescriptionDocument)) {
366
+ return computeServiceNode(apiDescriptionDocument, oas2SourceMap, transformOas2Service, transformOas2Operation);
367
+ }
368
+ return null;
369
+ }
370
+ function computeServiceNode(document, map, transformService, transformOperation) {
371
+ var _a;
372
+ const serviceDocument = transformService({ document });
373
+ const serviceNode = {
374
+ type: NodeType.HttpService,
375
+ uri: '/',
376
+ name: serviceDocument.name,
377
+ data: serviceDocument,
378
+ tags: ((_a = serviceDocument.tags) === null || _a === void 0 ? void 0 : _a.map(tag => tag.name)) || [],
379
+ children: computeChildNodes(document, document, map, transformOperation),
380
+ };
381
+ return serviceNode;
382
+ }
383
+ function computeChildNodes(document, data, map, transformer, parentUri = '') {
384
+ var _a;
385
+ const nodes = [];
386
+ if (!isObject(data))
387
+ return nodes;
388
+ for (const key of Object.keys(data)) {
389
+ const sanitizedKey = encodePointerFragment(key);
390
+ const match = findMapMatch(sanitizedKey, map);
391
+ if (match) {
392
+ const uri = `${parentUri}/${sanitizedKey}`;
393
+ const jsonPath = pointerToPath(`#${uri}`);
394
+ if (match.type === NodeTypes.Operation && jsonPath.length === 3) {
395
+ const path = String(jsonPath[1]);
396
+ const method = String(jsonPath[2]);
397
+ const operationDocument = transformer({ document, path, method });
398
+ let parsedUri;
399
+ const encodedPath = String(encodePointerFragment(path));
400
+ if (operationDocument.iid) {
401
+ parsedUri = `/operations/${operationDocument.iid}`;
402
+ }
403
+ else {
404
+ parsedUri = uri.replace(encodedPath, slugify(path));
405
+ }
406
+ nodes.push({
407
+ type: NodeType.HttpOperation,
408
+ uri: parsedUri,
409
+ data: operationDocument,
410
+ name: operationDocument.summary || operationDocument.iid || operationDocument.path,
411
+ tags: ((_a = operationDocument.tags) === null || _a === void 0 ? void 0 : _a.map(tag => tag.name)) || [],
412
+ });
413
+ }
414
+ else if (match.type === NodeTypes.Model) {
415
+ const schemaDocument = get(document, jsonPath);
416
+ const parsedUri = uri.replace(OAS_MODEL_REGEXP, 'schemas/');
417
+ nodes.push({
418
+ type: NodeType.Model,
419
+ uri: parsedUri,
420
+ data: schemaDocument,
421
+ name: schemaDocument.title || last(uri.split('/')) || '',
422
+ tags: schemaDocument['x-tags'] || [],
423
+ });
424
+ }
425
+ if (match.children) {
426
+ nodes.push(...computeChildNodes(document, data[key], match.children, transformer, uri));
427
+ }
428
+ }
429
+ }
430
+ return nodes;
431
+ }
432
+ function findMapMatch(key, map) {
433
+ var _a;
434
+ if (typeof key === 'number')
435
+ return;
436
+ for (const entry of map) {
437
+ if (!!((_a = entry.match) === null || _a === void 0 ? void 0 : _a.match(key)) || (entry.notMatch !== void 0 && !entry.notMatch.match(key))) {
438
+ return entry;
439
+ }
440
+ }
441
+ }
442
+ function isJson(value) {
443
+ try {
444
+ JSON.parse(value);
445
+ }
446
+ catch (e) {
447
+ return false;
448
+ }
449
+ return true;
450
+ }
451
+
452
+ function useExportDocumentProps({ originalDocument, bundledDocument, }) {
453
+ const isJsonDocument = typeof originalDocument === 'object' || (!!originalDocument && isJson(originalDocument));
454
+ const exportDocument = React.useCallback((document) => {
455
+ const type = isJsonDocument ? 'json' : 'yaml';
456
+ const blob = new Blob([document], {
457
+ type: `application/${type}`,
458
+ });
459
+ saver.saveAs(blob, `document.${type}`);
460
+ }, [isJsonDocument]);
461
+ const exportOriginalDocument = React.useCallback(() => {
462
+ const stringifiedDocument = typeof originalDocument === 'object' ? JSON.stringify(originalDocument, null, 2) : originalDocument || '';
463
+ exportDocument(stringifiedDocument);
464
+ }, [originalDocument, exportDocument]);
465
+ const exportBundledDocument = React.useCallback(() => {
466
+ const stringifiedDocument = isJsonDocument
467
+ ? JSON.stringify(bundledDocument, null, 2)
468
+ : safeStringify(bundledDocument);
469
+ exportDocument(stringifiedDocument);
470
+ }, [bundledDocument, isJsonDocument, exportDocument]);
471
+ return {
472
+ original: {
473
+ onPress: exportOriginalDocument,
474
+ },
475
+ bundled: {
476
+ onPress: exportBundledDocument,
477
+ },
478
+ };
479
+ }
480
+
481
+ const propsAreWithDocument = (props) => {
482
+ return props.hasOwnProperty('apiDescriptionDocument');
483
+ };
484
+ const APIImpl = props => {
485
+ const { layout, apiDescriptionUrl = '', logo, hideTryIt, hideSchemas, hideInternal, hideExport, tryItCredentialsPolicy, tryItCorsProxy, tryItOutDefaultServer, } = props;
486
+ const apiDescriptionDocument = propsAreWithDocument(props) ? props.apiDescriptionDocument : undefined;
487
+ const { data: fetchedDocument, error } = useQuery([apiDescriptionUrl], () => fetch(apiDescriptionUrl).then(res => {
488
+ if (res.ok) {
489
+ return res.text();
490
+ }
491
+ throw new Error(`Unable to load description document, status code: ${res.status}`);
492
+ }), {
493
+ enabled: apiDescriptionUrl !== '' && !apiDescriptionDocument,
494
+ });
495
+ const document = apiDescriptionDocument || fetchedDocument || '';
496
+ const parsedDocument = useParsedValue(document);
497
+ const bundledDocument = useBundleRefsIntoDocument(parsedDocument, { baseUrl: apiDescriptionUrl });
498
+ const serviceNode = React.useMemo(() => transformOasToServiceNode(bundledDocument), [bundledDocument]);
499
+ const exportProps = useExportDocumentProps({ originalDocument: document, bundledDocument });
500
+ if (error) {
501
+ return (React.createElement(Flex, { justify: "center", alignItems: "center", w: "full", minH: "screen" },
502
+ React.createElement(NonIdealState, { title: "Document could not be loaded", description: "The API description document could not be fetched. This could indicate connectivity problems, or issues with the server hosting the spec.", icon: "exclamation-triangle" })));
503
+ }
504
+ if (!bundledDocument) {
505
+ return (React.createElement(Flex, { justify: "center", alignItems: "center", w: "full", minH: "screen", color: "light" },
506
+ React.createElement(Box, { as: Icon, icon: ['fal', 'circle-notch'], size: "3x", spin: true })));
507
+ }
508
+ if (!serviceNode) {
509
+ return (React.createElement(Flex, { justify: "center", alignItems: "center", w: "full", minH: "screen" },
510
+ React.createElement(NonIdealState, { title: "Failed to parse OpenAPI file", description: "Please make sure your OpenAPI file is valid and try again" })));
511
+ }
512
+ return (React.createElement(InlineRefResolverProvider, { document: parsedDocument }, layout === 'stacked' ? (React.createElement(APIWithStackedLayout, { serviceNode: serviceNode, hideTryIt: hideTryIt, hideExport: hideExport, exportProps: exportProps, tryItCredentialsPolicy: tryItCredentialsPolicy, tryItCorsProxy: tryItCorsProxy, tryItOutDefaultServer: tryItOutDefaultServer })) : (React.createElement(APIWithSidebarLayout, { logo: logo, serviceNode: serviceNode, hideTryIt: hideTryIt, hideSchemas: hideSchemas, hideInternal: hideInternal, hideExport: hideExport, exportProps: exportProps, tryItCredentialsPolicy: tryItCredentialsPolicy, tryItCorsProxy: tryItCorsProxy, tryItOutDefaultServer: tryItOutDefaultServer }))));
513
+ };
514
+ const API = flow(withRouter, withStyles, withPersistenceBoundary, withMosaicProvider, withQueryClientProvider)(APIImpl);
515
+
516
+ export { API };