Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: simplify JSV #109

Merged
merged 30 commits into from
Mar 17, 2021
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
01df0f9
refactor: rebuild JSV vol 1
marcelltoth Mar 4, 2021
5e2ae49
feat: don't expand mirrored nodes by default
marcelltoth Mar 4, 2021
54c98ba
feat: implement array flattening
marcelltoth Mar 4, 2021
bab293b
chore: cleanup
marcelltoth Mar 4, 2021
c5b3b7f
feat: max-width
marcelltoth Mar 4, 2021
29639fc
feat: broken $ref handling
marcelltoth Mar 4, 2021
d0c9cac
chore: remove unnecessary context usage
marcelltoth Mar 4, 2021
530b91b
fix: visual nesting problem
marcelltoth Mar 4, 2021
28e0fab
chore: simplify SchemaRow
marcelltoth Mar 4, 2021
9565f61
fix: default expanded level
marcelltoth Mar 4, 2021
eab86cc
chore: remove nesting level context
marcelltoth Mar 4, 2021
2bb1af5
fix: padding
marcelltoth Mar 4, 2021
2436b1e
chore: get rid of tree-list
marcelltoth Mar 4, 2021
f76ab36
feat: add oneOf story
marcelltoth Mar 4, 2021
52a5053
chore: remove maxRows
marcelltoth Mar 10, 2021
9df1bf2
chore: clean up FallbackComponent
marcelltoth Mar 10, 2021
3c64ecb
chore: one less context
marcelltoth Mar 10, 2021
fb6b545
feat: onGoToRef support
marcelltoth Mar 10, 2021
98d0a2f
fix: don't make props required
marcelltoth Mar 10, 2021
7a4d70b
test: remove mergeAllOf from stories
marcelltoth Mar 10, 2021
d12d468
chore: undo unintended fixture modification
marcelltoth Mar 10, 2021
b5e209f
feat: add row addon feature (formerly rowRenderer)
marcelltoth Mar 10, 2021
c37d3f7
test: update snapshots
marcelltoth Mar 15, 2021
667072b
test: update some
marcelltoth Mar 16, 2021
6980a5b
test: fix SchemaRow tests
marcelltoth Mar 17, 2021
e0eae6b
test: update Format tests
marcelltoth Mar 17, 2021
075c68b
test: update Property tests
marcelltoth Mar 17, 2021
a1d48f7
test: remove expansion checks
marcelltoth Mar 17, 2021
758a26d
chore: lint
marcelltoth Mar 17, 2021
81505de
chore: no ui-kit styles in storybook
marcelltoth Mar 17, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
"peerDependencies": {
"@stoplight/markdown-viewer": "^4",
"@stoplight/ui-kit": "^3",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still rely on UI-Kit in JSV?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately it's still a peer of MDV :\

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the include from the storybook _styles.scss. That at least make Storybook rebuilds faster.... which is a win I guess... 😅

"mobx": "^5",
"react": ">=16.8",
"react-dom": ">=16.8"
},
Expand All @@ -47,7 +46,6 @@
"@stoplight/json-schema-tree": "^1.1.0",
"@stoplight/mosaic": "^1.0.0-beta.22",
"@stoplight/react-error-boundary": "^1.0.0",
"@stoplight/tree-list": "^5.0.3",
"classnames": "^2.2.6",
"lodash": "^4.17.19"
},
Expand Down Expand Up @@ -87,7 +85,6 @@
"fast-glob": "^3.2.4",
"jest": "^26.6.2",
"jest-enzyme": "^7.1.2",
"mobx": "^5.13.0",
"prettier": "^2.2.1",
"react": "^16.14.0",
"react-dom": "^16.14.0",
Expand Down
73 changes: 45 additions & 28 deletions src/__stories__/JsonSchemaViewer.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { Button, Flex, InvertTheme, subscribeTheme } from '@stoplight/mosaic';
import { action } from '@storybook/addon-actions';
import { boolean, number, object, select, withKnobs } from '@storybook/addon-knobs';
import { number, object, select, withKnobs } from '@storybook/addon-knobs';
import { storiesOf } from '@storybook/react';
import { JSONSchema4 } from 'json-schema';
import * as React from 'react';

import { JsonSchemaViewer, RowRenderer, SchemaRow } from '../';
import { JsonSchemaViewer, RowAddonRenderer } from '../';
import { Wrapper } from './utils/Wrapper';

const allOfSchema = require('../__fixtures__/combiners/allOfs/base.json');
const schema = require('../__fixtures__/default-schema.json');
const stressSchema = require('../__fixtures__/stress-schema.json');
const refSchema = require('../__fixtures__/references/base.json');
const nullRefSchema = require('../__fixtures__/references/nullish.json');
const brokenRefArraySchema = require('../__fixtures__/arrays/of-refs.json');
const oneOfWithArraySchema = require('../__fixtures__/combiners/oneof-with-array-type.json');

subscribeTheme({ mode: 'light' });

Expand All @@ -21,7 +25,6 @@ storiesOf('JsonSchemaViewer', module)
<JsonSchemaViewer
schema={schema as JSONSchema4}
defaultExpandedDepth={number('defaultExpandedDepth', 0)}
onGoToRef={action('onGoToRef')}
viewMode={select(
'viewMode',
{
Expand All @@ -31,58 +34,48 @@ storiesOf('JsonSchemaViewer', module)
},
'standalone',
)}
onGoToRef={action('onGoToRef')}
/>
))
.add('custom schema', () => (
<JsonSchemaViewer
schema={object('schema', {})}
defaultExpandedDepth={number('defaultExpandedDepth', 0)}
onGoToRef={action('onGoToRef')}
maxRows={number('maxRows', 5)}
mergeAllOf={boolean('mergeAllOf', true)}
/>
))
.add('custom row renderer', () => {
P0lip marked this conversation as resolved.
Show resolved Hide resolved
const customRowRenderer: RowRenderer = (node, rowOptions) => {
.add('custom row addon', () => {
const customRowAddonRenderer: RowAddonRenderer = () => {
return (
<>
<SchemaRow treeListNode={node} rowOptions={rowOptions} />
<Flex h="full" alignItems="center">
<Button pl={1} mr={1} size="sm" appearance="minimal" icon="issue" />
<input type="checkbox" />
</Flex>
</>
<Flex h="full" alignItems="center">
<Button pl={1} mr={1} size="sm" appearance="minimal" icon="issue" />
<input type="checkbox" />
</Flex>
);
};

return (
<JsonSchemaViewer
schema={object('schema', schema as JSONSchema4)}
onGoToRef={action('onGoToRef')}
maxRows={number('maxRows', 5)}
mergeAllOf={boolean('mergeAllOf', true)}
rowRenderer={customRowRenderer}
renderRowAddon={customRowAddonRenderer}
/>
);
})
.add('stress-test schema', () => (
<>
<div style={{ height: 345 }}>
<div style={{ height: 345, overflowY: 'scroll' }}>
<JsonSchemaViewer
schema={stressSchema as JSONSchema4}
defaultExpandedDepth={number('defaultExpandedDepth', 2)}
onGoToRef={action('onGoToRef')}
marcelltoth marked this conversation as resolved.
Show resolved Hide resolved
maxRows={number('maxRows', 10)}
mergeAllOf={boolean('mergeAllOf', true)}
/>
</div>
<div style={{ height: 345 }}>
<div style={{ height: 345, overflowY: 'scroll' }}>
<JsonSchemaViewer
schema={stressSchema as JSONSchema4}
defaultExpandedDepth={number('defaultExpandedDepth', 2)}
onGoToRef={action('onGoToRef')}
maxRows={number('maxRows', 10)}
mergeAllOf={boolean('mergeAllOf', true)}
/>
</div>
</>
Expand All @@ -91,7 +84,13 @@ storiesOf('JsonSchemaViewer', module)
<JsonSchemaViewer
schema={allOfSchema as JSONSchema4}
defaultExpandedDepth={number('defaultExpandedDepth', 2)}
mergeAllOf={boolean('mergeAllOf', true)}
onGoToRef={action('onGoToRef')}
/>
))
.add('anyOf-array-schema', () => (
<JsonSchemaViewer
schema={oneOfWithArraySchema as JSONSchema4}
defaultExpandedDepth={number('defaultExpandedDepth', 2)}
onGoToRef={action('onGoToRef')}
/>
))
Expand All @@ -108,7 +107,6 @@ storiesOf('JsonSchemaViewer', module)
)}
defaultExpandedDepth={number('defaultExpandedDepth', 2)}
onGoToRef={action('onGoToRef')}
mergeAllOf={boolean('mergeAllOf', true)}
/>
))
.add('invalid types property pretty error message', () => (
Expand Down Expand Up @@ -140,7 +138,6 @@ storiesOf('JsonSchemaViewer', module)
}}
defaultExpandedDepth={number('defaultExpandedDepth', 2)}
onGoToRef={action('onGoToRef')}
mergeAllOf={boolean('mergeAllOf', true)}
/>
))
.add('dark', () => {
Expand All @@ -151,9 +148,29 @@ storiesOf('JsonSchemaViewer', module)
schema={schema as JSONSchema4}
defaultExpandedDepth={number('defaultExpandedDepth', 2)}
onGoToRef={action('onGoToRef')}
mergeAllOf={boolean('mergeAllOf', true)}
/>
</div>
</InvertTheme>
);
});
})
.add('refs/normal', () => (
<JsonSchemaViewer
schema={refSchema as JSONSchema4}
defaultExpandedDepth={number('defaultExpandedDepth', 2)}
onGoToRef={action('onGoToRef')}
/>
))
.add('refs/nullish', () => (
<JsonSchemaViewer
schema={nullRefSchema as JSONSchema4}
defaultExpandedDepth={number('defaultExpandedDepth', 2)}
onGoToRef={action('onGoToRef')}
/>
))
.add('refs/broken', () => (
<JsonSchemaViewer
schema={brokenRefArraySchema as JSONSchema4}
defaultExpandedDepth={number('defaultExpandedDepth', 2)}
onGoToRef={action('onGoToRef')}
/>
));
1 change: 0 additions & 1 deletion src/__stories__/_styles.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
@import "~@stoplight/tree-list/styles/_tree-list.scss";
@import "~@stoplight/ui-kit/styles/_ui-kit.scss";
@import "~@stoplight/mosaic/styles.css";
@import "~@stoplight/mosaic/themes/default.css";
Expand Down
164 changes: 57 additions & 107 deletions src/components/JsonSchemaViewer.tsx
Original file line number Diff line number Diff line change
@@ -1,127 +1,77 @@
import type { SchemaTreeRefDereferenceFn } from '@stoplight/json-schema-tree';
import { isRegularNode } from '@stoplight/json-schema-tree';
import { Box, Flex } from '@stoplight/mosaic';
import { ErrorBoundaryForwardedProps, FallbackComponent, withErrorBoundary } from '@stoplight/react-error-boundary';
import { Tree, TreeState, TreeStore } from '@stoplight/tree-list';
import { isRegularNode, SchemaTree as JsonSchemaTree, SchemaTreeRefDereferenceFn } from '@stoplight/json-schema-tree';
import { Box, VStack } from '@stoplight/mosaic';
import { ErrorBoundaryForwardedProps, FallbackProps, withErrorBoundary } from '@stoplight/react-error-boundary';
import cn from 'classnames';
import type { JSONSchema4 } from 'json-schema';
import { action } from 'mobx';
import * as React from 'react';

import { SchemaTreeContext, ViewModeContext } from '../contexts';
import { SchemaTreeListTree, SchemaTreeOptions } from '../tree';
import type { GoToRefHandler, RowRenderer, ViewMode } from '../types';
import { SchemaTree as SchemaTreeComponent } from './SchemaTree';
import { JSVOptions, JSVOptionsContextProvider } from '../contexts';
import { SchemaRow } from './SchemaRow';

export interface IJsonSchemaViewer {
export type JsonSchemaProps = Partial<JSVOptions> & {
schema: JSONSchema4;
emptyText?: string;
defaultExpandedDepth?: number;
className?: string;
maxRows?: number;
onGoToRef?: GoToRefHandler;
mergeAllOf?: boolean;
FallbackComponent?: typeof FallbackComponent;
rowRenderer?: RowRenderer;
resolveRef?: SchemaTreeRefDereferenceFn;
viewMode?: ViewMode;
}

export class JsonSchemaViewerComponent extends React.PureComponent<
IJsonSchemaViewer & ErrorBoundaryForwardedProps,
{ isEmpty: boolean }
> {
protected readonly treeStore: TreeStore;
protected readonly tree: SchemaTreeListTree;
protected readonly treeState: TreeState;

public readonly state = {
isEmpty: true,
};

constructor(props: IJsonSchemaViewer & ErrorBoundaryForwardedProps) {
super(props);
};

this.treeState = new TreeState();
this.tree = new SchemaTreeListTree(props.schema, this.treeState, this.treeOptions);
this.treeStore = new TreeStore(this.tree, this.treeState, {
defaultExpandedDepth: this.expandedDepth,
const JsonSchemaViewerComponent: React.FC<JsonSchemaProps & ErrorBoundaryForwardedProps> = ({
schema,
viewMode = 'standalone',
className,
resolveRef,
emptyText = 'No schema defined',
defaultExpandedDepth = 2,
onGoToRef,
renderRowAddon,
}) => {
const jsonSchemaTreeRoot = React.useMemo(() => {
const jsonSchemaTree = new JsonSchemaTree(schema, {
mergeAllOf: true,
refResolver: resolveRef,
});
}
jsonSchemaTree.walker.hookInto('filter', node => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You lost stepIn in the transition.
This is quite important, otherwise really large schemas with a ton of $refs will be much slower to process than needed.
stepIn is needed, so that JST tree is not built upfront for the whole schema, but for actual parts that are currently rendered.
This is also one of the more notable factors contributing to the entire complexity of the former solution.
While, getting rid of virtualization is fine by me, we certainly should at least try to preserve that optimization.
By adding it back, we should be more or less on par with current v3.

if (!isRegularNode(node)) return true;

protected get treeOptions(): SchemaTreeOptions {
return {
expandedDepth: this.expandedDepth,
mergeAllOf: this.mergeAllOf,
resolveRef: this.props.resolveRef,
viewMode: this.props.viewMode,
};
}
const { validations } = node;

protected get mergeAllOf() {
return this.props.mergeAllOf !== false;
}

protected get expandedDepth(): number {
return this.props.defaultExpandedDepth ?? 1;
}
if (!!validations.writeOnly === !!validations.readOnly) {
return true;
}

protected renderSchema() {
if (this.tree.count > 0) {
this.tree.setRoot(Tree.createArtificialRoot());
}

this.tree.populate();
this.setState({
isEmpty: this.tree.jsonSchemaTree.root.children.every(node => !isRegularNode(node) || node.unknown),
return !((viewMode === 'read' && !!validations.writeOnly) || (viewMode === 'write' && !!validations.readOnly));
});
jsonSchemaTree.populate();
return jsonSchemaTree.root;
}, [schema, resolveRef, viewMode]);

const isEmpty = React.useMemo(() => jsonSchemaTreeRoot.children.every(node => !isRegularNode(node) || node.unknown), [
jsonSchemaTreeRoot,
]);

const options = React.useMemo(() => ({ defaultExpandedDepth, viewMode, onGoToRef, renderRowAddon }), [
defaultExpandedDepth,
viewMode,
onGoToRef,
renderRowAddon,
]);

if (isEmpty) {
return <Box className={cn(className, 'JsonSchemaViewer')}>{emptyText}</Box>;
}

public componentDidMount() {
this.renderSchema();
}

@action
public componentDidUpdate(prevProps: Readonly<IJsonSchemaViewer>) {
if (prevProps.resolveRef !== this.props.resolveRef) {
this.tree.treeOptions.resolveRef = this.props.resolveRef;
}

if (
this.treeStore.defaultExpandedDepth !== this.expandedDepth ||
prevProps.schema !== this.props.schema ||
prevProps.mergeAllOf !== this.props.mergeAllOf ||
prevProps.viewMode !== this.props.viewMode
) {
this.treeStore.defaultExpandedDepth = this.expandedDepth;
this.tree.treeOptions = this.treeOptions;
this.tree.schema = this.props.schema;
this.renderSchema();
}
}

public render() {
const {
props: { emptyText = 'No schema defined', schema, defaultExpandedDepth, className, ...props },
} = this;

if (this.state.isEmpty) {
return <div>{emptyText}</div>;
}

return (
<Flex pos="relative" h="full" className={cn(className, 'JsonSchemaViewer')}>
<SchemaTreeContext.Provider value={this.tree}>
<ViewModeContext.Provider value={this.props.viewMode ?? 'standalone'}>
<SchemaTreeComponent schema={schema} treeStore={this.treeStore} {...props} />
</ViewModeContext.Provider>
</SchemaTreeContext.Provider>
</Flex>
);
}
}
return (
<JSVOptionsContextProvider value={options}>
<VStack divider className={cn(className, 'JsonSchemaViewer')}>
{jsonSchemaTreeRoot.children.map(childJsonSchemaNode => (
<SchemaRow key={childJsonSchemaNode.id} schemaNode={childJsonSchemaNode} nestingLevel={0} />
))}
</VStack>
</JSVOptionsContextProvider>
);
};

const JsonSchemaFallbackComponent: typeof FallbackComponent = ({ error }) => {
const JsonSchemaFallbackComponent: React.FC<FallbackProps> = ({ error }) => {
return (
<Box p={4}>
<b className="text-danger">Error</b>
Expand All @@ -130,7 +80,7 @@ const JsonSchemaFallbackComponent: typeof FallbackComponent = ({ error }) => {
);
};

export const JsonSchemaViewer = withErrorBoundary<IJsonSchemaViewer>(JsonSchemaViewerComponent, {
export const JsonSchemaViewer = withErrorBoundary<JsonSchemaProps>(JsonSchemaViewerComponent, {
FallbackComponent: JsonSchemaFallbackComponent,
recoverableProps: ['schema'],
reportErrors: false,
Expand Down
Loading