Merge branch 'master' into master

This commit is contained in:
Helder Sepulveda
2017-12-12 17:48:40 -05:00
committed by GitHub
14 changed files with 115 additions and 56 deletions

View File

@@ -87,7 +87,8 @@
"worker-loader": "^0.7.1", "worker-loader": "^0.7.1",
"xml": "1.0.1", "xml": "1.0.1",
"xml-but-prettier": "^1.0.1", "xml-but-prettier": "^1.0.1",
"yaml-js": "0.2.0" "yaml-js": "0.2.0",
"zenscroll": "4.0.0"
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "7.1.1", "autoprefixer": "7.1.1",

View File

@@ -39,7 +39,7 @@ export default class ArrayModel extends Component {
*/ */
return <span className="model"> return <span className="model">
<ModelCollapse title={titleEl} collapsed={ depth > expandDepth } collapsedContent="[...]"> <ModelCollapse title={titleEl} expanded={ depth <= expandDepth } collapsedContent="[...]">
[ [
{ {
properties.size ? properties.entrySeq().map( ( [ key, v ] ) => <Property key={`${key}-${v}`} propKey={ key } propVal={ v } propStyle={ propStyle } />) : null properties.size ? properties.entrySeq().map( ( [ key, v ] ) => <Property key={`${key}-${v}`} propKey={ key } propVal={ v } propStyle={ propStyle } />) : null

View File

@@ -61,7 +61,18 @@ export default class BaseLayout extends React.Component {
const isSpecEmpty = !specSelectors.specStr() const isSpecEmpty = !specSelectors.specStr()
if(isSpecEmpty) { if(isSpecEmpty) {
return <h4>No spec provided.</h4> let loadingMessage
if(isLoading) {
loadingMessage = <div className="loading"></div>
} else {
loadingMessage = <h4>No API definition provided.</h4>
}
return <div className="swagger-ui">
<div className="loading-container">
{loadingMessage}
</div>
</div>
} }
return ( return (

View File

@@ -4,31 +4,47 @@ import PropTypes from "prop-types"
export default class ModelCollapse extends Component { export default class ModelCollapse extends Component {
static propTypes = { static propTypes = {
collapsedContent: PropTypes.any, collapsedContent: PropTypes.any,
collapsed: PropTypes.bool, expanded: PropTypes.bool,
children: PropTypes.any, children: PropTypes.any,
title: PropTypes.element title: PropTypes.element,
modelName: PropTypes.string,
onToggle: PropTypes.func.isRequired
} }
static defaultProps = { static defaultProps = {
collapsedContent: "{...}", collapsedContent: "{...}",
collapsed: true, expanded: false,
title: null title: null
} }
constructor(props, context) { constructor(props, context) {
super(props, context) super(props, context)
let { collapsed, collapsedContent } = this.props let { expanded, collapsedContent } = this.props
this.state = { this.state = {
collapsed: collapsed !== undefined ? collapsed : ModelCollapse.defaultProps.collapsed, expanded : expanded,
collapsedContent: collapsedContent || ModelCollapse.defaultProps.collapsedContent collapsedContent: collapsedContent || ModelCollapse.defaultProps.collapsedContent
} }
} }
componentWillReceiveProps(nextProps){
if(this.props.expanded!= nextProps.expanded){
this.setState({expanded: nextProps.expanded})
}
}
toggleCollapsed=()=>{ toggleCollapsed=()=>{
if(this.props.onToggle){
this.props.onToggle(this.props.modelName,!this.state.expanded)
}
this.setState({ this.setState({
collapsed: !this.state.collapsed expanded: !this.state.expanded
}) })
} }
@@ -38,10 +54,10 @@ export default class ModelCollapse extends Component {
<span> <span>
{ title && <span onClick={this.toggleCollapsed} style={{ "cursor": "pointer" }}>{title}</span> } { title && <span onClick={this.toggleCollapsed} style={{ "cursor": "pointer" }}>{title}</span> }
<span onClick={ this.toggleCollapsed } style={{ "cursor": "pointer" }}> <span onClick={ this.toggleCollapsed } style={{ "cursor": "pointer" }}>
<span className={ "model-toggle" + ( this.state.collapsed ? " collapsed" : "" ) }></span> <span className={ "model-toggle" + ( this.state.expanded ? "" : " collapsed" ) }></span>
</span> </span>
{ this.state.collapsed ? this.state.collapsedContent : this.props.children } { this.state.expanded ? this.props.children :this.state.collapsedContent }
</span> </span>
) )
} }
} }

View File

@@ -1,22 +1,41 @@
import React, { Component, } from "react" import React, { Component, } from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
//import layoutActions from "actions/layout"
export default class ModelWrapper extends Component {
export default class ModelComponent extends Component {
static propTypes = { static propTypes = {
schema: PropTypes.object.isRequired, schema: PropTypes.object.isRequired,
name: PropTypes.string, name: PropTypes.string,
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired, getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired, specSelectors: PropTypes.object.isRequired,
expandDepth: PropTypes.number expandDepth: PropTypes.number,
layoutActions: PropTypes.object,
layoutSelectors: PropTypes.object.isRequired
}
onToggle = (name,isShown) => {
// If this prop is present, we'll have deepLinking for it
if(this.props.layoutActions) {
this.props.layoutActions.show(["models", name],isShown)
}
} }
render(){ render(){
let { getComponent, getConfigs } = this.props let { getComponent, getConfigs } = this.props
const Model = getComponent("Model") 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(["models",this.props.name])
}
return <div className="model-box"> return <div className="model-box">
<Model { ...this.props } getConfigs={ getConfigs } depth={ 1 } expandDepth={ this.props.expandDepth || 0 }/> <Model { ...this.props } getConfigs={ getConfigs } expanded={expanded} depth={ 1 } onToggle={ this.onToggle } expandDepth={ this.props.expandDepth || 0 }/>
</div> </div>
} }
} }

View File

@@ -32,14 +32,17 @@ export default class Models extends Component {
<Collapse isOpened={showModels}> <Collapse isOpened={showModels}>
{ {
definitions.entrySeq().map( ( [ name, model ])=>{ definitions.entrySeq().map( ( [ name, model ])=>{
return <div className="model-container" key={ `models-section-${name}` }>
return <div id={ `model-${name}` } className="model-container" key={ `models-section-${name}` }>
<ModelWrapper name={ name } <ModelWrapper name={ name }
expandDepth={ defaultModelsExpandDepth } expandDepth={ defaultModelsExpandDepth }
schema={ model } schema={ model }
specPath={[...specPathBase, name]} specPath={[...specPathBase, name]}
getComponent={ getComponent } getComponent={ getComponent }
getConfigs={ getConfigs } specSelectors={ specSelectors }
specSelectors={ specSelectors }/> getConfigs = {getConfigs}
layoutSelectors = {layoutSelectors}
layoutActions = {layoutActions}/>
</div> </div>
}).toArray() }).toArray()
} }

View File

@@ -10,6 +10,8 @@ export default class ObjectModel extends Component {
schema: PropTypes.object.isRequired, schema: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired, getConfigs: PropTypes.func.isRequired,
expanded: PropTypes.bool,
onToggle: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired, specSelectors: PropTypes.object.isRequired,
name: PropTypes.string, name: PropTypes.string,
isRef: PropTypes.bool, isRef: PropTypes.bool,
@@ -19,9 +21,8 @@ export default class ObjectModel extends Component {
} }
render(){ render(){
let { schema, name, isRef, getComponent, getConfigs, depth, specPath, expandDepth, ...otherProps } = this.props let { schema, name, isRef, getComponent, getConfigs, depth, onToggle, expanded, specPath, ...otherProps } = this.props
let { specSelectors } = otherProps let { specSelectors,expandDepth } = otherProps
const { isOAS3 } = specSelectors const { isOAS3 } = specSelectors
if(!schema) { if(!schema) {
@@ -61,7 +62,13 @@ export default class ObjectModel extends Component {
</span> </span>
return <span className="model"> return <span className="model">
<ModelCollapse title={titleEl} collapsed={ depth > expandDepth } collapsedContent={ collapsedContent }> <ModelCollapse
modelName={name}
title={titleEl}
onToggle = {onToggle}
expanded={ expanded ? true : depth <= expandDepth }
collapsedContent={ collapsedContent }>
<span className="brace-open object">{ braceOpen }</span> <span className="brace-open object">{ braceOpen }</span>
{ {
!isRef ? null : <JumpToPathSection /> !isRef ? null : <JumpToPathSection />

View File

@@ -1,8 +1,7 @@
import scrollTo from "scroll-to-element" import zenscroll from "zenscroll"
import { escapeDeepLinkPath } from "core/utils" import { escapeDeepLinkPath } from "core/utils"
const SCROLL_OFFSET = -5 let hasHashBeenParsed = false //TODO this forces code to only run once which may prevent scrolling if page not refreshed
let hasHashBeenParsed = false
export const updateResolved = (ori, { layoutActions, getConfigs }) => (...args) => { export const updateResolved = (ori, { layoutActions, getConfigs }) => (...args) => {
@@ -12,7 +11,6 @@ export const updateResolved = (ori, { layoutActions, getConfigs }) => (...args)
if(!isDeepLinkingEnabled || isDeepLinkingEnabled === "false") { if(!isDeepLinkingEnabled || isDeepLinkingEnabled === "false") {
return return
} }
if(window.location.hash && !hasHashBeenParsed ) { if(window.location.hash && !hasHashBeenParsed ) {
let hash = window.location.hash.slice(1) // # is first character let hash = window.location.hash.slice(1) // # is first character
@@ -30,21 +28,23 @@ export const updateResolved = (ori, { layoutActions, getConfigs }) => (...args)
let [tag, operationId] = hash.split("/") let [tag, operationId] = hash.split("/")
let swaggerUI = document.querySelector(".swagger-ui")
let myScroller = zenscroll.createScroller(swaggerUI)
if(tag && operationId) { if(tag && operationId) {
// Pre-expand and scroll to the operation // Pre-expand and scroll to the operation
layoutActions.show(["operations-tag", tag], true) layoutActions.show(["operations-tag", tag], true)
layoutActions.show(["operations", tag, operationId], true) layoutActions.show(["operations", tag, operationId], true)
scrollTo(`#operations-${escapeDeepLinkPath(tag)}-${escapeDeepLinkPath(operationId)}`, { let target = document.getElementById(`operations-${escapeDeepLinkPath(tag)}-${escapeDeepLinkPath(operationId)}`)
offset: SCROLL_OFFSET myScroller.to(target)
})
} else if(tag) { } else if(tag) {
// Pre-expand and scroll to the tag // Pre-expand and scroll to the tag
layoutActions.show(["operations-tag", tag], true) layoutActions.show(["operations-tag", tag], true)
scrollTo(`#operations-tag-${escapeDeepLinkPath(tag)}`, { let target = document.getElementById(`operations-tag-${escapeDeepLinkPath(tag)}`)
offset: SCROLL_OFFSET myScroller.to(target)
})
} }
} }

View File

@@ -37,17 +37,3 @@ export function changeMode(thing, mode="") {
payload: {thing, mode} payload: {thing, mode}
} }
} }
// export function onlyShow(thing, shown=true) {
// thing = normalizeArray(thing)
// if(thing.length < 2)
// throw new Error("layoutActions.onlyShow only works, when `thing` is an array with length > 1")
// return {
// type: ONLY_SHOW,
// payload: {thing, shown}
// }
// }

View File

@@ -1,3 +1,4 @@
import { fromJS } from "immutable"
import { import {
UPDATE_LAYOUT, UPDATE_LAYOUT,
UPDATE_FILTER, UPDATE_FILTER,
@@ -12,9 +13,14 @@ export default {
[UPDATE_FILTER]: (state, action) => state.set("filter", action.payload), [UPDATE_FILTER]: (state, action) => state.set("filter", action.payload),
[SHOW]: (state, action) => { [SHOW]: (state, action) => {
let thing = action.payload.thing const isShown = action.payload.shown
let shown = action.payload.shown // This is one way to serialize an array, another (preferred) is to convert to json-pointer
return state.setIn(["shown"].concat(thing), shown) // TODO: use json-pointer serilization instead of fromJS(...), for performance
const thingToShow = fromJS(action.payload.thing)
// This is a map of paths to bools
// eg: [one, two] => true
// eg: [one] => false
return state.update("shown", fromJS({}), a => a.set(thingToShow, isShown))
}, },
[UPDATE_MODE]: (state, action) => { [UPDATE_MODE]: (state, action) => {
@@ -24,4 +30,3 @@ export default {
} }
} }

View File

@@ -1,5 +1,6 @@
import { createSelector } from "reselect" import { createSelector } from "reselect"
import { normalizeArray } from "core/utils" import { normalizeArray } from "core/utils"
import { fromJS } from "immutable"
const state = state => state const state = state => state
@@ -9,7 +10,7 @@ export const currentFilter = state => state.get("filter")
export const isShown = (state, thing, def) => { export const isShown = (state, thing, def) => {
thing = normalizeArray(thing) thing = normalizeArray(thing)
return Boolean(state.getIn(["shown", ...thing], def)) return state.get("shown", fromJS({})).get(fromJS(thing), def)
} }
export const whatMode = (state, thing, def="") => { export const whatMode = (state, thing, def="") => {
@@ -21,4 +22,3 @@ export const showSummary = createSelector(
state, state,
state => !isShown(state, "editor") state => !isShown(state, "editor")
) )

View File

@@ -1,3 +1,5 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
@@ -32,17 +34,21 @@ export default class StandaloneLayout extends React.Component {
{ Topbar ? <Topbar /> : null } { Topbar ? <Topbar /> : null }
{ loadingStatus === "loading" && { loadingStatus === "loading" &&
<div className="info"> <div className="info">
<h4 className="title">Loading...</h4> <div className="loading-container">
<div className="loading"></div>
</div>
</div> </div>
} }
{ loadingStatus === "failed" && { loadingStatus === "failed" &&
<div className="info"> <div className="info">
<h4 className="title">Failed to load spec.</h4> <div className="loading-container">
<h4 className="title">Failed to load API definition.</h4>
</div>
</div> </div>
} }
{ loadingStatus === "failedConfig" && { loadingStatus === "failedConfig" &&
<div className="info" style={{ maxWidth: "880px", marginLeft: "auto", marginRight: "auto", textAlign: "center" }}> <div className="info" style={{ maxWidth: "880px", marginLeft: "auto", marginRight: "auto", textAlign: "center" }}>
<h4 className="title">Failed to load config.</h4> <h4 className="title">Failed to load remote configuration.</h4>
</div> </div>
} }
{ !loadingStatus || loadingStatus === "success" && <BaseLayout /> } { !loadingStatus || loadingStatus === "success" && <BaseLayout /> }

View File

@@ -653,6 +653,11 @@
.loading-container .loading-container
{ {
padding: 40px 0 60px; padding: 40px 0 60px;
margin-top: 1em;
min-height: 1px;
display: flex;
justify-content: center;
.loading .loading
{ {
position: relative; position: relative;

View File

@@ -43,7 +43,7 @@ describe("<Models/>", function(){
// Then should render tabs // Then should render tabs
expect(wrapper.find("ModelCollapse").length).toEqual(1) expect(wrapper.find("ModelCollapse").length).toEqual(1)
expect(wrapper.find("ModelComponent").length).toBeGreaterThan(0) expect(wrapper.find("ModelWrapper").length).toBeGreaterThan(0)
wrapper.find("ModelComponent").forEach((modelWrapper) => { wrapper.find("ModelComponent").forEach((modelWrapper) => {
expect(modelWrapper.props().expandDepth).toBe(0) expect(modelWrapper.props().expandDepth).toBe(0)
}) })