Merge branch 'master' into ft/oas3
This commit is contained in:
47
src/core/components/array-model.jsx
Normal file
47
src/core/components/array-model.jsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import React, { Component } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
|
||||
const propStyle = { color: "#999", fontStyle: "italic" }
|
||||
|
||||
export default class ArrayModel extends Component {
|
||||
static propTypes = {
|
||||
schema: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
name: PropTypes.string,
|
||||
required: PropTypes.bool,
|
||||
expandDepth: PropTypes.number,
|
||||
depth: PropTypes.number
|
||||
}
|
||||
|
||||
render(){
|
||||
let { getComponent, required, schema, depth, expandDepth, name } = this.props
|
||||
let items = schema.get("items")
|
||||
let title = schema.get("title") || name
|
||||
let properties = schema.filter( ( v, key) => ["type", "items", "$$ref"].indexOf(key) === -1 )
|
||||
|
||||
const ModelCollapse = getComponent("ModelCollapse")
|
||||
const Model = getComponent("Model")
|
||||
|
||||
const titleEl = title &&
|
||||
<span className="model-title">
|
||||
<span className="model-title__text">{ title }</span>
|
||||
</span>
|
||||
|
||||
return <span className="model">
|
||||
<ModelCollapse title={titleEl} collapsed={ depth > expandDepth } collapsedContent="[...]">
|
||||
[
|
||||
<span><Model { ...this.props } schema={ items } required={ false }/></span>
|
||||
]
|
||||
{
|
||||
properties.size ? <span>
|
||||
{ properties.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={propStyle}>
|
||||
<br />{ `${key}:`}{ String(v) }</span>)
|
||||
}<br /></span>
|
||||
: null
|
||||
}
|
||||
</ModelCollapse>
|
||||
{ required && <span style={{ color: "red" }}>*</span>}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
19
src/core/components/enum-model.jsx
Normal file
19
src/core/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
|
||||
@@ -15,7 +15,7 @@ class Path extends React.Component {
|
||||
|
||||
return (
|
||||
<pre className="base-url">
|
||||
[ Base url: {host}{basePath}]
|
||||
[ Base URL: {host}{basePath} ]
|
||||
</pre>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -129,7 +129,8 @@ export class Select extends React.Component {
|
||||
value: PropTypes.any,
|
||||
onChange: PropTypes.func,
|
||||
multiple: PropTypes.bool,
|
||||
allowEmptyValue: PropTypes.bool
|
||||
allowEmptyValue: PropTypes.bool,
|
||||
className: PropTypes.string
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
@@ -142,7 +143,7 @@ export class Select extends React.Component {
|
||||
|
||||
let value
|
||||
|
||||
if (props.value !== undefined) {
|
||||
if (props.value) {
|
||||
value = props.value
|
||||
} else {
|
||||
value = props.multiple ? [""] : ""
|
||||
@@ -178,7 +179,7 @@ export class Select extends React.Component {
|
||||
let value = this.state.value.toJS ? this.state.value.toJS() : this.state.value
|
||||
|
||||
return (
|
||||
<select multiple={ multiple } value={ value } onChange={ this.onChange } >
|
||||
<select className={this.props.className} multiple={ multiple } value={ value } onChange={ this.onChange } >
|
||||
{ allowEmptyValue ? <option value="">--</option> : null }
|
||||
{
|
||||
allowedValues.map(function (item, key) {
|
||||
|
||||
@@ -13,8 +13,13 @@ export default class BaseLayout extends React.Component {
|
||||
getComponent: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
onFilterChange =(e) => {
|
||||
let {target: {value}} = e
|
||||
this.props.layoutActions.updateFilter(value)
|
||||
}
|
||||
|
||||
render() {
|
||||
let { specSelectors, specActions, getComponent } = this.props
|
||||
let { specSelectors, specActions, getComponent, layoutSelectors } = this.props
|
||||
|
||||
let info = specSelectors.info()
|
||||
let url = specSelectors.url()
|
||||
@@ -26,11 +31,20 @@ export default class BaseLayout extends React.Component {
|
||||
|
||||
let Info = getComponent("info")
|
||||
let Operations = getComponent("operations", true)
|
||||
let Models = getComponent("models", true)
|
||||
let Models = getComponent("Models", true)
|
||||
let AuthorizeBtn = getComponent("authorizeBtn", true)
|
||||
let Row = getComponent("Row")
|
||||
let Col = getComponent("Col")
|
||||
let Errors = getComponent("errors", true)
|
||||
|
||||
let isLoading = specSelectors.loadingStatus() === "loading"
|
||||
let isFailed = specSelectors.loadingStatus() === "failed"
|
||||
let filter = layoutSelectors.currentFilter()
|
||||
|
||||
let inputStyle = {}
|
||||
if(isFailed) inputStyle.color = "red"
|
||||
if(isLoading) inputStyle.color = "#aaa"
|
||||
|
||||
const Schemes = getComponent("schemes")
|
||||
|
||||
const isSpecEmpty = !specSelectors.specStr()
|
||||
@@ -57,6 +71,7 @@ export default class BaseLayout extends React.Component {
|
||||
{ schemes && schemes.size ? (
|
||||
<Schemes schemes={ schemes } specActions={ specActions } />
|
||||
) : null }
|
||||
|
||||
{ securityDefinitions ? (
|
||||
<AuthorizeBtn />
|
||||
) : null }
|
||||
@@ -64,6 +79,15 @@ export default class BaseLayout extends React.Component {
|
||||
</div>
|
||||
) : null }
|
||||
|
||||
{
|
||||
filter === null || filter === false ? null :
|
||||
<div className="filter-container">
|
||||
<Col className="filter wrapper" mobile={12}>
|
||||
<input className="operation-filter-input" placeholder="Filter by tag" type="text" onChange={this.onFilterChange} value={filter === true || filter === "true" ? "" : filter} disabled={isLoading} style={inputStyle} />
|
||||
</Col>
|
||||
</div>
|
||||
}
|
||||
|
||||
<Row>
|
||||
<Col mobile={12} desktop={12} >
|
||||
<Operations/>
|
||||
|
||||
47
src/core/components/model-collapse.jsx
Normal file
47
src/core/components/model-collapse.jsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import React, { Component } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
|
||||
export default class ModelCollapse extends Component {
|
||||
static propTypes = {
|
||||
collapsedContent: PropTypes.any,
|
||||
collapsed: PropTypes.bool,
|
||||
children: PropTypes.any,
|
||||
title: PropTypes.element
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
collapsedContent: "{...}",
|
||||
collapsed: true,
|
||||
title: null
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
|
||||
let { collapsed, collapsedContent } = this.props
|
||||
|
||||
this.state = {
|
||||
collapsed: collapsed !== undefined ? collapsed : ModelCollapse.defaultProps.collapsed,
|
||||
collapsedContent: collapsedContent || ModelCollapse.defaultProps.collapsedContent
|
||||
}
|
||||
}
|
||||
|
||||
toggleCollapsed=()=>{
|
||||
this.setState({
|
||||
collapsed: !this.state.collapsed
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
const {title} = this.props
|
||||
return (
|
||||
<span>
|
||||
{ title && <span onClick={this.toggleCollapsed} style={{ "cursor": "pointer" }}>{title}</span> }
|
||||
<span onClick={ this.toggleCollapsed } style={{ "cursor": "pointer" }}>
|
||||
<span className={ "model-toggle" + ( this.state.collapsed ? " collapsed" : "" ) }></span>
|
||||
</span>
|
||||
{ this.state.collapsed ? this.state.collapsedContent : this.props.children }
|
||||
</span>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ export default class ModelExample extends React.Component {
|
||||
|
||||
render() {
|
||||
let { getComponent, specSelectors, schema, example, isExecute } = this.props
|
||||
const Model = getComponent("model")
|
||||
const ModelWrapper = getComponent("ModelWrapper")
|
||||
|
||||
return <div>
|
||||
<ul className="tab">
|
||||
@@ -44,7 +44,7 @@ export default class ModelExample extends React.Component {
|
||||
(isExecute || this.state.activeTab === "example") && example
|
||||
}
|
||||
{
|
||||
!isExecute && this.state.activeTab === "model" && <Model schema={ schema }
|
||||
!isExecute && this.state.activeTab === "model" && <ModelWrapper schema={ schema }
|
||||
getComponent={ getComponent }
|
||||
specSelectors={ specSelectors }
|
||||
expandDepth={ 1 } />
|
||||
|
||||
23
src/core/components/model-wrapper.jsx
Normal file
23
src/core/components/model-wrapper.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React, { Component, } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
|
||||
export default class ModelComponent extends Component {
|
||||
static propTypes = {
|
||||
schema: PropTypes.object.isRequired,
|
||||
name: PropTypes.string,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
expandDepth: PropTypes.number
|
||||
}
|
||||
|
||||
render(){
|
||||
let { getComponent } = this.props
|
||||
const Model = getComponent("Model")
|
||||
|
||||
return <div className="model-box">
|
||||
<Model { ...this.props } depth={ 1 } expandDepth={ this.props.expandDepth || 0 }/>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,275 +1,7 @@
|
||||
import React, { Component } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
import { List } from "immutable"
|
||||
const braceOpen = "{"
|
||||
const braceClose = "}"
|
||||
|
||||
const propStyle = { color: "#999", fontStyle: "italic" }
|
||||
|
||||
const EnumModel = ({ value }) => {
|
||||
let collapsedContent = <span>Array [ { value.count() } ]</span>
|
||||
return <span className="prop-enum">
|
||||
Enum:<br />
|
||||
<Collapse collapsedContent={ collapsedContent }>
|
||||
[ { value.join(", ") } ]
|
||||
</Collapse>
|
||||
</span>
|
||||
}
|
||||
|
||||
EnumModel.propTypes = {
|
||||
value: ImPropTypes.iterable
|
||||
}
|
||||
|
||||
class ObjectModel extends Component {
|
||||
static propTypes = {
|
||||
schema: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
name: PropTypes.string,
|
||||
isRef: PropTypes.bool,
|
||||
expandDepth: PropTypes.number,
|
||||
depth: PropTypes.number,
|
||||
deprecated: PropTypes.boolean
|
||||
}
|
||||
|
||||
render(){
|
||||
let { schema, name, isRef, getComponent, depth, deprecated, ...props } = this.props
|
||||
let { expandDepth, specSelectors } = this.props
|
||||
const JumpToPath = getComponent("JumpToPath", true)
|
||||
let description = schema.get("description")
|
||||
let nullable = schema.get("nullable")
|
||||
let properties = schema.get("properties")
|
||||
let additionalProperties = schema.get("additionalProperties")
|
||||
let title = schema.get("title") || name
|
||||
let required = schema.get("required")
|
||||
|
||||
let anyOf = specSelectors.isOAS3() ? schema.get("anyOf") : null
|
||||
let oneOf = specSelectors.isOAS3() ? schema.get("oneOf") : null
|
||||
let not = specSelectors.isOAS3() ? schema.get("not") : null
|
||||
|
||||
const Markdown = getComponent("Markdown")
|
||||
const JumpToPathSection = ({ name }) => <span className="model-jump-to-path"><JumpToPath path={`definitions.${name}`} /></span>
|
||||
let collapsedContent = (<span>
|
||||
<span>{ braceOpen }</span>...<span>{ braceClose }</span>
|
||||
{
|
||||
isRef ? <JumpToPathSection name={ name }/> : ""
|
||||
}
|
||||
</span>)
|
||||
|
||||
return <span className={`model ${deprecated ? "deprecated" : ""}`}>
|
||||
{
|
||||
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>
|
||||
}
|
||||
<Collapse collapsed={ depth > expandDepth } collapsedContent={ collapsedContent }>
|
||||
<span className="brace-open object">{ braceOpen }</span>
|
||||
{
|
||||
!isRef ? null : <JumpToPathSection name={ name }/>
|
||||
}
|
||||
<span className="inner-object">
|
||||
{
|
||||
<table className="model" style={{ marginLeft: "2em" }}><tbody>
|
||||
{
|
||||
!description ? null : <tr style={{ color: "#999", fontStyle: "italic" }}>
|
||||
<td>description:</td>
|
||||
<td>
|
||||
<Markdown source={ description } />
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
{
|
||||
(!specSelectors.isOAS3() || !nullable) ? null : <tr style={{ color: "#999", fontStyle: "italic" }}>
|
||||
<td>nullable</td>
|
||||
<td>{ nullable }</td>
|
||||
</tr>
|
||||
}
|
||||
{
|
||||
!(properties && properties.size) ? null : properties.entrySeq().map(
|
||||
([key, value]) => {
|
||||
let isRequired = List.isList(required) && required.contains(key)
|
||||
let propertyStyle = { verticalAlign: "top", paddingRight: "0.2em" }
|
||||
if ( isRequired ) {
|
||||
propertyStyle.fontWeight = "bold"
|
||||
}
|
||||
|
||||
return (<tr key={key} className={`${value.get("deprecated") ? "deprecated" : ""}`}>
|
||||
<td style={ propertyStyle }>{ key }:</td>
|
||||
<td style={{ verticalAlign: "top" }}>
|
||||
<Model key={ `object-${name}-${key}_${value}` } { ...props }
|
||||
required={ isRequired }
|
||||
getComponent={ getComponent }
|
||||
schema={ value }
|
||||
depth={ depth + 1 } />
|
||||
</td>
|
||||
</tr>)
|
||||
}).toArray()
|
||||
}
|
||||
{
|
||||
!additionalProperties || !additionalProperties.size ? null
|
||||
: <tr>
|
||||
<td>{ "< * >:" }</td>
|
||||
<td>
|
||||
<Model { ...props } required={ false }
|
||||
getComponent={ getComponent }
|
||||
schema={ additionalProperties }
|
||||
depth={ depth + 1 } />
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
{
|
||||
!anyOf ? null
|
||||
: <tr>
|
||||
<td>{ "anyOf ->" }</td>
|
||||
<td>
|
||||
{anyOf.map(schema => {
|
||||
return <div><Model { ...props } required={ false }
|
||||
getComponent={ getComponent }
|
||||
schema={ schema }
|
||||
depth={ depth + 1 } /></div>
|
||||
})}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
{
|
||||
!oneOf ? null
|
||||
: <tr>
|
||||
<td>{ "oneOf ->" }</td>
|
||||
<td>
|
||||
{oneOf.map(schema => {
|
||||
return <div><Model { ...props } required={ false }
|
||||
getComponent={ getComponent }
|
||||
schema={ schema }
|
||||
depth={ depth + 1 } /></div>
|
||||
})}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
{
|
||||
!not ? null
|
||||
: <tr>
|
||||
<td>{ "not ->" }</td>
|
||||
<td>
|
||||
{not.map(schema => {
|
||||
return <div><Model { ...props } required={ false }
|
||||
getComponent={ getComponent }
|
||||
schema={ schema }
|
||||
depth={ depth + 1 } /></div>
|
||||
})}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody></table>
|
||||
}
|
||||
</span>
|
||||
<span className="brace-close">{ braceClose }</span>
|
||||
</Collapse>
|
||||
</span>
|
||||
}
|
||||
}
|
||||
|
||||
class Primitive extends Component {
|
||||
static propTypes = {
|
||||
schema: PropTypes.object.isRequired,
|
||||
name: PropTypes.string,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
required: PropTypes.bool,
|
||||
deprecated: PropTypes.bool
|
||||
}
|
||||
|
||||
render(){
|
||||
let { schema, getComponent, name, required, deprecated } = this.props
|
||||
|
||||
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") || name
|
||||
let description = schema.get("description")
|
||||
let properties = schema.filter( ( v, key) => ["enum", "type", "format", "description", "$$ref"].indexOf(key) === -1 )
|
||||
let style = required ? { fontWeight: "bold" } : {}
|
||||
const Markdown = getComponent("Markdown")
|
||||
|
||||
return <span className={`model ${deprecated ? "deprecated" : ""}`}>
|
||||
{
|
||||
title && <span className="model-title" style={{ marginRight: "2em" }}>
|
||||
<span className="model-title__text">{ title }</span>
|
||||
</span>
|
||||
}
|
||||
<span className="prop-type" style={ style }>{ type }</span> { required && <span style={{ color: "red" }}>*</span>}
|
||||
{ format && <span className="prop-format">(${format})</span>}
|
||||
{
|
||||
properties.size ? properties.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={ propStyle }>
|
||||
<br />{ key }: { String(v) }</span>)
|
||||
: null
|
||||
}
|
||||
{
|
||||
!description ? null :
|
||||
<Markdown source={ description } />
|
||||
}
|
||||
{
|
||||
xml && xml.size ? (<span><br /><span style={ propStyle }>xml:</span>
|
||||
{
|
||||
xml.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={ propStyle }><br/> {key}: { String(v) }</span>).toArray()
|
||||
}
|
||||
</span>): null
|
||||
}
|
||||
{
|
||||
enumArray && <EnumModel value={ enumArray } />
|
||||
}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
|
||||
class ArrayModel extends Component {
|
||||
static propTypes = {
|
||||
schema: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
name: PropTypes.string,
|
||||
required: PropTypes.bool,
|
||||
expandDepth: PropTypes.number,
|
||||
depth: PropTypes.number
|
||||
}
|
||||
|
||||
render(){
|
||||
let { required, schema, depth, name, expandDepth } = this.props
|
||||
let items = schema.get("items")
|
||||
let title = schema.get("title") || name
|
||||
let properties = schema.filter( ( v, key) => ["type", "items", "$$ref"].indexOf(key) === -1 )
|
||||
|
||||
return <span className="model">
|
||||
{
|
||||
title && <span className="model-title">
|
||||
<span className="model-title__text">{ title }</span>
|
||||
</span>
|
||||
}
|
||||
<Collapse collapsed={ depth > expandDepth } collapsedContent="[...]">
|
||||
[
|
||||
<span><Model { ...this.props } name="" schema={ items } required={ false }/></span>
|
||||
]
|
||||
{
|
||||
properties.size ? <span>
|
||||
{ properties.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={propStyle}>
|
||||
<br />{ `${key}:`}{ String(v) }</span>)
|
||||
}<br /></span>
|
||||
: null
|
||||
}
|
||||
</Collapse>
|
||||
{ required && <span style={{ color: "red" }}>*</span>}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class Model extends Component {
|
||||
export default class Model extends Component {
|
||||
static propTypes = {
|
||||
schema: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
@@ -285,6 +17,9 @@ export class Model extends Component {
|
||||
if ( ref.indexOf("#/definitions/") !== -1 ) {
|
||||
return ref.replace(/^.*#\/definitions\//, "")
|
||||
}
|
||||
if ( ref.indexOf("#/components/schemas/") !== -1 ) {
|
||||
return ref.replace("#/components/schemas/", "")
|
||||
}
|
||||
}
|
||||
|
||||
getRefSchema =( model )=> {
|
||||
@@ -295,6 +30,9 @@ export class Model extends Component {
|
||||
|
||||
render () {
|
||||
let { getComponent, specSelectors, schema, required, name, isRef } = this.props
|
||||
let ObjectModel = getComponent("ObjectModel")
|
||||
let ArrayModel = getComponent("ArrayModel")
|
||||
let PrimitiveModel = getComponent("PrimitiveModel")
|
||||
let $$ref = schema && schema.get("$$ref")
|
||||
let modelName = $$ref && this.getModelName( $$ref )
|
||||
let modelSchema, type
|
||||
@@ -332,69 +70,12 @@ export class Model extends Component {
|
||||
case "integer":
|
||||
case "boolean":
|
||||
default:
|
||||
return <Primitive
|
||||
return <PrimitiveModel
|
||||
{ ...this.props }
|
||||
getComponent={ getComponent }
|
||||
schema={ modelSchema }
|
||||
name={ name || modelName }
|
||||
deprecated={deprecated}
|
||||
required={ required }/>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default class ModelComponent extends Component {
|
||||
static propTypes = {
|
||||
schema: PropTypes.object.isRequired,
|
||||
name: PropTypes.string,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
expandDepth: PropTypes.number
|
||||
}
|
||||
|
||||
render(){
|
||||
return <div className="model-box">
|
||||
<Model { ...this.props } depth={ 1 } expandDepth={ this.props.expandDepth || 0 }/>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
class Collapse extends Component {
|
||||
static propTypes = {
|
||||
collapsedContent: PropTypes.any,
|
||||
collapsed: PropTypes.bool,
|
||||
children: PropTypes.any
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
collapsedContent: "{...}",
|
||||
collapsed: true,
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
|
||||
let { collapsed, collapsedContent } = this.props
|
||||
|
||||
this.state = {
|
||||
collapsed: collapsed !== undefined ? collapsed : Collapse.defaultProps.collapsed,
|
||||
collapsedContent: collapsedContent || Collapse.defaultProps.collapsedContent
|
||||
}
|
||||
}
|
||||
|
||||
toggleCollapsed=()=>{
|
||||
this.setState({
|
||||
collapsed: !this.state.collapsed
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
return (<span>
|
||||
<span onClick={ this.toggleCollapsed } style={{ "cursor": "pointer" }}>
|
||||
<span className={ "model-toggle" + ( this.state.collapsed ? " collapsed" : "" ) }></span>
|
||||
</span>
|
||||
{ this.state.collapsed ? this.state.collapsedContent : this.props.children }
|
||||
</span>)
|
||||
required={ required }/> }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export default class Models extends Component {
|
||||
let { docExpansion } = getConfigs()
|
||||
let showModels = layoutSelectors.isShown("models", docExpansion === "full" || docExpansion === "list" )
|
||||
|
||||
const Model = getComponent("model")
|
||||
const ModelWrapper = getComponent("ModelWrapper")
|
||||
const Collapse = getComponent("Collapse")
|
||||
|
||||
if (!definitions.size) return null
|
||||
@@ -28,11 +28,11 @@ export default class Models extends Component {
|
||||
<use xlinkHref={showModels ? "#large-arrow-down" : "#large-arrow"} />
|
||||
</svg>
|
||||
</h4>
|
||||
<Collapse isOpened={showModels} animated>
|
||||
<Collapse isOpened={showModels}>
|
||||
{
|
||||
definitions.entrySeq().map( ( [ name, model ])=>{
|
||||
return <div className="model-container" key={ `models-section-${name}` }>
|
||||
<Model name={ name }
|
||||
<ModelWrapper name={ name }
|
||||
schema={ model }
|
||||
isRef={ true }
|
||||
getComponent={ getComponent }
|
||||
|
||||
155
src/core/components/object-model.jsx
Normal file
155
src/core/components/object-model.jsx
Normal file
@@ -0,0 +1,155 @@
|
||||
import React, { Component, } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { List } from "immutable"
|
||||
|
||||
const braceOpen = "{"
|
||||
const braceClose = "}"
|
||||
|
||||
export default class ObjectModel extends Component {
|
||||
static propTypes = {
|
||||
schema: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
name: PropTypes.string,
|
||||
isRef: PropTypes.bool,
|
||||
expandDepth: PropTypes.number,
|
||||
depth: PropTypes.number
|
||||
}
|
||||
|
||||
render(){
|
||||
let { schema, name, isRef, getComponent, depth, ...props } = this.props
|
||||
let { expandDepth, specSelectors } = this.props
|
||||
let { isOAS3 } = specSelectors
|
||||
let description = schema.get("description")
|
||||
let properties = schema.get("properties")
|
||||
let additionalProperties = schema.get("additionalProperties")
|
||||
let title = schema.get("title") || name
|
||||
let requiredProperties = schema.get("required")
|
||||
|
||||
const JumpToPath = getComponent("JumpToPath", true)
|
||||
const Markdown = getComponent("Markdown")
|
||||
const Model = getComponent("Model")
|
||||
const ModelCollapse = getComponent("ModelCollapse")
|
||||
|
||||
const JumpToPathSection = ({ name }) => {
|
||||
const path = isOAS3 && isOAS3() ? `components.schemas.${name}` : `definitions.${name}`
|
||||
return <span className="model-jump-to-path"><JumpToPath path={path} /></span>
|
||||
}
|
||||
const collapsedContent = (<span>
|
||||
<span>{ braceOpen }</span>...<span>{ braceClose }</span>
|
||||
{
|
||||
isRef ? <JumpToPathSection name={ name }/> : ""
|
||||
}
|
||||
</span>)
|
||||
|
||||
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 title={titleEl} collapsed={ depth > expandDepth } collapsedContent={ collapsedContent }>
|
||||
<span className="brace-open object">{ braceOpen }</span>
|
||||
{
|
||||
!isRef ? null : <JumpToPathSection name={ name }/>
|
||||
}
|
||||
<span className="inner-object">
|
||||
{
|
||||
<table className="model" style={{ marginLeft: "2em" }}><tbody>
|
||||
{
|
||||
!description ? null : <tr style={{ color: "#999", fontStyle: "italic" }}>
|
||||
<td>description:</td>
|
||||
<td>
|
||||
<Markdown source={ description } />
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
{
|
||||
!(properties && properties.size) ? null : properties.entrySeq().map(
|
||||
([key, value]) => {
|
||||
let isRequired = List.isList(requiredProperties) && requiredProperties.contains(key)
|
||||
let propertyStyle = { verticalAlign: "top", paddingRight: "0.2em" }
|
||||
if ( isRequired ) {
|
||||
propertyStyle.fontWeight = "bold"
|
||||
}
|
||||
|
||||
return (<tr key={key}>
|
||||
<td style={ propertyStyle }>
|
||||
{ key }{ isRequired && <span style={{ color: "red" }}>*</span> }
|
||||
</td>
|
||||
<td style={{ verticalAlign: "top" }}>
|
||||
<Model key={ `object-${name}-${key}_${value}` } { ...props }
|
||||
required={ isRequired }
|
||||
getComponent={ getComponent }
|
||||
schema={ value }
|
||||
depth={ depth + 1 } />
|
||||
</td>
|
||||
</tr>)
|
||||
}).toArray()
|
||||
}
|
||||
{
|
||||
!additionalProperties || !additionalProperties.size ? null
|
||||
: <tr>
|
||||
<td>{ "< * >:" }</td>
|
||||
<td>
|
||||
<Model { ...props } required={ false }
|
||||
getComponent={ getComponent }
|
||||
schema={ additionalProperties }
|
||||
depth={ depth + 1 } />
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
{
|
||||
!anyOf ? null
|
||||
: <tr>
|
||||
<td>{ "anyOf ->" }</td>
|
||||
<td>
|
||||
{anyOf.map((schema, k) => {
|
||||
return <div key={k}><Model { ...props } required={ false }
|
||||
getComponent={ getComponent }
|
||||
schema={ schema }
|
||||
depth={ depth + 1 } /></div>
|
||||
})}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
{
|
||||
!oneOf ? null
|
||||
: <tr>
|
||||
<td>{ "oneOf ->" }</td>
|
||||
<td>
|
||||
{oneOf.map((schema, k) => {
|
||||
return <div key={k}><Model { ...props } required={ false }
|
||||
getComponent={ getComponent }
|
||||
schema={ schema }
|
||||
depth={ depth + 1 } /></div>
|
||||
})}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
{
|
||||
!not ? null
|
||||
: <tr>
|
||||
<td>{ "not ->" }</td>
|
||||
<td>
|
||||
{not.map((schema, k) => {
|
||||
return <div key={k}><Model { ...props } required={ false }
|
||||
getComponent={ getComponent }
|
||||
schema={ schema }
|
||||
depth={ depth + 1 } /></div>
|
||||
})}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody></table>
|
||||
}
|
||||
</span>
|
||||
<span className="brace-close">{ braceClose }</span>
|
||||
</ModelCollapse>
|
||||
</span>
|
||||
}
|
||||
}
|
||||
@@ -116,7 +116,8 @@ export default class Operation extends PureComponent {
|
||||
specActions,
|
||||
specSelectors,
|
||||
authActions,
|
||||
authSelectors
|
||||
authSelectors,
|
||||
getConfigs
|
||||
} = this.props
|
||||
|
||||
let summary = operation.get("summary")
|
||||
@@ -141,6 +142,10 @@ export default class Operation extends PureComponent {
|
||||
const Markdown = getComponent( "Markdown" )
|
||||
const Schemes = getComponent( "schemes" )
|
||||
|
||||
const { deepLinking } = getConfigs()
|
||||
|
||||
const isDeepLinkingEnabled = deepLinking && deepLinking !== "false"
|
||||
|
||||
// Merge in Live Response
|
||||
if(response && response.size > 0) {
|
||||
let notDocumented = !responses.get(String(response.get("status")))
|
||||
@@ -152,13 +157,18 @@ export default class Operation extends PureComponent {
|
||||
let onChangeKey = [ path, method ] // Used to add values to _this_ operation ( indexed by path and method )
|
||||
|
||||
return (
|
||||
<div className={deprecated ? "opblock opblock-deprecated" : shown ? `opblock opblock-${method} is-open` : `opblock opblock-${method}`} id={isShownKey} >
|
||||
<div className={deprecated ? "opblock opblock-deprecated" : shown ? `opblock opblock-${method} is-open` : `opblock opblock-${method}`} id={isShownKey.join("-")} >
|
||||
<div className={`opblock-summary opblock-summary-${method}`} onClick={this.toggleShown} >
|
||||
<span className="opblock-summary-method">{method.toUpperCase()}</span>
|
||||
<span className={ deprecated ? "opblock-summary-path__deprecated" : "opblock-summary-path" } >
|
||||
<span>{path}</span>
|
||||
<JumpToPath path={jumpToKey} />
|
||||
</span>
|
||||
<span className="opblock-summary-method">{method.toUpperCase()}</span>
|
||||
<span className={ deprecated ? "opblock-summary-path__deprecated" : "opblock-summary-path" } >
|
||||
<a
|
||||
className="nostyle"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
href={ isDeepLinkingEnabled ? `#/${isShownKey[1]}/${isShownKey[2]}` : ""} >
|
||||
<span>{path}</span>
|
||||
</a>
|
||||
<JumpToPath path={jumpToKey} />
|
||||
</span>
|
||||
|
||||
{ !showSummary ? null :
|
||||
<div className="opblock-summary-description">
|
||||
@@ -191,7 +201,9 @@ export default class Operation extends PureComponent {
|
||||
<div className="opblock-external-docs-wrapper">
|
||||
<h4 className="opblock-title_normal">Find more details</h4>
|
||||
<div className="opblock-external-docs">
|
||||
<span className="opblock-external-docs__description">{ externalDocs.get("description") }</span>
|
||||
<span className="opblock-external-docs__description">
|
||||
<Markdown source={ externalDocs.get("description") } />
|
||||
</span>
|
||||
<a className="opblock-external-docs__link" href={ externalDocs.get("url") }>{ externalDocs.get("url") }</a>
|
||||
</div>
|
||||
</div> : null
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { helpers } from "swagger-client"
|
||||
|
||||
const { opId } = helpers
|
||||
|
||||
export default class Operations extends React.Component {
|
||||
|
||||
@@ -33,7 +36,29 @@ export default class Operations extends React.Component {
|
||||
const Collapse = getComponent("Collapse")
|
||||
|
||||
let showSummary = layoutSelectors.showSummary()
|
||||
let { docExpansion, displayOperationId, displayRequestDuration } = getConfigs()
|
||||
let {
|
||||
docExpansion,
|
||||
displayOperationId,
|
||||
displayRequestDuration,
|
||||
maxDisplayedTags,
|
||||
deepLinking
|
||||
} = getConfigs()
|
||||
|
||||
const isDeepLinkingEnabled = deepLinking && deepLinking !== "false"
|
||||
|
||||
let filter = layoutSelectors.currentFilter()
|
||||
|
||||
if (filter) {
|
||||
if (filter !== true) {
|
||||
taggedOps = taggedOps.filter((tagObj, tag) => {
|
||||
return tag.indexOf(filter) !== -1
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (maxDisplayedTags && !isNaN(maxDisplayedTags) && maxDisplayedTags >= 0) {
|
||||
taggedOps = taggedOps.slice(0, maxDisplayedTags)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -41,6 +66,8 @@ export default class Operations extends React.Component {
|
||||
taggedOps.map( (tagObj, tag) => {
|
||||
let operations = tagObj.get("operations")
|
||||
let tagDescription = tagObj.getIn(["tagDetails", "description"], null)
|
||||
let tagExternalDocsDescription = tagObj.getIn(["tagDetails", "externalDocs", "description"])
|
||||
let tagExternalDocsUrl = tagObj.getIn(["tagDetails", "externalDocs", "url"])
|
||||
|
||||
let isShownKey = ["operations-tag", tag]
|
||||
let showTag = layoutSelectors.isShown(isShownKey, docExpansion === "full" || docExpansion === "list")
|
||||
@@ -48,14 +75,38 @@ export default class Operations extends React.Component {
|
||||
return (
|
||||
<div className={showTag ? "opblock-tag-section is-open" : "opblock-tag-section"} key={"operation-" + tag}>
|
||||
|
||||
<h4 onClick={() => layoutActions.show(isShownKey, !showTag)} className={!tagDescription ? "opblock-tag no-desc" : "opblock-tag" }>
|
||||
<span>{tag}</span>
|
||||
<h4
|
||||
onClick={() => layoutActions.show(isShownKey, !showTag)}
|
||||
className={!tagDescription ? "opblock-tag no-desc" : "opblock-tag" }
|
||||
id={isShownKey.join("-")}>
|
||||
<a
|
||||
className="nostyle"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
href={ isDeepLinkingEnabled ? `#/${tag}` : ""}>
|
||||
<span>{tag}</span>
|
||||
</a>
|
||||
{ !tagDescription ? null :
|
||||
<small>
|
||||
{ tagDescription }
|
||||
</small>
|
||||
}
|
||||
|
||||
<div>
|
||||
{ !tagExternalDocsDescription ? null :
|
||||
<small>
|
||||
{ tagExternalDocsDescription }
|
||||
{ tagExternalDocsUrl ? ": " : null }
|
||||
{ tagExternalDocsUrl ?
|
||||
<a
|
||||
href={tagExternalDocsUrl}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
target={"_blank"}
|
||||
>{tagExternalDocsUrl}</a> : null
|
||||
}
|
||||
</small>
|
||||
}
|
||||
</div>
|
||||
|
||||
<button className="expand-operation" title="Expand operation" onClick={() => layoutActions.show(isShownKey, !showTag)}>
|
||||
<svg className="arrow" width="20" height="20">
|
||||
<use xlinkHref={showTag ? "#large-arrow-down" : "#large-arrow"} />
|
||||
@@ -67,11 +118,14 @@ export default class Operations extends React.Component {
|
||||
{
|
||||
operations.map( op => {
|
||||
|
||||
const isShownKey = ["operations", op.get("id"), tag]
|
||||
const path = op.get("path", "")
|
||||
const method = op.get("method", "")
|
||||
const jumpToKey = `paths.${path}.${method}`
|
||||
|
||||
const operationId =
|
||||
op.getIn(["operation", "operationId"]) || op.getIn(["operation", "__originalOperationId"]) || opId(op.get("operation"), path, method) || op.get("id")
|
||||
const isShownKey = ["operations", tag, operationId]
|
||||
|
||||
const allowTryItOut = specSelectors.allowTryItOutFor(op.get("path"), op.get("method"))
|
||||
const response = specSelectors.responseFor(op.get("path"), op.get("method"))
|
||||
const request = specSelectors.requestFor(op.get("path"), op.get("method"))
|
||||
|
||||
@@ -49,10 +49,11 @@ export default class ParamBody extends PureComponent {
|
||||
let { specSelectors, pathMethod, param, isExecute, consumesValue="" } = props
|
||||
let parameter = specSelectors ? specSelectors.getParameter(pathMethod, param.get("name")) : {}
|
||||
let isXml = /xml/i.test(consumesValue)
|
||||
let isJson = /json/i.test(consumesValue)
|
||||
let paramValue = isXml ? parameter.get("value_xml") : parameter.get("value")
|
||||
|
||||
if ( paramValue !== undefined ) {
|
||||
let val = !paramValue && !isXml ? "{}" : paramValue
|
||||
let val = !paramValue && isJson ? "{}" : paramValue
|
||||
this.setState({ value: val })
|
||||
this.onChange(val, {isXml: isXml, isEditBox: isExecute})
|
||||
} else {
|
||||
@@ -79,8 +80,11 @@ export default class ParamBody extends PureComponent {
|
||||
_onChange = (val, isXml) => { (this.props.onChange || NOOP)(this.props.param, val, isXml) }
|
||||
|
||||
handleOnChange = e => {
|
||||
let {consumesValue} = this.props
|
||||
this.onChange(e.target.value.trim(), {isXml: /xml/i.test(consumesValue)})
|
||||
const {consumesValue} = this.props
|
||||
const isJson = /json/i.test(consumesValue)
|
||||
const isXml = /xml/i.test(consumesValue)
|
||||
const inputValue = isJson ? e.target.value.trim() : e.target.value
|
||||
this.onChange(inputValue, {isXml})
|
||||
}
|
||||
|
||||
toggleIsEditBox = () => this.setState( state => ({isEditBox: !state.isEditBox}))
|
||||
|
||||
@@ -94,7 +94,7 @@ export default class ParameterRow extends Component {
|
||||
{ param.get("name") }
|
||||
{ !required ? null : <span style={{color: "red"}}> *</span> }
|
||||
</div>
|
||||
<div className="parаmeter__type">{ param.get("type") } { itemType && `[${itemType}]` }</div>
|
||||
<div className="parameter__type">{ param.get("type") } { itemType && `[${itemType}]` }</div>
|
||||
<div className="parameter__in">({ param.get("in") })</div>
|
||||
</td>
|
||||
|
||||
|
||||
@@ -72,7 +72,9 @@ export default class Parameters extends Component {
|
||||
return (
|
||||
<div className="opblock-section">
|
||||
<div className="opblock-section-header">
|
||||
<h4 className="opblock-title">Parameters</h4>
|
||||
<div className="tab-header">
|
||||
<h4 className="opblock-title">Parameters</h4>
|
||||
</div>
|
||||
{ allowTryItOut ? (
|
||||
<TryItOutButton enabled={ tryItOutEnabled } onCancelClick={ onCancelClick } onTryoutClick={ onTryoutClick } />
|
||||
) : null }
|
||||
|
||||
55
src/core/components/primitive-model.jsx
Normal file
55
src/core/components/primitive-model.jsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import React, { Component } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
|
||||
const propStyle = { color: "#999", fontStyle: "italic" }
|
||||
|
||||
export default class Primitive extends Component {
|
||||
static propTypes = {
|
||||
schema: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
required: PropTypes.bool
|
||||
}
|
||||
|
||||
render(){
|
||||
let { schema, getComponent, required } = this.props
|
||||
|
||||
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 description = schema.get("description")
|
||||
let properties = schema.filter( ( v, key) => ["enum", "type", "format", "description", "$$ref"].indexOf(key) === -1 )
|
||||
let style = required ? { fontWeight: "bold" } : {}
|
||||
const Markdown = getComponent("Markdown")
|
||||
const EnumModel = getComponent("EnumModel")
|
||||
|
||||
return <span className="prop">
|
||||
<span className="prop-type" style={ style }>{ type }</span> { required && <span style={{ color: "red" }}>*</span>}
|
||||
{ format && <span className="prop-format">(${format})</span>}
|
||||
{
|
||||
properties.size ? properties.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={ propStyle }>
|
||||
<br />{ key }: { String(v) }</span>)
|
||||
: null
|
||||
}
|
||||
{
|
||||
!description ? null :
|
||||
<Markdown source={ description } />
|
||||
}
|
||||
{
|
||||
xml && xml.size ? (<span><br /><span style={ propStyle }>xml:</span>
|
||||
{
|
||||
xml.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={ propStyle }><br/> {key}: { String(v) }</span>).toArray()
|
||||
}
|
||||
</span>): null
|
||||
}
|
||||
{
|
||||
enumArray && <EnumModel value={ enumArray } getComponent={ getComponent } />
|
||||
}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
@@ -11,10 +11,12 @@ function Markdown({ source }) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <Remarkable
|
||||
options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}}
|
||||
source={sanitized}
|
||||
></Remarkable>
|
||||
return <div className="markdown">
|
||||
<Remarkable
|
||||
options={{html: true, typographer: true, breaks: true, linkify: true, linkTarget: "_blank"}}
|
||||
source={sanitized}
|
||||
></Remarkable>
|
||||
</div>
|
||||
}
|
||||
|
||||
Markdown.propTypes = {
|
||||
|
||||
@@ -40,7 +40,7 @@ export default class ResponseBody extends React.Component {
|
||||
|
||||
// Image
|
||||
} else if (/^image\//i.test(contentType)) {
|
||||
bodyEl = <img src={ url } />
|
||||
bodyEl = <img style={{ maxWidth: "100%" }} src={ window.URL.createObjectURL(content) } />
|
||||
|
||||
// Audio
|
||||
} else if (/^audio\//i.test(contentType)) {
|
||||
|
||||
@@ -79,14 +79,15 @@ export default class Response extends React.Component {
|
||||
const ContentType = getComponent("contentType")
|
||||
|
||||
var sampleResponse
|
||||
var schema
|
||||
|
||||
if(isOAS3()) {
|
||||
let oas3SchemaForContentType = response.getIn(["content", this.state.responseContentType, "schema"])
|
||||
sampleResponse = oas3SchemaForContentType ? getSampleSchema(oas3SchemaForContentType.toJS(), this.state.responseContentType, { includeReadOnly: true }) : null
|
||||
var schema = oas3SchemaForContentType ? inferSchema(oas3SchemaForContentType.toJS()) : null
|
||||
schema = oas3SchemaForContentType ? inferSchema(oas3SchemaForContentType.toJS()) : null
|
||||
} else {
|
||||
schema = inferSchema(response.toJS())
|
||||
sampleResponse = schema ? getSampleSchema(schema, contentType, { includeReadOnly: true }) : null
|
||||
var schema = inferSchema(response.toJS())
|
||||
}
|
||||
let example = getExampleComponent( sampleResponse, examples, HighlightCode )
|
||||
|
||||
|
||||
@@ -19,8 +19,9 @@ export default class Schemes extends React.Component {
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if ( this.props.operationScheme && !nextProps.schemes.has(this.props.operationScheme) ) {
|
||||
//fire 'change' event if our selected scheme is no longer an option
|
||||
if ( !this.props.operationScheme || !nextProps.schemes.has(this.props.operationScheme) ) {
|
||||
// if we don't have a selected operationScheme 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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
|
||||
export default ({ version }) => {
|
||||
const VersionStamp = ({ version }) => {
|
||||
return <small><pre className="version"> { version } </pre></small>
|
||||
}
|
||||
|
||||
VersionStamp.propTypes = {
|
||||
version: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default VersionStamp
|
||||
|
||||
@@ -4,37 +4,44 @@ import System from "core/system"
|
||||
import win from "core/window"
|
||||
import ApisPreset from "core/presets/apis"
|
||||
import * as AllPlugins from "core/plugins/all"
|
||||
import { parseSeach, filterConfigs } from "core/utils"
|
||||
|
||||
const CONFIGS = [ "url", "urls", "urls.primaryName", "spec", "validatorUrl", "onComplete", "onFailure", "authorizations", "docExpansion",
|
||||
"apisSorter", "operationsSorter", "supportedSubmitMethods", "dom_id", "defaultModelRendering", "oauth2RedirectUrl",
|
||||
"showRequestHeaders", "custom", "modelPropertyMacro", "parameterMacro", "displayOperationId" , "displayRequestDuration"]
|
||||
import { parseSearch } from "core/utils"
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const { GIT_DIRTY, GIT_COMMIT, PACKAGE_VERSION } = buildInfo
|
||||
const { GIT_DIRTY, GIT_COMMIT, PACKAGE_VERSION, HOSTNAME, BUILD_TIME } = buildInfo
|
||||
|
||||
module.exports = function SwaggerUI(opts) {
|
||||
|
||||
win.versions = win.versions || {}
|
||||
win.versions.swaggerUi = `${PACKAGE_VERSION}/${GIT_COMMIT || "unknown"}${GIT_DIRTY ? "-dirty" : ""}`
|
||||
win.versions.swaggerUi = {
|
||||
version: PACKAGE_VERSION,
|
||||
gitRevision: GIT_COMMIT,
|
||||
gitDirty: GIT_DIRTY,
|
||||
buildTimestamp: BUILD_TIME,
|
||||
machine: HOSTNAME
|
||||
}
|
||||
|
||||
const defaults = {
|
||||
// Some general settings, that we floated to the top
|
||||
dom_id: null,
|
||||
domNode: null,
|
||||
spec: {},
|
||||
url: "",
|
||||
urls: null,
|
||||
layout: "BaseLayout",
|
||||
docExpansion: "list",
|
||||
maxDisplayedTags: null,
|
||||
filter: null,
|
||||
validatorUrl: "https://online.swagger.io/validator",
|
||||
configs: {},
|
||||
custom: {},
|
||||
displayOperationId: false,
|
||||
displayRequestDuration: false,
|
||||
deepLinking: false,
|
||||
|
||||
// Initial set of plugins ( TODO rename this, or refactor - we don't need presets _and_ plugins. Its just there for performance.
|
||||
// Instead, we can compile the first plugin ( it can be a collection of plugins ), then batch the rest.
|
||||
presets: [
|
||||
ApisPreset
|
||||
],
|
||||
|
||||
// Plugins; ( loaded after presets )
|
||||
@@ -50,7 +57,9 @@ module.exports = function SwaggerUI(opts) {
|
||||
store: { },
|
||||
}
|
||||
|
||||
const constructorConfig = deepExtend({}, defaults, opts)
|
||||
let queryConfig = parseSearch()
|
||||
|
||||
const constructorConfig = deepExtend({}, defaults, opts, queryConfig)
|
||||
|
||||
const storeConfigs = deepExtend({}, constructorConfig.store, {
|
||||
system: {
|
||||
@@ -59,7 +68,8 @@ module.exports = function SwaggerUI(opts) {
|
||||
plugins: constructorConfig.presets,
|
||||
state: {
|
||||
layout: {
|
||||
layout: constructorConfig.layout
|
||||
layout: constructorConfig.layout,
|
||||
filter: constructorConfig.filter
|
||||
},
|
||||
spec: {
|
||||
spec: "",
|
||||
@@ -80,7 +90,6 @@ module.exports = function SwaggerUI(opts) {
|
||||
store.register([constructorConfig.plugins, inlinePlugin])
|
||||
|
||||
var system = store.getSystem()
|
||||
let queryConfig = parseSeach()
|
||||
|
||||
system.initOAuth = system.authActions.configureAuth
|
||||
|
||||
@@ -91,7 +100,13 @@ module.exports = function SwaggerUI(opts) {
|
||||
|
||||
let localConfig = system.specSelectors.getLocalConfig ? system.specSelectors.getLocalConfig() : {}
|
||||
let mergedConfig = deepExtend({}, localConfig, constructorConfig, fetchedConfig || {}, queryConfig)
|
||||
store.setConfigs(filterConfigs(mergedConfig, CONFIGS))
|
||||
|
||||
// deep extend mangles domNode, we need to set it manually
|
||||
if(opts.domNode) {
|
||||
mergedConfig.domNode = opts.domNode
|
||||
}
|
||||
|
||||
store.setConfigs(mergedConfig)
|
||||
|
||||
if (fetchedConfig !== null) {
|
||||
if (!queryConfig.url && typeof mergedConfig.spec === "object" && Object.keys(mergedConfig.spec).length) {
|
||||
@@ -104,10 +119,13 @@ module.exports = function SwaggerUI(opts) {
|
||||
}
|
||||
}
|
||||
|
||||
if(mergedConfig.dom_id) {
|
||||
system.render(mergedConfig.dom_id, "App")
|
||||
if(mergedConfig.domNode) {
|
||||
system.render(mergedConfig.domNode, "App")
|
||||
} else if(mergedConfig.dom_id) {
|
||||
let domNode = document.querySelector(mergedConfig.dom_id)
|
||||
system.render(domNode, "App")
|
||||
} else {
|
||||
console.error("Skipped rendering: no `dom_id` was specified")
|
||||
console.error("Skipped rendering: no `dom_id` or `domNode` was specified")
|
||||
}
|
||||
|
||||
return system
|
||||
|
||||
@@ -57,7 +57,8 @@ export class JsonSchema_string extends Component {
|
||||
|
||||
if ( enumValue ) {
|
||||
const Select = getComponent("Select")
|
||||
return (<Select allowedValues={ enumValue }
|
||||
return (<Select className={ errors.length ? "invalid" : ""}
|
||||
allowedValues={ enumValue }
|
||||
value={ value }
|
||||
allowEmptyValue={ !required }
|
||||
onChange={ this.onEnumChange }/>)
|
||||
@@ -121,6 +122,7 @@ export class JsonSchema_array extends PureComponent {
|
||||
render() {
|
||||
let { getComponent, required, schema, fn } = this.props
|
||||
|
||||
let errors = schema.errors || []
|
||||
let itemSchema = fn.inferSchema(schema.items)
|
||||
|
||||
const JsonSchemaForm = getComponent("JsonSchemaForm")
|
||||
@@ -131,19 +133,17 @@ export class JsonSchema_array extends PureComponent {
|
||||
|
||||
if ( enumValue ) {
|
||||
const Select = getComponent("Select")
|
||||
return (<Select multiple={ true }
|
||||
return (<Select className={ errors.length ? "invalid" : ""}
|
||||
multiple={ true }
|
||||
value={ value }
|
||||
allowedValues={ enumValue }
|
||||
allowEmptyValue={ !required }
|
||||
onChange={ this.onEnumChange }/>)
|
||||
}
|
||||
|
||||
let errors = schema.errors || []
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ !value || value.count() < 1 ?
|
||||
(errors.length ? <span style={{ color: "red", fortWeight: "bold" }}>{ errors[0] }</span> : null) :
|
||||
{ !value || value.count() < 1 ? null :
|
||||
value.map( (item,i) => {
|
||||
let schema = Object.assign({}, itemSchema)
|
||||
if ( errors.length ) {
|
||||
@@ -153,12 +153,12 @@ export class JsonSchema_array extends PureComponent {
|
||||
return (
|
||||
<div key={i} className="json-schema-form-item">
|
||||
<JsonSchemaForm fn={fn} getComponent={getComponent} value={item} onChange={(val) => this.onItemChange(val, i)} schema={schema} />
|
||||
<Button className="json-schema-form-item-remove" onClick={()=> this.removeItem(i)} > - </Button>
|
||||
<Button className="btn btn-sm json-schema-form-item-remove" onClick={()=> this.removeItem(i)} > - </Button>
|
||||
</div>
|
||||
)
|
||||
}).toArray()
|
||||
}
|
||||
<Button className="json-schema-form-item-add" onClick={this.addItem}> Add item </Button>
|
||||
<Button className={`btn btn-sm json-schema-form-item-add ${errors.length ? "invalid" : null}`} onClick={this.addItem}> Add item </Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -170,12 +170,14 @@ export class JsonSchema_boolean extends Component {
|
||||
|
||||
onEnumChange = (val) => this.props.onChange(val)
|
||||
render() {
|
||||
let { getComponent, required, value } = this.props
|
||||
let { getComponent, value, schema } = this.props
|
||||
let errors = schema.errors || []
|
||||
const Select = getComponent("Select")
|
||||
|
||||
return (<Select value={ String(value) }
|
||||
return (<Select className={ errors.length ? "invalid" : ""}
|
||||
value={ String(value) }
|
||||
allowedValues={ fromJS(["true", "false"]) }
|
||||
allowEmptyValue={ !required }
|
||||
allowEmptyValue={ true }
|
||||
onChange={ this.onEnumChange }/>)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,11 +68,21 @@ export default function authorize ( { auth, authActions, errActions, configs, au
|
||||
|
||||
// pass action authorizeOauth2 and authentication data through window
|
||||
// to authorize with oauth2
|
||||
|
||||
let callback
|
||||
if (flow === "implicit") {
|
||||
callback = authActions.preAuthorizeImplicit
|
||||
} else if (authConfigs.useBasicAuthenticationWithAccessCodeGrant) {
|
||||
callback = authActions.authorizeAccessCodeWithBasicAuthentication
|
||||
} else {
|
||||
callback = authActions.authorizeAccessCodeWithFormParams
|
||||
}
|
||||
|
||||
win.swaggerUIRedirectOauth2 = {
|
||||
auth: auth,
|
||||
state: state,
|
||||
redirectUrl: redirectUrl,
|
||||
callback: flow === "implicit" ? authActions.preAuthorizeImplicit : authActions.authorizeAccessCode,
|
||||
callback: callback,
|
||||
errCb: errActions.newAuthErr
|
||||
}
|
||||
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import get from "lodash/get"
|
||||
|
||||
export function transformPathToArray(property, jsSpec) {
|
||||
if(property.slice(0,9) === "instance.") {
|
||||
var str = property.slice(9)
|
||||
} else { // eslint-disable-next-line no-redeclare
|
||||
var str = property
|
||||
}
|
||||
|
||||
var pathArr = []
|
||||
|
||||
str
|
||||
.split(".")
|
||||
.map(item => {
|
||||
// "key[0]" becomes ["key", "0"]
|
||||
if(item.includes("[")) {
|
||||
let index = parseInt(item.match(/\[(.*)\]/)[1])
|
||||
let keyName = item.slice(0, item.indexOf("["))
|
||||
return [keyName, index.toString()]
|
||||
} else {
|
||||
return item
|
||||
}
|
||||
})
|
||||
.reduce(function(a, b) {
|
||||
// flatten!
|
||||
return a.concat(b)
|
||||
}, [])
|
||||
.concat([""]) // add an empty item into the array, so we don't get stuck with something in our buffer below
|
||||
.reduce((buffer, curr) => {
|
||||
let obj = pathArr.length ? get(jsSpec, pathArr) : jsSpec
|
||||
|
||||
if(get(obj, makeAccessArray(buffer, curr))) {
|
||||
if(buffer.length) {
|
||||
pathArr.push(buffer)
|
||||
}
|
||||
if(curr.length) {
|
||||
pathArr.push(curr)
|
||||
}
|
||||
return ""
|
||||
} else {
|
||||
// attach key to buffer
|
||||
return `${buffer}${buffer.length ? "." : ""}${curr}`
|
||||
}
|
||||
}, "")
|
||||
|
||||
if(typeof get(jsSpec, pathArr) !== "undefined") {
|
||||
return pathArr
|
||||
} else {
|
||||
// if our path is not correct (there is no value at the path),
|
||||
// return null
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function makeAccessArray(buffer, curr) {
|
||||
let arr = []
|
||||
|
||||
if(buffer.length) {
|
||||
arr.push(buffer)
|
||||
}
|
||||
|
||||
if(curr.length) {
|
||||
arr.push(curr)
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
||||
@@ -73,7 +73,7 @@ export const authorizePassword = ( auth ) => ( { authActions } ) => {
|
||||
let { schema, name, username, password, passwordType, clientId, clientSecret } = auth
|
||||
let form = {
|
||||
grant_type: "password",
|
||||
scopes: encodeURIComponent(auth.scopes.join(scopeSeparator))
|
||||
scope: encodeURIComponent(auth.scopes.join(scopeSeparator))
|
||||
}
|
||||
let query = {}
|
||||
let headers = {}
|
||||
@@ -111,7 +111,7 @@ export const authorizeApplication = ( auth ) => ( { authActions } ) => {
|
||||
return authActions.authorizeRequest({body: buildFormData(form), name, url: schema.get("tokenUrl"), auth, headers })
|
||||
}
|
||||
|
||||
export const authorizeAccessCode = ( { auth, redirectUrl } ) => ( { authActions } ) => {
|
||||
export const authorizeAccessCodeWithFormParams = ( { auth, redirectUrl } ) => ( { authActions } ) => {
|
||||
let { schema, name, clientId, clientSecret } = auth
|
||||
let form = {
|
||||
grant_type: "authorization_code",
|
||||
@@ -124,6 +124,21 @@ export const authorizeAccessCode = ( { auth, redirectUrl } ) => ( { authActions
|
||||
return authActions.authorizeRequest({body: buildFormData(form), name, url: schema.get("tokenUrl"), auth})
|
||||
}
|
||||
|
||||
export const authorizeAccessCodeWithBasicAuthentication = ( { auth, redirectUrl } ) => ( { authActions } ) => {
|
||||
let { schema, name, clientId, clientSecret } = auth
|
||||
let headers = {
|
||||
Authorization: "Basic " + btoa(clientId + ":" + clientSecret)
|
||||
}
|
||||
let form = {
|
||||
grant_type: "authorization_code",
|
||||
code: auth.code,
|
||||
client_id: clientId,
|
||||
redirect_uri: redirectUrl
|
||||
}
|
||||
|
||||
return authActions.authorizeRequest({body: buildFormData(form), name, url: schema.get("tokenUrl"), auth, headers})
|
||||
}
|
||||
|
||||
export const authorizeRequest = ( data ) => ( { fn, authActions, errActions, authSelectors } ) => {
|
||||
let { body, query={}, headers={}, name, url, auth } = data
|
||||
let { additionalQueryStringParams } = authSelectors.getConfigs() || {}
|
||||
@@ -135,7 +150,6 @@ export const authorizeRequest = ( data ) => ( { fn, authActions, errActions, aut
|
||||
|
||||
let _headers = Object.assign({
|
||||
"Accept":"application/json, text/plain, */*",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
}, headers)
|
||||
|
||||
|
||||
1
src/core/plugins/deep-linking/README.md
Normal file
1
src/core/plugins/deep-linking/README.md
Normal file
@@ -0,0 +1 @@
|
||||
See `docs/deep-linking.md`.
|
||||
7
src/core/plugins/deep-linking/helpers.js
Normal file
7
src/core/plugins/deep-linking/helpers.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export const setHash = (value) => {
|
||||
if(value) {
|
||||
return history.pushState(null, null, `#${value}`)
|
||||
} else {
|
||||
return window.location.hash = ""
|
||||
}
|
||||
}
|
||||
18
src/core/plugins/deep-linking/index.js
Normal file
18
src/core/plugins/deep-linking/index.js
Normal file
@@ -0,0 +1,18 @@
|
||||
// import reducers from "./reducers"
|
||||
// import * as actions from "./actions"
|
||||
// import * as selectors from "./selectors"
|
||||
import * as specWrapActions from "./spec-wrap-actions"
|
||||
import * as layoutWrapActions from "./layout-wrap-actions"
|
||||
|
||||
export default function() {
|
||||
return {
|
||||
statePlugins: {
|
||||
spec: {
|
||||
wrapActions: specWrapActions
|
||||
},
|
||||
layout: {
|
||||
wrapActions: layoutWrapActions
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/core/plugins/deep-linking/layout-wrap-actions.js
Normal file
36
src/core/plugins/deep-linking/layout-wrap-actions.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import { setHash } from "./helpers"
|
||||
|
||||
export const show = (ori, { getConfigs }) => (...args) => {
|
||||
ori(...args)
|
||||
|
||||
const isDeepLinkingEnabled = getConfigs().deepLinking
|
||||
if(!isDeepLinkingEnabled || isDeepLinkingEnabled === "false") {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
let [thing, shown] = args
|
||||
let [type] = thing
|
||||
|
||||
if(type === "operations-tag" || type === "operations") {
|
||||
if(!shown) {
|
||||
return setHash("/")
|
||||
}
|
||||
|
||||
if(type === "operations") {
|
||||
let [, tag, operationId] = thing
|
||||
setHash(`/${tag}/${operationId}`)
|
||||
}
|
||||
|
||||
if(type === "operations-tag") {
|
||||
let [, tag] = thing
|
||||
setHash(`/${tag}`)
|
||||
}
|
||||
}
|
||||
|
||||
} catch(e) {
|
||||
// This functionality is not mission critical, so if something goes wrong
|
||||
// we'll just move on
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
51
src/core/plugins/deep-linking/spec-wrap-actions.js
Normal file
51
src/core/plugins/deep-linking/spec-wrap-actions.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import scrollTo from "scroll-to-element"
|
||||
|
||||
const SCROLL_OFFSET = -5
|
||||
let hasHashBeenParsed = false
|
||||
|
||||
|
||||
export const updateResolved = (ori, { layoutActions, getConfigs }) => (...args) => {
|
||||
ori(...args)
|
||||
|
||||
const isDeepLinkingEnabled = getConfigs().deepLinking
|
||||
if(!isDeepLinkingEnabled || isDeepLinkingEnabled === "false") {
|
||||
return
|
||||
}
|
||||
|
||||
if(window.location.hash && !hasHashBeenParsed ) {
|
||||
let hash = window.location.hash.slice(1) // # is first character
|
||||
|
||||
if(hash[0] === "!") {
|
||||
// Parse UI 2.x shebangs
|
||||
hash = hash.slice(1)
|
||||
}
|
||||
|
||||
if(hash[0] === "/") {
|
||||
// "/pet/addPet" => "pet/addPet"
|
||||
// makes the split result cleaner
|
||||
// also handles forgotten leading slash
|
||||
hash = hash.slice(1)
|
||||
}
|
||||
|
||||
let [tag, operationId] = hash.split("/")
|
||||
|
||||
if(tag && operationId) {
|
||||
// Pre-expand and scroll to the operation
|
||||
layoutActions.show(["operations-tag", tag], true)
|
||||
layoutActions.show(["operations", tag, operationId], true)
|
||||
|
||||
scrollTo(`#operations-${tag}-${operationId}`, {
|
||||
offset: SCROLL_OFFSET
|
||||
})
|
||||
} else if(tag) {
|
||||
// Pre-expand and scroll to the tag
|
||||
layoutActions.show(["operations-tag", tag], true)
|
||||
|
||||
scrollTo(`#operations-tag-${tag}`, {
|
||||
offset: SCROLL_OFFSET
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
hasHashBeenParsed = true
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { normalizeArray } from "core/utils"
|
||||
|
||||
export const UPDATE_LAYOUT = "layout_update_layout"
|
||||
export const UPDATE_FILTER = "layout_update_filter"
|
||||
export const UPDATE_MODE = "layout_update_mode"
|
||||
export const SHOW = "layout_show"
|
||||
|
||||
@@ -13,6 +14,13 @@ export function updateLayout(layout) {
|
||||
}
|
||||
}
|
||||
|
||||
export function updateFilter(filter) {
|
||||
return {
|
||||
type: UPDATE_FILTER,
|
||||
payload: filter
|
||||
}
|
||||
}
|
||||
|
||||
export function show(thing, shown=true) {
|
||||
thing = normalizeArray(thing)
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
UPDATE_LAYOUT,
|
||||
UPDATE_FILTER,
|
||||
UPDATE_MODE,
|
||||
SHOW
|
||||
} from "./actions"
|
||||
@@ -8,6 +9,8 @@ export default {
|
||||
|
||||
[UPDATE_LAYOUT]: (state, action) => state.set("layout", action.payload),
|
||||
|
||||
[UPDATE_FILTER]: (state, action) => state.set("filter", action.payload),
|
||||
|
||||
[SHOW]: (state, action) => {
|
||||
let thing = action.payload.thing
|
||||
let shown = action.payload.shown
|
||||
|
||||
@@ -5,6 +5,8 @@ const state = state => state
|
||||
|
||||
export const current = state => state.get("layout")
|
||||
|
||||
export const currentFilter = state => state.get("filter")
|
||||
|
||||
export const isShown = (state, thing, def) => {
|
||||
thing = normalizeArray(thing)
|
||||
return Boolean(state.getIn(["shown", ...thing], def))
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
|
||||
export default (props) => {
|
||||
const Callbacks = (props) => {
|
||||
let { callbacks, getComponent } = props
|
||||
const Markdown = getComponent("Markdown")
|
||||
// const Markdown = getComponent("Markdown")
|
||||
const Operation = getComponent("operation", true)
|
||||
|
||||
if(!callbacks) {
|
||||
@@ -10,15 +11,16 @@ export default (props) => {
|
||||
}
|
||||
|
||||
let callbackElements = callbacks.map((callback, callbackName) => {
|
||||
return <div>
|
||||
return <div key={callbackName}>
|
||||
<h2>{callbackName}</h2>
|
||||
{ callback.map((pathItem, pathItemName) => {
|
||||
return <div>
|
||||
return <div key={pathItemName}>
|
||||
{ pathItem.map((operation, method) => {
|
||||
return <Operation
|
||||
operation={operation}
|
||||
key={method}
|
||||
method={method}
|
||||
isShownKey={["callbacks", operation.get("id"), callbackName]}
|
||||
path={pathItemName}
|
||||
allowTryItOut={false}
|
||||
{...props}></Operation>
|
||||
@@ -37,3 +39,11 @@ export default (props) => {
|
||||
{callbackElements}
|
||||
</div>
|
||||
}
|
||||
|
||||
Callbacks.propTypes = {
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
callbacks: PropTypes.array.isRequired
|
||||
|
||||
}
|
||||
|
||||
export default Callbacks
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { Component } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
|
||||
export default class OperationLink extends Component {
|
||||
class OperationLink extends Component {
|
||||
render() {
|
||||
const { link, name } = this.props
|
||||
|
||||
@@ -26,3 +28,10 @@ function padString(n, string) {
|
||||
.map((line, i) => i > 0 ? Array(n + 1).join(" ") + line : line)
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
OperationLink.propTypes = {
|
||||
link: ImPropTypes.orderedMap.isRequired,
|
||||
name: PropTypes.String
|
||||
}
|
||||
|
||||
export default OperationLink
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
import { OrderedMap } from "immutable"
|
||||
import { getSampleSchema } from "core/utils"
|
||||
|
||||
|
||||
export default ({ requestBody, getComponent, specSelectors, contentType }) => {
|
||||
const RequestBody = ({ requestBody, getComponent, specSelectors, contentType }) => {
|
||||
const Markdown = getComponent("Markdown")
|
||||
const ModelExample = getComponent("modelExample")
|
||||
const HighlightCode = getComponent("highlightCode")
|
||||
@@ -14,7 +16,7 @@ export default ({ requestBody, getComponent, specSelectors, contentType }) => {
|
||||
|
||||
const mediaTypeValue = requestBodyContent.get(contentType)
|
||||
|
||||
const sampleSchema = getSampleSchema(mediaTypeValue.get("schema"))
|
||||
const sampleSchema = getSampleSchema(mediaTypeValue.get("schema").toJS(), contentType)
|
||||
|
||||
return <div>
|
||||
{ requestBodyDescription &&
|
||||
@@ -29,3 +31,12 @@ export default ({ requestBody, getComponent, specSelectors, contentType }) => {
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
RequestBody.propTypes = {
|
||||
requestBody: ImPropTypes.orderedMap.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
contentType: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default RequestBody
|
||||
|
||||
@@ -3,11 +3,13 @@ import parameters from "./parameters"
|
||||
import VersionStamp from "./version-stamp"
|
||||
import OnlineValidatorBadge from "./online-validator-badge"
|
||||
import Model from "./model"
|
||||
import TryItOutButton from "./try-it-out-button"
|
||||
|
||||
export default {
|
||||
Markdown,
|
||||
parameters,
|
||||
VersionStamp,
|
||||
model: Model,
|
||||
onlineValidatorBadge: OnlineValidatorBadge
|
||||
onlineValidatorBadge: OnlineValidatorBadge,
|
||||
TryItOutButton
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { Component, PropTypes } from "react"
|
||||
import React, { Component } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { OAS3ComponentWrapFactory } from "../helpers"
|
||||
import { Model } from "core/components/model"
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react"
|
||||
import { OAS3ComponentWrapFactory } from "../helpers"
|
||||
|
||||
// We're disabling the Online Validator Badge until the online validator
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import React, { Component, PropTypes } from "react"
|
||||
import React, { Component } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import Im, { Map } from "immutable"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
import { OAS3ComponentWrapFactory } from "../helpers"
|
||||
|
||||
const mapRequestBody = (iterable, fn) => iterable.entries().filter(Im.Map.isMap).map((val) => {
|
||||
return fn(val.get(0), val.get(1))
|
||||
})
|
||||
|
||||
// More readable, just iterate over maps, only
|
||||
const eachMap = (iterable, fn) => iterable.valueSeq().filter(Im.Map.isMap).map(fn)
|
||||
|
||||
@@ -159,7 +156,7 @@ class Parameters extends Component {
|
||||
isOAS3() && requestBody && this.state.parametersVisible &&
|
||||
<div className="opblock-section">
|
||||
<div className="opblock-section-header">
|
||||
<h4 className="opblock-title">Request body</h4>
|
||||
<h4 className={`opblock-title parameter__name ${requestBody.get("required") && "required"}`}>Request body</h4>
|
||||
<label>
|
||||
<ContentType
|
||||
value={this.state.requestBodyContentType}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { OAS3ComponentWrapFactory } from "../helpers"
|
||||
|
||||
export default OAS3ComponentWrapFactory(() => {
|
||||
return null
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createSelector } from "reselect"
|
||||
import { sorters } from "core/utils"
|
||||
import { fromJS, Set, Map, List } from "immutable"
|
||||
import { fromJS, Set, Map, OrderedMap, List } from "immutable"
|
||||
|
||||
const DEFAULT_TAG = "default"
|
||||
|
||||
@@ -49,8 +49,10 @@ export const spec = state => {
|
||||
export const isOAS3 = createSelector(
|
||||
// isOAS3 is stubbed out here to work around an issue with injecting more selectors
|
||||
// in the OAS3 plugin, and to ensure that the function is always available.
|
||||
// It's not perfect, but our hybrid (core+plugin code) implementation for OAS3
|
||||
// needs this. //KS
|
||||
spec,
|
||||
spec => false
|
||||
() => false
|
||||
)
|
||||
|
||||
export const info = createSelector(
|
||||
@@ -194,25 +196,35 @@ export const tagDetails = (state, tag) => {
|
||||
|
||||
export const operationsWithTags = createSelector(
|
||||
operationsWithRootInherited,
|
||||
operations => {
|
||||
tags,
|
||||
(operations, tags) => {
|
||||
return operations.reduce( (taggedMap, op) => {
|
||||
let tags = Set(op.getIn(["operation","tags"]))
|
||||
if(tags.count() < 1)
|
||||
return taggedMap.update(DEFAULT_TAG, List(), ar => ar.push(op))
|
||||
return tags.reduce( (res, tag) => res.update(tag, List(), (ar) => ar.push(op)), taggedMap )
|
||||
}, Map())
|
||||
}, tags.reduce( (taggedMap, tag) => {
|
||||
return taggedMap.set(tag.get("name"), List())
|
||||
} , OrderedMap()))
|
||||
}
|
||||
)
|
||||
|
||||
export const taggedOperations = ( state ) =>( { getConfigs } ) => {
|
||||
let { operationsSorter }= getConfigs()
|
||||
export const taggedOperations = (state) => ({ getConfigs }) => {
|
||||
let { tagsSorter, operationsSorter } = getConfigs()
|
||||
return operationsWithTags(state)
|
||||
.sortBy(
|
||||
(val, key) => key, // get the name of the tag to be passed to the sorter
|
||||
(tagA, tagB) => {
|
||||
let sortFn = (typeof tagsSorter === "function" ? tagsSorter : sorters.tagsSorter[ tagsSorter ])
|
||||
return (!sortFn ? null : sortFn(tagA, tagB))
|
||||
}
|
||||
)
|
||||
.map((ops, tag) => {
|
||||
let sortFn = (typeof operationsSorter === "function" ? operationsSorter : sorters.operationsSorter[ operationsSorter ])
|
||||
let operations = (!sortFn ? ops : ops.sort(sortFn))
|
||||
|
||||
return operationsWithTags(state).map((ops, tag) => {
|
||||
let sortFn = typeof operationsSorter === "function" ? operationsSorter
|
||||
: sorters.operationsSorter[operationsSorter]
|
||||
let operations = !sortFn ? ops : ops.sort(sortFn)
|
||||
|
||||
return Map({tagDetails: tagDetails(state, tag), operations: operations})})
|
||||
return Map({ tagDetails: tagDetails(state, tag), operations: operations })
|
||||
})
|
||||
}
|
||||
|
||||
export const responses = createSelector(
|
||||
@@ -281,12 +293,13 @@ export function parametersIncludeType(parameters, typeValue="") {
|
||||
export function contentTypeValues(state, pathMethod) {
|
||||
let op = spec(state).getIn(["paths", ...pathMethod], fromJS({}))
|
||||
const parameters = op.get("parameters") || new List()
|
||||
const requestContentType = (
|
||||
parametersIncludeType(parameters, "file") ? "multipart/form-data"
|
||||
: parametersIncludeIn(parameters, "formData") ? "application/x-www-form-urlencoded"
|
||||
: op.get("consumes_value")
|
||||
)
|
||||
|
||||
const requestContentType = (
|
||||
op.get("consumes_value") ? op.get("consumes_value")
|
||||
: parametersIncludeType(parameters, "file") ? "multipart/form-data"
|
||||
: parametersIncludeType(parameters, "formData") ? "application/x-www-form-urlencoded"
|
||||
: undefined
|
||||
)
|
||||
|
||||
return fromJS({
|
||||
requestContentType,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import SplitPane from "react-split-pane"
|
||||
import "./split-pane-mode.less"
|
||||
|
||||
const MODE_KEY = ["split-pane-mode"]
|
||||
const MODE_LEFT = "left"
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
.swagger-ui {
|
||||
.Resizer.vertical.disabled {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
import { shallowEqualKeys } from "core/utils"
|
||||
import { transformPathToArray } from "core/path-translator"
|
||||
|
||||
export default function() {
|
||||
return {
|
||||
fn: { shallowEqualKeys, transformPathToArray }
|
||||
fn: { shallowEqualKeys }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,8 +58,7 @@ export const makeMappedContainer = (getSystem, getStore, memGetComponent, getCom
|
||||
|
||||
}
|
||||
|
||||
export const render = (getSystem, getStore, getComponent, getComponents, dom) => {
|
||||
let domNode = document.querySelector(dom)
|
||||
export const render = (getSystem, getStore, getComponent, getComponents, domNode) => {
|
||||
let App = (getComponent(getSystem, getStore, getComponents, "App", "root"))
|
||||
ReactDOM.render(( <App/> ), domNode)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import auth from "core/plugins/auth"
|
||||
import util from "core/plugins/util"
|
||||
import SplitPaneModePlugin from "core/plugins/split-pane-mode"
|
||||
import downloadUrlPlugin from "core/plugins/download-url"
|
||||
import deepLinkingPlugin from "core/plugins/deep-linking"
|
||||
|
||||
import App from "core/components/app"
|
||||
import AuthorizationPopup from "core/components/auth/authorization-popup"
|
||||
@@ -41,9 +42,15 @@ import Footer from "core/components/footer"
|
||||
import ParamBody from "core/components/param-body"
|
||||
import Curl from "core/components/curl"
|
||||
import Schemes from "core/components/schemes"
|
||||
import ModelCollapse from "core/components/model-collapse"
|
||||
import ModelExample from "core/components/model-example"
|
||||
import ModelWrapper from "core/components/model-wrapper"
|
||||
import Model from "core/components/model"
|
||||
import Models from "core/components/models"
|
||||
import EnumModel from "core/components/enum-model"
|
||||
import ObjectModel from "core/components/object-model"
|
||||
import ArrayModel from "core/components/array-model"
|
||||
import PrimitiveModel from "core/components/primitive-model"
|
||||
import TryItOutButton from "core/components/try-it-out-button"
|
||||
import VersionStamp from "core/components/version-stamp"
|
||||
|
||||
@@ -89,8 +96,14 @@ export default function() {
|
||||
curl: Curl,
|
||||
schemes: Schemes,
|
||||
modelExample: ModelExample,
|
||||
model: Model,
|
||||
models: Models,
|
||||
ModelWrapper,
|
||||
ModelCollapse,
|
||||
Model,
|
||||
Models,
|
||||
EnumModel,
|
||||
ObjectModel,
|
||||
ArrayModel,
|
||||
PrimitiveModel,
|
||||
TryItOutButton,
|
||||
Markdown,
|
||||
BaseLayout,
|
||||
@@ -121,6 +134,7 @@ export default function() {
|
||||
auth,
|
||||
ast,
|
||||
SplitPaneModePlugin,
|
||||
downloadUrlPlugin
|
||||
downloadUrlPlugin,
|
||||
deepLinkingPlugin
|
||||
]
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export function fromJSOrdered (js) {
|
||||
return !isObject(js) ? js :
|
||||
Array.isArray(js) ?
|
||||
Im.Seq(js).map(fromJSOrdered).toList() :
|
||||
Im.Seq(js).map(fromJSOrdered).toOrderedMap()
|
||||
Im.OrderedMap(js).map(fromJSOrdered)
|
||||
}
|
||||
|
||||
export function bindToState(obj, state) {
|
||||
@@ -228,13 +228,13 @@ export function highlight (el) {
|
||||
|
||||
var reset = function(el) {
|
||||
var text = el.textContent,
|
||||
pos = 0, // current position
|
||||
pos = 0, // current position
|
||||
next1 = text[0], // next character
|
||||
chr = 1, // current character
|
||||
prev1, // previous character
|
||||
prev2, // the one before the previous
|
||||
token = // current token content
|
||||
el.innerHTML = "", // (and cleaning the node)
|
||||
chr = 1, // current character
|
||||
prev1, // previous character
|
||||
prev2, // the one before the previous
|
||||
token = // current token content
|
||||
el.innerHTML = "", // (and cleaning the node)
|
||||
|
||||
// current token type:
|
||||
// 0: anything else (whitespaces / newlines)
|
||||
@@ -274,11 +274,11 @@ export function highlight (el) {
|
||||
(tokenType > 8 && chr == "\n") ||
|
||||
[ // finalize conditions for other token types
|
||||
// 0: whitespaces
|
||||
/\S/[test](chr), // merged together
|
||||
/\S/[test](chr), // merged together
|
||||
// 1: operators
|
||||
1, // consist of a single character
|
||||
1, // consist of a single character
|
||||
// 2: braces
|
||||
1, // consist of a single character
|
||||
1, // consist of a single character
|
||||
// 3: (key)word
|
||||
!/[$\w]/[test](chr),
|
||||
// 4: regex
|
||||
@@ -341,12 +341,12 @@ export function highlight (el) {
|
||||
// condition)
|
||||
tokenType = 11
|
||||
while (![
|
||||
1, // 0: whitespace
|
||||
1, // 0: whitespace
|
||||
// 1: operator or braces
|
||||
/[\/{}[(\-+*=<>:;|\\.,?!&@~]/[test](chr), // eslint-disable-line no-useless-escape
|
||||
/[\])]/[test](chr), // 2: closing brace
|
||||
/[$\w]/[test](chr), // 3: (key)word
|
||||
chr == "/" && // 4: regex
|
||||
/[\/{}[(\-+*=<>:;|\\.,?!&@~]/[test](chr), // eslint-disable-line no-useless-escape
|
||||
/[\])]/[test](chr), // 2: closing brace
|
||||
/[$\w]/[test](chr), // 3: (key)word
|
||||
chr == "/" && // 4: regex
|
||||
// previous token was an
|
||||
// opening brace or an
|
||||
// operator (otherwise
|
||||
@@ -355,13 +355,13 @@ export function highlight (el) {
|
||||
// workaround for xml
|
||||
// closing tags
|
||||
prev1 != "<",
|
||||
chr == "\"", // 5: string with "
|
||||
chr == "'", // 6: string with '
|
||||
chr == "\"", // 5: string with "
|
||||
chr == "'", // 6: string with '
|
||||
// 7: xml comment
|
||||
chr+next1+text[pos+1]+text[pos+2] == "<!--",
|
||||
chr+next1 == "/*", // 8: multiline comment
|
||||
chr+next1 == "//", // 9: single-line comment
|
||||
chr == "#" // 10: hash-style comment
|
||||
chr+next1 == "/*", // 8: multiline comment
|
||||
chr+next1 == "//", // 9: single-line comment
|
||||
chr == "#" // 10: hash-style comment
|
||||
][--tokenType]);
|
||||
}
|
||||
|
||||
@@ -451,17 +451,35 @@ export const propChecker = (props, nextProps, objectList=[], ignoreList=[]) => {
|
||||
}
|
||||
|
||||
export const validateNumber = ( val ) => {
|
||||
if ( !/^-?\d+(\.?\d+)?$/.test(val)) {
|
||||
if (!/^-?\d+(\.?\d+)?$/.test(val)) {
|
||||
return "Value must be a number"
|
||||
}
|
||||
}
|
||||
|
||||
export const validateInteger = ( val ) => {
|
||||
if ( !/^-?\d+$/.test(val)) {
|
||||
if (!/^-?\d+$/.test(val)) {
|
||||
return "Value must be an integer"
|
||||
}
|
||||
}
|
||||
|
||||
export const validateFile = ( val ) => {
|
||||
if ( val && !(val instanceof win.File) ) {
|
||||
return "Value must be a file"
|
||||
}
|
||||
}
|
||||
|
||||
export const validateBoolean = ( val ) => {
|
||||
if ( !(val === "true" || val === "false" || val === true || val === false) ) {
|
||||
return "Value must be a boolean"
|
||||
}
|
||||
}
|
||||
|
||||
export const validateString = ( val ) => {
|
||||
if ( val && typeof val !== "string" ) {
|
||||
return "Value must be a string"
|
||||
}
|
||||
}
|
||||
|
||||
// validation of parameters before execute
|
||||
export const validateParam = (param, isXml) => {
|
||||
let errors = []
|
||||
@@ -469,43 +487,69 @@ export const validateParam = (param, isXml) => {
|
||||
let required = param.get("required")
|
||||
let type = param.get("type")
|
||||
|
||||
let stringCheck = type === "string" && !value
|
||||
let arrayCheck = type === "array" && Array.isArray(value) && !value.length
|
||||
let listCheck = type === "array" && Im.List.isList(value) && !value.count()
|
||||
if ( required && (stringCheck || arrayCheck || listCheck) ) {
|
||||
errors.push("Required field is not provided")
|
||||
return errors
|
||||
}
|
||||
/*
|
||||
If the parameter is required OR the parameter has a value (meaning optional, but filled in)
|
||||
then we should do our validation routine.
|
||||
Only bother validating the parameter if the type was specified.
|
||||
*/
|
||||
if ( type && (required || value) ) {
|
||||
// These checks should evaluate to true if the parameter's value is valid
|
||||
let stringCheck = type === "string" && value && !validateString(value)
|
||||
let arrayCheck = type === "array" && Array.isArray(value) && value.length
|
||||
let listCheck = type === "array" && Im.List.isList(value) && value.count()
|
||||
let fileCheck = type === "file" && value instanceof win.File
|
||||
let booleanCheck = type === "boolean" && !validateBoolean(value)
|
||||
let numberCheck = type === "number" && !validateNumber(value) // validateNumber returns undefined if the value is a number
|
||||
let integerCheck = type === "integer" && !validateInteger(value) // validateInteger returns undefined if the value is an integer
|
||||
|
||||
if ( type === "number" ) {
|
||||
let err = validateNumber(value)
|
||||
if (!err) return errors
|
||||
errors.push(err)
|
||||
} else if ( type === "integer" ) {
|
||||
let err = validateInteger(value)
|
||||
if (!err) return errors
|
||||
errors.push(err)
|
||||
} else if ( type === "array" ) {
|
||||
let itemType
|
||||
if ( required && !(stringCheck || arrayCheck || listCheck || fileCheck || booleanCheck || numberCheck || integerCheck) ) {
|
||||
errors.push("Required field is not provided")
|
||||
return errors
|
||||
}
|
||||
|
||||
if ( !value.count() ) { return errors }
|
||||
if ( type === "string" ) {
|
||||
let err = validateString(value)
|
||||
if (!err) return errors
|
||||
errors.push(err)
|
||||
} else if ( type === "boolean" ) {
|
||||
let err = validateBoolean(value)
|
||||
if (!err) return errors
|
||||
errors.push(err)
|
||||
} else if ( type === "number" ) {
|
||||
let err = validateNumber(value)
|
||||
if (!err) return errors
|
||||
errors.push(err)
|
||||
} else if ( type === "integer" ) {
|
||||
let err = validateInteger(value)
|
||||
if (!err) return errors
|
||||
errors.push(err)
|
||||
} else if ( type === "array" ) {
|
||||
let itemType
|
||||
|
||||
itemType = param.getIn(["items", "type"])
|
||||
if ( !value.count() ) { return errors }
|
||||
|
||||
value.forEach((item, index) => {
|
||||
let err
|
||||
itemType = param.getIn(["items", "type"])
|
||||
|
||||
if (itemType === "number") {
|
||||
err = validateNumber(item)
|
||||
} else if (itemType === "integer") {
|
||||
err = validateInteger(item)
|
||||
}
|
||||
value.forEach((item, index) => {
|
||||
let err
|
||||
|
||||
if ( err ) {
|
||||
errors.push({ index: index, error: err})
|
||||
}
|
||||
})
|
||||
if (itemType === "number") {
|
||||
err = validateNumber(item)
|
||||
} else if (itemType === "integer") {
|
||||
err = validateInteger(item)
|
||||
} else if (itemType === "string") {
|
||||
err = validateString(item)
|
||||
}
|
||||
|
||||
if ( err ) {
|
||||
errors.push({ index: index, error: err})
|
||||
}
|
||||
})
|
||||
} else if ( type === "file" ) {
|
||||
let err = validateFile(value)
|
||||
if (!err) return errors
|
||||
errors.push(err)
|
||||
}
|
||||
}
|
||||
|
||||
return errors
|
||||
@@ -531,7 +575,7 @@ export const getSampleSchema = (schema, contentType="", config={}) => {
|
||||
return JSON.stringify(memoizedSampleFromSchema(schema, config), null, 2)
|
||||
}
|
||||
|
||||
export const parseSeach = () => {
|
||||
export const parseSearch = () => {
|
||||
let map = {}
|
||||
let search = window.location.search
|
||||
|
||||
@@ -563,6 +607,9 @@ export const sorters = {
|
||||
operationsSorter: {
|
||||
alpha: (a, b) => a.get("path").localeCompare(b.get("path")),
|
||||
method: (a, b) => a.get("method").localeCompare(b.get("method"))
|
||||
},
|
||||
tagsSorter: {
|
||||
alpha: (a, b) => a.localeCompare(b)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -578,18 +625,6 @@ export const buildFormData = (data) => {
|
||||
return formArr.join("&")
|
||||
}
|
||||
|
||||
export const filterConfigs = (configs, allowed) => {
|
||||
let i, filteredConfigs = {}
|
||||
|
||||
for (i in configs) {
|
||||
if (allowed.indexOf(i) !== -1) {
|
||||
filteredConfigs[i] = configs[i]
|
||||
}
|
||||
}
|
||||
|
||||
return filteredConfigs
|
||||
}
|
||||
|
||||
// Is this really required as a helper? Perhaps. TODO: expose the system of presets.apis in docs, so we know what is supported
|
||||
export const shallowEqualKeys = (a,b, keys) => {
|
||||
return !!find(keys, (key) => {
|
||||
|
||||
Reference in New Issue
Block a user