Skip to content

Commit 4815780

Browse files
committed
refactor: rewrite schema rendering [STD-95] (#11)
* refactor: rewrite pretty much everything * feat: dividers and more * refactor: remove props interfaces * refactor: move types * feat: support enums * feat: display additional separately * refactor: move getProperties * feat: support linking * fix: expanding * style: smaller text for validation tooltip * docs: readme * test: update some * refactor: pathToString fn * feat: remove hideRoot prop * fix: show more properties * fix: text should be selectable * feat: show required at the end * style: improve or divider * style: spacing between caret and property name * refactor: show pattern properties & properties for mixed types * fix: tslint * feat: add pattern annotation * refactor: prettify useProperties * test: schema view * feat: top bar component * test: isExpanded util * test: lookupRef util * test: some snapshots * feat: ref formatting
1 parent 69f0725 commit 4815780

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+1738
-1680
lines changed

.storybook/preview-head.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<link href="https://fonts.googleapis.com/css?family=Roboto:400,700" rel="stylesheet">
2+
<style>
3+
html {
4+
font-family: aktiv-grotesk, -apple-system, BlinkMacSystemFont, Roboto, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
5+
}
6+
</style>

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ A JSON Schema viewer React component
1212
- Full JSON Schema Draft 4 support, including `oneOf` and `anyOf` combiner properties
1313
- Renders complicated nested objects to any depth
1414
- Renders validation properties and markdown descriptions
15+
- Capable of linking resolved $refs
1516
- Theme-able
1617
- Collapsible
1718

@@ -31,7 +32,7 @@ import { JsonSchemaViewer, ThemeProvider } from "@stoplight/json-schema-viewer";
3132
import { dark } from "@stoplight/json-schema-viewer/themes";
3233

3334
<ThemeProvider theme={dark}>
34-
<JsonSchemaViewer schemas={schemas} schema={schema} expanded />
35+
<JsonSchemaViewer dereferencedSchema={dereferencedSchema} schema={schema} />
3536
</ThemeProvider>
3637
```
3738

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"@emotion/core": "^10.0.10",
4444
"@fortawesome/free-solid-svg-icons": "5.6.x",
4545
"@stoplight/json": "1.9.x",
46+
"@types/json-schema": "^7.0.3",
4647
"lodash": "4.17.x"
4748
},
4849
"devDependencies": {

src/JsonSchemaViewer.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { Omit } from '@stoplight/types';
22
import * as React from 'react';
3-
import { ErrorMessage } from './common/ErrorMessage';
4-
import { MutedText } from './common/MutedText';
3+
import { ErrorMessage } from './components/common/ErrorMessage';
4+
import { MutedText } from './components/common/MutedText';
55
import { ISchemaView, SchemaView } from './SchemaView';
66
import { ThemeZone } from './theme';
7-
import { isSchemaViewerEmpty } from './util/isSchemaViewerEmpty';
7+
import { isSchemaViewerEmpty } from './utils/isSchemaViewerEmpty';
88

99
export interface IJsonSchemaViewer extends Omit<ISchemaView, 'emptyText'> {
1010
emptyText?: string;
@@ -32,10 +32,8 @@ export class JsonSchemaViewer extends React.PureComponent<IJsonSchemaViewer, IJs
3232
schema,
3333
schemas,
3434
limitPropertyCount,
35-
hideRoot,
3635
expanded,
3736
defaultExpandedDepth,
38-
hideInheritedFrom,
3937
...props
4038
},
4139
state: { error },
@@ -64,12 +62,9 @@ export class JsonSchemaViewer extends React.PureComponent<IJsonSchemaViewer, IJs
6462
emptyText={emptyText}
6563
defaultExpandedDepth={defaultExpandedDepth}
6664
expanded={expanded}
67-
hideInheritedFrom={hideInheritedFrom}
68-
hideRoot={hideRoot}
6965
limitPropertyCount={limitPropertyCount}
7066
name={name}
7167
schema={schema}
72-
schemas={schemas}
7368
{...props}
7469
/>
7570
</ThemeZone>

src/PropValidations.tsx

Lines changed: 0 additions & 46 deletions
This file was deleted.

src/SchemaView.tsx

Lines changed: 38 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,58 @@
1-
import { Dictionary, ISchema } from '@stoplight/types';
1+
import { Dictionary, Omit } from '@stoplight/types';
22
import { Box, Button, IBox } from '@stoplight/ui-kit';
3-
import dropRight = require('lodash/dropRight');
3+
import { JSONSchema4 } from 'json-schema';
44
import * as React from 'react';
5-
import { MutedText } from './common/MutedText';
6-
import { renderSchema } from './renderers/renderSchema';
5+
import { MutedText } from './components/common/MutedText';
6+
import { IProperty, Property } from './components/Property';
7+
import { TopBar } from './components/TopBar';
8+
import { useMetadata } from './hooks/useMetadata';
9+
import { useProperties } from './hooks/useProperties';
710
import { useTheme } from './theme';
8-
import { buildAllOfSchema } from './util/buildAllOfSchema';
11+
import { isExpanded } from './utils/isExpanded';
12+
import { pathToString } from './utils/pathToString';
913

10-
export interface ISchemaViewProps {
14+
export interface ISchemaView extends Omit<IBox, 'onSelect'> {
1115
name?: string;
1216
defaultExpandedDepth?: number;
13-
schemas: object;
14-
schema: ISchema;
17+
dereferencedSchema?: JSONSchema4;
18+
schema: JSONSchema4;
1519
limitPropertyCount?: number;
16-
hideRoot?: boolean;
1720
expanded?: boolean;
18-
hideInheritedFrom?: boolean;
1921
emptyText: string;
2022
}
2123

22-
export interface ISchemaView extends ISchemaViewProps, IBox {}
23-
2424
export const SchemaView: React.FunctionComponent<ISchemaView> = props => {
2525
const {
2626
defaultExpandedDepth = 1,
2727
emptyText,
2828
expanded = false,
29-
hideInheritedFrom = false,
30-
hideRoot,
31-
limitPropertyCount = 0,
29+
limitPropertyCount,
3230
schema,
33-
schemas = {},
31+
dereferencedSchema,
32+
name,
3433
...rest
3534
} = props;
3635

3736
const theme = useTheme();
3837
const [showExtra, setShowExtra] = React.useState<boolean>(false);
3938
const [expandedRows, setExpandedRows] = React.useState<Dictionary<boolean>>({ all: expanded });
40-
41-
const toggleExpandRow = React.useCallback<(rowKey: string, expanded: boolean) => void>(
42-
(rowKey, expandRow) => {
43-
setExpandedRows({ ...expandedRows, [rowKey]: expandRow });
39+
const { properties, isOverflow } = useProperties(schema, dereferencedSchema, {
40+
expandedRows,
41+
defaultExpandedDepth,
42+
...(!showExtra && { limitPropertyCount }),
43+
});
44+
const metadata = useMetadata(schema);
45+
46+
const toggleExpandRow = React.useCallback<IProperty['onClick']>(
47+
node => {
48+
if (node.path.length > 0) {
49+
setExpandedRows({
50+
...expandedRows,
51+
[pathToString(node)]: !isExpanded(node, defaultExpandedDepth, expandedRows),
52+
});
53+
}
4454
},
45-
[expandedRows]
55+
[expandedRows, defaultExpandedDepth]
4656
);
4757

4858
const toggleShowExtra = React.useCallback<React.MouseEventHandler<HTMLElement>>(
@@ -52,57 +62,18 @@ export const SchemaView: React.FunctionComponent<ISchemaView> = props => {
5262
[showExtra]
5363
);
5464

55-
let actualSchema = schema;
56-
57-
if (
58-
!actualSchema ||
59-
!Object.keys(actualSchema).length ||
60-
(actualSchema.properties && !Object.keys(actualSchema.properties).length)
61-
) {
62-
return <MutedText>{emptyText}</MutedText>;
63-
}
64-
65-
if (actualSchema.allOf) {
66-
const schemaProps = actualSchema.allOf;
67-
68-
if (actualSchema.type) schemaProps.push({ type: actualSchema.type });
69-
70-
actualSchema = buildAllOfSchema(schemaProps);
71-
}
72-
73-
let rowElems: React.ReactNodeArray = [];
74-
75-
renderSchema({
76-
schemas,
77-
expandedRows,
78-
defaultExpandedDepth,
79-
schema: actualSchema,
80-
level: hideRoot && (actualSchema.type === 'object' || actualSchema.hasOwnProperty('allOf')) ? -1 : 0,
81-
name: 'root',
82-
rowElems,
83-
toggleExpandRow,
84-
hideInheritedFrom,
85-
jsonPath: 'root',
86-
hideRoot,
87-
});
88-
89-
const propOverflowCount = rowElems.length - Math.max(0, limitPropertyCount);
90-
91-
if (limitPropertyCount && !showExtra && propOverflowCount > 0) {
92-
rowElems = dropRight(rowElems, propOverflowCount);
93-
}
94-
95-
if (rowElems.length === 0) {
65+
if (properties.length === 0) {
9666
return <MutedText>{emptyText}</MutedText>;
9767
}
9868

9969
return (
10070
<Box backgroundColor={theme.canvas.bg} color={theme.canvas.fg} {...rest}>
101-
{rowElems}
102-
{showExtra || (limitPropertyCount > 0 && propOverflowCount > 0) ? (
103-
<Button onClick={toggleShowExtra}>
104-
{showExtra ? 'collapse' : `...show ${propOverflowCount} more properties`}
105-
</Button>
71+
<TopBar name={name} metadata={metadata} />
72+
{properties.map((node, i) => (
73+
<Property key={i} node={node} onClick={toggleExpandRow} />
74+
))}
75+
{showExtra || isOverflow ? (
76+
<Button onClick={toggleShowExtra}>{showExtra ? 'collapse' : `...show more properties`}</Button>
10677
) : null}
10778
</Box>
10879
);

src/__stories__/__fixtures__/default-schema.json renamed to src/__fixtures__/default-schema.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@
7575
"$ref": "#/definitions/error-response"
7676
}
7777
},
78+
"patternProperties": {
79+
"^id_": { "type": "number" },
80+
"foo": { "type": "integer" },
81+
"_name$": { "type": "string" }
82+
},
7883
"required": [
7984
"name",
8085
"age",

src/__fixtures__/ref/original.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"definitions": {
4+
"address": {
5+
"type": "object",
6+
"properties": {
7+
"street_address": {
8+
"type": "string"
9+
},
10+
"city": {
11+
"type": "string"
12+
},
13+
"state": {
14+
"type": "string"
15+
}
16+
},
17+
"required": [
18+
"street_address",
19+
"city",
20+
"state"
21+
]
22+
}
23+
},
24+
"type": "object",
25+
"properties": {
26+
"billing_address": {
27+
"$ref": "#/definitions/address"
28+
},
29+
"shipping_address": {
30+
"$ref": "#/definitions/address"
31+
}
32+
}
33+
}

src/__fixtures__/ref/resolved.json

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"definitions": {
4+
"address": {
5+
"type": "object",
6+
"properties": {
7+
"street_address": {
8+
"type": "string"
9+
},
10+
"city": {
11+
"type": "string"
12+
},
13+
"state": {
14+
"type": "string"
15+
}
16+
},
17+
"required": [
18+
"street_address",
19+
"city",
20+
"state"
21+
]
22+
}
23+
},
24+
"type": "object",
25+
"properties": {
26+
"billing_address": {
27+
"type": "object",
28+
"properties": {
29+
"street_address": {
30+
"type": "string"
31+
},
32+
"city": {
33+
"type": "string"
34+
},
35+
"state": {
36+
"type": "string"
37+
}
38+
},
39+
"required": [
40+
"street_address",
41+
"city",
42+
"state"
43+
]
44+
},
45+
"shipping_address": {
46+
"type": "object",
47+
"properties": {
48+
"street_address": {
49+
"type": "string"
50+
},
51+
"city": {
52+
"type": "string"
53+
},
54+
"state": {
55+
"type": "string"
56+
}
57+
},
58+
"required": [
59+
"street_address",
60+
"city",
61+
"state"
62+
]
63+
}
64+
}
65+
}

0 commit comments

Comments
 (0)