Merge remote-tracking branch 'upstream/master' into ft/deeplinking-link-component
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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={ defaultModelExpandDepth }
|
expandDepth={ defaultModelExpandDepth }
|
||||||
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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
@@ -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)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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}
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user