feat(json-schema-2020-12): add support for defaultExpandedLevels opt

optimizeExpansion config option was introduced as well
to support rendering extensive or very complex schemas.

Refs #8513
This commit is contained in:
Vladimir Gorej
2023-04-25 14:27:31 +02:00
committed by Vladimír Gorej
parent fa829e3368
commit 7c15f509b7
26 changed files with 343 additions and 174 deletions

View File

@@ -9,6 +9,7 @@ import * as propTypes from "../../prop-types"
import { import {
useComponent, useComponent,
useLevel, useLevel,
useConfig,
useFn, useFn,
useIsEmbedded, useIsEmbedded,
useIsExpandedDeeply, useIsExpandedDeeply,
@@ -23,6 +24,7 @@ import {
const JSONSchema = forwardRef(({ schema, name }, ref) => { const JSONSchema = forwardRef(({ schema, name }, ref) => {
const fn = useFn() const fn = useFn()
const config = useConfig()
const isExpandedDeeply = useIsExpandedDeeply() const isExpandedDeeply = useIsExpandedDeeply()
const [expanded, setExpanded] = useState(isExpandedDeeply) const [expanded, setExpanded] = useState(isExpandedDeeply)
const [expandedDeeply, setExpandedDeeply] = useState(false) const [expandedDeeply, setExpandedDeeply] = useState(false)
@@ -120,43 +122,49 @@ const JSONSchema = forwardRef(({ schema, name }, ref) => {
<KeywordType schema={schema} isCircular={isCircular} /> <KeywordType schema={schema} isCircular={isCircular} />
<KeywordFormat schema={schema} /> <KeywordFormat schema={schema} />
</div> </div>
{expanded && ( <div
<div className="json-schema-2020-12-body"> className={classNames("json-schema-2020-12-body", {
<KeywordDescription schema={schema} /> "json-schema-2020-12-body--collapsed": !expanded,
{!isCircular && isExpandable && ( })}
<> >
<KeywordProperties schema={schema} /> {!expanded && config.optimizeExpansion ? null : (
<KeywordPatternProperties schema={schema} /> <>
<KeywordAdditionalProperties schema={schema} /> <KeywordDescription schema={schema} />
<KeywordUnevaluatedProperties schema={schema} /> {!isCircular && isExpandable && (
<KeywordPropertyNames schema={schema} /> <>
<KeywordAllOf schema={schema} /> <KeywordProperties schema={schema} />
<KeywordAnyOf schema={schema} /> <KeywordPatternProperties schema={schema} />
<KeywordOneOf schema={schema} /> <KeywordAdditionalProperties schema={schema} />
<KeywordNot schema={schema} /> <KeywordUnevaluatedProperties schema={schema} />
<KeywordIf schema={schema} /> <KeywordPropertyNames schema={schema} />
<KeywordThen schema={schema} /> <KeywordAllOf schema={schema} />
<KeywordElse schema={schema} /> <KeywordAnyOf schema={schema} />
<KeywordDependentSchemas schema={schema} /> <KeywordOneOf schema={schema} />
<KeywordPrefixItems schema={schema} /> <KeywordNot schema={schema} />
<KeywordItems schema={schema} /> <KeywordIf schema={schema} />
<KeywordUnevaluatedItems schema={schema} /> <KeywordThen schema={schema} />
<KeywordContains schema={schema} /> <KeywordElse schema={schema} />
</> <KeywordDependentSchemas schema={schema} />
)} <KeywordPrefixItems schema={schema} />
<Keyword$schema schema={schema} /> <KeywordItems schema={schema} />
<Keyword$vocabulary schema={schema} /> <KeywordUnevaluatedItems schema={schema} />
<Keyword$id schema={schema} /> <KeywordContains schema={schema} />
<Keyword$anchor schema={schema} /> </>
<Keyword$dynamicAnchor schema={schema} /> )}
<Keyword$ref schema={schema} /> <Keyword$schema schema={schema} />
{!isCircular && isExpandable && ( <Keyword$vocabulary schema={schema} />
<Keyword$defs schema={schema} /> <Keyword$id schema={schema} />
)} <Keyword$anchor schema={schema} />
<Keyword$dynamicRef schema={schema} /> <Keyword$dynamicAnchor schema={schema} />
<Keyword$comment schema={schema} /> <Keyword$ref schema={schema} />
</div> {!isCircular && isExpandable && (
)} <Keyword$defs schema={schema} />
)}
<Keyword$dynamicRef schema={schema} />
<Keyword$comment schema={schema} />
</>
)}
</div>
</article> </article>
</JSONSchemaCyclesContext.Provider> </JSONSchemaCyclesContext.Provider>
</JSONSchemaDeepExpansionContext.Provider> </JSONSchemaDeepExpansionContext.Provider>

View File

@@ -20,6 +20,10 @@
&-body { &-body {
@include expansion-border; @include expansion-border;
margin: 2px 0; margin: 2px 0;
&--collapsed {
display: none;
}
} }
&__limit { &__limit {

View File

@@ -2,18 +2,15 @@
* @prettier * @prettier
*/ */
import React, { useCallback, useState } from "react" import React, { useCallback, useState } from "react"
import classNames from "classnames"
import { schema } from "../../prop-types" import { schema } from "../../prop-types"
import { useComponent, useIsExpandedDeeply } from "../../hooks" import { useConfig, useComponent, useIsExpandedDeeply } from "../../hooks"
import { JSONSchemaDeepExpansionContext } from "../../context" import { JSONSchemaDeepExpansionContext } from "../../context"
const $defs = ({ schema }) => { const $defs = ({ schema }) => {
const $defs = schema?.$defs || {} const $defs = schema?.$defs || {}
const config = useConfig()
if (Object.keys($defs).length === 0) {
return null
}
const isExpandedDeeply = useIsExpandedDeeply() const isExpandedDeeply = useIsExpandedDeeply()
const [expanded, setExpanded] = useState(isExpandedDeeply) const [expanded, setExpanded] = useState(isExpandedDeeply)
const [expandedDeeply, setExpandedDeeply] = useState(false) const [expandedDeeply, setExpandedDeeply] = useState(false)
@@ -32,6 +29,13 @@ const $defs = ({ schema }) => {
setExpandedDeeply(expandedDeepNew) setExpandedDeeply(expandedDeepNew)
}, []) }, [])
/**
* Rendering.
*/
if (Object.keys($defs).length === 0) {
return null
}
return ( return (
<JSONSchemaDeepExpansionContext.Provider value={expandedDeeply}> <JSONSchemaDeepExpansionContext.Provider value={expandedDeeply}>
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--$defs"> <div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--$defs">
@@ -42,15 +46,21 @@ const $defs = ({ schema }) => {
</Accordion> </Accordion>
<ExpandDeepButton expanded={expanded} onClick={handleExpansionDeep} /> <ExpandDeepButton expanded={expanded} onClick={handleExpansionDeep} />
<span className="json-schema-2020-12__type">object</span> <span className="json-schema-2020-12__type">object</span>
{expanded && ( <ul
<ul> className={classNames("json-schema-2020-12-keyword__children", {
{Object.entries($defs).map(([schemaName, schema]) => ( "json-schema-2020-12-keyword__children--collapsed": !expanded,
<li key={schemaName} className="json-schema-2020-12-property"> })}
<JSONSchema name={schemaName} schema={schema} /> >
</li> {!expanded && config.optimizeExpansion ? null : (
))} <>
</ul> {Object.entries($defs).map(([schemaName, schema]) => (
)} <li key={schemaName} className="json-schema-2020-12-property">
<JSONSchema name={schemaName} schema={schema} />
</li>
))}
</>
)}
</ul>
</div> </div>
</JSONSchemaDeepExpansionContext.Provider> </JSONSchemaDeepExpansionContext.Provider>
) )

View File

@@ -8,9 +8,6 @@ import { schema } from "../../../prop-types"
import { useComponent, useIsExpandedDeeply } from "../../../hooks" import { useComponent, useIsExpandedDeeply } from "../../../hooks"
const $vocabulary = ({ schema }) => { const $vocabulary = ({ schema }) => {
if (!schema?.$vocabulary) return null
if (typeof schema.$vocabulary !== "object") return null
const isExpandedDeeply = useIsExpandedDeeply() const isExpandedDeeply = useIsExpandedDeeply()
const [expanded, setExpanded] = useState(isExpandedDeeply) const [expanded, setExpanded] = useState(isExpandedDeeply)
const Accordion = useComponent("Accordion") const Accordion = useComponent("Accordion")
@@ -19,6 +16,12 @@ const $vocabulary = ({ schema }) => {
setExpanded((prev) => !prev) setExpanded((prev) => !prev)
}, []) }, [])
/**
* Rendering.
*/
if (!schema?.$vocabulary) return null
if (typeof schema.$vocabulary !== "object") return null
return ( return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--$vocabulary"> <div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--$vocabulary">
<Accordion expanded={expanded} onChange={handleExpansion}> <Accordion expanded={expanded} onChange={handleExpansion}>

View File

@@ -8,11 +8,14 @@ import { useFn, useComponent } from "../../hooks"
const AdditionalProperties = ({ schema }) => { const AdditionalProperties = ({ schema }) => {
const fn = useFn() const fn = useFn()
const { additionalProperties } = schema
const JSONSchema = useComponent("JSONSchema")
if (!fn.hasKeyword(schema, "additionalProperties")) return null if (!fn.hasKeyword(schema, "additionalProperties")) return null
const { additionalProperties } = schema /**
const JSONSchema = useComponent("JSONSchema") * Rendering.
*/
const name = ( const name = (
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"> <span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary">
Additional properties Additional properties

View File

@@ -2,19 +2,21 @@
* @prettier * @prettier
*/ */
import React, { useCallback, useState } from "react" import React, { useCallback, useState } from "react"
import classNames from "classnames"
import { schema } from "../../prop-types" import { schema } from "../../prop-types"
import { useFn, useComponent, useIsExpandedDeeply } from "../../hooks" import {
useFn,
useConfig,
useComponent,
useIsExpandedDeeply,
} from "../../hooks"
import { JSONSchemaDeepExpansionContext } from "../../context" import { JSONSchemaDeepExpansionContext } from "../../context"
const AllOf = ({ schema }) => { const AllOf = ({ schema }) => {
const allOf = schema?.allOf || [] const allOf = schema?.allOf || []
if (!Array.isArray(allOf) || allOf.length === 0) {
return null
}
const fn = useFn() const fn = useFn()
const config = useConfig()
const isExpandedDeeply = useIsExpandedDeeply() const isExpandedDeeply = useIsExpandedDeeply()
const [expanded, setExpanded] = useState(isExpandedDeeply) const [expanded, setExpanded] = useState(isExpandedDeeply)
const [expandedDeeply, setExpandedDeeply] = useState(false) const [expandedDeeply, setExpandedDeeply] = useState(false)
@@ -34,6 +36,13 @@ const AllOf = ({ schema }) => {
setExpandedDeeply(expandedDeepNew) setExpandedDeeply(expandedDeepNew)
}, []) }, [])
/**
* Rendering.
*/
if (!Array.isArray(allOf) || allOf.length === 0) {
return null
}
return ( return (
<JSONSchemaDeepExpansionContext.Provider value={expandedDeeply}> <JSONSchemaDeepExpansionContext.Provider value={expandedDeeply}>
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--allOf"> <div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--allOf">
@@ -44,18 +53,24 @@ const AllOf = ({ schema }) => {
</Accordion> </Accordion>
<ExpandDeepButton expanded={expanded} onClick={handleExpansionDeep} /> <ExpandDeepButton expanded={expanded} onClick={handleExpansionDeep} />
<KeywordType schema={{ allOf }} /> <KeywordType schema={{ allOf }} />
{expanded && ( <ul
<ul> className={classNames("json-schema-2020-12-keyword__children", {
{allOf.map((schema, index) => ( "json-schema-2020-12-keyword__children--collapsed": !expanded,
<li key={`#${index}`} className="json-schema-2020-12-property"> })}
<JSONSchema >
name={`#${index} ${fn.getTitle(schema)}`} {!expanded && config.optimizeExpansion ? null : (
schema={schema} <>
/> {allOf.map((schema, index) => (
</li> <li key={`#${index}`} className="json-schema-2020-12-property">
))} <JSONSchema
</ul> name={`#${index} ${fn.getTitle(schema)}`}
)} schema={schema}
/>
</li>
))}
</>
)}
</ul>
</div> </div>
</JSONSchemaDeepExpansionContext.Provider> </JSONSchemaDeepExpansionContext.Provider>
) )

View File

@@ -2,19 +2,21 @@
* @prettier * @prettier
*/ */
import React, { useCallback, useState } from "react" import React, { useCallback, useState } from "react"
import classNames from "classnames"
import { schema } from "../../prop-types" import { schema } from "../../prop-types"
import { useFn, useComponent, useIsExpandedDeeply } from "../../hooks" import {
useFn,
useConfig,
useComponent,
useIsExpandedDeeply,
} from "../../hooks"
import { JSONSchemaDeepExpansionContext } from "../../context" import { JSONSchemaDeepExpansionContext } from "../../context"
const AnyOf = ({ schema }) => { const AnyOf = ({ schema }) => {
const anyOf = schema?.anyOf || [] const anyOf = schema?.anyOf || []
if (!Array.isArray(anyOf) || anyOf.length === 0) {
return null
}
const fn = useFn() const fn = useFn()
const config = useConfig()
const isExpandedDeeply = useIsExpandedDeeply() const isExpandedDeeply = useIsExpandedDeeply()
const [expanded, setExpanded] = useState(isExpandedDeeply) const [expanded, setExpanded] = useState(isExpandedDeeply)
const [expandedDeeply, setExpandedDeeply] = useState(false) const [expandedDeeply, setExpandedDeeply] = useState(false)
@@ -34,6 +36,13 @@ const AnyOf = ({ schema }) => {
setExpandedDeeply(expandedDeepNew) setExpandedDeeply(expandedDeepNew)
}, []) }, [])
/**
* Rendering.
*/
if (!Array.isArray(anyOf) || anyOf.length === 0) {
return null
}
return ( return (
<JSONSchemaDeepExpansionContext.Provider value={expandedDeeply}> <JSONSchemaDeepExpansionContext.Provider value={expandedDeeply}>
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--anyOf"> <div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--anyOf">
@@ -44,18 +53,24 @@ const AnyOf = ({ schema }) => {
</Accordion> </Accordion>
<ExpandDeepButton expanded={expanded} onClick={handleExpansionDeep} /> <ExpandDeepButton expanded={expanded} onClick={handleExpansionDeep} />
<KeywordType schema={{ anyOf }} /> <KeywordType schema={{ anyOf }} />
{expanded && ( <ul
<ul> className={classNames("json-schema-2020-12-keyword__children", {
{anyOf.map((schema, index) => ( "json-schema-2020-12-keyword__children--collapsed": !expanded,
<li key={`#${index}`} className="json-schema-2020-12-property"> })}
<JSONSchema >
name={`#${index} ${fn.getTitle(schema)}`} {!expanded && config.optimizeExpansion ? null : (
schema={schema} <>
/> {anyOf.map((schema, index) => (
</li> <li key={`#${index}`} className="json-schema-2020-12-property">
))} <JSONSchema
</ul> name={`#${index} ${fn.getTitle(schema)}`}
)} schema={schema}
/>
</li>
))}
</>
)}
</ul>
</div> </div>
</JSONSchemaDeepExpansionContext.Provider> </JSONSchemaDeepExpansionContext.Provider>
) )

View File

@@ -8,9 +8,6 @@ import { useFn, useComponent } from "../../hooks"
const Contains = ({ schema }) => { const Contains = ({ schema }) => {
const fn = useFn() const fn = useFn()
if (!fn.hasKeyword(schema, "contains")) return null
const JSONSchema = useComponent("JSONSchema") const JSONSchema = useComponent("JSONSchema")
const name = ( const name = (
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"> <span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary">
@@ -18,6 +15,11 @@ const Contains = ({ schema }) => {
</span> </span>
) )
/**
* Rendering.
*/
if (!fn.hasKeyword(schema, "contains")) return null
return ( return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--contains"> <div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--contains">
<JSONSchema name={name} schema={schema.contains} /> <JSONSchema name={name} schema={schema.contains} />

View File

@@ -2,17 +2,15 @@
* @prettier * @prettier
*/ */
import React, { useCallback, useState } from "react" import React, { useCallback, useState } from "react"
import classNames from "classnames"
import { schema } from "../../prop-types" import { schema } from "../../prop-types"
import { useComponent, useIsExpandedDeeply } from "../../hooks" import { useConfig, useComponent, useIsExpandedDeeply } from "../../hooks"
import { JSONSchemaDeepExpansionContext } from "../../context" import { JSONSchemaDeepExpansionContext } from "../../context"
const DependentSchemas = ({ schema }) => { const DependentSchemas = ({ schema }) => {
const dependentSchemas = schema?.dependentSchemas || [] const dependentSchemas = schema?.dependentSchemas || []
const config = useConfig()
if (typeof dependentSchemas !== "object") return null
if (Object.keys(dependentSchemas).length === 0) return null
const isExpandedDeeply = useIsExpandedDeeply() const isExpandedDeeply = useIsExpandedDeeply()
const [expanded, setExpanded] = useState(isExpandedDeeply) const [expanded, setExpanded] = useState(isExpandedDeeply)
const [expandedDeeply, setExpandedDeeply] = useState(false) const [expandedDeeply, setExpandedDeeply] = useState(false)
@@ -31,6 +29,12 @@ const DependentSchemas = ({ schema }) => {
setExpandedDeeply(expandedDeepNew) setExpandedDeeply(expandedDeepNew)
}, []) }, [])
/**
* Rendering.
*/
if (typeof dependentSchemas !== "object") return null
if (Object.keys(dependentSchemas).length === 0) return null
return ( return (
<JSONSchemaDeepExpansionContext.Provider value={expandedDeeply}> <JSONSchemaDeepExpansionContext.Provider value={expandedDeeply}>
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--dependentSchemas"> <div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--dependentSchemas">
@@ -41,15 +45,21 @@ const DependentSchemas = ({ schema }) => {
</Accordion> </Accordion>
<ExpandDeepButton expanded={expanded} onClick={handleExpansionDeep} /> <ExpandDeepButton expanded={expanded} onClick={handleExpansionDeep} />
<span className="json-schema-2020-12__type">object</span> <span className="json-schema-2020-12__type">object</span>
{expanded && ( <ul
<ul> className={classNames("json-schema-2020-12-keyword__children", {
{Object.entries(dependentSchemas).map(([schemaName, schema]) => ( "json-schema-2020-12-keyword__children--collapsed": !expanded,
<li key={schemaName} className="json-schema-2020-12-property"> })}
<JSONSchema name={schemaName} schema={schema} /> >
</li> {!expanded && config.optimizeExpansion ? null : (
))} <>
</ul> {Object.entries(dependentSchemas).map(([schemaName, schema]) => (
)} <li key={schemaName} className="json-schema-2020-12-property">
<JSONSchema name={schemaName} schema={schema} />
</li>
))}
</>
)}
</ul>
</div> </div>
</JSONSchemaDeepExpansionContext.Provider> </JSONSchemaDeepExpansionContext.Provider>
) )

View File

@@ -8,10 +8,13 @@ import { useFn, useComponent } from "../../hooks"
const Else = ({ schema }) => { const Else = ({ schema }) => {
const fn = useFn() const fn = useFn()
const JSONSchema = useComponent("JSONSchema")
/**
* Rendering.
*/
if (!fn.hasKeyword(schema, "else")) return null if (!fn.hasKeyword(schema, "else")) return null
const JSONSchema = useComponent("JSONSchema")
const name = ( const name = (
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"> <span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary">
Else Else

View File

@@ -8,10 +8,13 @@ import { useFn, useComponent } from "../../hooks"
const If = ({ schema }) => { const If = ({ schema }) => {
const fn = useFn() const fn = useFn()
const JSONSchema = useComponent("JSONSchema")
/**
* Rendering.
*/
if (!fn.hasKeyword(schema, "if")) return null if (!fn.hasKeyword(schema, "if")) return null
const JSONSchema = useComponent("JSONSchema")
const name = ( const name = (
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"> <span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary">
If If

View File

@@ -7,9 +7,13 @@ import { schema } from "../../prop-types"
import { useComponent } from "../../hooks" import { useComponent } from "../../hooks"
const Items = ({ schema }) => { const Items = ({ schema }) => {
const JSONSchema = useComponent("JSONSchema")
/**
* Rendering.
*/
if (!schema?.items) return null if (!schema?.items) return null
const JSONSchema = useComponent("JSONSchema")
const name = ( const name = (
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"> <span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary">
Items Items

View File

@@ -8,10 +8,13 @@ import { useFn, useComponent } from "../../hooks"
const Not = ({ schema }) => { const Not = ({ schema }) => {
const fn = useFn() const fn = useFn()
const JSONSchema = useComponent("JSONSchema")
/**
* Rendering.
*/
if (!fn.hasKeyword(schema, "not")) return null if (!fn.hasKeyword(schema, "not")) return null
const JSONSchema = useComponent("JSONSchema")
const name = ( const name = (
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"> <span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary">
Not Not

View File

@@ -2,19 +2,21 @@
* @prettier * @prettier
*/ */
import React, { useCallback, useState } from "react" import React, { useCallback, useState } from "react"
import classNames from "classnames"
import { schema } from "../../prop-types" import { schema } from "../../prop-types"
import { useFn, useComponent, useIsExpandedDeeply } from "../../hooks" import {
useFn,
useConfig,
useComponent,
useIsExpandedDeeply,
} from "../../hooks"
import { JSONSchemaDeepExpansionContext } from "../../context" import { JSONSchemaDeepExpansionContext } from "../../context"
const OneOf = ({ schema }) => { const OneOf = ({ schema }) => {
const oneOf = schema?.oneOf || [] const oneOf = schema?.oneOf || []
if (!Array.isArray(oneOf) || oneOf.length === 0) {
return null
}
const fn = useFn() const fn = useFn()
const config = useConfig()
const isExpandedDeeply = useIsExpandedDeeply() const isExpandedDeeply = useIsExpandedDeeply()
const [expanded, setExpanded] = useState(isExpandedDeeply) const [expanded, setExpanded] = useState(isExpandedDeeply)
const [expandedDeeply, setExpandedDeeply] = useState(false) const [expandedDeeply, setExpandedDeeply] = useState(false)
@@ -34,6 +36,13 @@ const OneOf = ({ schema }) => {
setExpandedDeeply(expandedDeepNew) setExpandedDeeply(expandedDeepNew)
}, []) }, [])
/**
* Rendering.
*/
if (!Array.isArray(oneOf) || oneOf.length === 0) {
return null
}
return ( return (
<JSONSchemaDeepExpansionContext.Provider value={expandedDeeply}> <JSONSchemaDeepExpansionContext.Provider value={expandedDeeply}>
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--oneOf"> <div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--oneOf">
@@ -44,18 +53,24 @@ const OneOf = ({ schema }) => {
</Accordion> </Accordion>
<ExpandDeepButton expanded={expanded} onClick={handleExpansionDeep} /> <ExpandDeepButton expanded={expanded} onClick={handleExpansionDeep} />
<KeywordType schema={{ oneOf }} /> <KeywordType schema={{ oneOf }} />
{expanded && ( <ul
<ul> className={classNames("json-schema-2020-12-keyword__children", {
{oneOf.map((schema, index) => ( "json-schema-2020-12-keyword__children--collapsed": !expanded,
<li key={`#${index}`} className="json-schema-2020-12-property"> })}
<JSONSchema >
name={`#${index} ${fn.getTitle(schema)}`} {!expanded && config.optimizeExpansion ? null : (
schema={schema} <>
/> {oneOf.map((schema, index) => (
</li> <li key={`#${index}`} className="json-schema-2020-12-property">
))} <JSONSchema
</ul> name={`#${index} ${fn.getTitle(schema)}`}
)} schema={schema}
/>
</li>
))}
</>
)}
</ul>
</div> </div>
</JSONSchemaDeepExpansionContext.Provider> </JSONSchemaDeepExpansionContext.Provider>
) )

View File

@@ -8,13 +8,15 @@ import { useComponent } from "../../../hooks"
const PatternProperties = ({ schema }) => { const PatternProperties = ({ schema }) => {
const patternProperties = schema?.patternProperties || {} const patternProperties = schema?.patternProperties || {}
const JSONSchema = useComponent("JSONSchema")
/**
* Rendering.
*/
if (Object.keys(patternProperties).length === 0) { if (Object.keys(patternProperties).length === 0) {
return null return null
} }
const JSONSchema = useComponent("JSONSchema")
return ( return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--patternProperties"> <div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--patternProperties">
<ul> <ul>

View File

@@ -2,19 +2,21 @@
* @prettier * @prettier
*/ */
import React, { useCallback, useState } from "react" import React, { useCallback, useState } from "react"
import classNames from "classnames"
import { schema } from "../../prop-types" import { schema } from "../../prop-types"
import { useFn, useComponent, useIsExpandedDeeply } from "../../hooks" import {
useFn,
useConfig,
useComponent,
useIsExpandedDeeply,
} from "../../hooks"
import { JSONSchemaDeepExpansionContext } from "../../context" import { JSONSchemaDeepExpansionContext } from "../../context"
const PrefixItems = ({ schema }) => { const PrefixItems = ({ schema }) => {
const prefixItems = schema?.prefixItems || [] const prefixItems = schema?.prefixItems || []
if (!Array.isArray(prefixItems) || prefixItems.length === 0) {
return null
}
const fn = useFn() const fn = useFn()
const config = useConfig()
const isExpandedDeeply = useIsExpandedDeeply() const isExpandedDeeply = useIsExpandedDeeply()
const [expanded, setExpanded] = useState(isExpandedDeeply) const [expanded, setExpanded] = useState(isExpandedDeeply)
const [expandedDeeply, setExpandedDeeply] = useState(false) const [expandedDeeply, setExpandedDeeply] = useState(false)
@@ -34,6 +36,13 @@ const PrefixItems = ({ schema }) => {
setExpandedDeeply(expandedDeepNew) setExpandedDeeply(expandedDeepNew)
}, []) }, [])
/**
* Rendering.
*/
if (!Array.isArray(prefixItems) || prefixItems.length === 0) {
return null
}
return ( return (
<JSONSchemaDeepExpansionContext.Provider value={expandedDeeply}> <JSONSchemaDeepExpansionContext.Provider value={expandedDeeply}>
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--prefixItems"> <div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--prefixItems">
@@ -44,18 +53,24 @@ const PrefixItems = ({ schema }) => {
</Accordion> </Accordion>
<ExpandDeepButton expanded={expanded} onClick={handleExpansionDeep} /> <ExpandDeepButton expanded={expanded} onClick={handleExpansionDeep} />
<KeywordType schema={{ prefixItems }} /> <KeywordType schema={{ prefixItems }} />
{expanded && ( <ul
<ul> className={classNames("json-schema-2020-12-keyword__children", {
{prefixItems.map((schema, index) => ( "json-schema-2020-12-keyword__children--collapsed": !expanded,
<li key={`#${index}`} className="json-schema-2020-12-property"> })}
<JSONSchema >
name={`#${index} ${fn.getTitle(schema)}`} {!expanded && config.optimizeExpansion ? null : (
schema={schema} <>
/> {prefixItems.map((schema, index) => (
</li> <li key={`#${index}`} className="json-schema-2020-12-property">
))} <JSONSchema
</ul> name={`#${index} ${fn.getTitle(schema)}`}
)} schema={schema}
/>
</li>
))}
</>
)}
</ul>
</div> </div>
</JSONSchemaDeepExpansionContext.Provider> </JSONSchemaDeepExpansionContext.Provider>
) )

View File

@@ -8,13 +8,15 @@ import { useComponent } from "../../../hooks"
const Properties = ({ schema }) => { const Properties = ({ schema }) => {
const properties = schema?.properties || {} const properties = schema?.properties || {}
const JSONSchema = useComponent("JSONSchema")
/**
* Rendering.
*/
if (Object.keys(properties).length === 0) { if (Object.keys(properties).length === 0) {
return null return null
} }
const JSONSchema = useComponent("JSONSchema")
return ( return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--properties"> <div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--properties">
<ul> <ul>

View File

@@ -8,9 +8,6 @@ import { useFn, useComponent } from "../../hooks"
const PropertyNames = ({ schema }) => { const PropertyNames = ({ schema }) => {
const fn = useFn() const fn = useFn()
if (!fn.hasKeyword(schema, "propertyNames")) return null
const { propertyNames } = schema const { propertyNames } = schema
const JSONSchema = useComponent("JSONSchema") const JSONSchema = useComponent("JSONSchema")
const name = ( const name = (
@@ -19,6 +16,11 @@ const PropertyNames = ({ schema }) => {
</span> </span>
) )
/**
* Rendering.
*/
if (!fn.hasKeyword(schema, "propertyNames")) return null
return ( return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--propertyNames"> <div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--propertyNames">
<JSONSchema name={name} schema={propertyNames} /> <JSONSchema name={name} schema={propertyNames} />

View File

@@ -8,10 +8,13 @@ import { useFn, useComponent } from "../../hooks"
const Then = ({ schema }) => { const Then = ({ schema }) => {
const fn = useFn() const fn = useFn()
const JSONSchema = useComponent("JSONSchema")
/**
* Rendering.
*/
if (!fn.hasKeyword(schema, "then")) return null if (!fn.hasKeyword(schema, "then")) return null
const JSONSchema = useComponent("JSONSchema")
const name = ( const name = (
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"> <span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary">
Then Then

View File

@@ -8,11 +8,14 @@ import { useFn, useComponent } from "../../hooks"
const UnevaluatedItems = ({ schema }) => { const UnevaluatedItems = ({ schema }) => {
const fn = useFn() const fn = useFn()
if (!fn.hasKeyword(schema, "unevaluatedItems")) return null
const { unevaluatedItems } = schema const { unevaluatedItems } = schema
const JSONSchema = useComponent("JSONSchema") const JSONSchema = useComponent("JSONSchema")
/**
* Rendering.
*/
if (!fn.hasKeyword(schema, "unevaluatedItems")) return null
const name = ( const name = (
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"> <span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary">
Unevaluated items Unevaluated items

View File

@@ -8,11 +8,14 @@ import { useFn, useComponent } from "../../hooks"
const UnevaluatedProperties = ({ schema }) => { const UnevaluatedProperties = ({ schema }) => {
const fn = useFn() const fn = useFn()
if (!fn.hasKeyword(schema, "unevaluatedProperties")) return null
const { unevaluatedProperties } = schema const { unevaluatedProperties } = schema
const JSONSchema = useComponent("JSONSchema") const JSONSchema = useComponent("JSONSchema")
/**
* Rendering.
*/
if (!fn.hasKeyword(schema, "unevaluatedProperties")) return null
const name = ( const name = (
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"> <span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary">
Unevaluated properties Unevaluated properties

View File

@@ -1,9 +1,13 @@
.json-schema-2020-12-keyword { .json-schema-2020-12-keyword {
margin: 5px 0 5px 0; margin: 5px 0 5px 0;
& > ul { &__children {
@include expansion-border; @include expansion-border;
padding: 0; padding: 0;
&--collapsed {
display: none;
}
} }
&__name { &__name {

View File

@@ -88,6 +88,23 @@ export const withJSONSchemaContext = (Component, overrides = {}) => {
}, },
config: { config: {
default$schema: "https://json-schema.org/draft/2020-12/schema", default$schema: "https://json-schema.org/draft/2020-12/schema",
/**
* Defines an upper exclusive boundary of the level range for automatic expansion.
*
* 0 -> do nothing
* 1 -> [0]...(1)
* 2 -> [0]...(2)
* 3 -> [0]...(3)
*/
defaultExpandedLevels: 0, // 2 = 0...2
/**
* Can be turned on for complex and extensive schemas.
* Child schemas are not rendered until parent schema is expanded.
*
* By default, entire schema tree is rendered and collapsed parts of the
* tree are hidden with css.
*/
optimizeExpansion: false,
...overrides.config, ...overrides.config,
}, },
fn: { fn: {

View File

@@ -39,7 +39,11 @@ export const useIsEmbedded = () => {
} }
export const useIsExpandedDeeply = () => { export const useIsExpandedDeeply = () => {
return useContext(JSONSchemaDeepExpansionContext) const [level] = useLevel()
const { defaultExpandedLevels } = useConfig()
const isExpandedByDefault = defaultExpandedLevels - level > 0
return isExpandedByDefault || useContext(JSONSchemaDeepExpansionContext)
} }
export const useRenderedSchemas = (schema = undefined) => { export const useRenderedSchemas = (schema = undefined) => {

View File

@@ -15,6 +15,7 @@ const Models = ({
fn, fn,
}) => { }) => {
const schemas = specSelectors.selectSchemas() const schemas = specSelectors.selectSchemas()
const hasSchemas = Object.keys(schemas).length > 0
const schemasPath = ["components", "schemas"] const schemasPath = ["components", "schemas"]
const { docExpansion, defaultModelsExpandDepth } = getConfigs() const { docExpansion, defaultModelsExpandDepth } = getConfigs()
const isOpenDefault = defaultModelsExpandDepth > 0 && docExpansion !== "none" const isOpenDefault = defaultModelsExpandDepth > 0 && docExpansion !== "none"
@@ -34,15 +35,19 @@ const Models = ({
/** /**
* Event handlers. * Event handlers.
*/ */
const handleCollapse = useCallback(() => { const handleCollapse = useCallback(() => {
layoutActions.show(schemasPath, !isOpen) layoutActions.show(schemasPath, !isOpen)
}, [layoutActions, schemasPath, isOpen]) }, [isOpen, layoutActions])
const handleModelsRef = useCallback((node) => { const handleModelsRef = useCallback(
if (node !== null) { (node) => {
layoutActions.readyToScroll(schemasPath, node) if (node !== null) {
} layoutActions.readyToScroll(schemasPath, node)
}, []) }
},
[layoutActions]
)
const handleJSONSchema202012Ref = (schemaName) => (node) => { const handleJSONSchema202012Ref = (schemaName) => (node) => {
if (node !== null) { if (node !== null) {
@@ -50,6 +55,14 @@ const Models = ({
} }
} }
/**
* Rendering.
*/
if (!hasSchemas || defaultModelsExpandDepth < 0) {
return null
}
return ( return (
<section <section
className={classNames("models", { "is-open": isOpen })} className={classNames("models", { "is-open": isOpen })}
@@ -86,6 +99,7 @@ Models.propTypes = {
getConfigs: PropTypes.func.isRequired, getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.shape({ specSelectors: PropTypes.shape({
selectSchemas: PropTypes.func.isRequired, selectSchemas: PropTypes.func.isRequired,
specResolvedSubtree: PropTypes.func.isRequired,
}).isRequired, }).isRequired,
specActions: PropTypes.shape({ specActions: PropTypes.shape({
requestResolvedSubtree: PropTypes.func.isRequired, requestResolvedSubtree: PropTypes.func.isRequired,
@@ -95,6 +109,7 @@ Models.propTypes = {
}).isRequired, }).isRequired,
layoutActions: PropTypes.shape({ layoutActions: PropTypes.shape({
show: PropTypes.func.isRequired, show: PropTypes.func.isRequired,
readyToScroll: PropTypes.func.isRequired,
}).isRequired, }).isRequired,
fn: PropTypes.shape({ fn: PropTypes.shape({
upperFirst: PropTypes.func.isRequired, upperFirst: PropTypes.func.isRequired,

View File

@@ -6,7 +6,8 @@ import React from "react"
import { createOnlyOAS31ComponentWrapper } from "../fn" import { createOnlyOAS31ComponentWrapper } from "../fn"
const ModelsWrapper = createOnlyOAS31ComponentWrapper(({ getSystem }) => { const ModelsWrapper = createOnlyOAS31ComponentWrapper(({ getSystem }) => {
const { getComponent, fn } = getSystem() const { getComponent, fn, getConfigs } = getSystem()
const configs = getConfigs()
if (ModelsWrapper.ModelsWithJSONContext) { if (ModelsWrapper.ModelsWithJSONContext) {
return <ModelsWrapper.ModelsWithJSONContext /> return <ModelsWrapper.ModelsWithJSONContext />
@@ -61,7 +62,6 @@ const ModelsWrapper = createOnlyOAS31ComponentWrapper(({ getSystem }) => {
"JSONSchema202012KeywordDescription", "JSONSchema202012KeywordDescription",
true true
) )
const Accordion = getComponent("JSONSchema202012Accordion") const Accordion = getComponent("JSONSchema202012Accordion")
const ExpandDeepButton = getComponent("JSONSchema202012ExpandDeepButton") const ExpandDeepButton = getComponent("JSONSchema202012ExpandDeepButton")
const ChevronRightIcon = getComponent("JSONSchema202012ChevronRightIcon") const ChevronRightIcon = getComponent("JSONSchema202012ChevronRightIcon")
@@ -70,6 +70,7 @@ const ModelsWrapper = createOnlyOAS31ComponentWrapper(({ getSystem }) => {
ModelsWrapper.ModelsWithJSONContext = withSchemaContext(Models, { ModelsWrapper.ModelsWithJSONContext = withSchemaContext(Models, {
config: { config: {
default$schema: "https://spec.openapis.org/oas/3.1/dialect/base", default$schema: "https://spec.openapis.org/oas/3.1/dialect/base",
defaultExpandedLevels: configs.defaultModelsExpandDepth - 1,
}, },
components: { components: {
JSONSchema, JSONSchema,