-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
603 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { MarkdownViewer } from '@stoplight/markdown-viewer'; | ||
import { ITreeListNode, TreeStore } from '@stoplight/tree-list'; | ||
import { Dialog } from '@stoplight/ui-kit'; | ||
import * as cn from 'classnames'; | ||
import _get = require('lodash/get'); | ||
import _isEmpty = require('lodash/isEmpty'); | ||
import * as React from 'react'; | ||
|
||
import { SchemaNodeWithMeta } from '../types'; | ||
import { isCombiner, isRef } from '../utils'; | ||
import { Types } from './'; | ||
|
||
export interface IDetailDialog extends React.HTMLAttributes<HTMLDivElement> { | ||
node: ITreeListNode<SchemaNodeWithMeta>; | ||
treeStore: TreeStore; | ||
} | ||
|
||
export const DetailDialog: React.FunctionComponent<IDetailDialog> = ({ node, treeStore }) => { | ||
if (!node) return null; | ||
|
||
const meta = node.metadata as SchemaNodeWithMeta; | ||
const { name, subtype, $ref, required } = meta; | ||
|
||
const type = isRef(meta) ? '$ref' : isCombiner(meta) ? meta.combiner : meta.type; | ||
const description = _get(meta, 'annotations.description', 'No further description.'); | ||
|
||
const validations = 'validations' in meta && meta.validations ? meta.validations : []; | ||
const validationElems = []; | ||
for (const key in validations) { | ||
validationElems.push( | ||
<div className="flex py-1"> | ||
<div className="flex-1">{key}:</div> | ||
<div className="pl-10">{validations[key] as any}</div> | ||
</div> | ||
); | ||
} | ||
|
||
return ( | ||
<Dialog | ||
isOpen | ||
onClose={() => treeStore.setActiveNode()} | ||
title={ | ||
<div className="py-3"> | ||
<div className="flex items-center text-base"> | ||
{name && <span className="mr-3 te">{name}</span>} | ||
|
||
<Types type={type} subtype={subtype}> | ||
{type === '$ref' ? `[${$ref}]` : null} | ||
</Types> | ||
</div> | ||
|
||
<div className={cn('text-xs font-semibold', required ? 'text-red-6' : 'text-darken-7')}> | ||
{required ? 'REQUIRED' : 'OPTIONAL'} | ||
</div> | ||
</div> | ||
} | ||
> | ||
<div className="px-6 text-sm flex"> | ||
{description && ( | ||
<div className="flex-1"> | ||
<MarkdownViewer className="mt-6" markdown={description} /> | ||
</div> | ||
)} | ||
|
||
{!_isEmpty(validationElems) && <div className="mt-4 pl-4 border-l py-2">{validationElems}</div>} | ||
</div> | ||
</Dialog> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import { ITreeListNode, TreeStore } from '@stoplight/tree-list'; | ||
import { Omit } from '@stoplight/types'; | ||
import { Button, Checkbox, Icon } from '@stoplight/ui-kit'; | ||
import * as cn from 'classnames'; | ||
import * as pluralize from 'pluralize'; | ||
import * as React from 'react'; | ||
|
||
import { IMasking, SchemaNodeWithMeta } from '../types'; | ||
import { formatRef, isCombiner, isRef, pathToString } from '../utils'; | ||
import { Divider, Types } from './'; | ||
|
||
export interface ISchemaRow extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onClick' | 'onSelect'>, IMasking { | ||
node: ITreeListNode<object>; | ||
onMaskEdit(node: SchemaNodeWithMeta): void; | ||
treeStore: TreeStore; | ||
} | ||
|
||
export const SchemaRow: React.FunctionComponent<ISchemaRow> = ({ | ||
node, | ||
treeStore, | ||
canSelect, | ||
onSelect, | ||
onMaskEdit, | ||
selected, | ||
}) => { | ||
const schemaNode = node.metadata as SchemaNodeWithMeta; | ||
const { showDivider, name, $ref, subtype, required, path, inheritedFrom } = schemaNode; | ||
|
||
const handleChange = React.useCallback( | ||
() => { | ||
if (onSelect !== undefined) { | ||
onSelect(pathToString(path)); | ||
} | ||
}, | ||
[onSelect] | ||
); | ||
|
||
const handleEditMask = React.useCallback<React.MouseEventHandler<HTMLButtonElement>>( | ||
e => { | ||
e.stopPropagation(); | ||
onMaskEdit(schemaNode); | ||
}, | ||
[onMaskEdit] | ||
); | ||
|
||
const type = isRef(schemaNode) ? '$ref' : isCombiner(schemaNode) ? schemaNode.combiner : schemaNode.type; | ||
const description = 'annotations' in schemaNode && schemaNode.annotations.description; | ||
|
||
const validationCount = 'validations' in schemaNode ? Object.keys(schemaNode.validations).length : 0; | ||
|
||
return ( | ||
<div className="flex flex-1 items-center text-sm leading-tight relative select-none mr-3"> | ||
{showDivider && <Divider>or</Divider>} | ||
|
||
<div className="flex-1 truncate"> | ||
<div className="flex items-baseline"> | ||
{name && <span className="mr-3">{name}</span>} | ||
|
||
<Types type={type} subtype={subtype}> | ||
{type === '$ref' ? `[${$ref}]` : null} | ||
</Types> | ||
|
||
{inheritedFrom ? ( | ||
<> | ||
<span className="text-darken-7 mx-2">{`{${formatRef(inheritedFrom)}}`}</span> | ||
{onMaskEdit !== undefined && <span onClick={handleEditMask}>(edit mask)</span>} | ||
</> | ||
) : null} | ||
</div> | ||
|
||
{description && <span className="text-darken-7 text-xs">{description}</span>} | ||
</div> | ||
|
||
{(canSelect || validationCount || required) && ( | ||
<div className="items-center text-right ml-auto text-xs"> | ||
{canSelect ? ( | ||
<Checkbox onChange={handleChange} checked={selected && selected.includes(pathToString(path))} /> | ||
) : ( | ||
<> | ||
{validationCount ? ( | ||
<span className="mr-2 text-darken-7"> | ||
{validationCount} {pluralize('validation', validationCount)} | ||
</span> | ||
) : null} | ||
|
||
{required && <span className="font-semibold">required</span>} | ||
</> | ||
)} | ||
</div> | ||
)} | ||
|
||
{(validationCount || description) && | ||
node.canHaveChildren && ( | ||
<Button | ||
small | ||
className={cn(required && 'ml-2')} | ||
id={`${node.id}-showMore`} | ||
icon={<Icon icon="info-sign" className="opacity-75" iconSize={12} />} | ||
onClick={(e: React.MouseEvent) => { | ||
e.stopPropagation(); | ||
treeStore.setActiveNode(node.id); | ||
}} | ||
/> | ||
)} | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import { ITreeListNode, TreeList, TreeListEvents, TreeStore } from '@stoplight/tree-list'; | ||
import { Omit } from '@stoplight/types'; | ||
|
||
import * as cn from 'classnames'; | ||
import { JSONSchema4 } from 'json-schema'; | ||
import { observer } from 'mobx-react-lite'; | ||
import * as React from 'react'; | ||
|
||
import _isEmpty = require('lodash/isEmpty'); | ||
|
||
import { useMetadata } from '../hooks'; | ||
import { IMasking, SchemaNodeWithMeta } from '../types'; | ||
import { lookupRef } from '../utils'; | ||
import { DetailDialog, ISchemaRow, MaskedSchema, SchemaRow, TopBar } from './'; | ||
|
||
const canDrag = () => false; | ||
|
||
export interface ISchemaTree extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect'>, IMasking { | ||
name?: string; | ||
dereferencedSchema?: JSONSchema4; | ||
schema: JSONSchema4; | ||
expanded?: boolean; | ||
hideTopBar?: boolean; | ||
treeStore: TreeStore; | ||
} | ||
|
||
// @ts-ignore | ||
export const SchemaTree: React.NamedExoticComponent<ISchemaTree> = observer((props: ISchemaTree) => { | ||
const { | ||
expanded = false, | ||
schema, | ||
dereferencedSchema, | ||
hideTopBar, | ||
selected, | ||
canSelect, | ||
onSelect, | ||
name, | ||
treeStore, | ||
className, | ||
...rest | ||
} = props; | ||
|
||
const [maskedSchema, setMaskedSchema] = React.useState<JSONSchema4 | null>(null); | ||
|
||
const metadata = useMetadata(schema); | ||
const activeNode = treeStore.nodes.find(node => node.id === treeStore.activeNodeId); | ||
|
||
const handleMaskEdit = React.useCallback<ISchemaRow['onMaskEdit']>( | ||
node => { | ||
setMaskedSchema(lookupRef(node.path, dereferencedSchema)); | ||
}, | ||
[dereferencedSchema] | ||
); | ||
|
||
treeStore.on(TreeListEvents.NodeClick, (e, node) => { | ||
if (node.canHaveChildren) { | ||
treeStore.toggleExpand(node); | ||
} else { | ||
treeStore.setActiveNode(node.id); | ||
} | ||
}); | ||
|
||
const handleMaskedSchemaClose = React.useCallback(() => { | ||
setMaskedSchema(null); | ||
}, []); | ||
|
||
const shouldRenderTopBar = !hideTopBar && (name || !_isEmpty(metadata)); | ||
|
||
const itemData = { | ||
onSelect, | ||
onMaskEdit: handleMaskEdit, | ||
selected, | ||
canSelect, | ||
treeStore, | ||
}; | ||
|
||
return ( | ||
<div className={cn(className, 'h-full w-full')} {...rest}> | ||
{maskedSchema && ( | ||
<MaskedSchema onClose={handleMaskedSchemaClose} onSelect={onSelect} selected={selected} schema={maskedSchema} /> | ||
)} | ||
{shouldRenderTopBar && <TopBar name={name} metadata={metadata} />} | ||
<DetailDialog node={activeNode as ITreeListNode<SchemaNodeWithMeta>} treeStore={treeStore} /> | ||
<TreeList | ||
rowHeight={40} | ||
canDrag={canDrag} | ||
striped | ||
store={treeStore} | ||
rowRenderer={node => <SchemaRow node={node} {...itemData} />} | ||
/> | ||
</div> | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.