feat: apply cumulative update to address various issues (#10324)

This commit is contained in:
Vladimír Gorej
2025-02-27 11:12:42 +01:00
committed by GitHub
parent 621a7f0f76
commit 80d56c9518
298 changed files with 11066 additions and 11680 deletions

View File

@@ -2,7 +2,7 @@
maintained node versions
[node-development]
node 20.3.0
node 22
[browser-production]
> 1%
@@ -26,4 +26,4 @@ maintained node versions
last 1 chrome version
last 1 firefox version
last 1 safari version
node 20.3.0
node 22

View File

@@ -1,38 +0,0 @@
parser: "@babel/eslint-parser"
env:
browser: true
node: true
es6: true
jest: true
jest/globals: true
parserOptions:
ecmaFeatures:
jsx: true
extends:
- eslint:recommended
- plugin:react/recommended
plugins:
- react
- import
- jest
settings:
react:
pragma: React
version: '15.0'
rules:
semi: [2, never]
strict: 0
quotes: [2, double, { avoidEscape: true, allowTemplateLiterals: true }]
no-unused-vars: 2
no-multi-spaces: 1
camelcase: 1
no-use-before-define: [2, nofunc]
no-underscore-dangle: 0
no-unused-expressions: 1
comma-dangle: 0
no-console: [2, { allow: [warn, error] }]
react/jsx-no-bind: 1
react/jsx-no-target-blank: 2
react/display-name: 0
import/no-extraneous-dependencies: 2
react/jsx-filename-extension: 2

78
.eslintrc.js Normal file
View File

@@ -0,0 +1,78 @@
/**
* @prettier
*/
const path = require("node:path")
module.exports = {
parser: "@babel/eslint-parser",
env: {
browser: true,
node: true,
es6: true,
jest: true,
"jest/globals": true,
},
parserOptions: {
ecmaFeatures: { jsx: true },
babelOptions: { configFile: path.join(__dirname, "babel.config.js") },
},
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:prettier/recommended",
],
plugins: ["react", "import", "jest", "prettier"],
settings: {
react: {
pragma: "React",
version: "15.0",
},
},
rules: {
semi: [2, "never"],
strict: 0,
quotes: [
2,
"double",
{
avoidEscape: true,
allowTemplateLiterals: true,
},
],
"no-unused-vars": 2,
"no-multi-spaces": 1,
camelcase: [
"error",
{
allow: [
"^UNSAFE_",
"^requestSnippetGenerator_",
"^JsonSchema_",
"^curl_",
"^dom_",
"^api_",
"^client_",
"^grant_",
"^code_",
"^redirect_",
"^spec",
],
},
],
"no-use-before-define": [2, "nofunc"],
"no-underscore-dangle": 0,
"no-unused-expressions": 1,
"comma-dangle": 0,
"no-console": [
2,
{
allow: ["warn", "error"],
},
],
"react/jsx-no-bind": 1,
"react/jsx-no-target-blank": 2,
"react/display-name": 0,
"import/no-extraneous-dependencies": 2,
"react/jsx-filename-extension": 2,
},
}

View File

@@ -1,3 +1,4 @@
{
"*.js": "eslint"
"*.{js,jsx}": ["eslint --max-warnings 0"],
"*.scss": ["stylelint '**/*.scss'"]
}

2
.nvmrc
View File

@@ -1 +1 @@
20.3.0
22.11.0

View File

@@ -1,13 +1,6 @@
{
"branches": [
{
"name": "master"
},
{
"name": "next",
"channel": "alpha",
"prerelease": "alpha"
}
{"name": "master"}
],
"tagFormat": "v${version}",
"plugins": [
@@ -21,15 +14,12 @@
"@semantic-release/release-notes-generator",
"@semantic-release/npm",
"@semantic-release/github",
[
"@semantic-release/git",
{
"assets": [
"package.json",
"package-lock.json"
],
"message": "chore(release): cut the ${nextRelease.version} release\n\n${nextRelease.notes}"
}
]
["@semantic-release/git", {
"assets": [
"package.json",
"package-lock.json"
],
"message": "chore(release): cut the ${nextRelease.version} release\n\n${nextRelease.notes}"
}]
]
}
}

View File

@@ -4,6 +4,13 @@
FROM nginx:1.27.4-alpine
LABEL maintainer="vladimir.gorej@gmail.com" \
org.opencontainers.image.authors="vladimir.gorej@gmail.com" \
org.opencontainers.image.url="docker.swagger.io/swaggerapi/swagger-ui" \
org.opencontainers.image.source="https://github.com/swagger-api/swagger-ui" \
org.opencontainers.image.description="SwaggerUI Docker image" \
org.opencontainers.image.licenses="Apache-2.0"
RUN apk add --update-cache --no-cache "nodejs"
LABEL maintainer="char0n"

View File

@@ -18,6 +18,6 @@ module.exports = {
moduleNameMapper: {
'^.+\\.svg$': 'jest-transform-stub'
},
transformIgnorePatterns: ['/node_modules/(?!(sinon|react-syntax-highlighter)/)'],
transformIgnorePatterns: ['/node_modules/(?!(sinon|react-syntax-highlighter|@asamuzakjp/css-color)/)'],
silent: true, // set to `false` to allow console.* calls to be printed
};

View File

@@ -10,6 +10,8 @@ Script name | Description
`lint` | Report ESLint style errors and warnings.
`lint-errors` | Report ESLint style errors, without warnings.
`lint-fix` | Attempt to fix style errors automatically.
`lint-styles` | Report Stylelint style errors and warnings.
`lint-styles-fix` | Attempt to fix Stylelint errors and warnings automatically.
`watch` | Rebuild the core files in `/dist` when the source code changes. Useful for `npm link` with Swagger Editor.
### Building

View File

@@ -1,20 +1,23 @@
# Setting up a dev environment
Swagger UI includes a development server that provides hot module reloading and unminified stack traces, for easier development.
SwaggerUI includes a development server that provides hot module reloading and unminified stack traces, for easier development.
### Prerequisites
- git, any version
- **Node.js >=20.3.0** and **npm >=9.6.7** are the minimum required versions that this repo runs on, but we always recommend using the latest version of Node.js.
- **Node.js >=22.11.0** and **npm >=10.9.0** are the minimum required versions that this repo runs on, but we always recommend using the latest version of Node.js.
### Steps
1. `git clone https://github.com/swagger-api/swagger-ui.git`
2. `cd swagger-ui`
3. `npm install`
4. `npm run dev`
5. Wait a bit
6. Open http://localhost:3200/
4. `npx husky init` (optional)
5. `npm run dev`
6. Wait a bit
7. Open http://localhost:3200/
### Using your own local api definition with local dev build

View File

@@ -1,4 +1,4 @@
openapi: "3.0.0"
openapi: "3.0.4"
info:
version: "0.0.1"
title: "Swagger UI Webpack Setup"

View File

@@ -371,7 +371,7 @@ Set the value to the literal object value you'd like, taking care to escape char
Example:
```sh
SPEC="{ \"openapi\": \"3.0.0\" }"
SPEC="{ \"openapi\": \"3.0.4\" }"
```
### Docker-Compose

View File

@@ -10,6 +10,23 @@ We publish three modules to npm: **`swagger-ui`**, **`swagger-ui-dist`** and **`
such as Webpack, Browserify, and Rollup. Its main file exports Swagger UI's main function,
and the module also includes a namespaced stylesheet at `swagger-ui/dist/swagger-ui.css`. Here's an example:
### Installation
You can now install SwaggerUI packages using `npm`:
```sh
$ npm install swagger-ui
````
```sh
$ npm install swagger-ui-react
````
```sh
$ npm install swagger-ui-dist
````
```javascript
import SwaggerUI from 'swagger-ui'
// or use require if you prefer

View File

@@ -33,7 +33,7 @@ Alternatively, you can set the environment variable `SCARF_ANALYTICS` to `false`
Install `swagger-ui-react`:
```
$ npm i --save swagger-ui-react
$ npm install swagger-ui-react
```
Use it in your React application:
@@ -194,6 +194,11 @@ Redirect url given as parameter to the oauth2 provider. Default the url refers t
⚠️ This prop is currently only applied once, on mount. Changes to this prop's value will not be propagated to the underlying Swagger UI instance. A future version of this module will remove this limitation, and the change will not be considered a breaking change.
#### `initialState`: PropTypes.object
Passes initial values to the Swagger UI state.
⚠️ This prop is currently only applied once, on mount. Changes to this prop's value will not be propagated to the underlying Swagger UI instance. A future version of this module will remove this limitation, and the change will not be considered a breaking change.
## Limitations

View File

@@ -45,6 +45,7 @@ const SwaggerUI = ({
persistAuthorization = config.defaults.persistAuthorization,
oauth2RedirectUrl = config.defaults.oauth2RedirectUrl,
onComplete = null,
initialState = config.defaults.initialState,
}) => {
const [system, setSystem] = useState(null)
const SwaggerUIComponent = system?.getComponent("App", "root")
@@ -83,6 +84,7 @@ const SwaggerUI = ({
filter,
persistAuthorization,
withCredentials,
initialState,
...(typeof oauth2RedirectUrl === "string"
? { oauth2RedirectUrl: oauth2RedirectUrl }
: {}),
@@ -165,6 +167,7 @@ SwaggerUI.propTypes = {
persistAuthorization: PropTypes.bool,
withCredentials: PropTypes.bool,
oauth2RedirectUrl: PropTypes.string,
initialState: PropTypes.object,
}
SwaggerUI.System = SwaggerUIConstructor.System
SwaggerUI.presets = SwaggerUIConstructor.presets

11522
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -53,6 +53,8 @@
"lint": "eslint --ext \".js,.jsx\" src test dev-helpers flavors",
"lint-errors": "eslint --quiet --ext \".js,.jsx\" src test dev-helpers flavors",
"lint-fix": "eslint --ext \".js,.jsx\" src test dev-helpers flavors --fix",
"lint-styles": "stylelint \"**/*.scss\"",
"lint-styles-fix": "stylelint \"**/*.scss\" --fix",
"test": "run-s lint-errors test:unit cy:ci",
"test:artifact": "cross-env NODE_ENV=production BABEL_ENV=commonjs BROWSERSLIST_ENV=node-development jest --config ./config/jest/jest.artifact.config.js",
"test:unit": "cross-env NODE_ENV=test BABEL_ENV=commonjs BROWSERSLIST_ENV=node-development jest --config ./config/jest/jest.unit.config.js",
@@ -71,8 +73,7 @@
"start": "npm-run-all --parallel serve-static open-static"
},
"dependencies": {
"@babel/runtime-corejs3": "^7.26.9",
"@braintree/sanitize-url": "=7.0.4",
"@babel/runtime-corejs3": "^7.26.7",
"@scarf/scarf": "=1.4.0",
"base64-js": "^1.5.1",
"classnames": "^2.5.1",
@@ -94,15 +95,15 @@
"react-immutable-proptypes": "2.2.0",
"react-immutable-pure-component": "^2.2.0",
"react-inspector": "^6.0.1",
"react-redux": "^9.1.2",
"react-syntax-highlighter": "^15.5.0",
"react-redux": "^9.2.0",
"react-syntax-highlighter": "^15.6.1",
"redux": "^5.0.1",
"redux-immutable": "^4.0.0",
"remarkable": "^2.0.1",
"reselect": "^5.1.1",
"serialize-error": "^8.1.0",
"sha.js": "^2.4.11",
"swagger-client": "^3.34.0",
"swagger-client": "^3.34.1",
"url-parse": "^1.5.10",
"xml": "=1.0.1",
"xml-but-prettier": "^1.0.1",
@@ -110,43 +111,46 @@
},
"devDependencies": {
"@babel/cli": "=7.26.4",
"@babel/core": "=7.26.8",
"@babel/core": "=7.26.9",
"@babel/eslint-parser": "=7.26.8",
"@babel/plugin-transform-runtime": "=7.26.9",
"@babel/plugin-transform-runtime": "=7.26.8",
"@babel/preset-env": "=7.26.9",
"@babel/preset-react": "=7.24.7",
"@babel/preset-react": "=7.26.3",
"@babel/register": "=7.25.9",
"@cfaester/enzyme-adapter-react-18": "=0.8.0",
"@commitlint/cli": "^19.6.1",
"@commitlint/config-conventional": "^19.2.2",
"@commitlint/cli": "^19.7.1",
"@commitlint/config-conventional": "^19.7.1",
"@jest/globals": "=29.7.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
"@release-it/conventional-changelog": "=8.0.1",
"@release-it/conventional-changelog": "=8.0.2",
"@svgr/webpack": "=8.1.0",
"autoprefixer": "^10.4.19",
"babel-loader": "^9.1.3",
"autoprefixer": "^10.4.20",
"babel-loader": "^9.2.1",
"babel-plugin-lodash": "=3.3.4",
"babel-plugin-module-resolver": "=5.0.2",
"babel-plugin-transform-react-remove-prop-types": "=0.4.24",
"body-parser": "^1.19.0",
"buffer": "^6.0.3",
"cheerio": "=1.0.0-rc.12",
"cors": "^2.8.5",
"cross-env": "=7.0.3",
"css-loader": "=7.1.2",
"cssnano": "=7.0.6",
"cypress": "=13.13.0",
"cypress": "=14.0.3",
"dedent": "^1.5.3",
"deepmerge": "^4.3.1",
"enzyme": "=3.11.0",
"eslint": "^8.57.0",
"eslint-plugin-import": "^2.29.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jest": "^28.11.0",
"eslint-plugin-react": "^7.34.3",
"eslint-plugin-prettier": "^5.2.3",
"eslint-plugin-react": "^7.37.4",
"esm": "=3.2.25",
"expect": "=29.7.0",
"express": "^4.21.1",
"express": "^4.21.2",
"git-describe": "^4.1.0",
"html-webpack-plugin": "^5.5.1",
"html-webpack-plugin": "^5.6.3",
"html-webpack-skip-assets-plugin": "^1.0.4",
"husky": "=9.1.7",
"inspectpack": "=4.7.1",
@@ -157,31 +161,34 @@
"json-loader": "^0.5.7",
"json-merger": "^2.0.0",
"json-server": "=0.17.4",
"less": "^4.2.0",
"less": "^4.2.2",
"license-checker": "^25.0.0",
"lint-staged": "^15.4.3",
"local-web-server": "^5.3.3",
"mini-css-extract-plugin": "^2.9.0",
"local-web-server": "^5.4.0",
"mini-css-extract-plugin": "^2.9.2",
"npm-audit-ci-wrapper": "^3.0.2",
"npm-run-all": "^4.1.5",
"oauth2-server": "^2.4.1",
"open": "^10.1.0",
"postcss": "^8.5.2",
"postcss-scss": "^4.0.9",
"postcss-loader": "^8.1.1",
"postcss-preset-env": "^9.6.0",
"postcss-preset-env": "^10.1.4",
"prettier": "^3.5.2",
"process": "^0.11.10",
"react-refresh": "^0.16.0",
"react-test-renderer": "^18.3.1",
"release-it": "=17.10.0",
"rimraf": "^5.0.7",
"sass": "^1.77.6",
"sass-loader": "^14.2.1",
"release-it": "=17.11.0",
"rimraf": "^6.0.1",
"sass-embedded": "=1.83.4",
"sass-loader": "^16.0.4",
"shx": "=0.3.4",
"sinon": "=19.0.2",
"source-map-support": "^0.5.21",
"start-server-and-test": "^2.0.10",
"stream-browserify": "^3.0.0",
"stylelint": "^16.14.1",
"stylelint-prettier": "^5.0.3",
"tachyons-sass": "^4.9.5",
"terser-webpack-plugin": "^5.3.11",
"webpack": "^5.97.1",

View File

@@ -1,45 +0,0 @@
{
"extends": [
"config:base"
],
"packageRules": [
{
"managers": ["npm"],
"commitMessage": "{{{commitMessagePrefix}}} {{{commitMessageTopic}}}{{{commitMessageExtra}}} {{{commitMessageSuffix}}}",
"commitMessageTopic": "{{depName}}",
"commitMessageExtra": "{{#if isMajor}} v{{{newMajor}}}{{else}}{{#if isSingleVersion}}@{{{toVersion}}}{{else}}@{{{newValue}}}{{/if}}{{/if}}"
},
{
"packagePatterns": [
"*"
],
"rangeStrategy": "replace",
"semanticCommitType": "housekeeping"
},
{
"depTypeList": [
"devDependencies"
],
"rangeStrategy": "update-lockfile",
"semanticCommitScope": "dev-deps",
"automerge": true,
"major": {
"automerge": false
}
},
{
"depTypeList": [
"peerDependencies"
],
"rangeStrategy": "widen",
"semanticCommitScope": "peer-deps"
},
{
"packagePatterns": ["^react"],
"enabled": false
}
],
"ignorePaths": ["swagger-ui-dist-package/package.json"],
"ignoreDeps": ["release-it", "@release-it/conventional-changelog"],
"prConcurrentLimit": 3
}

View File

@@ -8,7 +8,8 @@ export default class ApiKeyAuth extends React.Component {
errSelectors: PropTypes.object.isRequired,
schema: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
onChange: PropTypes.func
onChange: PropTypes.func,
authSelectors: PropTypes.object.isRequired
}
constructor(props, context) {
@@ -39,13 +40,14 @@ export default class ApiKeyAuth extends React.Component {
}
render() {
let { schema, getComponent, errSelectors, name } = this.props
let { schema, getComponent, errSelectors, name, authSelectors } = this.props
const Input = getComponent("Input")
const Row = getComponent("Row")
const Col = getComponent("Col")
const AuthError = getComponent("authError")
const Markdown = getComponent("Markdown", true)
const JumpToPath = getComponent("JumpToPath", true)
const path = authSelectors.selectAuthPath(name)
let value = this.getValue()
let errors = errSelectors.allErrors().filter( err => err.get("authId") === name)
@@ -53,7 +55,7 @@ export default class ApiKeyAuth extends React.Component {
<div>
<h4>
<code>{ name || schema.get("name") }</code>&nbsp;(apiKey)
<JumpToPath path={[ "securityDefinitions", name ]} />
<JumpToPath path={path} />
</h4>
{ value && <h6>Authorized</h6>}
<Row>

View File

@@ -10,6 +10,7 @@ export default class Auths extends React.Component {
getComponent: PropTypes.func.isRequired,
onAuthChange: PropTypes.func.isRequired,
errSelectors: PropTypes.object.isRequired,
authSelectors: PropTypes.object.isRequired,
}
render() {
@@ -19,7 +20,8 @@ export default class Auths extends React.Component {
getComponent,
onAuthChange,
authorized,
errSelectors
errSelectors,
authSelectors
} = this.props
const ApiKeyAuth = getComponent("apiKeyAuth")
const BasicAuth = getComponent("basicAuth")
@@ -35,7 +37,8 @@ export default class Auths extends React.Component {
errSelectors={ errSelectors }
authorized={ authorized }
getComponent={ getComponent }
onChange={ onAuthChange } />
onChange={ onAuthChange }
authSelectors = { authSelectors } />
break
case "basic": authEl = <BasicAuth key={ name }
schema={ schema }
@@ -43,7 +46,8 @@ export default class Auths extends React.Component {
errSelectors={ errSelectors }
authorized={ authorized }
getComponent={ getComponent }
onChange={ onAuthChange } />
onChange={ onAuthChange }
authSelectors = { authSelectors } />
break
default: authEl = <div key={ name }>Unknown security definition type { type }</div>
}

View File

@@ -83,6 +83,7 @@ export default class Auths extends React.Component {
onAuthChange={this.onAuthChange}
authorized={authorized}
errSelectors={errSelectors}
authSelectors={authSelectors}
/>
}).toArray()
}

View File

@@ -10,6 +10,7 @@ export default class BasicAuth extends React.Component {
onChange: PropTypes.func.isRequired,
name: PropTypes.string.isRequired,
errSelectors: PropTypes.object.isRequired,
authSelectors: PropTypes.object.isRequired
}
constructor(props, context) {
@@ -47,19 +48,20 @@ export default class BasicAuth extends React.Component {
}
render() {
let { schema, getComponent, name, errSelectors } = this.props
let { schema, getComponent, name, errSelectors, authSelectors } = this.props
const Input = getComponent("Input")
const Row = getComponent("Row")
const Col = getComponent("Col")
const AuthError = getComponent("authError")
const JumpToPath = getComponent("JumpToPath", true)
const Markdown = getComponent("Markdown", true)
const path = authSelectors.selectAuthPath(name)
let username = this.getValue().username
let errors = errSelectors.allErrors().filter( err => err.get("authId") === name)
return (
<div>
<h4>Basic authorization<JumpToPath path={[ "securityDefinitions", name ]} /></h4>
<h4>Basic authorization<JumpToPath path={path} /></h4>
{ username && <h6>Authorized</h6> }
<Row>
<Markdown source={ schema.get("description") } />

View File

@@ -130,6 +130,8 @@ export default class Oauth2 extends React.Component {
const AUTH_FLOW_ACCESS_CODE = isOAS3() ? (oidcUrl ? "authorization_code" : "authorizationCode") : "accessCode"
const AUTH_FLOW_APPLICATION = isOAS3() ? (oidcUrl ? "client_credentials" : "clientCredentials") : "application"
const path = authSelectors.selectAuthPath(name)
let authConfigs = authSelectors.getConfigs() || {}
let isPkceCodeGrant = !!authConfigs.usePkceWithAuthorizationCodeGrant
@@ -144,7 +146,7 @@ export default class Oauth2 extends React.Component {
return (
<div>
<h4>{name} (OAuth2, { flowToDisplay }) <JumpToPath path={[ "securityDefinitions", name ]} /></h4>
<h4>{name} (OAuth2, { flowToDisplay }) <JumpToPath path={path} /></h4>
{ !this.state.appName ? null : <h5>Application: { this.state.appName } </h5> }
{ description && <Markdown source={ schema.get("description") } /> }

View File

@@ -3,8 +3,7 @@
*/
import React from "react"
import PropTypes from "prop-types"
import { safeBuildUrl } from "core/utils/url"
import { sanitizeUrl } from "core/utils"
import { safeBuildUrl, sanitizeUrl } from "core/utils/url"
class Contact extends React.Component {
static propTypes = {

View File

@@ -6,6 +6,7 @@ import React from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import { stringify } from "core/utils"
import { Map } from "immutable"
export default function Example(props) {
const { example, showValue, getComponent } = props
@@ -13,7 +14,7 @@ export default function Example(props) {
const Markdown = getComponent("Markdown", true)
const HighlightCode = getComponent("HighlightCode", true)
if (!example) return null
if (!example || !Map.isMap(example)) return null
return (
<div className="example">

View File

@@ -28,7 +28,7 @@ import { stringify } from "core/utils"
// Note that `currentNamespace` isn't currently used anywhere!
const stringifyUnlessList = input =>
const stringifyUnlessList = (input) =>
List.isList(input) ? input : stringify(input)
export default class ExamplesSelectValueRetainer extends React.PureComponent {
@@ -53,12 +53,14 @@ export default class ExamplesSelectValueRetainer extends React.PureComponent {
// NOOP
},
onSelect: (...args) =>
console.log( // eslint-disable-line no-console
// eslint-disable-next-line no-console
console.log(
"ExamplesSelectValueRetainer: no `onSelect` function was provided",
...args
),
updateValue: (...args) =>
console.log( // eslint-disable-line no-console
// eslint-disable-next-line no-console
console.log(
"ExamplesSelectValueRetainer: no `updateValue` function was provided",
...args
),
@@ -94,7 +96,7 @@ export default class ExamplesSelectValueRetainer extends React.PureComponent {
return (this.state[currentNamespace] || Map()).toObject()
}
_setStateForCurrentNamespace = obj => {
_setStateForCurrentNamespace = (obj) => {
const { currentNamespace } = this.props
return this._setStateForNamespace(currentNamespace, obj)
@@ -125,7 +127,7 @@ export default class ExamplesSelectValueRetainer extends React.PureComponent {
)
}
_getCurrentExampleValue = props => {
_getCurrentExampleValue = (props) => {
// props are accepted so that this can be used in UNSAFE_componentWillReceiveProps,
// which has access to `nextProps`
const { currentKey } = props || this.props
@@ -133,12 +135,8 @@ export default class ExamplesSelectValueRetainer extends React.PureComponent {
}
_onExamplesSelect = (key, { isSyntheticChange } = {}, ...otherArgs) => {
const {
onSelect,
updateValue,
currentUserInputValue,
userHasEditedBody,
} = this.props
const { onSelect, updateValue, currentUserInputValue, userHasEditedBody } =
this.props
const { lastUserEditedValue } = this._getStateForCurrentNamespace()
const valueFromExample = this._getValueForExample(key)
@@ -179,10 +177,8 @@ export default class ExamplesSelectValueRetainer extends React.PureComponent {
userHasEditedBody,
} = nextProps
const {
lastUserEditedValue,
lastDownstreamValue,
} = this._getStateForCurrentNamespace()
const { lastUserEditedValue, lastDownstreamValue } =
this._getStateForCurrentNamespace()
const valueFromCurrentExample = this._getValueForExample(
nextProps.currentKey,
@@ -199,8 +195,7 @@ export default class ExamplesSelectValueRetainer extends React.PureComponent {
if (examplesMatchingNewValue.size) {
let key
if(examplesMatchingNewValue.has(nextProps.currentKey))
{
if (examplesMatchingNewValue.has(nextProps.currentKey)) {
key = nextProps.currentKey
} else {
key = examplesMatchingNewValue.keySeq().first()

View File

@@ -3,7 +3,7 @@
*/
import React from "react"
import Im from "immutable"
import { Map } from "immutable"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
@@ -18,9 +18,10 @@ export default class ExamplesSelect extends React.PureComponent {
}
static defaultProps = {
examples: Im.Map({}),
examples: Map({}),
onSelect: (...args) =>
console.log( // eslint-disable-line no-console
// eslint-disable-next-line no-console
console.log(
// FIXME: remove before merging to master...
`DEBUG: ExamplesSelect was not given an onSelect callback`,
...args
@@ -37,7 +38,7 @@ export default class ExamplesSelect extends React.PureComponent {
}
}
_onDomSelect = e => {
_onDomSelect = (e) => {
if (typeof this.props.onSelect === "function") {
const element = e.target.selectedOptions[0]
const key = element.getAttribute("value")
@@ -103,18 +104,16 @@ export default class ExamplesSelect extends React.PureComponent {
return (
<div className="examples-select">
{
showLabels ? (
<span className="examples-select__section-label">Examples: </span>
) : null
}
{showLabels ? (
<span className="examples-select__section-label">Examples: </span>
) : null}
<select
className="examples-select-element"
onChange={this._onDomSelect}
value={
isModifiedValueAvailable && isValueModified
? "__MODIFIED__VALUE__"
: (currentExampleKey || "")
: currentExampleKey || ""
}
>
{isModifiedValueAvailable ? (
@@ -127,7 +126,8 @@ export default class ExamplesSelect extends React.PureComponent {
key={exampleName} // for React
value={exampleName} // for matching to select's `value`
>
{example.get("summary") || exampleName}
{(Map.isMap(example) && example.get("summary")) ||
exampleName}
</option>
)
})

View File

@@ -4,8 +4,7 @@
import React from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import { sanitizeUrl } from "core/utils"
import { safeBuildUrl } from "core/utils/url"
import { safeBuildUrl, sanitizeUrl } from "core/utils/url"
export class InfoBasePath extends React.Component {
static propTypes = {

View File

@@ -178,7 +178,9 @@ export class Select extends React.Component {
this.setState({value: value})
onChange && onChange(value)
if(onChange) {
onChange(value)
}
}
UNSAFE_componentWillReceiveProps(nextProps) {

View File

@@ -3,8 +3,7 @@
*/
import React from "react"
import PropTypes from "prop-types"
import { safeBuildUrl } from "core/utils/url"
import { sanitizeUrl } from "core/utils"
import { safeBuildUrl, sanitizeUrl } from "core/utils/url"
class License extends React.Component {
static propTypes = {

View File

@@ -2,7 +2,8 @@ import React from "react"
import URL from "url-parse"
import PropTypes from "prop-types"
import { sanitizeUrl, requiresValidationURL } from "core/utils"
import { requiresValidationURL } from "core/utils"
import { sanitizeUrl } from "core/utils/url"
import win from "core/window"
export default class OnlineValidatorBadge extends React.Component {

View File

@@ -2,9 +2,10 @@ import React from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import Im from "immutable"
import { createDeepLinkPath, escapeDeepLinkPath, sanitizeUrl } from "core/utils"
import { safeBuildUrl } from "core/utils/url"
import { isFunc } from "core/utils"
import { createDeepLinkPath, escapeDeepLinkPath, isFunc } from "core/utils"
import { safeBuildUrl, sanitizeUrl } from "core/utils/url"
/* eslint-disable react/jsx-no-bind */
export default class OperationTag extends React.Component {

View File

@@ -1,8 +1,7 @@
import React, { PureComponent } from "react"
import PropTypes from "prop-types"
import { getList } from "core/utils"
import { getExtensions, sanitizeUrl, escapeDeepLinkPath } from "core/utils"
import { safeBuildUrl } from "core/utils/url"
import { getExtensions, escapeDeepLinkPath, getList } from "core/utils"
import { safeBuildUrl, sanitizeUrl } from "core/utils/url"
import { Iterable, List } from "immutable"
import ImPropTypes from "react-immutable-proptypes"

View File

@@ -3,7 +3,7 @@ import { Map, List, fromJS } from "immutable"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import win from "core/window"
import { getExtensions, getCommonExtensions, numberToString, stringify, isEmptyValue } from "core/utils"
import { getExtensions, getCommonExtensions, numberToString, stringify, isEmptyValue, immutableToJS } from "core/utils"
import getParameterSchema from "core/utils/get-parameter-schema.js"
export default class ParameterRow extends Component {
@@ -152,10 +152,13 @@ export default class ParameterRow extends Component {
//// Dispatch the initial value
const type = fn.jsonSchema202012.foldType(immutableToJS(schema?.get("type")))
const itemType = fn.jsonSchema202012.foldType(immutableToJS(schema?.getIn(["items", "type"])))
if(initialValue !== undefined) {
this.onChangeWrapper(initialValue)
} else if(
schema && schema.get("type") === "object"
type === "object"
&& generatedSampleValue
&& !paramWithMeta.get("examples")
) {
@@ -171,6 +174,20 @@ export default class ParameterRow extends Component {
stringify(generatedSampleValue)
)
)
}
else if (
type === "array"
&& itemType === "object"
&& generatedSampleValue
&& !paramWithMeta.get("examples")
) {
this.onChangeWrapper(
List.isList(generatedSampleValue) ? (
generatedSampleValue
) : (
List(JSON.parse(generatedSampleValue))
)
)
}
}
}
@@ -236,12 +253,16 @@ export default class ParameterRow extends Component {
}
let format = schema ? schema.get("format") : null
let type = schema ? schema.get("type") : null
let itemType = schema ? schema.getIn(["items", "type"]) : null
let isFormData = inType === "formData"
let isFormDataSupported = "FormData" in win
let required = param.get("required")
const typeLabel = fn.jsonSchema202012.getType(immutableToJS(schema))
const type = fn.jsonSchema202012.foldType(immutableToJS(schema?.get("type")))
const itemType = fn.jsonSchema202012.foldType(immutableToJS(schema?.getIn(["items", "type"])))
const isObject = !bodyParam && type === "object"
const isArrayOfObjects = !bodyParam && itemType === "object"
let value = paramWithMeta ? paramWithMeta.get("value") : ""
let commonExt = showCommonExtensions ? getCommonExtensions(schema) : null
let extensions = showExtensions ? getExtensions(param) : null
@@ -281,6 +302,18 @@ export default class ParameterRow extends Component {
}
}
const jsonSchemaForm = bodyParam ? null
: <JsonSchemaForm fn={fn}
getComponent={getComponent}
value={ value }
required={ required }
disabled={!isExecute}
description={param.get("name")}
onChange={ this.onChangeWrapper }
errors={ paramWithMeta.get("errors") }
schema={ schema }
/>
return (
<tr data-param-name={param.get("name")} data-param-in={param.get("in")}>
<td className="parameters-col_name">
@@ -289,8 +322,7 @@ export default class ParameterRow extends Component {
{ !required ? null : <span>&nbsp;*</span> }
</div>
<div className="parameter__type">
{ type }
{ itemType && `[${itemType}]` }
{ typeLabel }
{ format && <span className="prop-format">(${format})</span>}
</div>
<div className="parameter__deprecated">
@@ -338,19 +370,19 @@ export default class ParameterRow extends Component {
) : null
}
{ bodyParam ? null
: <JsonSchemaForm fn={fn}
getComponent={getComponent}
value={ value }
required={ required }
disabled={!isExecute}
description={param.get("name")}
onChange={ this.onChangeWrapper }
errors={ paramWithMeta.get("errors") }
schema={ schema }/>
{ (isObject || isArrayOfObjects) ? (
<ModelExample
getComponent={getComponent}
specPath={specPath.push("schema")}
getConfigs={getConfigs}
isExecute={isExecute}
specSelectors={specSelectors}
schema={schema}
example={jsonSchemaForm}
/>
) : jsonSchemaForm
}
{
bodyParam && schema ? <ModelExample getComponent={ getComponent }
specPath={specPath.push("schema")}

View File

@@ -6,6 +6,7 @@ import { fromJS, Seq, Iterable, List, Map } from "immutable"
import { getExtensions, fromJSOrdered, stringify } from "core/utils"
import { getKnownSyntaxHighlighterLanguage } from "core/utils/jsonParse"
/* eslint-disable react/jsx-no-bind */
const getExampleComponent = ( sampleResponse, HighlightCode ) => {
if (sampleResponse == null) return null
@@ -139,7 +140,9 @@ export default class Response extends React.Component {
const targetExample = examplesForMediaType
.get(targetExamplesKey, Map({}))
const getMediaTypeExample = (targetExample) =>
targetExample.get("value")
Map.isMap(targetExample)
? targetExample.get("value")
: undefined
mediaTypeExample = getMediaTypeExample(targetExample)
if(mediaTypeExample === undefined) {
mediaTypeExample = getMediaTypeExample(examplesForMediaType.values().next().value)

View File

@@ -30,7 +30,7 @@ export default class VersionPragmaFilter extends React.PureComponent {
<div>
<h3>Unable to render this definition</h3>
<p><code>swagger</code> and <code>openapi</code> fields cannot be present in the same Swagger or OpenAPI definition. Please remove one of the fields.</p>
<p>Supported version fields are <code>swagger: {"\"2.0\""}</code> and those that match <code>openapi: 3.0.n</code> (for example, <code>openapi: 3.0.0</code>).</p>
<p>Supported version fields are <code>swagger: {"\"2.0\""}</code> and those that match <code>openapi: 3.0.n</code> (for example, <code>openapi: 3.0.4</code>).</p>
</div>
</div>
</div>
@@ -43,7 +43,7 @@ export default class VersionPragmaFilter extends React.PureComponent {
<div>
<h3>Unable to render this definition</h3>
<p>The provided definition does not specify a valid version field.</p>
<p>Please indicate a valid Swagger or OpenAPI version field. Supported version fields are <code>swagger: {"\"2.0\""}</code> and those that match <code>openapi: 3.0.n</code> (for example, <code>openapi: 3.0.0</code>).</p>
<p>Please indicate a valid Swagger or OpenAPI version field. Supported version fields are <code>swagger: {"\"2.0\""}</code> and those that match <code>openapi: 3.0.n</code> (for example, <code>openapi: 3.0.4</code>).</p>
</div>
</div>
</div>

View File

@@ -1,6 +1,7 @@
import parseUrl from "url-parse"
import Im from "immutable"
import { btoa, sanitizeUrl, generateCodeVerifier, createCodeChallenge } from "core/utils"
import { btoa, generateCodeVerifier, createCodeChallenge } from "core/utils"
import { sanitizeUrl } from "core/utils/url"
export default function authorize ( { auth, authActions, errActions, configs, authConfigs={}, currentServer } ) {
let { schema, scopes, name, clientId } = auth

View File

@@ -5,9 +5,7 @@ import { btoa, buildFormData } from "core/utils"
export const SHOW_AUTH_POPUP = "show_popup"
export const AUTHORIZE = "authorize"
export const LOGOUT = "logout"
export const PRE_AUTHORIZE_OAUTH2 = "pre_authorize_oauth2"
export const AUTHORIZE_OAUTH2 = "authorize_oauth2"
export const VALIDATE = "validate"
export const CONFIGURE_AUTH = "configure_auth"
export const RESTORE_AUTHORIZATION = "restore_authorization"

View File

@@ -26,7 +26,15 @@ export const definitionsToAuthorize = createSelector(
}
)
export const selectAuthPath =
(state, name) =>
({ specSelectors }) =>
List(
specSelectors.isOAS3()
? ["components", "securitySchemes", name]
: ["securityDefinitions", name]
)
export const getDefinitionsByNames = ( state, securities ) => ( { specSelectors } ) => {
console.warn("WARNING: getDefinitionsByNames is deprecated and will be removed in the next major version.")
let securityDefinitions = specSelectors.securityDefinitions()

View File

@@ -10,15 +10,15 @@ import LockIcon from "./components/lock"
import UnlockIcon from "./components/unlock"
const IconsPlugin = () => ({
components: {
ArrowUpIcon,
ArrowDownIcon,
ArrowIcon,
CloseIcon,
CopyIcon,
LockIcon,
UnlockIcon,
}
components: {
ArrowUpIcon,
ArrowDownIcon,
ArrowIcon,
CloseIcon,
CopyIcon,
LockIcon,
UnlockIcon,
},
})
export default IconsPlugin
export default IconsPlugin

View File

@@ -13,3 +13,4 @@ export { default as encoderAPI } from "./api/encoderAPI"
export { default as formatAPI } from "./api/formatAPI"
export { default as mediaTypeAPI } from "./api/mediaTypeAPI"
export { default as mergeJsonSchema } from "./core/merge"
export { foldType } from "./core/type"

View File

@@ -12,6 +12,7 @@ import {
mediaTypeAPI,
formatAPI,
mergeJsonSchema,
foldType,
} from "./fn/index"
import makeGetJsonSampleSchema from "./fn/get-json-sample-schema"
import makeGetYamlSampleSchema from "./fn/get-yaml-sample-schema"
@@ -41,6 +42,7 @@ const JSONSchema202012SamplesPlugin = ({ getSystem }) => {
getXmlSampleSchema,
getSampleSchema,
mergeJsonSchema,
foldType,
},
},
}

View File

@@ -14,13 +14,13 @@
vertical-align: bottom;
&--expanded {
transition: transform .15s ease-in;
transition: transform 0.15s ease-in;
transform: rotate(-90deg);
transform-origin: 50% 50%;
}
&--collapsed {
transition: transform .15s ease-in;
transition: transform 0.15s ease-in;
transform: rotate(0deg);
transform-origin: 50% 50%;
}
@@ -31,4 +31,3 @@
}
}
}

View File

@@ -1,5 +1,8 @@
@use "./../../../../../style/variables" as *;
@use "./../../../../../style/type";
.json-schema-2020-12-expand-deep-button {
@include text_headline($section-models-model-title-font-color);
@include type.text_headline($section-models-model-title-font-color);
font-size: 12px;
color: rgb(175, 174, 174);
border: none;

View File

@@ -1,7 +1,7 @@
/**
* @prettier
*/
import React, { forwardRef, useState, useCallback, useEffect } from "react"
import React, { forwardRef, useCallback } from "react"
import PropTypes from "prop-types"
import classNames from "classnames"
@@ -12,23 +12,32 @@ import {
useFn,
useIsEmbedded,
useIsExpanded,
useIsExpandedDeeply,
useIsCircular,
useRenderedSchemas,
usePath,
} from "../../hooks"
import {
JSONSchemaLevelContext,
JSONSchemaDeepExpansionContext,
JSONSchemaCyclesContext,
JSONSchemaPathContext,
} from "../../context"
const JSONSchema = forwardRef(
({ schema, name = "", dependentRequired = [], onExpand = () => {} }, ref) => {
(
{
schema,
name = "",
dependentRequired = [],
onExpand = () => {},
identifier = "",
},
ref
) => {
const fn = useFn()
const isExpanded = useIsExpanded()
const isExpandedDeeply = useIsExpandedDeeply()
const [expanded, setExpanded] = useState(isExpanded || isExpandedDeeply)
const [expandedDeeply, setExpandedDeeply] = useState(isExpandedDeeply)
// this implementation assumes that $id is always non-relative URI
const pathToken = identifier || schema.$id || name
const { path } = usePath(pathToken)
const { isExpanded, setExpanded, setCollapsed } = useIsExpanded(pathToken)
const [level, nextLevel] = useLevel()
const isEmbedded = useIsEmbedded()
const isExpandable = fn.isExpandable(schema) || dependentRequired.length > 0
@@ -78,42 +87,39 @@ const JSONSchema = forwardRef(
const KeywordDeprecated = useComponent("KeywordDeprecated")
const KeywordReadOnly = useComponent("KeywordReadOnly")
const KeywordWriteOnly = useComponent("KeywordWriteOnly")
const KeywordExamples = useComponent("KeywordExamples")
const ExtensionKeywords = useComponent("ExtensionKeywords")
const ExpandDeepButton = useComponent("ExpandDeepButton")
/**
* Effects handlers.
*/
useEffect(() => {
setExpandedDeeply(isExpandedDeeply)
}, [isExpandedDeeply])
useEffect(() => {
setExpandedDeeply(expandedDeeply)
}, [expandedDeeply])
/**
* Event handlers.
*/
const handleExpansion = useCallback(
(e, expandedNew) => {
setExpanded(expandedNew)
!expandedNew && setExpandedDeeply(false)
if (expandedNew) {
setExpanded()
} else {
setCollapsed()
}
onExpand(e, expandedNew, false)
},
[onExpand]
[onExpand, setExpanded, setCollapsed]
)
const handleExpansionDeep = useCallback(
(e, expandedDeepNew) => {
setExpanded(expandedDeepNew)
setExpandedDeeply(expandedDeepNew)
if (expandedDeepNew) {
setExpanded({ deep: true })
} else {
setCollapsed({ deep: true })
}
onExpand(e, expandedDeepNew, true)
},
[onExpand]
[onExpand, setExpanded, setCollapsed]
)
return (
<JSONSchemaLevelContext.Provider value={nextLevel}>
<JSONSchemaDeepExpansionContext.Provider value={expandedDeeply}>
<JSONSchemaPathContext.Provider value={path}>
<JSONSchemaLevelContext.Provider value={nextLevel}>
<JSONSchemaCyclesContext.Provider value={renderedSchemas}>
<article
ref={ref}
@@ -126,11 +132,11 @@ const JSONSchema = forwardRef(
<div className="json-schema-2020-12-head">
{isExpandable && !isCircular ? (
<>
<Accordion expanded={expanded} onChange={handleExpansion}>
<Accordion expanded={isExpanded} onChange={handleExpansion}>
<KeywordTitle title={name} schema={schema} />
</Accordion>
<ExpandDeepButton
expanded={expanded}
expanded={isExpanded}
onClick={handleExpansionDeep}
/>
</>
@@ -151,10 +157,10 @@ const JSONSchema = forwardRef(
</div>
<div
className={classNames("json-schema-2020-12-body", {
"json-schema-2020-12-body--collapsed": !expanded,
"json-schema-2020-12-body--collapsed": !isExpanded,
})}
>
{expanded && (
{isExpanded && (
<>
<KeywordDescription schema={schema} />
{!isCircular && isExpandable && (
@@ -186,6 +192,7 @@ const JSONSchema = forwardRef(
dependentRequired={dependentRequired}
/>
<KeywordDefault schema={schema} />
<KeywordExamples schema={schema} />
<Keyword$schema schema={schema} />
<Keyword$vocabulary schema={schema} />
<Keyword$id schema={schema} />
@@ -197,13 +204,14 @@ const JSONSchema = forwardRef(
)}
<Keyword$dynamicRef schema={schema} />
<Keyword$comment schema={schema} />
<ExtensionKeywords schema={schema} />
</>
)}
</div>
</article>
</JSONSchemaCyclesContext.Provider>
</JSONSchemaDeepExpansionContext.Provider>
</JSONSchemaLevelContext.Provider>
</JSONSchemaLevelContext.Provider>
</JSONSchemaPathContext.Provider>
)
}
)
@@ -213,6 +221,7 @@ JSONSchema.propTypes = {
schema: propTypes.schema.isRequired,
dependentRequired: PropTypes.arrayOf(PropTypes.string),
onExpand: PropTypes.func,
identifier: PropTypes.string,
}
export default JSONSchema

View File

@@ -1,8 +1,14 @@
@use "./../../../../../style/variables" as *;
@use "./../../components/mixins";
.json-schema-2020-12 {
margin: 0 20px 15px 20px;
border-radius: 4px;
padding: 12px 0 12px 20px;
background-color: rgba($section-models-model-container-background-color, .05);
background-color: rgba(
$section-models-model-container-background-color,
0.05
);
&:first-of-type {
margin: 20px;
@@ -18,7 +24,7 @@
}
&-body {
@include expansion-border;
@include mixins.expansion-border;
margin: 2px 0;
&--collapsed {
@@ -26,5 +32,3 @@
}
}
}

View File

@@ -0,0 +1,153 @@
/**
* @prettier
*/
import React, { useCallback } from "react"
import PropTypes from "prop-types"
import classNames from "classnames"
import {
useComponent,
useIsExpanded,
useFn,
usePath,
useLevel,
} from "../../hooks"
import { JSONSchemaLevelContext, JSONSchemaPathContext } from "../../context"
import { isEmptyObject, isEmptyArray } from "../../fn"
const JSONViewer = ({ name, value, className }) => {
const fn = useFn()
const { path } = usePath(name)
const { isExpanded, setExpanded, setCollapsed } = useIsExpanded(name)
const [level, nextLevel] = useLevel()
const Accordion = useComponent("Accordion")
const ExpandDeepButton = useComponent("ExpandDeepButton")
const isPrimitive =
typeof value === "string" ||
typeof value === "number" ||
typeof value === "bigint" ||
typeof value === "boolean" ||
typeof value === "symbol" ||
value == null
const isEmpty = isEmptyObject(value) || isEmptyArray(value)
/**
* Event handlers.
*/
const handleExpansion = useCallback(() => {
if (isExpanded) {
setCollapsed()
} else {
setExpanded()
}
}, [isExpanded, setExpanded, setCollapsed])
const handleExpansionDeep = useCallback(
(e, expandedDeepNew) => {
if (expandedDeepNew) {
setExpanded({ deep: true })
} else {
setCollapsed({ deep: true })
}
},
[setExpanded, setCollapsed]
)
/**
* Rendering.
*/
if (isPrimitive) {
return (
<div className={classNames("json-schema-2020-12-json-viewer", className)}>
<span className="json-schema-2020-12-json-viewer__name json-schema-2020-12-json-viewer__name--secondary">
{name}
</span>
<span className="json-schema-2020-12-json-viewer__value json-schema-2020-12-json-viewer__value--secondary">
{fn.stringify(value)}
</span>
</div>
)
}
if (isEmpty) {
return (
<div className={classNames("json-schema-2020-12-json-viewer", className)}>
<span className="json-schema-2020-12-json-viewer__name json-schema-2020-12-json-viewer__name--secondary">
{name}
</span>
<strong className="json-schema-2020-12__attribute json-schema-2020-12__attribute--primary">
{Array.isArray(value) ? "empty array" : "empty object"}
</strong>
</div>
)
}
return (
<JSONSchemaPathContext.Provider value={path}>
<JSONSchemaLevelContext.Provider value={nextLevel}>
<div
className={classNames("json-schema-2020-12-json-viewer", className)}
data-json-schema-level={level}
>
<Accordion expanded={isExpanded} onChange={handleExpansion}>
<span className="json-schema-2020-12-json-viewer__name json-schema-2020-12-json-viewer__name--secondary">
{name}
</span>
</Accordion>
<ExpandDeepButton
expanded={isExpanded}
onClick={handleExpansionDeep}
/>
<strong className="json-schema-2020-12__attribute json-schema-2020-12__attribute--primary">
{Array.isArray(value) ? "array" : "object"}
</strong>
<ul
className={classNames("json-schema-2020-12-json-viewer__children", {
"json-schema-2020-12-json-viewer__children--collapsed":
!isExpanded,
})}
>
{isExpanded && (
<>
{Array.isArray(value)
? value.map((item, index) => (
<li
key={`#${index}`}
className="json-schema-2020-12-property"
>
<JSONViewer
name={`#${index}`}
value={item}
className={className}
/>
</li>
))
: Object.entries(value).map(
([propertyName, propertyValue]) => (
<li
key={propertyName}
className="json-schema-2020-12-property"
>
<JSONViewer
name={propertyName}
value={propertyValue}
className={className}
/>
</li>
)
)}
</>
)}
</ul>
</div>
</JSONSchemaLevelContext.Provider>
</JSONSchemaPathContext.Provider>
)
}
JSONViewer.propTypes = {
name: PropTypes.string.isRequired,
value: PropTypes.any.isRequired,
className: PropTypes.string,
}
export default JSONViewer

View File

@@ -0,0 +1,11 @@
@use "./../mixins";
@use "./../keywords/all";
.json-schema-2020-12-json-viewer {
@include mixins.json-schema-2020-12-keyword;
}
.json-schema-2020-12-json-viewer__name--secondary
+ .json-schema-2020-12-json-viewer__value--secondary::before {
content: "=";
}

View File

@@ -1,9 +1,4 @@
@mixin expansion-border {
margin: 0 0 0 20px;
border-left: 1px dashed rgba($section-models-model-container-background-color, 0.1);
}
@import './JSONSchema/json-schema';
@import './Accordion/accordion';
@import './ExpandDeepButton/expand-deep-button';
@import './keywords/all';
@use "./JSONViewer/json-viewer";
@use "./JSONSchema/json-schema";
@use "./Accordion/accordion";
@use "./ExpandDeepButton/expand-deep-button";

View File

@@ -0,0 +1,82 @@
@use "./../../../../style/variables" as *;
@use "./../../../../style/type";
@mixin expansion-border {
margin: 0 0 0 20px;
border-left: 1px dashed
rgba($section-models-model-container-background-color, 0.1);
}
@mixin json-schema-2020-12-keyword--primary {
color: $text-code-default-font-color;
font-style: normal;
}
@mixin json-schema-2020-12-keyword--extension {
color: #929292;
font-style: italic;
}
@mixin json-schema-2020-12-keyword {
margin: 5px 0 5px 0;
&__children {
@include expansion-border;
padding: 0;
&--collapsed {
display: none;
}
}
&__name {
font-size: 12px;
margin-left: 20px;
font-weight: bold;
&--primary {
@include json-schema-2020-12-keyword--primary;
}
&--secondary {
color: #6b6b6b;
font-style: italic;
}
&--extension {
@include json-schema-2020-12-keyword--extension;
}
}
&__value {
color: #6b6b6b;
font-style: italic;
font-size: 12px;
font-weight: normal;
&--primary {
@include json-schema-2020-12-keyword--primary;
}
&--secondary {
color: #6b6b6b;
font-style: italic;
}
&--extension {
@include json-schema-2020-12-keyword--extension;
}
&--warning {
@include type.text_code();
font-style: normal;
display: inline-block;
margin-left: 10px;
line-height: 1.5;
padding: 1px 4px 1px 4px;
border-radius: 4px;
color: red;
border: 1px dashed red;
}
}
}

View File

@@ -1,19 +1,19 @@
/**
* @prettier
*/
import React, { useCallback, useState } from "react"
import React, { useCallback } from "react"
import classNames from "classnames"
import { schema } from "../../prop-types"
import { useComponent, useIsExpanded, useIsExpandedDeeply } from "../../hooks"
import { JSONSchemaDeepExpansionContext } from "../../context"
import { useComponent, useIsExpanded, usePath, useLevel } from "../../hooks"
import { JSONSchemaLevelContext, JSONSchemaPathContext } from "../../context"
const $defs = ({ schema }) => {
const $defs = schema?.$defs || {}
const isExpanded = useIsExpanded()
const isExpandedDeeply = useIsExpandedDeeply()
const [expanded, setExpanded] = useState(isExpanded || isExpandedDeeply)
const [expandedDeeply, setExpandedDeeply] = useState(false)
const pathToken = "$defs"
const { path } = usePath(pathToken)
const { isExpanded, setExpanded, setCollapsed } = useIsExpanded(pathToken)
const [level, nextLevel] = useLevel()
const Accordion = useComponent("Accordion")
const ExpandDeepButton = useComponent("ExpandDeepButton")
const JSONSchema = useComponent("JSONSchema")
@@ -22,12 +22,22 @@ const $defs = ({ schema }) => {
* Event handlers.
*/
const handleExpansion = useCallback(() => {
setExpanded((prev) => !prev)
}, [])
const handleExpansionDeep = useCallback((e, expandedDeepNew) => {
setExpanded(expandedDeepNew)
setExpandedDeeply(expandedDeepNew)
}, [])
if (isExpanded) {
setCollapsed()
} else {
setExpanded()
}
}, [isExpanded, setExpanded, setCollapsed])
const handleExpansionDeep = useCallback(
(e, expandedDeepNew) => {
if (expandedDeepNew) {
setExpanded({ deep: true })
} else {
setCollapsed({ deep: true })
}
},
[setExpanded, setCollapsed]
)
/**
* Rendering.
@@ -37,34 +47,42 @@ const $defs = ({ schema }) => {
}
return (
<JSONSchemaDeepExpansionContext.Provider value={expandedDeeply}>
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--$defs">
<Accordion expanded={expanded} onChange={handleExpansion}>
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary">
$defs
</span>
</Accordion>
<ExpandDeepButton expanded={expanded} onClick={handleExpansionDeep} />
<strong className="json-schema-2020-12__attribute json-schema-2020-12__attribute--primary">
object
</strong>
<ul
className={classNames("json-schema-2020-12-keyword__children", {
"json-schema-2020-12-keyword__children--collapsed": !expanded,
})}
<JSONSchemaPathContext.Provider value={path}>
<JSONSchemaLevelContext.Provider value={nextLevel}>
<div
className="json-schema-2020-12-keyword json-schema-2020-12-keyword--$defs"
data-json-schema-level={level}
>
{expanded && (
<>
{Object.entries($defs).map(([schemaName, schema]) => (
<li key={schemaName} className="json-schema-2020-12-property">
<JSONSchema name={schemaName} schema={schema} />
</li>
))}
</>
)}
</ul>
</div>
</JSONSchemaDeepExpansionContext.Provider>
<Accordion expanded={isExpanded} onChange={handleExpansion}>
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary">
$defs
</span>
</Accordion>
<ExpandDeepButton
expanded={isExpanded}
onClick={handleExpansionDeep}
/>
<strong className="json-schema-2020-12__attribute json-schema-2020-12__attribute--primary">
object
</strong>
<ul
className={classNames("json-schema-2020-12-keyword__children", {
"json-schema-2020-12-keyword__children--collapsed": !isExpanded,
})}
>
{isExpanded && (
<>
{Object.entries($defs).map(([schemaName, schema]) => (
<li key={schemaName} className="json-schema-2020-12-property">
<JSONSchema name={schemaName} schema={schema} />
</li>
))}
</>
)}
</ul>
</div>
</JSONSchemaLevelContext.Provider>
</JSONSchemaPathContext.Provider>
)
}

View File

@@ -1,25 +1,26 @@
/**
* @prettier
*/
import React, { useCallback, useState } from "react"
import React, { useCallback } from "react"
import classNames from "classnames"
import { schema } from "../../../prop-types"
import {
useComponent,
useIsExpanded,
useIsExpandedDeeply,
} from "../../../hooks"
import { useComponent, useIsExpanded, usePath } from "../../../hooks"
import { JSONSchemaPathContext } from "../../../context"
const $vocabulary = ({ schema }) => {
const isExpanded = useIsExpanded()
const isExpandedDeeply = useIsExpandedDeeply()
const [expanded, setExpanded] = useState(isExpanded || isExpandedDeeply)
const pathToken = "$vocabulary"
const { path } = usePath(pathToken)
const { isExpanded, setExpanded, setCollapsed } = useIsExpanded(pathToken)
const Accordion = useComponent("Accordion")
const handleExpansion = useCallback(() => {
setExpanded((prev) => !prev)
}, [])
if (isExpanded) {
setCollapsed()
} else {
setExpanded()
}
}, [isExpanded, setExpanded, setCollapsed])
/**
* Rendering.
@@ -28,31 +29,33 @@ const $vocabulary = ({ schema }) => {
if (typeof schema.$vocabulary !== "object") return null
return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--$vocabulary">
<Accordion expanded={expanded} onChange={handleExpansion}>
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary">
$vocabulary
</span>
</Accordion>
<strong className="json-schema-2020-12__attribute json-schema-2020-12__attribute--primary">
object
</strong>
<ul>
{expanded &&
Object.entries(schema.$vocabulary).map(([uri, enabled]) => (
<li
key={uri}
className={classNames("json-schema-2020-12-$vocabulary-uri", {
"json-schema-2020-12-$vocabulary-uri--disabled": !enabled,
})}
>
<span className="json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary">
{uri}
</span>
</li>
))}
</ul>
</div>
<JSONSchemaPathContext.Provider value={path}>
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--$vocabulary">
<Accordion expanded={isExpanded} onChange={handleExpansion}>
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--secondary">
$vocabulary
</span>
</Accordion>
<strong className="json-schema-2020-12__attribute json-schema-2020-12__attribute--primary">
object
</strong>
<ul>
{isExpanded &&
Object.entries(schema.$vocabulary).map(([uri, enabled]) => (
<li
key={uri}
className={classNames("json-schema-2020-12-$vocabulary-uri", {
"json-schema-2020-12-$vocabulary-uri--disabled": !enabled,
})}
>
<span className="json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--secondary">
{uri}
</span>
</li>
))}
</ul>
</div>
</JSONSchemaPathContext.Provider>
)
}

View File

@@ -1,7 +1,9 @@
@use "./../../../components/mixins";
.json-schema-2020-12 {
&-keyword--\$vocabulary {
ul {
@include expansion-border;
@include mixins.expansion-border;
}
}

View File

@@ -39,7 +39,11 @@ const AdditionalProperties = ({ schema }) => {
</span>
</>
) : (
<JSONSchema name={name} schema={additionalProperties} />
<JSONSchema
name={name}
schema={additionalProperties}
identifier="additionalProperties"
/>
)}
</div>
)

View File

@@ -1,7 +1,7 @@
/**
* @prettier
*/
import React, { useCallback, useState } from "react"
import React, { useCallback } from "react"
import classNames from "classnames"
import { schema } from "../../prop-types"
@@ -9,17 +9,18 @@ import {
useFn,
useComponent,
useIsExpanded,
useIsExpandedDeeply,
usePath,
useLevel,
} from "../../hooks"
import { JSONSchemaDeepExpansionContext } from "../../context"
import { JSONSchemaLevelContext, JSONSchemaPathContext } from "../../context"
const AllOf = ({ schema }) => {
const allOf = schema?.allOf || []
const fn = useFn()
const isExpanded = useIsExpanded()
const isExpandedDeeply = useIsExpandedDeeply()
const [expanded, setExpanded] = useState(isExpanded || isExpandedDeeply)
const [expandedDeeply, setExpandedDeeply] = useState(false)
const pathToken = "allOf"
const { path } = usePath(pathToken)
const { isExpanded, setExpanded, setCollapsed } = useIsExpanded(pathToken)
const [level, nextLevel] = useLevel()
const Accordion = useComponent("Accordion")
const ExpandDeepButton = useComponent("ExpandDeepButton")
const JSONSchema = useComponent("JSONSchema")
@@ -29,12 +30,22 @@ const AllOf = ({ schema }) => {
* Event handlers.
*/
const handleExpansion = useCallback(() => {
setExpanded((prev) => !prev)
}, [])
const handleExpansionDeep = useCallback((e, expandedDeepNew) => {
setExpanded(expandedDeepNew)
setExpandedDeeply(expandedDeepNew)
}, [])
if (isExpanded) {
setCollapsed()
} else {
setExpanded()
}
}, [isExpanded, setExpanded, setCollapsed])
const handleExpansionDeep = useCallback(
(e, expandedDeepNew) => {
if (expandedDeepNew) {
setExpanded({ deep: true })
} else {
setCollapsed({ deep: true })
}
},
[setExpanded, setCollapsed]
)
/**
* Rendering.
@@ -44,35 +55,46 @@ const AllOf = ({ schema }) => {
}
return (
<JSONSchemaDeepExpansionContext.Provider value={expandedDeeply}>
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--allOf">
<Accordion expanded={expanded} onChange={handleExpansion}>
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary">
All of
</span>
</Accordion>
<ExpandDeepButton expanded={expanded} onClick={handleExpansionDeep} />
<KeywordType schema={{ allOf }} />
<ul
className={classNames("json-schema-2020-12-keyword__children", {
"json-schema-2020-12-keyword__children--collapsed": !expanded,
})}
<JSONSchemaPathContext.Provider value={path}>
<JSONSchemaLevelContext.Provider value={nextLevel}>
<div
className="json-schema-2020-12-keyword json-schema-2020-12-keyword--allOf"
data-json-schema-level={level}
>
{expanded && (
<>
{allOf.map((schema, index) => (
<li key={`#${index}`} className="json-schema-2020-12-property">
<JSONSchema
name={`#${index} ${fn.getTitle(schema)}`}
schema={schema}
/>
</li>
))}
</>
)}
</ul>
</div>
</JSONSchemaDeepExpansionContext.Provider>
<Accordion expanded={isExpanded} onChange={handleExpansion}>
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary">
All of
</span>
</Accordion>
<ExpandDeepButton
expanded={isExpanded}
onClick={handleExpansionDeep}
/>
<KeywordType schema={{ allOf }} />
<ul
className={classNames("json-schema-2020-12-keyword__children", {
"json-schema-2020-12-keyword__children--collapsed": !isExpanded,
})}
>
{isExpanded && (
<>
{allOf.map((schema, index) => (
<li
key={`#${index}`}
className="json-schema-2020-12-property"
>
<JSONSchema
name={`#${index} ${fn.getTitle(schema)}`}
schema={schema}
/>
</li>
))}
</>
)}
</ul>
</div>
</JSONSchemaLevelContext.Provider>
</JSONSchemaPathContext.Provider>
)
}

View File

@@ -1,7 +1,7 @@
/**
* @prettier
*/
import React, { useCallback, useState } from "react"
import React, { useCallback } from "react"
import classNames from "classnames"
import { schema } from "../../prop-types"
@@ -9,17 +9,18 @@ import {
useFn,
useComponent,
useIsExpanded,
useIsExpandedDeeply,
usePath,
useLevel,
} from "../../hooks"
import { JSONSchemaDeepExpansionContext } from "../../context"
import { JSONSchemaLevelContext, JSONSchemaPathContext } from "../../context"
const AnyOf = ({ schema }) => {
const anyOf = schema?.anyOf || []
const fn = useFn()
const isExpanded = useIsExpanded()
const isExpandedDeeply = useIsExpandedDeeply()
const [expanded, setExpanded] = useState(isExpanded || isExpandedDeeply)
const [expandedDeeply, setExpandedDeeply] = useState(false)
const pathToken = "anyOf"
const { path } = usePath(pathToken)
const { isExpanded, setExpanded, setCollapsed } = useIsExpanded(pathToken)
const [level, nextLevel] = useLevel()
const Accordion = useComponent("Accordion")
const ExpandDeepButton = useComponent("ExpandDeepButton")
const JSONSchema = useComponent("JSONSchema")
@@ -29,12 +30,22 @@ const AnyOf = ({ schema }) => {
* Event handlers.
*/
const handleExpansion = useCallback(() => {
setExpanded((prev) => !prev)
}, [])
const handleExpansionDeep = useCallback((e, expandedDeepNew) => {
setExpanded(expandedDeepNew)
setExpandedDeeply(expandedDeepNew)
}, [])
if (isExpanded) {
setCollapsed()
} else {
setExpanded()
}
}, [isExpanded, setExpanded, setCollapsed])
const handleExpansionDeep = useCallback(
(e, expandedDeepNew) => {
if (expandedDeepNew) {
setExpanded({ deep: true })
} else {
setCollapsed({ deep: true })
}
},
[setExpanded, setCollapsed]
)
/**
* Rendering.
@@ -44,35 +55,46 @@ const AnyOf = ({ schema }) => {
}
return (
<JSONSchemaDeepExpansionContext.Provider value={expandedDeeply}>
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--anyOf">
<Accordion expanded={expanded} onChange={handleExpansion}>
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary">
Any of
</span>
</Accordion>
<ExpandDeepButton expanded={expanded} onClick={handleExpansionDeep} />
<KeywordType schema={{ anyOf }} />
<ul
className={classNames("json-schema-2020-12-keyword__children", {
"json-schema-2020-12-keyword__children--collapsed": !expanded,
})}
<JSONSchemaPathContext.Provider value={path}>
<JSONSchemaLevelContext.Provider value={nextLevel}>
<div
className="json-schema-2020-12-keyword json-schema-2020-12-keyword--anyOf"
data-json-schema-level={level}
>
{expanded && (
<>
{anyOf.map((schema, index) => (
<li key={`#${index}`} className="json-schema-2020-12-property">
<JSONSchema
name={`#${index} ${fn.getTitle(schema)}`}
schema={schema}
/>
</li>
))}
</>
)}
</ul>
</div>
</JSONSchemaDeepExpansionContext.Provider>
<Accordion expanded={isExpanded} onChange={handleExpansion}>
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary">
Any of
</span>
</Accordion>
<ExpandDeepButton
expanded={isExpanded}
onClick={handleExpansionDeep}
/>
<KeywordType schema={{ anyOf }} />
<ul
className={classNames("json-schema-2020-12-keyword__children", {
"json-schema-2020-12-keyword__children--collapsed": !isExpanded,
})}
>
{isExpanded && (
<>
{anyOf.map((schema, index) => (
<li
key={`#${index}`}
className="json-schema-2020-12-property"
>
<JSONSchema
name={`#${index} ${fn.getTitle(schema)}`}
schema={schema}
/>
</li>
))}
</>
)}
</ul>
</div>
</JSONSchemaLevelContext.Provider>
</JSONSchemaPathContext.Provider>
)
}

View File

@@ -1,30 +0,0 @@
/**
* @prettier
*/
import React from "react"
import { schema } from "../../prop-types"
import { useFn } from "../../hooks"
const Const = ({ schema }) => {
const fn = useFn()
if (!fn.hasKeyword(schema, "const")) return null
return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--const">
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary">
Const
</span>
<span className="json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--const">
{fn.stringify(schema.const)}
</span>
</div>
)
}
Const.propTypes = {
schema: schema.isRequired,
}
export default Const

View File

@@ -0,0 +1,28 @@
/**
* @prettier
*/
import React from "react"
import { schema } from "../../../prop-types"
import { useComponent, useFn } from "../../../hooks"
const Const = ({ schema }) => {
const fn = useFn()
const JSONViewer = useComponent("JSONViewer")
if (!fn.hasKeyword(schema, "const")) return null
return (
<JSONViewer
name="Const"
value={schema.const}
className="json-schema-2020-12-keyword json-schema-2020-12-keyword--const"
/>
)
}
Const.propTypes = {
schema: schema.isRequired,
}
export default Const

View File

@@ -0,0 +1,11 @@
@use "./../../mixins";
.json-schema-2020-12-keyword--const {
.json-schema-2020-12-json-viewer__name {
@include mixins.json-schema-2020-12-keyword--primary;
}
.json-schema-2020-12-json-viewer__value {
@include mixins.json-schema-2020-12-keyword--primary;
}
}

View File

@@ -1,14 +1,16 @@
@use "./../../../../../../style/type";
.json-schema-2020-12__constraint {
@include text_code();
@include type.text_code();
margin-left: 10px;
line-height: 1.5;
padding: 1px 3px;
color: white;
background-color: #805AD5;
background-color: #805ad5;
border-radius: 4px;
&--string {
color: white;
background-color: #D69E2E;
background-color: #d69e2e;
}
}

View File

@@ -23,7 +23,7 @@ const Contains = ({ schema }) => {
return (
<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} identifier="contains" />
</div>
)
}

View File

@@ -23,7 +23,11 @@ const ContentSchema = ({ schema }) => {
return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--contentSchema">
<JSONSchema name={name} schema={schema.contentSchema} />
<JSONSchema
name={name}
schema={schema.contentSchema}
identifier="contentSchema"
/>
</div>
)
}

View File

@@ -1,30 +0,0 @@
/**
* @prettier
*/
import React from "react"
import { schema } from "../../prop-types"
import { useFn } from "../../hooks"
const Default = ({ schema }) => {
const fn = useFn()
if (!fn.hasKeyword(schema, "default")) return null
return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--default">
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary">
Default
</span>
<span className="json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--const">
{fn.stringify(schema.default)}
</span>
</div>
)
}
Default.propTypes = {
schema: schema.isRequired,
}
export default Default

View File

@@ -0,0 +1,28 @@
/**
* @prettier
*/
import React from "react"
import { schema } from "../../../prop-types"
import { useComponent, useFn } from "../../../hooks"
const Default = ({ schema }) => {
const fn = useFn()
const JSONViewer = useComponent("JSONViewer")
if (!fn.hasKeyword(schema, "default")) return null
return (
<JSONViewer
name="Default"
value={schema.default}
className="json-schema-2020-12-keyword json-schema-2020-12-keyword--default"
/>
)
}
Default.propTypes = {
schema: schema.isRequired,
}
export default Default

View File

@@ -0,0 +1,11 @@
@use "./../../mixins";
.json-schema-2020-12-keyword--default {
.json-schema-2020-12-json-viewer__name {
@include mixins.json-schema-2020-12-keyword--primary;
}
.json-schema-2020-12-json-viewer__value {
@include mixins.json-schema-2020-12-keyword--primary;
}
}

View File

@@ -1,19 +1,19 @@
/**
* @prettier
*/
import React, { useCallback, useState } from "react"
import React, { useCallback } from "react"
import classNames from "classnames"
import { schema } from "../../prop-types"
import { useComponent, useIsExpanded, useIsExpandedDeeply } from "../../hooks"
import { JSONSchemaDeepExpansionContext } from "../../context"
import { useComponent, useIsExpanded, useLevel, usePath } from "../../hooks"
import { JSONSchemaLevelContext, JSONSchemaPathContext } from "../../context"
const DependentSchemas = ({ schema }) => {
const dependentSchemas = schema?.dependentSchemas || []
const isExpanded = useIsExpanded()
const isExpandedDeeply = useIsExpandedDeeply()
const [expanded, setExpanded] = useState(isExpanded || isExpandedDeeply)
const [expandedDeeply, setExpandedDeeply] = useState(false)
const pathToken = "dependentSchemas"
const { path } = usePath(pathToken)
const { isExpanded, setExpanded, setCollapsed } = useIsExpanded(pathToken)
const [level, nextLevel] = useLevel()
const Accordion = useComponent("Accordion")
const ExpandDeepButton = useComponent("ExpandDeepButton")
const JSONSchema = useComponent("JSONSchema")
@@ -22,12 +22,22 @@ const DependentSchemas = ({ schema }) => {
* Event handlers.
*/
const handleExpansion = useCallback(() => {
setExpanded((prev) => !prev)
}, [])
const handleExpansionDeep = useCallback((e, expandedDeepNew) => {
setExpanded(expandedDeepNew)
setExpandedDeeply(expandedDeepNew)
}, [])
if (isExpanded) {
setCollapsed()
} else {
setExpanded()
}
}, [isExpanded, setExpanded, setCollapsed])
const handleExpansionDeep = useCallback(
(e, expandedDeepNew) => {
if (expandedDeepNew) {
setExpanded({ deep: true })
} else {
setCollapsed({ deep: true })
}
},
[setExpanded, setCollapsed]
)
/**
* Rendering.
@@ -36,34 +46,47 @@ const DependentSchemas = ({ schema }) => {
if (Object.keys(dependentSchemas).length === 0) return null
return (
<JSONSchemaDeepExpansionContext.Provider value={expandedDeeply}>
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--dependentSchemas">
<Accordion expanded={expanded} onChange={handleExpansion}>
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary">
Dependent schemas
</span>
</Accordion>
<ExpandDeepButton expanded={expanded} onClick={handleExpansionDeep} />
<strong className="json-schema-2020-12__attribute json-schema-2020-12__attribute--primary">
object
</strong>
<ul
className={classNames("json-schema-2020-12-keyword__children", {
"json-schema-2020-12-keyword__children--collapsed": !expanded,
})}
<JSONSchemaPathContext.Provider value={path}>
<JSONSchemaLevelContext.Provider value={nextLevel}>
<div
className="json-schema-2020-12-keyword json-schema-2020-12-keyword--dependentSchemas"
data-json-schema-level={level}
>
{expanded && (
<>
{Object.entries(dependentSchemas).map(([schemaName, schema]) => (
<li key={schemaName} className="json-schema-2020-12-property">
<JSONSchema name={schemaName} schema={schema} />
</li>
))}
</>
)}
</ul>
</div>
</JSONSchemaDeepExpansionContext.Provider>
<Accordion expanded={isExpanded} onChange={handleExpansion}>
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary">
Dependent schemas
</span>
</Accordion>
<ExpandDeepButton
expanded={isExpanded}
onClick={handleExpansionDeep}
/>
<strong className="json-schema-2020-12__attribute json-schema-2020-12__attribute--primary">
object
</strong>
<ul
className={classNames("json-schema-2020-12-keyword__children", {
"json-schema-2020-12-keyword__children--collapsed": !isExpanded,
})}
>
{isExpanded && (
<>
{Object.entries(dependentSchemas).map(
([schemaName, schema]) => (
<li
key={schemaName}
className="json-schema-2020-12-property"
>
<JSONSchema name={schemaName} schema={schema} />
</li>
)
)}
</>
)}
</ul>
</div>
</JSONSchemaLevelContext.Provider>
</JSONSchemaPathContext.Provider>
)
}

View File

@@ -23,7 +23,7 @@ const Else = ({ schema }) => {
return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--if">
<JSONSchema name={name} schema={schema.else} />
<JSONSchema name={name} schema={schema.else} identifier="else" />
</div>
)
}

View File

@@ -4,32 +4,19 @@
import React from "react"
import { schema } from "../../../prop-types"
import { useFn } from "../../../hooks"
import { useComponent } from "../../../hooks"
const Enum = ({ schema }) => {
const fn = useFn()
const JSONViewer = useComponent("JSONViewer")
if (!Array.isArray(schema?.enum)) return null
return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--enum">
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary">
Allowed values
</span>
<ul>
{schema.enum.map((element) => {
const strigifiedElement = fn.stringify(element)
return (
<li key={strigifiedElement}>
<span className="json-schema-2020-12-keyword__value json-schema-2020-12-keyword__value--const">
{strigifiedElement}
</span>
</li>
)
})}
</ul>
</div>
<JSONViewer
name="Enum"
value={schema.enum}
className="json-schema-2020-12-keyword json-schema-2020-12-keyword--enum"
/>
)
}

View File

@@ -1,12 +1,11 @@
.json-schema-2020-12-keyword--enum {
& > ul {
display: inline-block;
padding: 0;
margin: 0;
@use "./../../mixins";
li {
display: inline;
list-style-type: none;
}
.json-schema-2020-12-keyword--enum {
.json-schema-2020-12-json-viewer__name {
@include mixins.json-schema-2020-12-keyword--primary;
}
.json-schema-2020-12-json-viewer__value {
@include mixins.json-schema-2020-12-keyword--primary;
}
}

View File

@@ -0,0 +1,30 @@
/**
* @prettier
*/
import React from "react"
import { schema } from "../../../prop-types"
import { useComponent } from "../../../hooks"
const Examples = ({ schema }) => {
const examples = schema?.examples || []
const JSONViewer = useComponent("JSONViewer")
if (!Array.isArray(examples) || examples.length === 0) {
return null
}
return (
<JSONViewer
name="Examples"
value={schema.examples}
className="json-schema-2020-12-keyword json-schema-2020-12-keyword--examples"
/>
)
}
Examples.propTypes = {
schema: schema.isRequired,
}
export default Examples

View File

@@ -0,0 +1,11 @@
@use "./../../mixins";
.json-schema-2020-12-keyword--examples {
.json-schema-2020-12-json-viewer__name {
@include mixins.json-schema-2020-12-keyword--primary;
}
.json-schema-2020-12-json-viewer__value {
@include mixins.json-schema-2020-12-keyword--primary;
}
}

View File

@@ -0,0 +1,103 @@
/**
* @prettier
*/
import React, { useCallback } from "react"
import classNames from "classnames"
import { schema } from "../../../prop-types"
import {
useComponent,
useIsExpanded,
useFn,
usePath,
useLevel,
useConfig,
} from "../../../hooks"
import { JSONSchemaLevelContext, JSONSchemaPathContext } from "../../../context"
const ExtensionKeywords = ({ schema }) => {
const fn = useFn()
const pathToken = "ExtensionKeywords"
const { path } = usePath(pathToken)
const { isExpanded, setExpanded, setCollapsed } = useIsExpanded(pathToken)
const [level, nextLevel] = useLevel()
const Accordion = useComponent("Accordion")
const ExpandDeepButton = useComponent("ExpandDeepButton")
const JSONViewer = useComponent("JSONViewer")
const { showExtensionKeywords } = useConfig("showExtensionKeywords")
const extensionKeywords = fn.getExtensionKeywords(schema)
/**
* Event handlers.
*/
const handleExpansion = useCallback(() => {
if (isExpanded) {
setCollapsed()
} else {
setExpanded()
}
}, [isExpanded, setExpanded, setCollapsed])
const handleExpansionDeep = useCallback(
(e, expandedDeepNew) => {
if (expandedDeepNew) {
setExpanded({ deep: true })
} else {
setCollapsed({ deep: true })
}
},
[setExpanded, setCollapsed]
)
/**
* Rendering.
*/
if (!showExtensionKeywords || extensionKeywords.length === 0) {
return null
}
return (
<JSONSchemaPathContext.Provider value={path}>
<JSONSchemaLevelContext.Provider value={nextLevel}>
<div
className="json-schema-2020-12-keyword json-schema-2020-12-keyword--extension-keywords"
data-json-schema-level={level}
>
<Accordion expanded={isExpanded} onChange={handleExpansion}>
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--extension">
Extension Keywords
</span>
</Accordion>
<ExpandDeepButton
expanded={isExpanded}
onClick={handleExpansionDeep}
/>
<ul
className={classNames("json-schema-2020-12-keyword__children", {
"json-schema-2020-12-keyword__children--collapsed": !isExpanded,
})}
>
{isExpanded && (
<>
{extensionKeywords.map((keyword) => (
<JSONViewer
key={keyword}
name={keyword}
value={schema[keyword]}
className="json-schema-2020-12-json-viewer-extension-keyword"
/>
))}
</>
)}
</ul>
</div>
</JSONSchemaLevelContext.Provider>
</JSONSchemaPathContext.Provider>
)
}
ExtensionKeywords.propTypes = {
schema: schema.isRequired,
}
export default ExtensionKeywords

View File

@@ -0,0 +1,11 @@
@use "./../../mixins";
.json-schema-2020-12-json-viewer-extension-keyword {
.json-schema-2020-12-json-viewer__name {
@include mixins.json-schema-2020-12-keyword--extension;
}
.json-schema-2020-12-json-viewer__value {
@include mixins.json-schema-2020-12-keyword--extension;
}
}

View File

@@ -23,7 +23,7 @@ const If = ({ schema }) => {
return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--if">
<JSONSchema name={name} schema={schema.if} />
<JSONSchema name={name} schema={schema.if} identifier="if" />
</div>
)
}

View File

@@ -23,7 +23,7 @@ const Items = ({ schema }) => {
return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--items">
<JSONSchema name={name} schema={schema.items} />
<JSONSchema name={name} schema={schema.items} identifier="items" />
</div>
)
}

View File

@@ -23,7 +23,7 @@ const Not = ({ schema }) => {
return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--not">
<JSONSchema name={name} schema={schema.not} />
<JSONSchema name={name} schema={schema.not} identifier="not" />
</div>
)
}

View File

@@ -1,7 +1,7 @@
/**
* @prettier
*/
import React, { useCallback, useState } from "react"
import React, { useCallback } from "react"
import classNames from "classnames"
import { schema } from "../../prop-types"
@@ -9,17 +9,18 @@ import {
useFn,
useComponent,
useIsExpanded,
useIsExpandedDeeply,
usePath,
useLevel,
} from "../../hooks"
import { JSONSchemaDeepExpansionContext } from "../../context"
import { JSONSchemaLevelContext, JSONSchemaPathContext } from "../../context"
const OneOf = ({ schema }) => {
const oneOf = schema?.oneOf || []
const fn = useFn()
const isExpanded = useIsExpanded()
const isExpandedDeeply = useIsExpandedDeeply()
const [expanded, setExpanded] = useState(isExpanded || isExpandedDeeply)
const [expandedDeeply, setExpandedDeeply] = useState(false)
const pathToken = "oneOf"
const { path } = usePath(pathToken)
const { isExpanded, setExpanded, setCollapsed } = useIsExpanded(pathToken)
const [level, nextLevel] = useLevel()
const Accordion = useComponent("Accordion")
const ExpandDeepButton = useComponent("ExpandDeepButton")
const JSONSchema = useComponent("JSONSchema")
@@ -29,12 +30,22 @@ const OneOf = ({ schema }) => {
* Event handlers.
*/
const handleExpansion = useCallback(() => {
setExpanded((prev) => !prev)
}, [])
const handleExpansionDeep = useCallback((e, expandedDeepNew) => {
setExpanded(expandedDeepNew)
setExpandedDeeply(expandedDeepNew)
}, [])
if (isExpanded) {
setCollapsed()
} else {
setExpanded()
}
}, [isExpanded, setExpanded, setCollapsed])
const handleExpansionDeep = useCallback(
(e, expandedDeepNew) => {
if (expandedDeepNew) {
setExpanded({ deep: true })
} else {
setCollapsed({ deep: true })
}
},
[setExpanded, setCollapsed]
)
/**
* Rendering.
@@ -44,35 +55,46 @@ const OneOf = ({ schema }) => {
}
return (
<JSONSchemaDeepExpansionContext.Provider value={expandedDeeply}>
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--oneOf">
<Accordion expanded={expanded} onChange={handleExpansion}>
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary">
One of
</span>
</Accordion>
<ExpandDeepButton expanded={expanded} onClick={handleExpansionDeep} />
<KeywordType schema={{ oneOf }} />
<ul
className={classNames("json-schema-2020-12-keyword__children", {
"json-schema-2020-12-keyword__children--collapsed": !expanded,
})}
<JSONSchemaPathContext.Provider value={path}>
<JSONSchemaLevelContext.Provider value={nextLevel}>
<div
className="json-schema-2020-12-keyword json-schema-2020-12-keyword--oneOf"
data-json-schema-level={level}
>
{expanded && (
<>
{oneOf.map((schema, index) => (
<li key={`#${index}`} className="json-schema-2020-12-property">
<JSONSchema
name={`#${index} ${fn.getTitle(schema)}`}
schema={schema}
/>
</li>
))}
</>
)}
</ul>
</div>
</JSONSchemaDeepExpansionContext.Provider>
<Accordion expanded={isExpanded} onChange={handleExpansion}>
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary">
One of
</span>
</Accordion>
<ExpandDeepButton
expanded={isExpanded}
onClick={handleExpansionDeep}
/>
<KeywordType schema={{ oneOf }} />
<ul
className={classNames("json-schema-2020-12-keyword__children", {
"json-schema-2020-12-keyword__children--collapsed": !isExpanded,
})}
>
{isExpanded && (
<>
{oneOf.map((schema, index) => (
<li
key={`#${index}`}
className="json-schema-2020-12-property"
>
<JSONSchema
name={`#${index} ${fn.getTitle(schema)}`}
schema={schema}
/>
</li>
))}
</>
)}
</ul>
</div>
</JSONSchemaLevelContext.Provider>
</JSONSchemaPathContext.Provider>
)
}

View File

@@ -4,11 +4,13 @@
import React from "react"
import { schema } from "../../../prop-types"
import { useComponent } from "../../../hooks"
import { useComponent, usePath } from "../../../hooks"
import { JSONSchemaPathContext } from "../../../context"
const PatternProperties = ({ schema }) => {
const patternProperties = schema?.patternProperties || {}
const JSONSchema = useComponent("JSONSchema")
const { path } = usePath("patternProperties")
/**
* Rendering.
@@ -18,15 +20,17 @@ const PatternProperties = ({ schema }) => {
}
return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--patternProperties">
<ul>
{Object.entries(patternProperties).map(([propertyName, schema]) => (
<li key={propertyName} className="json-schema-2020-12-property">
<JSONSchema name={propertyName} schema={schema} />
</li>
))}
</ul>
</div>
<JSONSchemaPathContext.Provider value={path}>
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--patternProperties">
<ul>
{Object.entries(patternProperties).map(([propertyName, schema]) => (
<li key={propertyName} className="json-schema-2020-12-property">
<JSONSchema name={propertyName} schema={schema} />
</li>
))}
</ul>
</div>
</JSONSchemaPathContext.Provider>
)
}

View File

@@ -1,3 +1,5 @@
@use "./../../../../../../style/variables" as *;
.json-schema-2020-12 {
&-keyword--patternProperties {
ul {

View File

@@ -1,25 +1,26 @@
/**
* @prettier
*/
import React, { useCallback, useState } from "react"
import React, { useCallback } from "react"
import classNames from "classnames"
import { schema } from "../../prop-types"
import {
useFn,
useComponent,
useIsExpandedDeeply,
useIsExpanded,
usePath,
useLevel,
} from "../../hooks"
import { JSONSchemaDeepExpansionContext } from "../../context"
import { JSONSchemaLevelContext, JSONSchemaPathContext } from "../../context"
const PrefixItems = ({ schema }) => {
const prefixItems = schema?.prefixItems || []
const fn = useFn()
const isExpanded = useIsExpanded()
const isExpandedDeeply = useIsExpandedDeeply()
const [expanded, setExpanded] = useState(isExpanded || isExpandedDeeply)
const [expandedDeeply, setExpandedDeeply] = useState(false)
const pathToken = "prefixItems"
const { path } = usePath(pathToken)
const { isExpanded, setExpanded, setCollapsed } = useIsExpanded(pathToken)
const [level, nextLevel] = useLevel()
const Accordion = useComponent("Accordion")
const ExpandDeepButton = useComponent("ExpandDeepButton")
const JSONSchema = useComponent("JSONSchema")
@@ -29,12 +30,22 @@ const PrefixItems = ({ schema }) => {
* Event handlers.
*/
const handleExpansion = useCallback(() => {
setExpanded((prev) => !prev)
}, [])
const handleExpansionDeep = useCallback((e, expandedDeepNew) => {
setExpanded(expandedDeepNew)
setExpandedDeeply(expandedDeepNew)
}, [])
if (isExpanded) {
setCollapsed()
} else {
setExpanded()
}
}, [isExpanded, setExpanded, setCollapsed])
const handleExpansionDeep = useCallback(
(e, expandedDeepNew) => {
if (expandedDeepNew) {
setExpanded({ deep: true })
} else {
setCollapsed({ deep: true })
}
},
[setExpanded, setCollapsed]
)
/**
* Rendering.
@@ -44,35 +55,46 @@ const PrefixItems = ({ schema }) => {
}
return (
<JSONSchemaDeepExpansionContext.Provider value={expandedDeeply}>
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--prefixItems">
<Accordion expanded={expanded} onChange={handleExpansion}>
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary">
Prefix items
</span>
</Accordion>
<ExpandDeepButton expanded={expanded} onClick={handleExpansionDeep} />
<KeywordType schema={{ prefixItems }} />
<ul
className={classNames("json-schema-2020-12-keyword__children", {
"json-schema-2020-12-keyword__children--collapsed": !expanded,
})}
<JSONSchemaPathContext.Provider value={path}>
<JSONSchemaLevelContext.Provider value={nextLevel}>
<div
className="json-schema-2020-12-keyword json-schema-2020-12-keyword--prefixItems"
data-json-schema-level={level}
>
{expanded && (
<>
{prefixItems.map((schema, index) => (
<li key={`#${index}`} className="json-schema-2020-12-property">
<JSONSchema
name={`#${index} ${fn.getTitle(schema)}`}
schema={schema}
/>
</li>
))}
</>
)}
</ul>
</div>
</JSONSchemaDeepExpansionContext.Provider>
<Accordion expanded={isExpanded} onChange={handleExpansion}>
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary">
Prefix items
</span>
</Accordion>
<ExpandDeepButton
expanded={isExpanded}
onClick={handleExpansionDeep}
/>
<KeywordType schema={{ prefixItems }} />
<ul
className={classNames("json-schema-2020-12-keyword__children", {
"json-schema-2020-12-keyword__children--collapsed": !isExpanded,
})}
>
{isExpanded && (
<>
{prefixItems.map((schema, index) => (
<li
key={`#${index}`}
className="json-schema-2020-12-property"
>
<JSONSchema
name={`#${index} ${fn.getTitle(schema)}`}
schema={schema}
/>
</li>
))}
</>
)}
</ul>
</div>
</JSONSchemaLevelContext.Provider>
</JSONSchemaPathContext.Provider>
)
}

View File

@@ -5,13 +5,15 @@ import React from "react"
import classNames from "classnames"
import { schema } from "../../../prop-types"
import { useFn, useComponent } from "../../../hooks"
import { useFn, useComponent, usePath } from "../../../hooks"
import { JSONSchemaPathContext } from "../../../context"
const Properties = ({ schema }) => {
const fn = useFn()
const properties = schema?.properties || {}
const required = Array.isArray(schema?.required) ? schema.required : []
const JSONSchema = useComponent("JSONSchema")
const { path } = usePath("properties")
/**
* Rendering.
@@ -21,32 +23,34 @@ const Properties = ({ schema }) => {
}
return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--properties">
<ul>
{Object.entries(properties).map(([propertyName, propertySchema]) => {
const isRequired = required.includes(propertyName)
const dependentRequired = fn.getDependentRequired(
propertyName,
schema
)
<JSONSchemaPathContext.Provider value={path}>
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--properties">
<ul>
{Object.entries(properties).map(([propertyName, propertySchema]) => {
const isRequired = required.includes(propertyName)
const dependentRequired = fn.getDependentRequired(
propertyName,
schema
)
return (
<li
key={propertyName}
className={classNames("json-schema-2020-12-property", {
"json-schema-2020-12-property--required": isRequired,
})}
>
<JSONSchema
name={propertyName}
schema={propertySchema}
dependentRequired={dependentRequired}
/>
</li>
)
})}
</ul>
</div>
return (
<li
key={propertyName}
className={classNames("json-schema-2020-12-property", {
"json-schema-2020-12-property--required": isRequired,
})}
>
<JSONSchema
name={propertyName}
schema={propertySchema}
dependentRequired={dependentRequired}
/>
</li>
)
})}
</ul>
</div>
</JSONSchemaPathContext.Provider>
)
}

View File

@@ -11,8 +11,11 @@
list-style-type: none;
&--required {
& > .json-schema-2020-12:first-of-type > .json-schema-2020-12-head .json-schema-2020-12__title:after {
content: '*';
&
> .json-schema-2020-12:first-of-type
> .json-schema-2020-12-head
.json-schema-2020-12__title:after {
content: "*";
color: red;
font-weight: bold;
}

View File

@@ -23,7 +23,11 @@ const PropertyNames = ({ schema }) => {
return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--propertyNames">
<JSONSchema name={name} schema={propertyNames} />
<JSONSchema
name={name}
schema={propertyNames}
identifier="propertyNames"
/>
</div>
)
}

View File

@@ -23,7 +23,7 @@ const Then = ({ schema }) => {
return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--then">
<JSONSchema name={name} schema={schema.then} />
<JSONSchema name={name} schema={schema.then} identifier="then" />
</div>
)
}

View File

@@ -1,6 +1,9 @@
@use "./../../../../../../style/variables" as *;
@use "./../../../../../../style/type";
.json-schema-2020-12 {
&__title {
@include text_headline($section-models-model-title-font-color);
@include type.text_headline($section-models-model-title-font-color);
display: inline-block;
font-weight: bold;
font-size: 12px;
@@ -15,7 +18,7 @@
margin: 7px 0;
.json-schema-2020-12__title {
@include text_code();
@include type.text_code();
font-size: 12px;
vertical-align: middle;
}

View File

@@ -24,7 +24,11 @@ const UnevaluatedItems = ({ schema }) => {
return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--unevaluatedItems">
<JSONSchema name={name} schema={unevaluatedItems} />
<JSONSchema
name={name}
schema={unevaluatedItems}
identifier="unevaluatedItems"
/>
</div>
)
}

View File

@@ -24,7 +24,11 @@ const UnevaluatedProperties = ({ schema }) => {
return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--unevaluatedProperties">
<JSONSchema name={name} schema={unevaluatedProperties} />
<JSONSchema
name={name}
schema={unevaluatedProperties}
identifier="unevaluatedProperties"
/>
</div>
)
}

View File

@@ -1,68 +1,25 @@
@use "./../../../../../style/variables" as *;
@use "./../mixins";
@use "./$vocabulary/$vocabulary" as vocabulary;
@use "./Const/const";
@use "./Constraint/constraint";
@use "./Default/default";
@use "./DependentRequired/dependent-required";
@use "./Description/description";
@use "./Enum/enum";
@use "./Examples/examples";
@use "./ExtensionKeywords/extension-keywords";
@use "./PatternProperties/pattern-properties";
@use "./Properties/properties";
@use "./Title/title";
.json-schema-2020-12-keyword {
margin: 5px 0 5px 0;
&__children {
@include expansion-border;
padding: 0;
&--collapsed {
display: none;
}
}
&__name {
font-size: 12px;
margin-left: 20px;
font-weight: bold;
&--primary {
color: $text-code-default-font-color;
font-style: normal;
}
&--secondary {
color: #6b6b6b;
font-style: italic;
}
}
&__value {
color: #6b6b6b;
font-style: italic;
font-size: 12px;
font-weight: normal;
&--primary {
color: $text-code-default-font-color;
font-style: normal;
}
&--secondary {
color: #6b6b6b;
font-style: italic;
}
&--const {
@include text_code();
color: #6b6b6b;
font-style: normal;
display: inline-block;
margin-left: 10px;
line-height: 1.5;
padding: 1px 4px 1px 4px;
border: 1px dashed #6b6b6b;
border-radius: 4px;
}
&--warning {
@extend .json-schema-2020-12-keyword__value--const;
color: red;
border: 1px dashed red;
}
}
@include mixins.json-schema-2020-12-keyword;
}
.json-schema-2020-12-keyword__name--secondary + .json-schema-2020-12-keyword__value--secondary::before {
content: '='
.json-schema-2020-12-keyword__name--secondary
+ .json-schema-2020-12-keyword__value--secondary::before {
content: "=";
}
.json-schema-2020-12__attribute {
@@ -72,7 +29,7 @@
text-transform: lowercase;
padding-left: 10px;
&--primary {
&--primary {
color: $prop-type-font-color;
}
@@ -84,12 +41,3 @@
color: red;
}
}
@import './$vocabulary/$vocabulary';
@import './Description/description';
@import './Title/title';
@import './Properties/properties';
@import './PatternProperties/pattern-properties';
@import './Enum/enum';
@import './Constraint/constraint';
@import './DependentRequired/dependent-required';

View File

@@ -9,7 +9,6 @@ JSONSchemaContext.displayName = "JSONSchemaContext"
export const JSONSchemaLevelContext = createContext(0)
JSONSchemaLevelContext.displayName = "JSONSchemaLevelContext"
export const JSONSchemaDeepExpansionContext = createContext(false)
JSONSchemaDeepExpansionContext.displayName = "JSONSchemaDeepExpansionContext"
export const JSONSchemaCyclesContext = createContext(new Set())
export const JSONSchemaPathContext = createContext([])

View File

@@ -0,0 +1,10 @@
/**
* @prettier
*/
export class JSONSchemaIsExpandedState {
static Collapsed = "collapsed"
static Expanded = "expanded"
static DeeplyExpanded = "deeply-expanded"
}

View File

@@ -1,8 +1,6 @@
/**
* @prettier
*/
import { useFn } from "./hooks"
export const upperFirst = (value) => {
if (typeof value === "string") {
return `${value.charAt(0).toUpperCase()}${value.slice(1)}`
@@ -13,148 +11,156 @@ export const upperFirst = (value) => {
/**
* Lookup can be `basic` or `extended`. By default the lookup is `extended`.
*/
export const getTitle = (schema, { lookup = "extended" } = {}) => {
const fn = useFn()
export const makeGetTitle = (fnAccessor) => {
const getTitle = (schema, { lookup = "extended" } = {}) => {
const fn = fnAccessor()
if (schema?.title != null) return fn.upperFirst(String(schema.title))
if (lookup === "extended") {
if (schema?.$anchor != null) return fn.upperFirst(String(schema.$anchor))
if (schema?.$id != null) return String(schema.$id)
if (schema?.title != null) return fn.upperFirst(String(schema.title))
if (lookup === "extended") {
if (schema?.$anchor != null) return fn.upperFirst(String(schema.$anchor))
if (schema?.$id != null) return String(schema.$id)
}
return ""
}
return ""
return getTitle
}
export const getType = (schema, processedSchemas = new WeakSet()) => {
const fn = useFn()
export const makeGetType = (fnAccessor) => {
const getType = (schema, processedSchemas = new WeakSet()) => {
const fn = fnAccessor()
if (schema == null) {
return "any"
}
if (fn.isBooleanJSONSchema(schema)) {
return schema ? "any" : "never"
}
if (typeof schema !== "object") {
return "any"
}
if (processedSchemas.has(schema)) {
return "any" // detect a cycle
}
processedSchemas.add(schema)
const { type, prefixItems, items } = schema
const getArrayType = () => {
if (Array.isArray(prefixItems)) {
const prefixItemsTypes = prefixItems.map((itemSchema) =>
getType(itemSchema, processedSchemas)
)
const itemsType = items ? getType(items, processedSchemas) : "any"
return `array<[${prefixItemsTypes.join(", ")}], ${itemsType}>`
} else if (items) {
const itemsType = getType(items, processedSchemas)
return `array<${itemsType}>`
} else {
return "array<any>"
if (schema == null) {
return "any"
}
}
const inferType = () => {
if (
Object.hasOwn(schema, "prefixItems") ||
Object.hasOwn(schema, "items") ||
Object.hasOwn(schema, "contains")
) {
return getArrayType()
} else if (
Object.hasOwn(schema, "properties") ||
Object.hasOwn(schema, "additionalProperties") ||
Object.hasOwn(schema, "patternProperties")
) {
return "object"
} else if (["int32", "int64"].includes(schema.format)) {
// OpenAPI 3.1.0 integer custom formats
return "integer"
} else if (["float", "double"].includes(schema.format)) {
// OpenAPI 3.1.0 number custom formats
return "number"
} else if (
Object.hasOwn(schema, "minimum") ||
Object.hasOwn(schema, "maximum") ||
Object.hasOwn(schema, "exclusiveMinimum") ||
Object.hasOwn(schema, "exclusiveMaximum") ||
Object.hasOwn(schema, "multipleOf")
) {
return "number | integer"
} else if (
Object.hasOwn(schema, "pattern") ||
Object.hasOwn(schema, "format") ||
Object.hasOwn(schema, "minLength") ||
Object.hasOwn(schema, "maxLength")
) {
return "string"
} else if (typeof schema.const !== "undefined") {
if (schema.const === null) {
return "null"
} else if (typeof schema.const === "boolean") {
return "boolean"
} else if (typeof schema.const === "number") {
return Number.isInteger(schema.const) ? "integer" : "number"
} else if (typeof schema.const === "string") {
return "string"
} else if (Array.isArray(schema.const)) {
if (fn.isBooleanJSONSchema(schema)) {
return schema ? "any" : "never"
}
if (typeof schema !== "object") {
return "any"
}
if (processedSchemas.has(schema)) {
return "any" // detect a cycle
}
processedSchemas.add(schema)
const { type, prefixItems, items } = schema
const getArrayType = () => {
if (Array.isArray(prefixItems)) {
const prefixItemsTypes = prefixItems.map((itemSchema) =>
getType(itemSchema, processedSchemas)
)
const itemsType = items ? getType(items, processedSchemas) : "any"
return `array<[${prefixItemsTypes.join(", ")}], ${itemsType}>`
} else if (items) {
const itemsType = getType(items, processedSchemas)
return `array<${itemsType}>`
} else {
return "array<any>"
} else if (typeof schema.const === "object") {
return "object"
}
}
return null
}
if (schema.not && getType(schema.not) === "any") {
return "never"
}
const typeString = Array.isArray(type)
? type.map((t) => (t === "array" ? getArrayType() : t)).join(" | ")
: type === "array"
? getArrayType()
: [
"null",
"boolean",
"object",
"array",
"number",
"integer",
"string",
].includes(type)
? type
: inferType()
const handleCombiningKeywords = (keyword, separator) => {
if (Array.isArray(schema[keyword])) {
const combinedTypes = schema[keyword].map((subSchema) =>
getType(subSchema, processedSchemas)
)
return `(${combinedTypes.join(separator)})`
const inferType = () => {
if (
Object.hasOwn(schema, "prefixItems") ||
Object.hasOwn(schema, "items") ||
Object.hasOwn(schema, "contains")
) {
return getArrayType()
} else if (
Object.hasOwn(schema, "properties") ||
Object.hasOwn(schema, "additionalProperties") ||
Object.hasOwn(schema, "patternProperties")
) {
return "object"
} else if (["int32", "int64"].includes(schema.format)) {
// OpenAPI 3.1.0 integer custom formats
return "integer"
} else if (["float", "double"].includes(schema.format)) {
// OpenAPI 3.1.0 number custom formats
return "number"
} else if (
Object.hasOwn(schema, "minimum") ||
Object.hasOwn(schema, "maximum") ||
Object.hasOwn(schema, "exclusiveMinimum") ||
Object.hasOwn(schema, "exclusiveMaximum") ||
Object.hasOwn(schema, "multipleOf")
) {
return "number | integer"
} else if (
Object.hasOwn(schema, "pattern") ||
Object.hasOwn(schema, "format") ||
Object.hasOwn(schema, "minLength") ||
Object.hasOwn(schema, "maxLength")
) {
return "string"
} else if (typeof schema.const !== "undefined") {
if (schema.const === null) {
return "null"
} else if (typeof schema.const === "boolean") {
return "boolean"
} else if (typeof schema.const === "number") {
return Number.isInteger(schema.const) ? "integer" : "number"
} else if (typeof schema.const === "string") {
return "string"
} else if (Array.isArray(schema.const)) {
return "array<any>"
} else if (typeof schema.const === "object") {
return "object"
}
}
return null
}
return null
if (schema.not && getType(schema.not) === "any") {
return "never"
}
const typeString = Array.isArray(type)
? type.map((t) => (t === "array" ? getArrayType() : t)).join(" | ")
: type === "array"
? getArrayType()
: [
"null",
"boolean",
"object",
"array",
"number",
"integer",
"string",
].includes(type)
? type
: inferType()
const handleCombiningKeywords = (keyword, separator) => {
if (Array.isArray(schema[keyword])) {
const combinedTypes = schema[keyword].map((subSchema) =>
getType(subSchema, processedSchemas)
)
return `(${combinedTypes.join(separator)})`
}
return null
}
const oneOfString = handleCombiningKeywords("oneOf", " | ")
const anyOfString = handleCombiningKeywords("anyOf", " | ")
const allOfString = handleCombiningKeywords("allOf", " & ")
const combinedStrings = [typeString, oneOfString, anyOfString, allOfString]
.filter(Boolean)
.join(" | ")
processedSchemas.delete(schema)
return combinedStrings || "any"
}
const oneOfString = handleCombiningKeywords("oneOf", " | ")
const anyOfString = handleCombiningKeywords("anyOf", " | ")
const allOfString = handleCombiningKeywords("allOf", " & ")
const combinedStrings = [typeString, oneOfString, anyOfString, allOfString]
.filter(Boolean)
.join(" | ")
processedSchemas.delete(schema)
return combinedStrings || "any"
return getType
}
export const isBooleanJSONSchema = (schema) => typeof schema === "boolean"
@@ -164,42 +170,48 @@ export const hasKeyword = (schema, keyword) =>
typeof schema === "object" &&
Object.hasOwn(schema, keyword)
export const isExpandable = (schema) => {
const fn = useFn()
export const makeIsExpandable = (fnAccessor) => {
const isExpandable = (schema) => {
const fn = fnAccessor()
return (
schema?.$schema ||
schema?.$vocabulary ||
schema?.$id ||
schema?.$anchor ||
schema?.$dynamicAnchor ||
schema?.$ref ||
schema?.$dynamicRef ||
schema?.$defs ||
schema?.$comment ||
schema?.allOf ||
schema?.anyOf ||
schema?.oneOf ||
fn.hasKeyword(schema, "not") ||
fn.hasKeyword(schema, "if") ||
fn.hasKeyword(schema, "then") ||
fn.hasKeyword(schema, "else") ||
schema?.dependentSchemas ||
schema?.prefixItems ||
fn.hasKeyword(schema, "items") ||
fn.hasKeyword(schema, "contains") ||
schema?.properties ||
schema?.patternProperties ||
fn.hasKeyword(schema, "additionalProperties") ||
fn.hasKeyword(schema, "propertyNames") ||
fn.hasKeyword(schema, "unevaluatedItems") ||
fn.hasKeyword(schema, "unevaluatedProperties") ||
schema?.description ||
schema?.enum ||
fn.hasKeyword(schema, "const") ||
fn.hasKeyword(schema, "contentSchema") ||
fn.hasKeyword(schema, "default")
)
return (
schema?.$schema ||
schema?.$vocabulary ||
schema?.$id ||
schema?.$anchor ||
schema?.$dynamicAnchor ||
schema?.$ref ||
schema?.$dynamicRef ||
schema?.$defs ||
schema?.$comment ||
schema?.allOf ||
schema?.anyOf ||
schema?.oneOf ||
fn.hasKeyword(schema, "not") ||
fn.hasKeyword(schema, "if") ||
fn.hasKeyword(schema, "then") ||
fn.hasKeyword(schema, "else") ||
schema?.dependentSchemas ||
schema?.prefixItems ||
fn.hasKeyword(schema, "items") ||
fn.hasKeyword(schema, "contains") ||
schema?.properties ||
schema?.patternProperties ||
fn.hasKeyword(schema, "additionalProperties") ||
fn.hasKeyword(schema, "propertyNames") ||
fn.hasKeyword(schema, "unevaluatedItems") ||
fn.hasKeyword(schema, "unevaluatedProperties") ||
schema?.description ||
schema?.enum ||
fn.hasKeyword(schema, "const") ||
fn.hasKeyword(schema, "contentSchema") ||
fn.hasKeyword(schema, "default") ||
schema?.examples ||
fn.getExtensionKeywords(schema).length > 0
)
}
return isExpandable
}
export const stringify = (value) => {
@@ -339,13 +351,16 @@ export const stringifyConstraints = (schema) => {
// validation Keywords for Arrays
const arrayRange = stringifyConstraintRange(
schema?.hasUniqueItems ? "unique items" : "items",
schema?.uniqueItems ? "unique items" : "items",
schema?.minItems,
schema?.maxItems
)
if (arrayRange !== null) {
constraints.push({ scope: "array", value: arrayRange })
}
if (schema?.uniqueItems && !arrayRange) {
constraints.push({ scope: "array", value: "unique" })
}
const containsRange = stringifyConstraintRange(
"contained items",
schema?.minContains,
@@ -382,3 +397,108 @@ export const getDependentRequired = (propertyName, schema) => {
}, new Set())
)
}
export const isPlainObject = (value) =>
typeof value === "object" &&
value !== null &&
!Array.isArray(value) &&
(Object.getPrototypeOf(value) === null ||
Object.getPrototypeOf(value) === Object.prototype)
export const isEmptyObject = (value) =>
isPlainObject(value) && Object.keys(value).length === 0
export const isEmptyArray = (value) =>
Array.isArray(value) && value.length === 0
export const difference = (value, comparisonValue) => {
const comparisonSet = new Set(comparisonValue)
return value.filter((item) => !comparisonSet.has(item))
}
export const getSchemaKeywords = () => {
return [
// core vocabulary
"$schema",
"$vocabulary",
"$id",
"$anchor",
"$dynamicAnchor",
"$dynamicRef",
"$ref",
"$defs",
"$comment",
// applicator vocabulary
"allOf",
"anyOf",
"oneOf",
"not",
"if",
"then",
"else",
"dependentSchemas",
"prefixItems",
"items",
"contains",
"properties",
"patternProperties",
"additionalProperties",
"propertyNames",
// unevaluated Locations vocabulary
"unevaluatedItems",
"unevaluatedProperties",
// validation vocabulary
// validation Keywords for Any Instance Type
"type",
"enum",
"const",
// validation Keywords for Numeric Instances (number and integer)
"multipleOf",
"maximum",
"exclusiveMaximum",
"minimum",
"exclusiveMinimum",
// validation Keywords for Strings
"maxLength",
"minLength",
"pattern",
// validation Keywords for Arrays
"maxItems",
"minItems",
"uniqueItems",
"maxContains",
"minContains",
// validation Keywords for Objects
"maxProperties",
"minProperties",
"required",
"dependentRequired",
// basic Meta-Data Annotations vocabulary
"title",
"description",
"default",
"deprecated",
"readOnly",
"writeOnly",
"examples",
// semantic Content With "format" vocabulary
"format",
// contents of String-Encoded Data vocabulary
"contentEncoding",
"contentMediaType",
"contentSchema",
]
}
export const makeGetExtensionKeywords = (fnAccessor) => {
const getExtensionKeywords = (schema) => {
const fn = fnAccessor()
const keywords = fn.getSchemaKeywords()
return isPlainObject(schema)
? difference(Object.keys(schema), keywords)
: []
}
return getExtensionKeywords
}

View File

@@ -32,30 +32,36 @@ import KeywordUnevaluatedItems from "./components/keywords/UnevaluatedItems"
import KeywordUnevaluatedProperties from "./components/keywords/UnevaluatedProperties"
import KeywordType from "./components/keywords/Type"
import KeywordEnum from "./components/keywords/Enum/Enum"
import KeywordConst from "./components/keywords/Const"
import KeywordConst from "./components/keywords/Const/Const"
import KeywordConstraint from "./components/keywords/Constraint/Constraint"
import KeywordDependentRequired from "./components/keywords/DependentRequired/DependentRequired"
import KeywordContentSchema from "./components/keywords/ContentSchema"
import KeywordTitle from "./components/keywords/Title/Title"
import KeywordDescription from "./components/keywords/Description/Description"
import KeywordDefault from "./components/keywords/Default"
import KeywordDefault from "./components/keywords/Default/Default"
import KeywordDeprecated from "./components/keywords/Deprecated"
import KeywordReadOnly from "./components/keywords/ReadOnly"
import KeywordWriteOnly from "./components/keywords/WriteOnly"
import KeywordExamples from "./components/keywords/Examples/Examples"
import ExtensionKeywords from "./components/keywords/ExtensionKeywords/ExtensionKeywords"
import JSONViewer from "./components/JSONViewer/JSONViewer"
import Accordion from "./components/Accordion/Accordion"
import ExpandDeepButton from "./components/ExpandDeepButton/ExpandDeepButton"
import ChevronRightIcon from "./components/icons/ChevronRight"
import { JSONSchemaContext } from "./context"
import { useFn } from "./hooks"
import {
getTitle,
makeGetTitle,
isBooleanJSONSchema,
upperFirst,
getType,
makeGetType,
hasKeyword,
isExpandable,
makeIsExpandable,
stringify,
stringifyConstraints,
getDependentRequired,
getSchemaKeywords,
makeGetExtensionKeywords,
} from "./fn"
export const withJSONSchemaContext = (Component, overrides = {}) => {
@@ -100,6 +106,9 @@ export const withJSONSchemaContext = (Component, overrides = {}) => {
KeywordDeprecated,
KeywordReadOnly,
KeywordWriteOnly,
KeywordExamples,
ExtensionKeywords,
JSONViewer,
Accordion,
ExpandDeepButton,
ChevronRightIcon,
@@ -116,20 +125,24 @@ export const withJSONSchemaContext = (Component, overrides = {}) => {
* 3 -> [0]...(3)
*/
defaultExpandedLevels: 0, // 2 = 0...2
showExtensionKeywords: true,
...overrides.config,
},
fn: {
upperFirst,
getTitle,
getType,
getTitle: makeGetTitle(useFn),
getType: makeGetType(useFn),
isBooleanJSONSchema,
hasKeyword,
isExpandable,
isExpandable: makeIsExpandable(useFn),
stringify,
stringifyConstraints,
getDependentRequired,
getSchemaKeywords,
getExtensionKeywords: makeGetExtensionKeywords(useFn),
...overrides.fn,
},
state: { paths: {} },
}
const HOC = (props) => (
@@ -144,3 +157,140 @@ export const withJSONSchemaContext = (Component, overrides = {}) => {
return HOC
}
export const makeWithJSONSchemaSystemContext =
({ getSystem }) =>
(Component, overrides = {}) => {
const { getComponent, getConfigs } = getSystem()
const configs = getConfigs()
const JSONSchema = getComponent("JSONSchema202012")
const Keyword$schema = getComponent("JSONSchema202012Keyword$schema")
const Keyword$vocabulary = getComponent(
"JSONSchema202012Keyword$vocabulary"
)
const Keyword$id = getComponent("JSONSchema202012Keyword$id")
const Keyword$anchor = getComponent("JSONSchema202012Keyword$anchor")
const Keyword$dynamicAnchor = getComponent(
"JSONSchema202012Keyword$dynamicAnchor"
)
const Keyword$ref = getComponent("JSONSchema202012Keyword$ref")
const Keyword$dynamicRef = getComponent(
"JSONSchema202012Keyword$dynamicRef"
)
const Keyword$defs = getComponent("JSONSchema202012Keyword$defs")
const Keyword$comment = getComponent("JSONSchema202012Keyword$comment")
const KeywordAllOf = getComponent("JSONSchema202012KeywordAllOf")
const KeywordAnyOf = getComponent("JSONSchema202012KeywordAnyOf")
const KeywordOneOf = getComponent("JSONSchema202012KeywordOneOf")
const KeywordNot = getComponent("JSONSchema202012KeywordNot")
const KeywordIf = getComponent("JSONSchema202012KeywordIf")
const KeywordThen = getComponent("JSONSchema202012KeywordThen")
const KeywordElse = getComponent("JSONSchema202012KeywordElse")
const KeywordDependentSchemas = getComponent(
"JSONSchema202012KeywordDependentSchemas"
)
const KeywordPrefixItems = getComponent(
"JSONSchema202012KeywordPrefixItems"
)
const KeywordItems = getComponent("JSONSchema202012KeywordItems")
const KeywordContains = getComponent("JSONSchema202012KeywordContains")
const KeywordProperties = getComponent("JSONSchema202012KeywordProperties")
const KeywordPatternProperties = getComponent(
"JSONSchema202012KeywordPatternProperties"
)
const KeywordAdditionalProperties = getComponent(
"JSONSchema202012KeywordAdditionalProperties"
)
const KeywordPropertyNames = getComponent(
"JSONSchema202012KeywordPropertyNames"
)
const KeywordUnevaluatedItems = getComponent(
"JSONSchema202012KeywordUnevaluatedItems"
)
const KeywordUnevaluatedProperties = getComponent(
"JSONSchema202012KeywordUnevaluatedProperties"
)
const KeywordType = getComponent("JSONSchema202012KeywordType")
const KeywordEnum = getComponent("JSONSchema202012KeywordEnum")
const KeywordConst = getComponent("JSONSchema202012KeywordConst")
const KeywordConstraint = getComponent("JSONSchema202012KeywordConstraint")
const KeywordDependentRequired = getComponent(
"JSONSchema202012KeywordDependentRequired"
)
const KeywordContentSchema = getComponent(
"JSONSchema202012KeywordContentSchema"
)
const KeywordTitle = getComponent("JSONSchema202012KeywordTitle")
const KeywordDescription = getComponent(
"JSONSchema202012KeywordDescription"
)
const KeywordDefault = getComponent("JSONSchema202012KeywordDefault")
const KeywordDeprecated = getComponent("JSONSchema202012KeywordDeprecated")
const KeywordReadOnly = getComponent("JSONSchema202012KeywordReadOnly")
const KeywordWriteOnly = getComponent("JSONSchema202012KeywordWriteOnly")
const KeywordExamples = getComponent("JSONSchema202012KeywordExamples")
const ExtensionKeywords = getComponent("JSONSchema202012ExtensionKeywords")
const JSONViewer = getComponent("JSONSchema202012JSONViewer")
const Accordion = getComponent("JSONSchema202012Accordion")
const ExpandDeepButton = getComponent("JSONSchema202012ExpandDeepButton")
const ChevronRightIcon = getComponent("JSONSchema202012ChevronRightIcon")
return withJSONSchemaContext(Component, {
components: {
JSONSchema,
Keyword$schema,
Keyword$vocabulary,
Keyword$id,
Keyword$anchor,
Keyword$dynamicAnchor,
Keyword$ref,
Keyword$dynamicRef,
Keyword$defs,
Keyword$comment,
KeywordAllOf,
KeywordAnyOf,
KeywordOneOf,
KeywordNot,
KeywordIf,
KeywordThen,
KeywordElse,
KeywordDependentSchemas,
KeywordPrefixItems,
KeywordItems,
KeywordContains,
KeywordProperties,
KeywordPatternProperties,
KeywordAdditionalProperties,
KeywordPropertyNames,
KeywordUnevaluatedItems,
KeywordUnevaluatedProperties,
KeywordType,
KeywordEnum,
KeywordConst,
KeywordConstraint,
KeywordDependentRequired,
KeywordContentSchema,
KeywordTitle,
KeywordDescription,
KeywordDefault,
KeywordDeprecated,
KeywordReadOnly,
KeywordWriteOnly,
KeywordExamples,
ExtensionKeywords,
JSONViewer,
Accordion,
ExpandDeepButton,
ChevronRightIcon,
...overrides.components,
},
config: {
showExtensionKeywords: configs.showExtensions,
...overrides.config,
},
fn: {
...overrides.fn,
},
})
}

View File

@@ -1,14 +1,15 @@
/**
* @prettier
*/
import { useContext } from "react"
import { useCallback, useContext, useEffect, useState } from "react"
import {
JSONSchemaContext,
JSONSchemaLevelContext,
JSONSchemaDeepExpansionContext,
JSONSchemaCyclesContext,
JSONSchemaPathContext,
} from "./context"
import { JSONSchemaIsExpandedState } from "./enum"
export const useConfig = () => {
const { config } = useContext(JSONSchemaContext)
@@ -26,6 +27,17 @@ export const useFn = (fnName = undefined) => {
return typeof fnName !== "undefined" ? fn[fnName] : fn
}
export const useJSONSchemaContextState = () => {
const [, setFakeState] = useState(null)
const { state } = useContext(JSONSchemaContext)
const setState = (updateFn) => {
updateFn(state)
setFakeState({})
}
return { state, setState }
}
export const useLevel = () => {
const level = useContext(JSONSchemaLevelContext)
@@ -38,15 +50,84 @@ export const useIsEmbedded = () => {
return level > 0
}
export const useIsExpanded = () => {
const [level] = useLevel()
const { defaultExpandedLevels } = useConfig()
export const usePath = (pathToken) => {
const path = useContext(JSONSchemaPathContext)
const { setState } = useJSONSchemaContextState()
const currentPath =
typeof pathToken === "string" ? [...path, pathToken] : path
return defaultExpandedLevels - level > 0
const pathMutator = (value, options = { deep: false }) => {
const startPath = currentPath.toString()
const updateFn = (state) => {
state.paths[startPath] = value
if (value === JSONSchemaIsExpandedState.Collapsed) {
Object.keys(state.paths).forEach((key) => {
if (
key.startsWith(startPath) &&
state.paths[key] === JSONSchemaIsExpandedState.DeeplyExpanded
) {
state.paths[key] = JSONSchemaIsExpandedState.Expanded
}
})
}
}
const updateDeepFn = (state) => {
Object.keys(state.paths).forEach((key) => {
if (key.startsWith(startPath)) {
state.paths[key] = value
}
})
}
if (options.deep) {
setState(updateDeepFn)
} else {
setState(updateFn)
}
}
return { path: currentPath, pathMutator }
}
export const useIsExpandedDeeply = () => {
return useContext(JSONSchemaDeepExpansionContext)
export const useIsExpanded = (name) => {
const [level] = useLevel()
const { defaultExpandedLevels } = useConfig()
const { path, pathMutator } = usePath(name)
const { path: parentPath } = usePath()
const { state } = useJSONSchemaContextState()
const currentState = state.paths[path.toString()]
const parentState =
state.paths[parentPath.toString()] ??
state.paths[parentPath.slice(0, -1).toString()]
const isExpandedState =
currentState ??
(defaultExpandedLevels - level > 0
? JSONSchemaIsExpandedState.Expanded
: JSONSchemaIsExpandedState.Collapsed)
const isExpanded = isExpandedState !== JSONSchemaIsExpandedState.Collapsed
useEffect(() => {
pathMutator(
parentState === JSONSchemaIsExpandedState.DeeplyExpanded
? JSONSchemaIsExpandedState.DeeplyExpanded
: isExpandedState
)
}, [parentState])
const setExpanded = useCallback((options = { deep: false }) => {
pathMutator(
options.deep
? JSONSchemaIsExpandedState.DeeplyExpanded
: JSONSchemaIsExpandedState.Expanded
)
}, [])
const setCollapsed = useCallback((options = { deep: false }) => {
pathMutator(JSONSchemaIsExpandedState.Collapsed, options)
}, [])
return { isExpanded, setExpanded, setCollapsed }
}
export const useRenderedSchemas = (schema = undefined) => {

View File

@@ -30,82 +30,122 @@ import KeywordUnevaluatedItems from "./components/keywords/UnevaluatedItems"
import KeywordUnevaluatedProperties from "./components/keywords/UnevaluatedProperties"
import KeywordType from "./components/keywords/Type"
import KeywordEnum from "./components/keywords/Enum/Enum"
import KeywordConst from "./components/keywords/Const"
import KeywordConst from "./components/keywords/Const/Const"
import KeywordConstraint from "./components/keywords/Constraint/Constraint"
import KeywordDependentRequired from "./components/keywords/DependentRequired/DependentRequired"
import KeywordContentSchema from "./components/keywords/ContentSchema"
import KeywordTitle from "./components/keywords/Title/Title"
import KeywordDescription from "./components/keywords/Description/Description"
import KeywordDefault from "./components/keywords/Default"
import KeywordDefault from "./components/keywords/Default/Default"
import KeywordDeprecated from "./components/keywords/Deprecated"
import KeywordReadOnly from "./components/keywords/ReadOnly"
import KeywordWriteOnly from "./components/keywords/WriteOnly"
import KeywordExamples from "./components/keywords/Examples/Examples"
import ExtensionKeywords from "./components/keywords/ExtensionKeywords/ExtensionKeywords"
import JSONViewer from "./components/JSONViewer/JSONViewer"
import Accordion from "./components/Accordion/Accordion"
import ExpandDeepButton from "./components/ExpandDeepButton/ExpandDeepButton"
import ChevronRightIcon from "./components/icons/ChevronRight"
import { upperFirst, hasKeyword, isExpandable } from "./fn"
import { JSONSchemaDeepExpansionContext } from "./context"
import { useFn, useConfig, useComponent, useIsExpandedDeeply } from "./hooks"
import { withJSONSchemaContext } from "./hoc"
import {
upperFirst,
hasKeyword,
makeGetTitle,
makeGetType,
makeIsExpandable,
isBooleanJSONSchema,
getSchemaKeywords,
makeGetExtensionKeywords,
} from "./fn"
import { JSONSchemaPathContext, JSONSchemaLevelContext } from "./context"
import {
useFn,
useConfig,
useComponent,
useIsExpanded,
usePath,
useLevel,
} from "./hooks"
import { withJSONSchemaContext, makeWithJSONSchemaSystemContext } from "./hoc"
const JSONSchema202012Plugin = () => ({
components: {
JSONSchema202012: JSONSchema,
JSONSchema202012Keyword$schema: Keyword$schema,
JSONSchema202012Keyword$vocabulary: Keyword$vocabulary,
JSONSchema202012Keyword$id: Keyword$id,
JSONSchema202012Keyword$anchor: Keyword$anchor,
JSONSchema202012Keyword$dynamicAnchor: Keyword$dynamicAnchor,
JSONSchema202012Keyword$ref: Keyword$ref,
JSONSchema202012Keyword$dynamicRef: Keyword$dynamicRef,
JSONSchema202012Keyword$defs: Keyword$defs,
JSONSchema202012Keyword$comment: Keyword$comment,
JSONSchema202012KeywordAllOf: KeywordAllOf,
JSONSchema202012KeywordAnyOf: KeywordAnyOf,
JSONSchema202012KeywordOneOf: KeywordOneOf,
JSONSchema202012KeywordNot: KeywordNot,
JSONSchema202012KeywordIf: KeywordIf,
JSONSchema202012KeywordThen: KeywordThen,
JSONSchema202012KeywordElse: KeywordElse,
JSONSchema202012KeywordDependentSchemas: KeywordDependentSchemas,
JSONSchema202012KeywordPrefixItems: KeywordPrefixItems,
JSONSchema202012KeywordItems: KeywordItems,
JSONSchema202012KeywordContains: KeywordContains,
JSONSchema202012KeywordProperties: KeywordProperties,
JSONSchema202012KeywordPatternProperties: KeywordPatternProperties,
JSONSchema202012KeywordAdditionalProperties: KeywordAdditionalProperties,
JSONSchema202012KeywordPropertyNames: KeywordPropertyNames,
JSONSchema202012KeywordUnevaluatedItems: KeywordUnevaluatedItems,
JSONSchema202012KeywordUnevaluatedProperties: KeywordUnevaluatedProperties,
JSONSchema202012KeywordType: KeywordType,
JSONSchema202012KeywordEnum: KeywordEnum,
JSONSchema202012KeywordConst: KeywordConst,
JSONSchema202012KeywordConstraint: KeywordConstraint,
JSONSchema202012KeywordDependentRequired: KeywordDependentRequired,
JSONSchema202012KeywordContentSchema: KeywordContentSchema,
JSONSchema202012KeywordTitle: KeywordTitle,
JSONSchema202012KeywordDescription: KeywordDescription,
JSONSchema202012KeywordDefault: KeywordDefault,
JSONSchema202012KeywordDeprecated: KeywordDeprecated,
JSONSchema202012KeywordReadOnly: KeywordReadOnly,
JSONSchema202012KeywordWriteOnly: KeywordWriteOnly,
JSONSchema202012Accordion: Accordion,
JSONSchema202012ExpandDeepButton: ExpandDeepButton,
JSONSchema202012ChevronRightIcon: ChevronRightIcon,
withJSONSchema202012Context: withJSONSchemaContext,
JSONSchema202012DeepExpansionContext: () => JSONSchemaDeepExpansionContext,
},
fn: {
upperFirst,
jsonSchema202012: {
isExpandable,
hasKeyword,
useFn,
useConfig,
useComponent,
useIsExpandedDeeply,
const JSONSchema202012Plugin = ({ getSystem, fn }) => {
const fnAccessor = () => ({
upperFirst: fn.upperFirst,
...fn.jsonSchema202012,
})
return {
components: {
JSONSchema202012: JSONSchema,
JSONSchema202012Keyword$schema: Keyword$schema,
JSONSchema202012Keyword$vocabulary: Keyword$vocabulary,
JSONSchema202012Keyword$id: Keyword$id,
JSONSchema202012Keyword$anchor: Keyword$anchor,
JSONSchema202012Keyword$dynamicAnchor: Keyword$dynamicAnchor,
JSONSchema202012Keyword$ref: Keyword$ref,
JSONSchema202012Keyword$dynamicRef: Keyword$dynamicRef,
JSONSchema202012Keyword$defs: Keyword$defs,
JSONSchema202012Keyword$comment: Keyword$comment,
JSONSchema202012KeywordAllOf: KeywordAllOf,
JSONSchema202012KeywordAnyOf: KeywordAnyOf,
JSONSchema202012KeywordOneOf: KeywordOneOf,
JSONSchema202012KeywordNot: KeywordNot,
JSONSchema202012KeywordIf: KeywordIf,
JSONSchema202012KeywordThen: KeywordThen,
JSONSchema202012KeywordElse: KeywordElse,
JSONSchema202012KeywordDependentSchemas: KeywordDependentSchemas,
JSONSchema202012KeywordPrefixItems: KeywordPrefixItems,
JSONSchema202012KeywordItems: KeywordItems,
JSONSchema202012KeywordContains: KeywordContains,
JSONSchema202012KeywordProperties: KeywordProperties,
JSONSchema202012KeywordPatternProperties: KeywordPatternProperties,
JSONSchema202012KeywordAdditionalProperties: KeywordAdditionalProperties,
JSONSchema202012KeywordPropertyNames: KeywordPropertyNames,
JSONSchema202012KeywordUnevaluatedItems: KeywordUnevaluatedItems,
JSONSchema202012KeywordUnevaluatedProperties:
KeywordUnevaluatedProperties,
JSONSchema202012KeywordType: KeywordType,
JSONSchema202012KeywordEnum: KeywordEnum,
JSONSchema202012KeywordConst: KeywordConst,
JSONSchema202012KeywordConstraint: KeywordConstraint,
JSONSchema202012KeywordDependentRequired: KeywordDependentRequired,
JSONSchema202012KeywordContentSchema: KeywordContentSchema,
JSONSchema202012KeywordTitle: KeywordTitle,
JSONSchema202012KeywordDescription: KeywordDescription,
JSONSchema202012KeywordDefault: KeywordDefault,
JSONSchema202012KeywordDeprecated: KeywordDeprecated,
JSONSchema202012KeywordReadOnly: KeywordReadOnly,
JSONSchema202012KeywordWriteOnly: KeywordWriteOnly,
JSONSchema202012KeywordExamples: KeywordExamples,
JSONSchema202012ExtensionKeywords: ExtensionKeywords,
JSONSchema202012JSONViewer: JSONViewer,
JSONSchema202012Accordion: Accordion,
JSONSchema202012ExpandDeepButton: ExpandDeepButton,
JSONSchema202012ChevronRightIcon: ChevronRightIcon,
withJSONSchema202012Context: withJSONSchemaContext,
withJSONSchema202012SystemContext:
makeWithJSONSchemaSystemContext(getSystem()),
JSONSchema202012PathContext: () => JSONSchemaPathContext,
JSONSchema202012LevelContext: () => JSONSchemaLevelContext,
},
},
})
fn: {
upperFirst,
jsonSchema202012: {
getTitle: makeGetTitle(fnAccessor),
getType: makeGetType(fnAccessor),
isExpandable: makeIsExpandable(fnAccessor),
isBooleanJSONSchema,
hasKeyword,
useFn,
useConfig,
useComponent,
useIsExpanded,
usePath,
useLevel,
getSchemaKeywords,
getExtensionKeywords: makeGetExtensionKeywords(fnAccessor),
},
},
}
}
export default JSONSchema202012Plugin

View File

@@ -1,7 +1,7 @@
import React, { Component } from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import { sanitizeUrl } from "core/utils"
import { sanitizeUrl } from "core/utils/url"
const propClass = "property"

View File

@@ -4,7 +4,9 @@ import { List, fromJS } from "immutable"
import cx from "classnames"
import ImPropTypes from "react-immutable-proptypes"
import DebounceInput from "react-debounce-input"
import { stringify } from "core/utils"
import { stringify, isImmutable, immutableToJS } from "core/utils"
/* eslint-disable react/jsx-no-bind */
const noop = ()=> {}
const JsonSchemaPropShape = {
@@ -48,15 +50,22 @@ export class JsonSchemaForm extends Component {
let { schema, errors, value, onChange, getComponent, fn, disabled } = this.props
const format = schema && schema.get ? schema.get("format") : null
const type = schema && schema.get ? schema.get("type") : null
const foldedType = fn.jsonSchema202012.foldType(immutableToJS(type))
let getComponentSilently = (name) => getComponent(name, false, { failSilently: true })
let Comp = type ? format ?
getComponentSilently(`JsonSchema_${type}_${format}`) :
getComponentSilently(`JsonSchema_${type}`) :
getComponent("JsonSchema_string")
if (List.isList(type) && (foldedType === "array" || foldedType === "object")) {
Comp = getComponent("JsonSchema_object")
}
if (!Comp) {
Comp = getComponent("JsonSchema_string")
}
return <Comp { ...this.props } errors={errors} fn={fn} getComponent={getComponent} value={value} onChange={onChange} schema={schema} disabled={disabled}/>
}
}
@@ -77,7 +86,10 @@ export class JsonSchema_string extends Component {
const schemaIn = schema && schema.get ? schema.get("in") : null
if (!value) {
value = "" // value should not be null; this fixes a Debounce error
} else if (isImmutable(value) || typeof value === "object") {
value = stringify(value)
}
errors = errors.toJS ? errors.toJS() : []
if ( enumValue ) {
@@ -182,6 +194,8 @@ export class JsonSchema_array extends PureComponent {
value && value.count && value.count() > 0 ? true : false
const schemaItemsEnum = schema.getIn(["items", "enum"])
const schemaItemsType = schema.getIn(["items", "type"])
const foldedSchemaItemsType = fn.jsonSchema202012.foldType(immutableToJS(schemaItemsType))
const schemaItemsTypeLabel = fn.jsonSchema202012.getType(immutableToJS(schema.get("items")))
const schemaItemsFormat = schema.getIn(["items", "format"])
const schemaItemsSchema = schema.get("items")
let ArrayItemsComponent
@@ -192,6 +206,11 @@ export class JsonSchema_array extends PureComponent {
} else if (schemaItemsType === "boolean" || schemaItemsType === "array" || schemaItemsType === "object") {
ArrayItemsComponent = getComponent(`JsonSchema_${schemaItemsType}`)
}
if (List.isList(schemaItemsType) && (foldedSchemaItemsType === "array" || foldedSchemaItemsType === "object")) {
ArrayItemsComponent = getComponent(`JsonSchema_object`)
}
// if ArrayItemsComponent not assigned or does not exist,
// use default schemaItemsType === "string" & JsonSchemaArrayItemText component
if (!ArrayItemsComponent && !isArrayItemFile) {
@@ -266,7 +285,7 @@ export class JsonSchema_array extends PureComponent {
title={arrayErrors.length ? arrayErrors : ""}
onClick={this.addItem}
>
Add {schemaItemsType ? `${schemaItemsType} ` : ""}item
Add {schemaItemsTypeLabel} item
</Button>
) : null}
</div>
@@ -287,7 +306,10 @@ export class JsonSchemaArrayItemText extends Component {
let { value, errors, description, disabled } = this.props
if (!value) {
value = "" // value should not be null
} else if (isImmutable(value) || typeof value === "object") {
value = stringify(value)
}
errors = errors.toJS ? errors.toJS() : []
return (<DebounceInput

View File

@@ -120,6 +120,7 @@ const ModelExample = ({
{activeTab === tabs.model && (
<div
className="model-container"
aria-hidden={activeTab === tabs.example}
aria-labelledby={modelTabId}
data-name="modelPanel"

View File

@@ -0,0 +1,28 @@
/**
* @prettier
*/
import React from "react"
import PropTypes from "prop-types"
import { immutableToJS } from "core/utils"
import ImPropTypes from "react-immutable-proptypes"
export const ModelExtensions = ({ extensions, propClass = "" }) => {
return extensions
.entrySeq()
.map(([key, value]) => {
const normalizedValue = immutableToJS(value) ?? null
return (
<tr key={key} className={propClass}>
<td>{key}</td>
<td>{JSON.stringify(normalizedValue)}</td>
</tr>
)
})
.toArray()
}
ModelExtensions.propTypes = {
extensions: ImPropTypes.map.isRequired,
propClass: PropTypes.string,
}

View File

@@ -66,7 +66,7 @@ export default class Model extends ImmutablePureComponent {
/*
* If we have an unresolved ref, get the schema and name from the ref.
* If the ref is external, we can't resolve it, so we just display the ref location.
* This is for situations where:
* This is for situations where:
* - the ref was not resolved by Swagger Client because we reached the traversal depth limit
* - we had a circular ref inside the allOf keyword
*/
@@ -74,9 +74,9 @@ export default class Model extends ImmutablePureComponent {
const refName = this.getModelName($ref)
const refSchema = this.getRefSchema(refName)
if (Map.isMap(refSchema)) {
schema = refSchema.mergeDeep(schema)
schema = refSchema.mergeDeep(schema)
if (!$$ref) {
schema = schema.set("$$ref", $ref)
schema = schema.set("$$ref", $ref)
$$ref = $ref
}
} else if (Map.isMap(schema) && schema.size === 1) {

View File

@@ -2,6 +2,8 @@ import React, { Component } from "react"
import Im, { Map } from "immutable"
import PropTypes from "prop-types"
/* eslint-disable react/jsx-no-bind */
export default class Models extends Component {
static propTypes = {
getComponent: PropTypes.func,
@@ -114,7 +116,7 @@ export default class Models extends Component {
return <div id={ `model-${name}` } className="model-container" key={ `models-section-${name}` }
data-name={name} ref={this.onLoadModel} >
<span className="models-jump-to-path"><JumpToPath specPath={specPath} /></span>
<span className="models-jump-to-path"><JumpToPath path={specPath} /></span>
<ModelCollapse
classes="model-box"
collapsedContent={this.getCollapsedContent(name)}

Some files were not shown because too many files have changed in this diff Show More