Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 24 additions & 2 deletions src/components/JsonSchemaViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,22 @@ export interface IJsonSchemaViewer {
export const ViewModeContext = React.createContext<ViewMode>('standalone');
ViewModeContext.displayName = 'ViewModeContext';

export const SchemaTreeStoreContext = React.createContext<TreeStore<SchemaTree>>(
new TreeStore(
new SchemaTree({}, new TreeState(), {
mergeAllOf: true,
expandedDepth: -1,
resolveRef: void 0,
shouldResolveEagerly: false,
onPopulate: void 0,
}),
new TreeState(),
),
);
SchemaTreeStoreContext.displayName = 'SchemaTreeStoreContext';

export class JsonSchemaViewerComponent extends React.PureComponent<IJsonSchemaViewer & ErrorBoundaryForwardedProps> {
protected readonly treeStore: TreeStore;
protected readonly treeStore: TreeStore<SchemaTree>;
protected readonly tree: SchemaTree;
protected readonly treeState: TreeState;

Expand Down Expand Up @@ -124,7 +138,15 @@ export class JsonSchemaViewerComponent extends React.PureComponent<IJsonSchemaVi
return (
<div className={cn(className, 'JsonSchemaViewer flex flex-col relative')}>
<ViewModeContext.Provider value={this.props.viewMode ?? 'standalone'}>
<SchemaTreeComponent expanded={expanded} name={name} schema={schema} treeStore={this.treeStore} {...props} />
<SchemaTreeStoreContext.Provider value={this.treeStore}>
<SchemaTreeComponent
expanded={expanded}
name={name}
schema={schema}
treeStore={this.treeStore}
{...props}
/>
</SchemaTreeStoreContext.Provider>
</ViewModeContext.Provider>
</div>
);
Expand Down
30 changes: 4 additions & 26 deletions src/components/shared/Property.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,19 @@
import { isLocalRef } from '@stoplight/json';
import { Optional } from '@stoplight/types';
import { JSONSchema4 } from 'json-schema';
import { isObject as _isObject, size as _size } from 'lodash';
import * as React from 'react';
import { getSchemaNodeMetadata } from '../../tree/metadata';
import { GoToRefHandler, IArrayNode, IObjectNode, SchemaKind, SchemaNode, SchemaTreeListNode } from '../../types';
import { GoToRefHandler, IArrayNode, SchemaKind, SchemaNode, SchemaTreeListNode } from '../../types';
import { getPrimaryType } from '../../utils/getPrimaryType';
import { hasRefItems, isArrayNodeWithItems, isCombinerNode, isRefNode } from '../../utils/guards';
import { inferType } from '../../utils/inferType';
import { SchemaTreeStoreContext } from '../JsonSchemaViewer';
import { Types } from './Types';

export interface IProperty {
node: SchemaTreeListNode;
onGoToRef?: GoToRefHandler;
}

function count(obj: Optional<JSONSchema4 | null>): number | null {
if (_isObject(obj)) {
return _size(obj);
}

return null;
}

function shouldShowPropertyName(treeNode: SchemaTreeListNode) {
if (treeNode.parent === null) return false;
try {
Expand Down Expand Up @@ -77,22 +68,9 @@ export const Property: React.FunctionComponent<IProperty> = ({ node: treeNode, o
const type = isRefNode(node) ? '$ref' : isCombinerNode(node) ? node.combiner : node.type;
const subtype = isArrayNodeWithItems(node) ? (hasRefItems(node) ? '$ref' : inferType(node.items)) : void 0;
const title = getTitle(node);
const treeStore = React.useContext(SchemaTreeStoreContext);

const childrenCount = React.useMemo<number | null>(() => {
if (type === SchemaKind.Object || (Array.isArray(type) && type.includes(SchemaKind.Object))) {
return count((node as IObjectNode).properties);
}

if (subtype === SchemaKind.Object) {
return count(((node as IArrayNode).items as IObjectNode).properties);
}

if (subtype === SchemaKind.Array) {
return count((node as IArrayNode).items as IArrayNode);
}

return null;
}, [node]);
const childrenCount = React.useMemo<number | null>(() => treeStore.tree.getChildrenCount(node), [node, treeStore]);

const handleGoToRef = React.useCallback<React.MouseEventHandler>(() => {
if (onGoToRef && isRefNode(node) && node.$ref !== null) {
Expand Down
47 changes: 40 additions & 7 deletions src/tree/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { JsonPath, Optional } from '@stoplight/types';
import { JSONSchema4 } from 'json-schema';
import { get as _get, isEqual as _isEqual, isObject as _isObject } from 'lodash';
import { ResolvingError } from '../errors';
import { ViewMode } from '../types';
import { hasRefItems, isRefNode } from '../utils/guards';
import { IArrayNode, IObjectNode, SchemaKind, SchemaNode, ViewMode } from '../types';
import { hasRefItems, isArrayNodeWithItems, isCombinerNode, isRefNode } from '../utils/guards';
import { inferType } from '../utils/inferType';
import { getSchemaNodeMetadata } from './metadata';
import { canStepIn } from './utils/canStepIn';
import { createErrorTreeNode } from './utils/createErrorTreeNode';
Expand Down Expand Up @@ -50,16 +51,45 @@ export class SchemaTree extends Tree {

protected readonly visited = new WeakSet();

protected isViewModeRespected = (fragment: JSONSchema4) => {
return !(
!!fragment.writeOnly !== !!fragment.readOnly &&
((this.treeOptions.viewMode === 'read' && fragment.writeOnly) ||
(this.treeOptions.viewMode === 'write' && fragment.readOnly))
);
};

public getChildrenCount(node: SchemaNode) {
const type = isRefNode(node) ? '$ref' : isCombinerNode(node) ? node.combiner : node.type;
const subtype = isArrayNodeWithItems(node) ? (hasRefItems(node) ? '$ref' : inferType(node.items)) : void 0;

let children;

if (type === SchemaKind.Object || (Array.isArray(type) && type.includes(SchemaKind.Object))) {
children = (node as IObjectNode).properties;
}

if (subtype === SchemaKind.Object) {
children = ((node as IArrayNode).items as IObjectNode).properties;
}

if (subtype === SchemaKind.Array) {
children = (node as IArrayNode).items as IArrayNode;
}

if (typeof children === 'object' && children !== null) {
return Object.values(children).filter(this.isViewModeRespected).length;
}

return null;
}

public populate() {
const expanded = {};
populateTree(this.schema, this.root, 0, [], {
mergeAllOf: this.treeOptions.mergeAllOf,
onNode: (fragment, node, parentTreeNode, level): boolean => {
if (
!!fragment.writeOnly !== !!fragment.readOnly &&
((this.treeOptions.viewMode === 'read' && fragment.writeOnly) ||
(this.treeOptions.viewMode === 'write' && fragment.readOnly))
) {
if (!this.isViewModeRespected(fragment)) {
return false;
}
if (
Expand Down Expand Up @@ -88,6 +118,9 @@ export class SchemaTree extends Tree {
populateTree(schema, artificialRoot, initialLevel, path, {
mergeAllOf: this.treeOptions.mergeAllOf,
onNode: (fragment, node, parentTreeNode, level) => {
if (!this.isViewModeRespected(fragment)) {
return false;
}
if (level <= this.treeOptions.expandedDepth || level <= initialLevel) return true;
return stepIn && level <= initialLevel + 1 && canStepIn(getSchemaNodeMetadata(parentTreeNode).schema);
},
Expand Down