refactor: consolidate all JSON Schema 5 rendering code into json-schema-5 plugin (#9798)
This commit is contained in:
80
src/core/plugins/json-schema-5/components/array-model.jsx
Normal file
80
src/core/plugins/json-schema-5/components/array-model.jsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import React, { Component } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
import { sanitizeUrl } from "core/utils"
|
||||
|
||||
const propClass = "property"
|
||||
|
||||
export default class ArrayModel extends Component {
|
||||
static propTypes = {
|
||||
schema: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
getConfigs: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
name: PropTypes.string,
|
||||
displayName: PropTypes.string,
|
||||
required: PropTypes.bool,
|
||||
expandDepth: PropTypes.number,
|
||||
specPath: ImPropTypes.list.isRequired,
|
||||
depth: PropTypes.number,
|
||||
includeReadOnly: PropTypes.bool,
|
||||
includeWriteOnly: PropTypes.bool,
|
||||
}
|
||||
|
||||
render(){
|
||||
let { getComponent, getConfigs, schema, depth, expandDepth, name, displayName, specPath } = this.props
|
||||
let description = schema.get("description")
|
||||
let items = schema.get("items")
|
||||
let title = schema.get("title") || displayName || name
|
||||
let properties = schema.filter( ( v, key) => ["type", "items", "description", "$$ref", "externalDocs"].indexOf(key) === -1 )
|
||||
let externalDocsUrl = schema.getIn(["externalDocs", "url"])
|
||||
let externalDocsDescription = schema.getIn(["externalDocs", "description"])
|
||||
|
||||
|
||||
const Markdown = getComponent("Markdown", true)
|
||||
const ModelCollapse = getComponent("ModelCollapse")
|
||||
const Model = getComponent("Model")
|
||||
const Property = getComponent("Property")
|
||||
const Link = getComponent("Link")
|
||||
|
||||
const titleEl = title &&
|
||||
<span className="model-title">
|
||||
<span className="model-title__text">{ title }</span>
|
||||
</span>
|
||||
|
||||
/*
|
||||
Note: we set `name={null}` in <Model> below because we don't want
|
||||
the name of the current Model passed (and displayed) as the name of the array element Model
|
||||
*/
|
||||
|
||||
return <span className="model">
|
||||
<ModelCollapse title={titleEl} expanded={ depth <= expandDepth } collapsedContent="[...]">
|
||||
[
|
||||
{
|
||||
properties.size ? properties.entrySeq().map( ( [ key, v ] ) => <Property key={`${key}-${v}`} propKey={ key } propVal={ v } propClass={ propClass } />) : null
|
||||
}
|
||||
{
|
||||
!description ? (properties.size ? <div className="markdown"></div> : null) :
|
||||
<Markdown source={ description } />
|
||||
}
|
||||
{ externalDocsUrl &&
|
||||
<div className="external-docs">
|
||||
<Link target="_blank" href={sanitizeUrl(externalDocsUrl)}>{externalDocsDescription || externalDocsUrl}</Link>
|
||||
</div>
|
||||
}
|
||||
<span>
|
||||
<Model
|
||||
{ ...this.props }
|
||||
getConfigs={ getConfigs }
|
||||
specPath={specPath.push("items")}
|
||||
name={null}
|
||||
schema={ items }
|
||||
required={ false }
|
||||
depth={ depth + 1 }
|
||||
/>
|
||||
</span>
|
||||
]
|
||||
</ModelCollapse>
|
||||
</span>
|
||||
}
|
||||
}
|
||||
19
src/core/plugins/json-schema-5/components/enum-model.jsx
Normal file
19
src/core/plugins/json-schema-5/components/enum-model.jsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from "react"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
|
||||
const EnumModel = ({ value, getComponent }) => {
|
||||
let ModelCollapse = getComponent("ModelCollapse")
|
||||
let collapsedContent = <span>Array [ { value.count() } ]</span>
|
||||
return <span className="prop-enum">
|
||||
Enum:<br />
|
||||
<ModelCollapse collapsedContent={ collapsedContent }>
|
||||
[ { value.join(", ") } ]
|
||||
</ModelCollapse>
|
||||
</span>
|
||||
}
|
||||
EnumModel.propTypes = {
|
||||
value: ImPropTypes.iterable,
|
||||
getComponent: ImPropTypes.func
|
||||
}
|
||||
|
||||
export default EnumModel
|
||||
@@ -0,0 +1,420 @@
|
||||
import React, { PureComponent, Component } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { List, fromJS } from "immutable"
|
||||
import cx from "classnames"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
import DebounceInput from "react-debounce-input"
|
||||
import { stringify } from "core/utils"
|
||||
|
||||
const noop = ()=> {}
|
||||
const JsonSchemaPropShape = {
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
value: PropTypes.any,
|
||||
onChange: PropTypes.func,
|
||||
keyName: PropTypes.any,
|
||||
fn: PropTypes.object.isRequired,
|
||||
schema: PropTypes.object,
|
||||
errors: ImPropTypes.list,
|
||||
required: PropTypes.bool,
|
||||
dispatchInitialValue: PropTypes.bool,
|
||||
description: PropTypes.any,
|
||||
disabled: PropTypes.bool,
|
||||
}
|
||||
|
||||
const JsonSchemaDefaultProps = {
|
||||
value: "",
|
||||
onChange: noop,
|
||||
schema: {},
|
||||
keyName: "",
|
||||
required: false,
|
||||
errors: List()
|
||||
}
|
||||
|
||||
export class JsonSchemaForm extends Component {
|
||||
|
||||
static propTypes = JsonSchemaPropShape
|
||||
static defaultProps = JsonSchemaDefaultProps
|
||||
|
||||
componentDidMount() {
|
||||
const { dispatchInitialValue, value, onChange } = this.props
|
||||
if(dispatchInitialValue) {
|
||||
onChange(value)
|
||||
} else if(dispatchInitialValue === false) {
|
||||
onChange("")
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let { schema, errors, value, onChange, getComponent, fn, disabled } = this.props
|
||||
const format = schema && schema.get ? schema.get("format") : null
|
||||
const type = schema && schema.get ? schema.get("type") : null
|
||||
|
||||
let getComponentSilently = (name) => getComponent(name, false, { failSilently: true })
|
||||
let Comp = type ? format ?
|
||||
getComponentSilently(`JsonSchema_${type}_${format}`) :
|
||||
getComponentSilently(`JsonSchema_${type}`) :
|
||||
getComponent("JsonSchema_string")
|
||||
if (!Comp) {
|
||||
Comp = getComponent("JsonSchema_string")
|
||||
}
|
||||
return <Comp { ...this.props } errors={errors} fn={fn} getComponent={getComponent} value={value} onChange={onChange} schema={schema} disabled={disabled}/>
|
||||
}
|
||||
}
|
||||
|
||||
export class JsonSchema_string extends Component {
|
||||
static propTypes = JsonSchemaPropShape
|
||||
static defaultProps = JsonSchemaDefaultProps
|
||||
onChange = (e) => {
|
||||
const value = this.props.schema && this.props.schema.get("type") === "file" ? e.target.files[0] : e.target.value
|
||||
this.props.onChange(value, this.props.keyName)
|
||||
}
|
||||
onEnumChange = (val) => this.props.onChange(val)
|
||||
render() {
|
||||
let { getComponent, value, schema, errors, required, description, disabled } = this.props
|
||||
const enumValue = schema && schema.get ? schema.get("enum") : null
|
||||
const format = schema && schema.get ? schema.get("format") : null
|
||||
const type = schema && schema.get ? schema.get("type") : null
|
||||
const schemaIn = schema && schema.get ? schema.get("in") : null
|
||||
if (!value) {
|
||||
value = "" // value should not be null; this fixes a Debounce error
|
||||
}
|
||||
errors = errors.toJS ? errors.toJS() : []
|
||||
|
||||
if ( enumValue ) {
|
||||
const Select = getComponent("Select")
|
||||
return (<Select className={ errors.length ? "invalid" : ""}
|
||||
title={ errors.length ? errors : ""}
|
||||
allowedValues={ [...enumValue] }
|
||||
value={ value }
|
||||
allowEmptyValue={ !required }
|
||||
disabled={disabled}
|
||||
onChange={ this.onEnumChange }/>)
|
||||
}
|
||||
|
||||
const isDisabled = disabled || (schemaIn && schemaIn === "formData" && !("FormData" in window))
|
||||
const Input = getComponent("Input")
|
||||
if (type && type === "file") {
|
||||
return (
|
||||
<Input type="file"
|
||||
className={errors.length ? "invalid" : ""}
|
||||
title={errors.length ? errors : ""}
|
||||
onChange={this.onChange}
|
||||
disabled={isDisabled} />
|
||||
)
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<DebounceInput
|
||||
type={format && format === "password" ? "password" : "text"}
|
||||
className={errors.length ? "invalid" : ""}
|
||||
title={errors.length ? errors : ""}
|
||||
value={value}
|
||||
minLength={0}
|
||||
debounceTimeout={350}
|
||||
placeholder={description}
|
||||
onChange={this.onChange}
|
||||
disabled={isDisabled} />
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class JsonSchema_array extends PureComponent {
|
||||
|
||||
static propTypes = JsonSchemaPropShape
|
||||
static defaultProps = JsonSchemaDefaultProps
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
this.state = { value: valueOrEmptyList(props.value), schema: props.schema}
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(props) {
|
||||
const value = valueOrEmptyList(props.value)
|
||||
if(value !== this.state.value)
|
||||
this.setState({ value })
|
||||
|
||||
if(props.schema !== this.state.schema)
|
||||
this.setState({ schema: props.schema })
|
||||
}
|
||||
|
||||
onChange = () => {
|
||||
this.props.onChange(this.state.value)
|
||||
}
|
||||
|
||||
onItemChange = (itemVal, i) => {
|
||||
this.setState(({ value }) => ({
|
||||
value: value.set(i, itemVal)
|
||||
}), this.onChange)
|
||||
}
|
||||
|
||||
removeItem = (i) => {
|
||||
this.setState(({ value }) => ({
|
||||
value: value.delete(i)
|
||||
}), this.onChange)
|
||||
}
|
||||
|
||||
addItem = () => {
|
||||
const { fn } = this.props
|
||||
let newValue = valueOrEmptyList(this.state.value)
|
||||
this.setState(() => ({
|
||||
value: newValue.push(fn.getSampleSchema(this.state.schema.get("items"), false, {
|
||||
includeWriteOnly: true
|
||||
}))
|
||||
}), this.onChange)
|
||||
}
|
||||
|
||||
onEnumChange = (value) => {
|
||||
this.setState(() => ({
|
||||
value: value
|
||||
}), this.onChange)
|
||||
}
|
||||
|
||||
render() {
|
||||
let { getComponent, required, schema, errors, fn, disabled } = this.props
|
||||
|
||||
errors = errors.toJS ? errors.toJS() : Array.isArray(errors) ? errors : []
|
||||
const arrayErrors = errors.filter(e => typeof e === "string")
|
||||
const needsRemoveError = errors.filter(e => e.needRemove !== undefined)
|
||||
.map(e => e.error)
|
||||
const value = this.state.value // expect Im List
|
||||
const shouldRenderValue =
|
||||
value && value.count && value.count() > 0 ? true : false
|
||||
const schemaItemsEnum = schema.getIn(["items", "enum"])
|
||||
const schemaItemsType = schema.getIn(["items", "type"])
|
||||
const schemaItemsFormat = schema.getIn(["items", "format"])
|
||||
const schemaItemsSchema = schema.get("items")
|
||||
let ArrayItemsComponent
|
||||
let isArrayItemText = false
|
||||
let isArrayItemFile = (schemaItemsType === "file" || (schemaItemsType === "string" && schemaItemsFormat === "binary")) ? true : false
|
||||
if (schemaItemsType && schemaItemsFormat) {
|
||||
ArrayItemsComponent = getComponent(`JsonSchema_${schemaItemsType}_${schemaItemsFormat}`)
|
||||
} else if (schemaItemsType === "boolean" || schemaItemsType === "array" || schemaItemsType === "object") {
|
||||
ArrayItemsComponent = getComponent(`JsonSchema_${schemaItemsType}`)
|
||||
}
|
||||
// if ArrayItemsComponent not assigned or does not exist,
|
||||
// use default schemaItemsType === "string" & JsonSchemaArrayItemText component
|
||||
if (!ArrayItemsComponent && !isArrayItemFile) {
|
||||
isArrayItemText = true
|
||||
}
|
||||
|
||||
if ( schemaItemsEnum ) {
|
||||
const Select = getComponent("Select")
|
||||
return (<Select className={ errors.length ? "invalid" : ""}
|
||||
title={ errors.length ? errors : ""}
|
||||
multiple={ true }
|
||||
value={ value }
|
||||
disabled={disabled}
|
||||
allowedValues={ schemaItemsEnum }
|
||||
allowEmptyValue={ !required }
|
||||
onChange={ this.onEnumChange }/>)
|
||||
}
|
||||
|
||||
const Button = getComponent("Button")
|
||||
return (
|
||||
<div className="json-schema-array">
|
||||
{shouldRenderValue ?
|
||||
(value.map((item, i) => {
|
||||
const itemErrors = fromJS([
|
||||
...errors.filter((err) => err.index === i)
|
||||
.map(e => e.error)
|
||||
])
|
||||
return (
|
||||
<div key={i} className="json-schema-form-item">
|
||||
{
|
||||
isArrayItemFile ?
|
||||
<JsonSchemaArrayItemFile
|
||||
value={item}
|
||||
onChange={(val)=> this.onItemChange(val, i)}
|
||||
disabled={disabled}
|
||||
errors={itemErrors}
|
||||
getComponent={getComponent}
|
||||
/>
|
||||
: isArrayItemText ?
|
||||
<JsonSchemaArrayItemText
|
||||
value={item}
|
||||
onChange={(val) => this.onItemChange(val, i)}
|
||||
disabled={disabled}
|
||||
errors={itemErrors}
|
||||
/>
|
||||
: <ArrayItemsComponent {...this.props}
|
||||
value={item}
|
||||
onChange={(val) => this.onItemChange(val, i)}
|
||||
disabled={disabled}
|
||||
errors={itemErrors}
|
||||
schema={schemaItemsSchema}
|
||||
getComponent={getComponent}
|
||||
fn={fn}
|
||||
/>
|
||||
}
|
||||
{!disabled ? (
|
||||
<Button
|
||||
className={`btn btn-sm json-schema-form-item-remove ${needsRemoveError.length ? "invalid" : null}`}
|
||||
title={needsRemoveError.length ? needsRemoveError : ""}
|
||||
|
||||
onClick={() => this.removeItem(i)}
|
||||
> - </Button>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
) : null
|
||||
}
|
||||
{!disabled ? (
|
||||
<Button
|
||||
className={`btn btn-sm json-schema-form-item-add ${arrayErrors.length ? "invalid" : null}`}
|
||||
title={arrayErrors.length ? arrayErrors : ""}
|
||||
onClick={this.addItem}
|
||||
>
|
||||
Add {schemaItemsType ? `${schemaItemsType} ` : ""}item
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class JsonSchemaArrayItemText extends Component {
|
||||
static propTypes = JsonSchemaPropShape
|
||||
static defaultProps = JsonSchemaDefaultProps
|
||||
|
||||
onChange = (e) => {
|
||||
const value = e.target.value
|
||||
this.props.onChange(value, this.props.keyName)
|
||||
}
|
||||
|
||||
render() {
|
||||
let { value, errors, description, disabled } = this.props
|
||||
if (!value) {
|
||||
value = "" // value should not be null
|
||||
}
|
||||
errors = errors.toJS ? errors.toJS() : []
|
||||
|
||||
return (<DebounceInput
|
||||
type={"text"}
|
||||
className={errors.length ? "invalid" : ""}
|
||||
title={errors.length ? errors : ""}
|
||||
value={value}
|
||||
minLength={0}
|
||||
debounceTimeout={350}
|
||||
placeholder={description}
|
||||
onChange={this.onChange}
|
||||
disabled={disabled} />)
|
||||
}
|
||||
}
|
||||
|
||||
export class JsonSchemaArrayItemFile extends Component {
|
||||
static propTypes = JsonSchemaPropShape
|
||||
static defaultProps = JsonSchemaDefaultProps
|
||||
|
||||
onFileChange = (e) => {
|
||||
const value = e.target.files[0]
|
||||
this.props.onChange(value, this.props.keyName)
|
||||
}
|
||||
|
||||
render() {
|
||||
let { getComponent, errors, disabled } = this.props
|
||||
const Input = getComponent("Input")
|
||||
const isDisabled = disabled || !("FormData" in window)
|
||||
|
||||
return (<Input type="file"
|
||||
className={errors.length ? "invalid" : ""}
|
||||
title={errors.length ? errors : ""}
|
||||
onChange={this.onFileChange}
|
||||
disabled={isDisabled} />)
|
||||
}
|
||||
}
|
||||
|
||||
export class JsonSchema_boolean extends Component {
|
||||
static propTypes = JsonSchemaPropShape
|
||||
static defaultProps = JsonSchemaDefaultProps
|
||||
|
||||
onEnumChange = (val) => this.props.onChange(val)
|
||||
render() {
|
||||
let { getComponent, value, errors, schema, required, disabled } = this.props
|
||||
errors = errors.toJS ? errors.toJS() : []
|
||||
let enumValue = schema && schema.get ? schema.get("enum") : null
|
||||
let allowEmptyValue = !enumValue || !required
|
||||
let booleanValue = !enumValue && ["true", "false"]
|
||||
const Select = getComponent("Select")
|
||||
|
||||
return (<Select className={ errors.length ? "invalid" : ""}
|
||||
title={ errors.length ? errors : ""}
|
||||
value={ String(value) }
|
||||
disabled={ disabled }
|
||||
allowedValues={ enumValue ? [...enumValue] : booleanValue }
|
||||
allowEmptyValue={ allowEmptyValue }
|
||||
onChange={ this.onEnumChange }/>)
|
||||
}
|
||||
}
|
||||
|
||||
const stringifyObjectErrors = (errors) => {
|
||||
return errors.map(err => {
|
||||
const meta = err.propKey !== undefined ? err.propKey : err.index
|
||||
let stringError = typeof err === "string" ? err : typeof err.error === "string" ? err.error : null
|
||||
|
||||
if(!meta && stringError) {
|
||||
return stringError
|
||||
}
|
||||
let currentError = err.error
|
||||
let path = `/${err.propKey}`
|
||||
while(typeof currentError === "object") {
|
||||
const part = currentError.propKey !== undefined ? currentError.propKey : currentError.index
|
||||
if(part === undefined) {
|
||||
break
|
||||
}
|
||||
path += `/${part}`
|
||||
if (!currentError.error) {
|
||||
break
|
||||
}
|
||||
currentError = currentError.error
|
||||
}
|
||||
return `${path}: ${currentError}`
|
||||
})
|
||||
}
|
||||
|
||||
export class JsonSchema_object extends PureComponent {
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
static propTypes = JsonSchemaPropShape
|
||||
static defaultProps = JsonSchemaDefaultProps
|
||||
|
||||
onChange = (value) => {
|
||||
this.props.onChange(value)
|
||||
}
|
||||
|
||||
handleOnChange = e => {
|
||||
const inputValue = e.target.value
|
||||
|
||||
this.onChange(inputValue)
|
||||
}
|
||||
|
||||
render() {
|
||||
let {
|
||||
getComponent,
|
||||
value,
|
||||
errors,
|
||||
disabled
|
||||
} = this.props
|
||||
|
||||
const TextArea = getComponent("TextArea")
|
||||
errors = errors.toJS ? errors.toJS() : Array.isArray(errors) ? errors : []
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TextArea
|
||||
className={cx({ invalid: errors.length })}
|
||||
title={ errors.length ? stringifyObjectErrors(errors).join(", ") : ""}
|
||||
value={stringify(value)}
|
||||
disabled={disabled}
|
||||
onChange={ this.handleOnChange }/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function valueOrEmptyList(value) {
|
||||
return List.isList(value) ? value : Array.isArray(value) ? fromJS(value) : List()
|
||||
}
|
||||
99
src/core/plugins/json-schema-5/components/model-collapse.jsx
Normal file
99
src/core/plugins/json-schema-5/components/model-collapse.jsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import React, { Component } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
import Im from "immutable"
|
||||
|
||||
export default class ModelCollapse extends Component {
|
||||
static propTypes = {
|
||||
collapsedContent: PropTypes.any,
|
||||
expanded: PropTypes.bool,
|
||||
children: PropTypes.any,
|
||||
title: PropTypes.element,
|
||||
modelName: PropTypes.string,
|
||||
classes: PropTypes.string,
|
||||
onToggle: PropTypes.func,
|
||||
hideSelfOnExpand: PropTypes.bool,
|
||||
layoutActions: PropTypes.object,
|
||||
layoutSelectors: PropTypes.object.isRequired,
|
||||
specPath: ImPropTypes.list.isRequired,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
collapsedContent: "{...}",
|
||||
expanded: false,
|
||||
title: null,
|
||||
onToggle: () => {},
|
||||
hideSelfOnExpand: false,
|
||||
specPath: Im.List([]),
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
|
||||
let { expanded, collapsedContent } = this.props
|
||||
|
||||
this.state = {
|
||||
expanded : expanded,
|
||||
collapsedContent: collapsedContent || ModelCollapse.defaultProps.collapsedContent
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { hideSelfOnExpand, expanded, modelName } = this.props
|
||||
if(hideSelfOnExpand && expanded) {
|
||||
// We just mounted pre-expanded, and we won't be going back..
|
||||
// So let's give our parent an `onToggle` call..
|
||||
// Since otherwise it will never be called.
|
||||
this.props.onToggle(modelName, expanded)
|
||||
}
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps){
|
||||
if(this.props.expanded !== nextProps.expanded){
|
||||
this.setState({expanded: nextProps.expanded})
|
||||
}
|
||||
}
|
||||
|
||||
toggleCollapsed=()=>{
|
||||
if(this.props.onToggle){
|
||||
this.props.onToggle(this.props.modelName,!this.state.expanded)
|
||||
}
|
||||
|
||||
this.setState({
|
||||
expanded: !this.state.expanded
|
||||
})
|
||||
}
|
||||
|
||||
onLoad = (ref) => {
|
||||
if (ref && this.props.layoutSelectors) {
|
||||
const scrollToKey = this.props.layoutSelectors.getScrollToKey()
|
||||
|
||||
if( Im.is(scrollToKey, this.props.specPath) ) this.toggleCollapsed()
|
||||
this.props.layoutActions.readyToScroll(this.props.specPath, ref.parentElement)
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { title, classes } = this.props
|
||||
|
||||
if(this.state.expanded ) {
|
||||
if(this.props.hideSelfOnExpand) {
|
||||
return <span className={classes || ""}>
|
||||
{this.props.children}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={classes || ""} ref={this.onLoad}>
|
||||
<button aria-expanded={this.state.expanded} className="model-box-control" onClick={this.toggleCollapsed}>
|
||||
{ title && <span className="pointer">{title}</span> }
|
||||
<span className={ "model-toggle" + ( this.state.expanded ? "" : " collapsed" ) }></span>
|
||||
{ !this.state.expanded && <span>{this.state.collapsedContent}</span> }
|
||||
</button>
|
||||
|
||||
{ this.state.expanded && this.props.children }
|
||||
</span>
|
||||
)
|
||||
}
|
||||
}
|
||||
159
src/core/plugins/json-schema-5/components/model-example.jsx
Normal file
159
src/core/plugins/json-schema-5/components/model-example.jsx
Normal file
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import React, { useMemo, useState, useEffect, useCallback, useRef } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
import cx from "classnames"
|
||||
import randomBytes from "randombytes"
|
||||
|
||||
const usePrevious = (value) => {
|
||||
const ref = useRef()
|
||||
useEffect(() => {
|
||||
ref.current = value
|
||||
})
|
||||
return ref.current
|
||||
}
|
||||
|
||||
const useTabs = ({ initialTab, isExecute, schema, example }) => {
|
||||
const tabs = useMemo(() => ({ example: "example", model: "model" }), [])
|
||||
const allowedTabs = useMemo(() => Object.keys(tabs), [tabs])
|
||||
const tab =
|
||||
!allowedTabs.includes(initialTab) || !schema || isExecute
|
||||
? tabs.example
|
||||
: initialTab
|
||||
const prevIsExecute = usePrevious(isExecute)
|
||||
const [activeTab, setActiveTab] = useState(tab)
|
||||
const handleTabChange = useCallback((e) => {
|
||||
setActiveTab(e.target.dataset.name)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (prevIsExecute && !isExecute && example) {
|
||||
setActiveTab(tabs.example)
|
||||
}
|
||||
}, [prevIsExecute, isExecute, example])
|
||||
|
||||
return { activeTab, onTabChange: handleTabChange, tabs }
|
||||
}
|
||||
|
||||
const ModelExample = ({
|
||||
schema,
|
||||
example,
|
||||
isExecute = false,
|
||||
specPath,
|
||||
includeWriteOnly = false,
|
||||
includeReadOnly = false,
|
||||
getComponent,
|
||||
getConfigs,
|
||||
specSelectors,
|
||||
}) => {
|
||||
const { defaultModelRendering, defaultModelExpandDepth } = getConfigs()
|
||||
const ModelWrapper = getComponent("ModelWrapper")
|
||||
const HighlightCode = getComponent("HighlightCode", true)
|
||||
const exampleTabId = randomBytes(5).toString("base64")
|
||||
const examplePanelId = randomBytes(5).toString("base64")
|
||||
const modelTabId = randomBytes(5).toString("base64")
|
||||
const modelPanelId = randomBytes(5).toString("base64")
|
||||
const isOAS3 = specSelectors.isOAS3()
|
||||
const { activeTab, tabs, onTabChange } = useTabs({
|
||||
initialTab: defaultModelRendering,
|
||||
isExecute,
|
||||
schema,
|
||||
example,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="model-example">
|
||||
<ul className="tab" role="tablist">
|
||||
<li
|
||||
className={cx("tabitem", { active: activeTab === tabs.example })}
|
||||
role="presentation"
|
||||
>
|
||||
<button
|
||||
aria-controls={examplePanelId}
|
||||
aria-selected={activeTab === tabs.example}
|
||||
className="tablinks"
|
||||
data-name="example"
|
||||
id={exampleTabId}
|
||||
onClick={onTabChange}
|
||||
role="tab"
|
||||
>
|
||||
{isExecute ? "Edit Value" : "Example Value"}
|
||||
</button>
|
||||
</li>
|
||||
{schema && (
|
||||
<li
|
||||
className={cx("tabitem", { active: activeTab === tabs.model })}
|
||||
role="presentation"
|
||||
>
|
||||
<button
|
||||
aria-controls={modelPanelId}
|
||||
aria-selected={activeTab === tabs.model}
|
||||
className={cx("tablinks", { inactive: isExecute })}
|
||||
data-name="model"
|
||||
id={modelTabId}
|
||||
onClick={onTabChange}
|
||||
role="tab"
|
||||
>
|
||||
{isOAS3 ? "Schema" : "Model"}
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
{activeTab === tabs.example && (
|
||||
<div
|
||||
aria-hidden={activeTab !== tabs.example}
|
||||
aria-labelledby={exampleTabId}
|
||||
data-name="examplePanel"
|
||||
id={examplePanelId}
|
||||
role="tabpanel"
|
||||
tabIndex="0"
|
||||
>
|
||||
{example ? (
|
||||
example
|
||||
) : (
|
||||
<HighlightCode>(no example available</HighlightCode>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === tabs.model && (
|
||||
<div
|
||||
aria-hidden={activeTab === tabs.example}
|
||||
aria-labelledby={modelTabId}
|
||||
data-name="modelPanel"
|
||||
id={modelPanelId}
|
||||
role="tabpanel"
|
||||
tabIndex="0"
|
||||
>
|
||||
<ModelWrapper
|
||||
schema={schema}
|
||||
getComponent={getComponent}
|
||||
getConfigs={getConfigs}
|
||||
specSelectors={specSelectors}
|
||||
expandDepth={defaultModelExpandDepth}
|
||||
specPath={specPath}
|
||||
includeReadOnly={includeReadOnly}
|
||||
includeWriteOnly={includeWriteOnly}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ModelExample.propTypes = {
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.shape({ isOAS3: PropTypes.func.isRequired })
|
||||
.isRequired,
|
||||
schema: PropTypes.object.isRequired,
|
||||
example: PropTypes.any.isRequired,
|
||||
isExecute: PropTypes.bool,
|
||||
getConfigs: PropTypes.func.isRequired,
|
||||
specPath: ImPropTypes.list.isRequired,
|
||||
includeReadOnly: PropTypes.bool,
|
||||
includeWriteOnly: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default ModelExample
|
||||
44
src/core/plugins/json-schema-5/components/model-wrapper.jsx
Normal file
44
src/core/plugins/json-schema-5/components/model-wrapper.jsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React, { Component, } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
|
||||
export default class ModelWrapper extends Component {
|
||||
|
||||
static propTypes = {
|
||||
schema: PropTypes.object.isRequired,
|
||||
name: PropTypes.string,
|
||||
displayName: PropTypes.string,
|
||||
fullPath: PropTypes.array.isRequired,
|
||||
specPath: ImPropTypes.list.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
getConfigs: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
expandDepth: PropTypes.number,
|
||||
layoutActions: PropTypes.object,
|
||||
layoutSelectors: PropTypes.object.isRequired,
|
||||
includeReadOnly: PropTypes.bool,
|
||||
includeWriteOnly: PropTypes.bool,
|
||||
}
|
||||
|
||||
onToggle = (name,isShown) => {
|
||||
// If this prop is present, we'll have deepLinking for it
|
||||
if(this.props.layoutActions) {
|
||||
this.props.layoutActions.show(this.props.fullPath, isShown)
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
let { getComponent, getConfigs } = this.props
|
||||
const Model = getComponent("Model")
|
||||
|
||||
let expanded
|
||||
if(this.props.layoutSelectors) {
|
||||
// If this is prop is present, we'll have deepLinking for it
|
||||
expanded = this.props.layoutSelectors.isShown(this.props.fullPath)
|
||||
}
|
||||
|
||||
return <div className="model-box">
|
||||
<Model { ...this.props } getConfigs={ getConfigs } expanded={expanded} depth={ 1 } onToggle={ this.onToggle } expandDepth={ this.props.expandDepth || 0 }/>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
136
src/core/plugins/json-schema-5/components/model.jsx
Normal file
136
src/core/plugins/json-schema-5/components/model.jsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import React from "react"
|
||||
import ImmutablePureComponent from "react-immutable-pure-component"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
import PropTypes from "prop-types"
|
||||
import { Map } from "immutable"
|
||||
|
||||
import RollingLoadSVG from "core/assets/rolling-load.svg"
|
||||
|
||||
const decodeRefName = uri => {
|
||||
const unescaped = uri.replace(/~1/g, "/").replace(/~0/g, "~")
|
||||
|
||||
try {
|
||||
return decodeURIComponent(unescaped)
|
||||
} catch {
|
||||
return unescaped
|
||||
}
|
||||
}
|
||||
|
||||
export default class Model extends ImmutablePureComponent {
|
||||
static propTypes = {
|
||||
schema: ImPropTypes.map.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
getConfigs: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
name: PropTypes.string,
|
||||
displayName: PropTypes.string,
|
||||
isRef: PropTypes.bool,
|
||||
required: PropTypes.bool,
|
||||
expandDepth: PropTypes.number,
|
||||
depth: PropTypes.number,
|
||||
specPath: ImPropTypes.list.isRequired,
|
||||
includeReadOnly: PropTypes.bool,
|
||||
includeWriteOnly: PropTypes.bool,
|
||||
}
|
||||
|
||||
getModelName =( ref )=> {
|
||||
if ( ref.indexOf("#/definitions/") !== -1 ) {
|
||||
return decodeRefName(ref.replace(/^.*#\/definitions\//, ""))
|
||||
}
|
||||
if ( ref.indexOf("#/components/schemas/") !== -1 ) {
|
||||
return decodeRefName(ref.replace(/^.*#\/components\/schemas\//, ""))
|
||||
}
|
||||
}
|
||||
|
||||
getRefSchema =( model )=> {
|
||||
let { specSelectors } = this.props
|
||||
|
||||
return specSelectors.findDefinition(model)
|
||||
}
|
||||
|
||||
render () {
|
||||
let { getComponent, getConfigs, specSelectors, schema, required, name, isRef, specPath, displayName,
|
||||
includeReadOnly, includeWriteOnly} = this.props
|
||||
const ObjectModel = getComponent("ObjectModel")
|
||||
const ArrayModel = getComponent("ArrayModel")
|
||||
const PrimitiveModel = getComponent("PrimitiveModel")
|
||||
let type = "object"
|
||||
let $$ref = schema && schema.get("$$ref")
|
||||
let $ref = schema && schema.get("$ref")
|
||||
|
||||
// If we weren't passed a `name` but have a resolved ref, grab the name from the ref
|
||||
if (!name && $$ref) {
|
||||
name = this.getModelName($$ref)
|
||||
}
|
||||
|
||||
/*
|
||||
* If we have an unresolved ref, get the schema and name from the ref.
|
||||
* If the ref is external, we can't resolve it, so we just display the ref location.
|
||||
* This is for situations where:
|
||||
* - the ref was not resolved by Swagger Client because we reached the traversal depth limit
|
||||
* - we had a circular ref inside the allOf keyword
|
||||
*/
|
||||
if ($ref) {
|
||||
const refName = this.getModelName($ref)
|
||||
const refSchema = this.getRefSchema(refName)
|
||||
if (Map.isMap(refSchema)) {
|
||||
schema = refSchema.mergeDeep(schema)
|
||||
if (!$$ref) {
|
||||
schema = schema.set("$$ref", $ref)
|
||||
$$ref = $ref
|
||||
}
|
||||
} else if (Map.isMap(schema) && schema.size === 1) {
|
||||
schema = null
|
||||
name = $ref
|
||||
}
|
||||
}
|
||||
|
||||
if(!schema) {
|
||||
return <span className="model model-title">
|
||||
<span className="model-title__text">{ displayName || name }</span>
|
||||
{!$ref && <RollingLoadSVG height="20px" width="20px" />}
|
||||
</span>
|
||||
}
|
||||
|
||||
const deprecated = specSelectors.isOAS3() && schema.get("deprecated")
|
||||
isRef = isRef !== undefined ? isRef : !!$$ref
|
||||
type = schema && schema.get("type") || type
|
||||
|
||||
switch(type) {
|
||||
case "object":
|
||||
return <ObjectModel
|
||||
className="object" { ...this.props }
|
||||
specPath={specPath}
|
||||
getConfigs={ getConfigs }
|
||||
schema={ schema }
|
||||
name={ name }
|
||||
deprecated={deprecated}
|
||||
isRef={ isRef }
|
||||
includeReadOnly = {includeReadOnly}
|
||||
includeWriteOnly = {includeWriteOnly}/>
|
||||
case "array":
|
||||
return <ArrayModel
|
||||
className="array" { ...this.props }
|
||||
getConfigs={ getConfigs }
|
||||
schema={ schema }
|
||||
name={ name }
|
||||
deprecated={deprecated}
|
||||
required={ required }
|
||||
includeReadOnly = {includeReadOnly}
|
||||
includeWriteOnly = {includeWriteOnly}/>
|
||||
case "string":
|
||||
case "number":
|
||||
case "integer":
|
||||
case "boolean":
|
||||
default:
|
||||
return <PrimitiveModel
|
||||
{ ...this.props }
|
||||
getComponent={ getComponent }
|
||||
getConfigs={ getConfigs }
|
||||
schema={ schema }
|
||||
name={ name }
|
||||
deprecated={deprecated}
|
||||
required={ required }/>
|
||||
}
|
||||
}
|
||||
}
|
||||
137
src/core/plugins/json-schema-5/components/models.jsx
Normal file
137
src/core/plugins/json-schema-5/components/models.jsx
Normal file
@@ -0,0 +1,137 @@
|
||||
import React, { Component } from "react"
|
||||
import Im, { Map } from "immutable"
|
||||
import PropTypes from "prop-types"
|
||||
|
||||
export default class Models extends Component {
|
||||
static propTypes = {
|
||||
getComponent: PropTypes.func,
|
||||
specSelectors: PropTypes.object,
|
||||
specActions: PropTypes.object.isRequired,
|
||||
layoutSelectors: PropTypes.object,
|
||||
layoutActions: PropTypes.object,
|
||||
getConfigs: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
getSchemaBasePath = () => {
|
||||
const isOAS3 = this.props.specSelectors.isOAS3()
|
||||
return isOAS3 ? ["components", "schemas"] : ["definitions"]
|
||||
}
|
||||
|
||||
getCollapsedContent = () => {
|
||||
return " "
|
||||
}
|
||||
|
||||
handleToggle = (name, isExpanded) => {
|
||||
const { layoutActions } = this.props
|
||||
layoutActions.show([...this.getSchemaBasePath(), name], isExpanded)
|
||||
if(isExpanded) {
|
||||
this.props.specActions.requestResolvedSubtree([...this.getSchemaBasePath(), name])
|
||||
}
|
||||
}
|
||||
|
||||
onLoadModels = (ref) => {
|
||||
if (ref) {
|
||||
this.props.layoutActions.readyToScroll(this.getSchemaBasePath(), ref)
|
||||
}
|
||||
}
|
||||
|
||||
onLoadModel = (ref) => {
|
||||
if (ref) {
|
||||
const name = ref.getAttribute("data-name")
|
||||
this.props.layoutActions.readyToScroll([...this.getSchemaBasePath(), name], ref)
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
let { specSelectors, getComponent, layoutSelectors, layoutActions, getConfigs } = this.props
|
||||
let definitions = specSelectors.definitions()
|
||||
let { docExpansion, defaultModelsExpandDepth } = getConfigs()
|
||||
if (!definitions.size || defaultModelsExpandDepth < 0) return null
|
||||
|
||||
const specPathBase = this.getSchemaBasePath()
|
||||
let showModels = layoutSelectors.isShown(specPathBase, defaultModelsExpandDepth > 0 && docExpansion !== "none")
|
||||
const isOAS3 = specSelectors.isOAS3()
|
||||
|
||||
const ModelWrapper = getComponent("ModelWrapper")
|
||||
const Collapse = getComponent("Collapse")
|
||||
const ModelCollapse = getComponent("ModelCollapse")
|
||||
const JumpToPath = getComponent("JumpToPath", true)
|
||||
const ArrowUpIcon = getComponent("ArrowUpIcon")
|
||||
const ArrowDownIcon = getComponent("ArrowDownIcon")
|
||||
|
||||
return <section className={ showModels ? "models is-open" : "models"} ref={this.onLoadModels}>
|
||||
<h4>
|
||||
<button
|
||||
aria-expanded={showModels}
|
||||
className="models-control"
|
||||
onClick={() => layoutActions.show(specPathBase, !showModels)}
|
||||
>
|
||||
<span>{isOAS3 ? "Schemas" : "Models"}</span>
|
||||
{showModels ? <ArrowUpIcon /> : <ArrowDownIcon />}
|
||||
</button>
|
||||
</h4>
|
||||
<Collapse isOpened={showModels}>
|
||||
{
|
||||
definitions.entrySeq().map(([name])=>{
|
||||
|
||||
const fullPath = [...specPathBase, name]
|
||||
const specPath = Im.List(fullPath)
|
||||
|
||||
const schemaValue = specSelectors.specResolvedSubtree(fullPath)
|
||||
const rawSchemaValue = specSelectors.specJson().getIn(fullPath)
|
||||
|
||||
const schema = Map.isMap(schemaValue) ? schemaValue : Im.Map()
|
||||
const rawSchema = Map.isMap(rawSchemaValue) ? rawSchemaValue : Im.Map()
|
||||
|
||||
const displayName = schema.get("title") || rawSchema.get("title") || name
|
||||
const isShown = layoutSelectors.isShown(fullPath, false)
|
||||
|
||||
if( isShown && (schema.size === 0 && rawSchema.size > 0) ) {
|
||||
// Firing an action in a container render is not great,
|
||||
// but it works for now.
|
||||
this.props.specActions.requestResolvedSubtree(fullPath)
|
||||
}
|
||||
|
||||
const content = <ModelWrapper name={ name }
|
||||
expandDepth={ defaultModelsExpandDepth }
|
||||
schema={ schema || Im.Map() }
|
||||
displayName={displayName}
|
||||
fullPath={fullPath}
|
||||
specPath={specPath}
|
||||
getComponent={ getComponent }
|
||||
specSelectors={ specSelectors }
|
||||
getConfigs = {getConfigs}
|
||||
layoutSelectors = {layoutSelectors}
|
||||
layoutActions = {layoutActions}
|
||||
includeReadOnly = {true}
|
||||
includeWriteOnly = {true}/>
|
||||
|
||||
const title = <span className="model-box">
|
||||
<span className="model model-title">
|
||||
{displayName}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
return <div id={ `model-${name}` } className="model-container" key={ `models-section-${name}` }
|
||||
data-name={name} ref={this.onLoadModel} >
|
||||
<span className="models-jump-to-path"><JumpToPath specPath={specPath} /></span>
|
||||
<ModelCollapse
|
||||
classes="model-box"
|
||||
collapsedContent={this.getCollapsedContent(name)}
|
||||
onToggle={this.handleToggle}
|
||||
title={title}
|
||||
displayName={displayName}
|
||||
modelName={name}
|
||||
specPath={specPath}
|
||||
layoutSelectors={layoutSelectors}
|
||||
layoutActions={layoutActions}
|
||||
hideSelfOnExpand={true}
|
||||
expanded={ defaultModelsExpandDepth > 0 && isShown }
|
||||
>{content}</ModelCollapse>
|
||||
</div>
|
||||
}).toArray()
|
||||
}
|
||||
</Collapse>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
273
src/core/plugins/json-schema-5/components/object-model.jsx
Normal file
273
src/core/plugins/json-schema-5/components/object-model.jsx
Normal file
@@ -0,0 +1,273 @@
|
||||
import React, { Component, } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { List } from "immutable"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
import { sanitizeUrl } from "core/utils"
|
||||
|
||||
const braceOpen = "{"
|
||||
const braceClose = "}"
|
||||
const propClass = "property"
|
||||
|
||||
export default class ObjectModel extends Component {
|
||||
static propTypes = {
|
||||
schema: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
getConfigs: PropTypes.func.isRequired,
|
||||
expanded: PropTypes.bool,
|
||||
onToggle: PropTypes.func,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
name: PropTypes.string,
|
||||
displayName: PropTypes.string,
|
||||
isRef: PropTypes.bool,
|
||||
expandDepth: PropTypes.number,
|
||||
depth: PropTypes.number,
|
||||
specPath: ImPropTypes.list.isRequired,
|
||||
includeReadOnly: PropTypes.bool,
|
||||
includeWriteOnly: PropTypes.bool,
|
||||
}
|
||||
|
||||
render(){
|
||||
let { schema, name, displayName, isRef, getComponent, getConfigs, depth, onToggle, expanded, specPath, ...otherProps } = this.props
|
||||
let { specSelectors,expandDepth, includeReadOnly, includeWriteOnly} = otherProps
|
||||
const { isOAS3 } = specSelectors
|
||||
|
||||
if(!schema) {
|
||||
return null
|
||||
}
|
||||
|
||||
const { showExtensions } = getConfigs()
|
||||
|
||||
let description = schema.get("description")
|
||||
let properties = schema.get("properties")
|
||||
let additionalProperties = schema.get("additionalProperties")
|
||||
let title = schema.get("title") || displayName || name
|
||||
let requiredProperties = schema.get("required")
|
||||
let infoProperties = schema
|
||||
.filter( ( v, key) => ["maxProperties", "minProperties", "nullable", "example"].indexOf(key) !== -1 )
|
||||
let deprecated = schema.get("deprecated")
|
||||
let externalDocsUrl = schema.getIn(["externalDocs", "url"])
|
||||
let externalDocsDescription = schema.getIn(["externalDocs", "description"])
|
||||
|
||||
const JumpToPath = getComponent("JumpToPath", true)
|
||||
const Markdown = getComponent("Markdown", true)
|
||||
const Model = getComponent("Model")
|
||||
const ModelCollapse = getComponent("ModelCollapse")
|
||||
const Property = getComponent("Property")
|
||||
const Link = getComponent("Link")
|
||||
|
||||
const JumpToPathSection = () => {
|
||||
return <span className="model-jump-to-path"><JumpToPath specPath={specPath} /></span>
|
||||
}
|
||||
const collapsedContent = (<span>
|
||||
<span>{ braceOpen }</span>...<span>{ braceClose }</span>
|
||||
{
|
||||
isRef ? <JumpToPathSection /> : ""
|
||||
}
|
||||
</span>)
|
||||
|
||||
const allOf = specSelectors.isOAS3() ? schema.get("allOf") : null
|
||||
const anyOf = specSelectors.isOAS3() ? schema.get("anyOf") : null
|
||||
const oneOf = specSelectors.isOAS3() ? schema.get("oneOf") : null
|
||||
const not = specSelectors.isOAS3() ? schema.get("not") : null
|
||||
|
||||
const titleEl = title && <span className="model-title">
|
||||
{ isRef && schema.get("$$ref") && <span className="model-hint">{ schema.get("$$ref") }</span> }
|
||||
<span className="model-title__text">{ title }</span>
|
||||
</span>
|
||||
|
||||
return <span className="model">
|
||||
<ModelCollapse
|
||||
modelName={name}
|
||||
title={titleEl}
|
||||
onToggle = {onToggle}
|
||||
expanded={ expanded ? true : depth <= expandDepth }
|
||||
collapsedContent={ collapsedContent }>
|
||||
|
||||
<span className="brace-open object">{ braceOpen }</span>
|
||||
{
|
||||
!isRef ? null : <JumpToPathSection />
|
||||
}
|
||||
<span className="inner-object">
|
||||
{
|
||||
<table className="model"><tbody>
|
||||
{
|
||||
!description ? null : <tr className="description">
|
||||
<td>description:</td>
|
||||
<td>
|
||||
<Markdown source={ description } />
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
{
|
||||
externalDocsUrl &&
|
||||
<tr className={"external-docs"}>
|
||||
<td>
|
||||
externalDocs:
|
||||
</td>
|
||||
<td>
|
||||
<Link target="_blank" href={sanitizeUrl(externalDocsUrl)}>{externalDocsDescription || externalDocsUrl}</Link>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
{
|
||||
!deprecated ? null :
|
||||
<tr className={"property"}>
|
||||
<td>
|
||||
deprecated:
|
||||
</td>
|
||||
<td>
|
||||
true
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
{
|
||||
!(properties && properties.size) ? null : properties.entrySeq().filter(
|
||||
([, value]) => {
|
||||
return (!value.get("readOnly") || includeReadOnly) &&
|
||||
(!value.get("writeOnly") || includeWriteOnly)
|
||||
}
|
||||
).map(
|
||||
([key, value]) => {
|
||||
let isDeprecated = isOAS3() && value.get("deprecated")
|
||||
let isRequired = List.isList(requiredProperties) && requiredProperties.contains(key)
|
||||
|
||||
let classNames = ["property-row"]
|
||||
|
||||
if (isDeprecated) {
|
||||
classNames.push("deprecated")
|
||||
}
|
||||
|
||||
if (isRequired) {
|
||||
classNames.push("required")
|
||||
}
|
||||
|
||||
return (<tr key={key} className={classNames.join(" ")}>
|
||||
<td>
|
||||
{ key }{ isRequired && <span className="star">*</span> }
|
||||
</td>
|
||||
<td>
|
||||
<Model key={ `object-${name}-${key}_${value}` } { ...otherProps }
|
||||
required={ isRequired }
|
||||
getComponent={ getComponent }
|
||||
specPath={specPath.push("properties", key)}
|
||||
getConfigs={ getConfigs }
|
||||
schema={ value }
|
||||
depth={ depth + 1 } />
|
||||
</td>
|
||||
</tr>)
|
||||
}).toArray()
|
||||
}
|
||||
{
|
||||
// empty row before extensions...
|
||||
!showExtensions ? null : <tr><td> </td></tr>
|
||||
}
|
||||
{
|
||||
!showExtensions ? null :
|
||||
schema.entrySeq().map(
|
||||
([key, value]) => {
|
||||
if(key.slice(0,2) !== "x-") {
|
||||
return
|
||||
}
|
||||
|
||||
const normalizedValue = !value ? null : value.toJS ? value.toJS() : value
|
||||
|
||||
return (<tr key={key} className="extension">
|
||||
<td>
|
||||
{ key }
|
||||
</td>
|
||||
<td>
|
||||
{ JSON.stringify(normalizedValue) }
|
||||
</td>
|
||||
</tr>)
|
||||
}).toArray()
|
||||
}
|
||||
{
|
||||
!additionalProperties || !additionalProperties.size ? null
|
||||
: <tr>
|
||||
<td>{ "< * >:" }</td>
|
||||
<td>
|
||||
<Model { ...otherProps } required={ false }
|
||||
getComponent={ getComponent }
|
||||
specPath={specPath.push("additionalProperties")}
|
||||
getConfigs={ getConfigs }
|
||||
schema={ additionalProperties }
|
||||
depth={ depth + 1 } />
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
{
|
||||
!allOf ? null
|
||||
: <tr>
|
||||
<td>{ "allOf ->" }</td>
|
||||
<td>
|
||||
{allOf.map((schema, k) => {
|
||||
return <div key={k}><Model { ...otherProps } required={ false }
|
||||
getComponent={ getComponent }
|
||||
specPath={specPath.push("allOf", k)}
|
||||
getConfigs={ getConfigs }
|
||||
schema={ schema }
|
||||
depth={ depth + 1 } /></div>
|
||||
})}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
{
|
||||
!anyOf ? null
|
||||
: <tr>
|
||||
<td>{ "anyOf ->" }</td>
|
||||
<td>
|
||||
{anyOf.map((schema, k) => {
|
||||
return <div key={k}><Model { ...otherProps } required={ false }
|
||||
getComponent={ getComponent }
|
||||
specPath={specPath.push("anyOf", k)}
|
||||
getConfigs={ getConfigs }
|
||||
schema={ schema }
|
||||
depth={ depth + 1 } /></div>
|
||||
})}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
{
|
||||
!oneOf ? null
|
||||
: <tr>
|
||||
<td>{ "oneOf ->" }</td>
|
||||
<td>
|
||||
{oneOf.map((schema, k) => {
|
||||
return <div key={k}><Model { ...otherProps } required={ false }
|
||||
getComponent={ getComponent }
|
||||
specPath={specPath.push("oneOf", k)}
|
||||
getConfigs={ getConfigs }
|
||||
schema={ schema }
|
||||
depth={ depth + 1 } /></div>
|
||||
})}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
{
|
||||
!not ? null
|
||||
: <tr>
|
||||
<td>{ "not ->" }</td>
|
||||
<td>
|
||||
<div>
|
||||
<Model { ...otherProps }
|
||||
required={ false }
|
||||
getComponent={ getComponent }
|
||||
specPath={specPath.push("not")}
|
||||
getConfigs={ getConfigs }
|
||||
schema={ not }
|
||||
depth={ depth + 1 } />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody></table>
|
||||
}
|
||||
</span>
|
||||
<span className="brace-close">{ braceClose }</span>
|
||||
</ModelCollapse>
|
||||
{
|
||||
infoProperties.size ? infoProperties.entrySeq().map( ( [ key, v ] ) => <Property key={`${key}-${v}`} propKey={ key } propVal={ v } propClass={ propClass } />) : null
|
||||
}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import React, { Component } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { getExtensions, sanitizeUrl } from "core/utils"
|
||||
|
||||
const propClass = "property primitive"
|
||||
|
||||
export default class Primitive extends Component {
|
||||
static propTypes = {
|
||||
schema: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
getConfigs: PropTypes.func.isRequired,
|
||||
name: PropTypes.string,
|
||||
displayName: PropTypes.string,
|
||||
depth: PropTypes.number,
|
||||
expandDepth: PropTypes.number
|
||||
}
|
||||
|
||||
render() {
|
||||
let { schema, getComponent, getConfigs, name, displayName, depth, expandDepth } = this.props
|
||||
|
||||
const { showExtensions } = getConfigs()
|
||||
|
||||
if (!schema || !schema.get) {
|
||||
// don't render if schema isn't correctly formed
|
||||
return <div></div>
|
||||
}
|
||||
|
||||
let type = schema.get("type")
|
||||
let format = schema.get("format")
|
||||
let xml = schema.get("xml")
|
||||
let enumArray = schema.get("enum")
|
||||
let title = schema.get("title") || displayName || name
|
||||
let description = schema.get("description")
|
||||
let extensions = getExtensions(schema)
|
||||
let properties = schema
|
||||
.filter((_, key) => ["enum", "type", "format", "description", "$$ref", "externalDocs"].indexOf(key) === -1)
|
||||
.filterNot((_, key) => extensions.has(key))
|
||||
let externalDocsUrl = schema.getIn(["externalDocs", "url"])
|
||||
let externalDocsDescription = schema.getIn(["externalDocs", "description"])
|
||||
|
||||
const Markdown = getComponent("Markdown", true)
|
||||
const EnumModel = getComponent("EnumModel")
|
||||
const Property = getComponent("Property")
|
||||
const ModelCollapse = getComponent("ModelCollapse")
|
||||
const Link = getComponent("Link")
|
||||
|
||||
const titleEl = title &&
|
||||
<span className="model-title">
|
||||
<span className="model-title__text">{title}</span>
|
||||
</span>
|
||||
|
||||
return <span className="model">
|
||||
<ModelCollapse title={titleEl} expanded={depth <= expandDepth} collapsedContent="[...]" hideSelfOnExpand={expandDepth !== depth}>
|
||||
<span className="prop">
|
||||
{name && depth > 1 && <span className="prop-name">{title}</span>}
|
||||
<span className="prop-type">{type}</span>
|
||||
{format && <span className="prop-format">(${format})</span>}
|
||||
{
|
||||
properties.size ? properties.entrySeq().map(([key, v]) => <Property key={`${key}-${v}`} propKey={key} propVal={v} propClass={propClass} />) : null
|
||||
}
|
||||
{
|
||||
showExtensions && extensions.size ? extensions.entrySeq().map(([key, v]) => <Property key={`${key}-${v}`} propKey={key} propVal={v} propClass={propClass} />) : null
|
||||
}
|
||||
{
|
||||
!description ? null :
|
||||
<Markdown source={description} />
|
||||
}
|
||||
{
|
||||
externalDocsUrl &&
|
||||
<div className="external-docs">
|
||||
<Link target="_blank" href={sanitizeUrl(externalDocsUrl)}>{externalDocsDescription || externalDocsUrl}</Link>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
xml && xml.size ? (<span><br /><span className={propClass}>xml:</span>
|
||||
{
|
||||
xml.entrySeq().map(([key, v]) => <span key={`${key}-${v}`} className={propClass}><br /> {key}: {String(v)}</span>).toArray()
|
||||
}
|
||||
</span>) : null
|
||||
}
|
||||
{
|
||||
enumArray && <EnumModel value={enumArray} getComponent={getComponent} />
|
||||
}
|
||||
</span>
|
||||
</ModelCollapse>
|
||||
</span>
|
||||
}
|
||||
}
|
||||
53
src/core/plugins/json-schema-5/components/schemes.jsx
Normal file
53
src/core/plugins/json-schema-5/components/schemes.jsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
|
||||
export default class Schemes extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
specActions: PropTypes.object.isRequired,
|
||||
schemes: PropTypes.object.isRequired,
|
||||
currentScheme: PropTypes.string.isRequired,
|
||||
path: PropTypes.string,
|
||||
method: PropTypes.string,
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
let { schemes } = this.props
|
||||
|
||||
//fire 'change' event to set default 'value' of select
|
||||
this.setScheme(schemes.first())
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
if ( !this.props.currentScheme || !nextProps.schemes.includes(this.props.currentScheme) ) {
|
||||
// if we don't have a selected currentScheme or if our selected scheme is no longer an option,
|
||||
// then fire 'change' event and select the first scheme in the list of options
|
||||
this.setScheme(nextProps.schemes.first())
|
||||
}
|
||||
}
|
||||
|
||||
onChange =( e ) => {
|
||||
this.setScheme( e.target.value )
|
||||
}
|
||||
|
||||
setScheme = ( value ) => {
|
||||
let { path, method, specActions } = this.props
|
||||
|
||||
specActions.setScheme( value, path, method )
|
||||
}
|
||||
|
||||
render() {
|
||||
let { schemes, currentScheme } = this.props
|
||||
|
||||
return (
|
||||
<label htmlFor="schemes">
|
||||
<span className="schemes-title">Schemes</span>
|
||||
<select onChange={ this.onChange } value={currentScheme} id="schemes">
|
||||
{ schemes.valueSeq().map(
|
||||
( scheme ) => <option value={ scheme } key={ scheme }>{ scheme }</option>
|
||||
).toArray()}
|
||||
</select>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
}
|
||||
30
src/core/plugins/json-schema-5/containers/schemes.jsx
Normal file
30
src/core/plugins/json-schema-5/containers/schemes.jsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
|
||||
export default class SchemesContainer extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
specActions: PropTypes.object.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
render () {
|
||||
const {specActions, specSelectors, getComponent} = this.props
|
||||
|
||||
const currentScheme = specSelectors.operationScheme()
|
||||
const schemes = specSelectors.schemes()
|
||||
|
||||
const Schemes = getComponent("schemes")
|
||||
|
||||
const schemesArePresent = schemes && schemes.size
|
||||
|
||||
return schemesArePresent ? (
|
||||
<Schemes
|
||||
currentScheme={currentScheme}
|
||||
schemes={schemes}
|
||||
specActions={specActions}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
}
|
||||
34
src/core/plugins/json-schema-5/index.js
Normal file
34
src/core/plugins/json-schema-5/index.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import ModelCollapse from "./components/model-collapse"
|
||||
import ModelExample from "./components/model-example"
|
||||
import ModelWrapper from "./components/model-wrapper"
|
||||
import Model from "./components/model"
|
||||
import Models from "./components/models"
|
||||
import EnumModel from "./components/enum-model"
|
||||
import ObjectModel from "./components/object-model"
|
||||
import ArrayModel from "./components/array-model"
|
||||
import PrimitiveModel from "./components/primitive-model"
|
||||
import Schemes from "./components/schemes"
|
||||
import SchemesContainer from "./containers/schemes"
|
||||
import * as JSONSchemaComponents from "./components/json-schema-components"
|
||||
|
||||
const JSONSchema5Plugin = () => ({
|
||||
components: {
|
||||
modelExample: ModelExample,
|
||||
ModelWrapper,
|
||||
ModelCollapse,
|
||||
Model,
|
||||
Models,
|
||||
EnumModel,
|
||||
ObjectModel,
|
||||
ArrayModel,
|
||||
PrimitiveModel,
|
||||
schemes: Schemes,
|
||||
SchemesContainer,
|
||||
...JSONSchemaComponents,
|
||||
},
|
||||
})
|
||||
|
||||
export default JSONSchema5Plugin
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { Component } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { OAS3ComponentWrapFactory } from "../helpers"
|
||||
import Model from "core/components/model"
|
||||
|
||||
class ModelComponent extends Component {
|
||||
static propTypes = {
|
||||
@@ -13,10 +12,11 @@ class ModelComponent extends Component {
|
||||
expandDepth: PropTypes.number,
|
||||
includeReadOnly: PropTypes.bool,
|
||||
includeWriteOnly: PropTypes.bool,
|
||||
Ori: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
render(){
|
||||
let { getConfigs, schema } = this.props
|
||||
let { getConfigs, schema, Ori: Model } = this.props
|
||||
let classes = ["model-box"]
|
||||
let isDeprecated = schema.get("deprecated") === true
|
||||
let message = null
|
||||
|
||||
Reference in New Issue
Block a user