diff --git a/src/__stories__/JsonSchemaViewer.tsx b/src/__stories__/JsonSchemaViewer.tsx index 103b8962..756718e6 100644 --- a/src/__stories__/JsonSchemaViewer.tsx +++ b/src/__stories__/JsonSchemaViewer.tsx @@ -108,4 +108,15 @@ storiesOf('JsonSchemaViewer', module) onGoToRef={action('onGoToRef')} /> + )) + .add('with mask controls', () => ( + ['properties/name']} + name={text('name', 'my schema')} + schema={schema as JSONSchema4} + defaultExpandedDepth={number('defaultExpandedDepth', 2)} + expanded={boolean('expanded', false)} + hideTopBar={boolean('hideTopBar', false)} + onGoToRef={action('onGoToRef')} + /> )); diff --git a/src/components/JsonSchemaViewer.tsx b/src/components/JsonSchemaViewer.tsx index 20c91473..df258084 100644 --- a/src/components/JsonSchemaViewer.tsx +++ b/src/components/JsonSchemaViewer.tsx @@ -6,6 +6,7 @@ import * as React from 'react'; import { JSONSchema4 } from 'json-schema'; import { GoToRefHandler } from '../types'; import { isSchemaViewerEmpty, renderSchema } from '../utils'; +import { SelectedPaths } from './MaskControls'; import { SchemaTree } from './SchemaTree'; export type FallbackComponent = React.ComponentType<{ error: Error | null }>; @@ -22,6 +23,9 @@ export interface IJsonSchemaViewer { hideTopBar?: boolean; maxRows?: number; onGoToRef?: GoToRefHandler; + maskControlsHandler?: (attrs: SelectedPaths) => string[]; + maskUpdater?: () => React.ReactElement; + maskProps?: SelectedPaths; FallbackComponent?: FallbackComponent; } @@ -33,7 +37,14 @@ export class JsonSchemaViewerComponent extends React.PureComponent; + +export type MaskingProps = Array<{ path: string; required: any }>; + +interface IMaskGenericControls { + node: { level: number; metadata: { path: string[] } }; + maskControlsHandler?: (attrs: SelectedPaths) => string[]; + setSelectedProps: Dispatch>>; +} + +interface IMaskControls extends IMaskGenericControls { + selectedProps: SelectedPaths; +} +interface ICheckbox extends IMaskGenericControls { + isChecked: boolean; +} + +interface IRequired extends IMaskGenericControls { + idx: number; + setIdx: Dispatch>; +} + +function addAttr(old: Array<{ path: string; required: number }>, path: string, required: number) { + const attr = old.find(oldAttr => oldAttr.path === path); + + return attr + ? old.map(oldAttr => { + return oldAttr.path === path ? Object.assign({}, oldAttr, { required }) : oldAttr; + }) + : old.concat({ path, required }); +} + +function updateSelectedAttrs( + isChecked: boolean, + oldAttrs: Array<{ path: string; required: number }>, + path: string, + idx: number, +): Array<{ path: string; required: number }> { + return isChecked ? addAttr(oldAttrs, path, idx) : oldAttrs.filter(oldAttr => oldAttr.path !== path); +} + +const toRequiredNumForm = (node: { required?: boolean } = {}) => { + if (node.required === true) { + return 1; + } else if (node.required === false) { + return 2; + } else { + return 0; + } +}; + +function toMaskAttrsWitReqAsBool(maskAttrs: Array<{ path: string; required: number }>, nodePath: string) { + return maskAttrs.map(maskAttr => { + const { path, required } = maskAttr; + + const requiredState = { + 0: {}, + 1: { required: true }, + 2: { required: false }, + }[required || 0]; + + return nodePath === path ? Object.assign({}, { path }, requiredState) : maskAttr; + }); +} + +const toNodePath = (node: { metadata: { path: string[] } }) => node.metadata.path.join('/'); + +function updateMaskAttrs( + setSelectedProps: Dispatch>>, + isChecked: boolean, + node: { metadata: { path: string[] } }, + idx: number, + maskControlsHandler?: (attrs: SelectedPaths) => string[], +) { + setSelectedProps(oldProps => { + const nodePath = toNodePath(node); + + const maskAttrs = updateSelectedAttrs(isChecked, oldProps, nodePath, idx); + const maskAttrsWithBools = toMaskAttrsWitReqAsBool(maskAttrs, nodePath); + + if (maskControlsHandler) { + maskControlsHandler(maskAttrsWithBools); + } + + return maskAttrsWithBools; + }); +} + +const Required: React.FunctionComponent = ({ + idx, + setIdx, + setSelectedProps, + maskControlsHandler, + node, +}: IRequired) => { + return ( + + + + )} ); }); diff --git a/src/components/__tests__/SchemaRow.spec.tsx b/src/components/__tests__/SchemaRow.spec.tsx index 0f362567..14aa4d76 100644 --- a/src/components/__tests__/SchemaRow.spec.tsx +++ b/src/components/__tests__/SchemaRow.spec.tsx @@ -27,7 +27,9 @@ describe('SchemaRow component', () => { isExpanded: true, }; - const wrapper = shallow(shallow() + const wrapper = shallow(shallow( + null} node={node as SchemaTreeListNode} rowOptions={rowOptions} />, + ) .find(Popover) .prop('content') as React.ReactElement); diff --git a/src/utils/renderSchema.ts b/src/utils/renderSchema.ts index 738c8b90..9e75ee32 100644 --- a/src/utils/renderSchema.ts +++ b/src/utils/renderSchema.ts @@ -67,7 +67,7 @@ export const renderSchema: Walker = function*(schema, level = 0, meta = { path: const { path } = meta; - for (const node of walk(parsedSchema)) { + for (const node of walk(resolvedSchema)) { const baseNode: SchemaTreeListNode = { id: node.id, level,