deep linking for models, operations and tags

This commit is contained in:
Josh Ponelat
2017-10-25 09:31:43 +02:00
parent f6b86d2d33
commit 3893cb4396
6 changed files with 67 additions and 30 deletions

View File

@@ -38,7 +38,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

@@ -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,9 +54,9 @@ 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,32 @@
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) => {
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")
const 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

@@ -31,13 +31,16 @@ 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={ defaultModelExpandDepth } expandDepth={ defaultModelExpandDepth }
schema={ model } schema={ model }
getComponent={ getComponent } getComponent={ getComponent }
specSelectors={ specSelectors }
getConfigs = {getConfigs} getConfigs = {getConfigs}
specSelectors={ specSelectors }/> 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,
@@ -18,8 +20,8 @@ export default class ObjectModel extends Component {
} }
render(){ render(){
let { schema, name, isRef, getComponent, getConfigs, depth, expandDepth, ...otherProps } = this.props let { schema, name, isRef, getComponent, getConfigs, depth, onToggle, expanded, ...otherProps } = this.props
let { specSelectors } = otherProps let { specSelectors,expandDepth } = otherProps
let { isOAS3 } = specSelectors let { isOAS3 } = specSelectors
if(!schema) { if(!schema) {
@@ -60,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 name={ name }/> !isRef ? null : <JumpToPathSection name={ name }/>

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)
})
} }
} }