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 maintained node versions
[node-development] [node-development]
node 20.3.0 node 22
[browser-production] [browser-production]
> 1% > 1%
@@ -26,4 +26,4 @@ maintained node versions
last 1 chrome version last 1 chrome version
last 1 firefox version last 1 firefox version
last 1 safari 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": [ "branches": [
{ {"name": "master"}
"name": "master"
},
{
"name": "next",
"channel": "alpha",
"prerelease": "alpha"
}
], ],
"tagFormat": "v${version}", "tagFormat": "v${version}",
"plugins": [ "plugins": [
@@ -21,15 +14,12 @@
"@semantic-release/release-notes-generator", "@semantic-release/release-notes-generator",
"@semantic-release/npm", "@semantic-release/npm",
"@semantic-release/github", "@semantic-release/github",
[ ["@semantic-release/git", {
"@semantic-release/git",
{
"assets": [ "assets": [
"package.json", "package.json",
"package-lock.json" "package-lock.json"
], ],
"message": "chore(release): cut the ${nextRelease.version} release\n\n${nextRelease.notes}" "message": "chore(release): cut the ${nextRelease.version} release\n\n${nextRelease.notes}"
} }]
]
] ]
} }

View File

@@ -4,6 +4,13 @@
FROM nginx:1.27.4-alpine 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" RUN apk add --update-cache --no-cache "nodejs"
LABEL maintainer="char0n" LABEL maintainer="char0n"

View File

@@ -18,6 +18,6 @@ module.exports = {
moduleNameMapper: { moduleNameMapper: {
'^.+\\.svg$': 'jest-transform-stub' '^.+\\.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 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` | Report ESLint style errors and warnings.
`lint-errors` | Report ESLint style errors, without warnings. `lint-errors` | Report ESLint style errors, without warnings.
`lint-fix` | Attempt to fix style errors automatically. `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. `watch` | Rebuild the core files in `/dist` when the source code changes. Useful for `npm link` with Swagger Editor.
### Building ### Building

View File

@@ -1,20 +1,23 @@
# Setting up a dev environment # 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 ### Prerequisites
- git, any version - 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 ### Steps
1. `git clone https://github.com/swagger-api/swagger-ui.git` 1. `git clone https://github.com/swagger-api/swagger-ui.git`
2. `cd swagger-ui` 2. `cd swagger-ui`
3. `npm install` 3. `npm install`
4. `npm run dev` 4. `npx husky init` (optional)
5. Wait a bit 5. `npm run dev`
6. Open http://localhost:3200/ 6. Wait a bit
7. Open http://localhost:3200/
### Using your own local api definition with local dev build ### 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: info:
version: "0.0.1" version: "0.0.1"
title: "Swagger UI Webpack Setup" 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: Example:
```sh ```sh
SPEC="{ \"openapi\": \"3.0.0\" }" SPEC="{ \"openapi\": \"3.0.4\" }"
``` ```
### Docker-Compose ### 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, 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: 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 ```javascript
import SwaggerUI from 'swagger-ui' import SwaggerUI from 'swagger-ui'
// or use require if you prefer // 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`: Install `swagger-ui-react`:
``` ```
$ npm i --save swagger-ui-react $ npm install swagger-ui-react
``` ```
Use it in your React application: 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. ⚠️ 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 ## Limitations

View File

@@ -45,6 +45,7 @@ const SwaggerUI = ({
persistAuthorization = config.defaults.persistAuthorization, persistAuthorization = config.defaults.persistAuthorization,
oauth2RedirectUrl = config.defaults.oauth2RedirectUrl, oauth2RedirectUrl = config.defaults.oauth2RedirectUrl,
onComplete = null, onComplete = null,
initialState = config.defaults.initialState,
}) => { }) => {
const [system, setSystem] = useState(null) const [system, setSystem] = useState(null)
const SwaggerUIComponent = system?.getComponent("App", "root") const SwaggerUIComponent = system?.getComponent("App", "root")
@@ -83,6 +84,7 @@ const SwaggerUI = ({
filter, filter,
persistAuthorization, persistAuthorization,
withCredentials, withCredentials,
initialState,
...(typeof oauth2RedirectUrl === "string" ...(typeof oauth2RedirectUrl === "string"
? { oauth2RedirectUrl: oauth2RedirectUrl } ? { oauth2RedirectUrl: oauth2RedirectUrl }
: {}), : {}),
@@ -165,6 +167,7 @@ SwaggerUI.propTypes = {
persistAuthorization: PropTypes.bool, persistAuthorization: PropTypes.bool,
withCredentials: PropTypes.bool, withCredentials: PropTypes.bool,
oauth2RedirectUrl: PropTypes.string, oauth2RedirectUrl: PropTypes.string,
initialState: PropTypes.object,
} }
SwaggerUI.System = SwaggerUIConstructor.System SwaggerUI.System = SwaggerUIConstructor.System
SwaggerUI.presets = SwaggerUIConstructor.presets 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": "eslint --ext \".js,.jsx\" src test dev-helpers flavors",
"lint-errors": "eslint --quiet --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-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": "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: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", "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" "start": "npm-run-all --parallel serve-static open-static"
}, },
"dependencies": { "dependencies": {
"@babel/runtime-corejs3": "^7.26.9", "@babel/runtime-corejs3": "^7.26.7",
"@braintree/sanitize-url": "=7.0.4",
"@scarf/scarf": "=1.4.0", "@scarf/scarf": "=1.4.0",
"base64-js": "^1.5.1", "base64-js": "^1.5.1",
"classnames": "^2.5.1", "classnames": "^2.5.1",
@@ -94,15 +95,15 @@
"react-immutable-proptypes": "2.2.0", "react-immutable-proptypes": "2.2.0",
"react-immutable-pure-component": "^2.2.0", "react-immutable-pure-component": "^2.2.0",
"react-inspector": "^6.0.1", "react-inspector": "^6.0.1",
"react-redux": "^9.1.2", "react-redux": "^9.2.0",
"react-syntax-highlighter": "^15.5.0", "react-syntax-highlighter": "^15.6.1",
"redux": "^5.0.1", "redux": "^5.0.1",
"redux-immutable": "^4.0.0", "redux-immutable": "^4.0.0",
"remarkable": "^2.0.1", "remarkable": "^2.0.1",
"reselect": "^5.1.1", "reselect": "^5.1.1",
"serialize-error": "^8.1.0", "serialize-error": "^8.1.0",
"sha.js": "^2.4.11", "sha.js": "^2.4.11",
"swagger-client": "^3.34.0", "swagger-client": "^3.34.1",
"url-parse": "^1.5.10", "url-parse": "^1.5.10",
"xml": "=1.0.1", "xml": "=1.0.1",
"xml-but-prettier": "^1.0.1", "xml-but-prettier": "^1.0.1",
@@ -110,43 +111,46 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "=7.26.4", "@babel/cli": "=7.26.4",
"@babel/core": "=7.26.8", "@babel/core": "=7.26.9",
"@babel/eslint-parser": "=7.26.8", "@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-env": "=7.26.9",
"@babel/preset-react": "=7.24.7", "@babel/preset-react": "=7.26.3",
"@babel/register": "=7.25.9", "@babel/register": "=7.25.9",
"@cfaester/enzyme-adapter-react-18": "=0.8.0", "@cfaester/enzyme-adapter-react-18": "=0.8.0",
"@commitlint/cli": "^19.6.1", "@commitlint/cli": "^19.7.1",
"@commitlint/config-conventional": "^19.2.2", "@commitlint/config-conventional": "^19.7.1",
"@jest/globals": "=29.7.0", "@jest/globals": "=29.7.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", "@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", "@svgr/webpack": "=8.1.0",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.20",
"babel-loader": "^9.1.3", "babel-loader": "^9.2.1",
"babel-plugin-lodash": "=3.3.4", "babel-plugin-lodash": "=3.3.4",
"babel-plugin-module-resolver": "=5.0.2", "babel-plugin-module-resolver": "=5.0.2",
"babel-plugin-transform-react-remove-prop-types": "=0.4.24", "babel-plugin-transform-react-remove-prop-types": "=0.4.24",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"cheerio": "=1.0.0-rc.12",
"cors": "^2.8.5", "cors": "^2.8.5",
"cross-env": "=7.0.3", "cross-env": "=7.0.3",
"css-loader": "=7.1.2", "css-loader": "=7.1.2",
"cssnano": "=7.0.6", "cssnano": "=7.0.6",
"cypress": "=13.13.0", "cypress": "=14.0.3",
"dedent": "^1.5.3", "dedent": "^1.5.3",
"deepmerge": "^4.3.1", "deepmerge": "^4.3.1",
"enzyme": "=3.11.0", "enzyme": "=3.11.0",
"eslint": "^8.57.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-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", "esm": "=3.2.25",
"expect": "=29.7.0", "expect": "=29.7.0",
"express": "^4.21.1", "express": "^4.21.2",
"git-describe": "^4.1.0", "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", "html-webpack-skip-assets-plugin": "^1.0.4",
"husky": "=9.1.7", "husky": "=9.1.7",
"inspectpack": "=4.7.1", "inspectpack": "=4.7.1",
@@ -157,31 +161,34 @@
"json-loader": "^0.5.7", "json-loader": "^0.5.7",
"json-merger": "^2.0.0", "json-merger": "^2.0.0",
"json-server": "=0.17.4", "json-server": "=0.17.4",
"less": "^4.2.0", "less": "^4.2.2",
"license-checker": "^25.0.0", "license-checker": "^25.0.0",
"lint-staged": "^15.4.3", "lint-staged": "^15.4.3",
"local-web-server": "^5.3.3", "local-web-server": "^5.4.0",
"mini-css-extract-plugin": "^2.9.0", "mini-css-extract-plugin": "^2.9.2",
"npm-audit-ci-wrapper": "^3.0.2", "npm-audit-ci-wrapper": "^3.0.2",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"oauth2-server": "^2.4.1", "oauth2-server": "^2.4.1",
"open": "^10.1.0", "open": "^10.1.0",
"postcss": "^8.5.2", "postcss": "^8.5.2",
"postcss-scss": "^4.0.9",
"postcss-loader": "^8.1.1", "postcss-loader": "^8.1.1",
"postcss-preset-env": "^9.6.0", "postcss-preset-env": "^10.1.4",
"prettier": "^3.5.2", "prettier": "^3.5.2",
"process": "^0.11.10", "process": "^0.11.10",
"react-refresh": "^0.16.0", "react-refresh": "^0.16.0",
"react-test-renderer": "^18.3.1", "react-test-renderer": "^18.3.1",
"release-it": "=17.10.0", "release-it": "=17.11.0",
"rimraf": "^5.0.7", "rimraf": "^6.0.1",
"sass": "^1.77.6", "sass-embedded": "=1.83.4",
"sass-loader": "^14.2.1", "sass-loader": "^16.0.4",
"shx": "=0.3.4", "shx": "=0.3.4",
"sinon": "=19.0.2", "sinon": "=19.0.2",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"start-server-and-test": "^2.0.10", "start-server-and-test": "^2.0.10",
"stream-browserify": "^3.0.0", "stream-browserify": "^3.0.0",
"stylelint": "^16.14.1",
"stylelint-prettier": "^5.0.3",
"tachyons-sass": "^4.9.5", "tachyons-sass": "^4.9.5",
"terser-webpack-plugin": "^5.3.11", "terser-webpack-plugin": "^5.3.11",
"webpack": "^5.97.1", "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, errSelectors: PropTypes.object.isRequired,
schema: PropTypes.object.isRequired, schema: PropTypes.object.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
onChange: PropTypes.func onChange: PropTypes.func,
authSelectors: PropTypes.object.isRequired
} }
constructor(props, context) { constructor(props, context) {
@@ -39,13 +40,14 @@ export default class ApiKeyAuth extends React.Component {
} }
render() { render() {
let { schema, getComponent, errSelectors, name } = this.props let { schema, getComponent, errSelectors, name, authSelectors } = this.props
const Input = getComponent("Input") const Input = getComponent("Input")
const Row = getComponent("Row") const Row = getComponent("Row")
const Col = getComponent("Col") const Col = getComponent("Col")
const AuthError = getComponent("authError") const AuthError = getComponent("authError")
const Markdown = getComponent("Markdown", true) const Markdown = getComponent("Markdown", true)
const JumpToPath = getComponent("JumpToPath", true) const JumpToPath = getComponent("JumpToPath", true)
const path = authSelectors.selectAuthPath(name)
let value = this.getValue() let value = this.getValue()
let errors = errSelectors.allErrors().filter( err => err.get("authId") === name) let errors = errSelectors.allErrors().filter( err => err.get("authId") === name)
@@ -53,7 +55,7 @@ export default class ApiKeyAuth extends React.Component {
<div> <div>
<h4> <h4>
<code>{ name || schema.get("name") }</code>&nbsp;(apiKey) <code>{ name || schema.get("name") }</code>&nbsp;(apiKey)
<JumpToPath path={[ "securityDefinitions", name ]} /> <JumpToPath path={path} />
</h4> </h4>
{ value && <h6>Authorized</h6>} { value && <h6>Authorized</h6>}
<Row> <Row>

View File

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

View File

@@ -10,6 +10,7 @@ export default class BasicAuth extends React.Component {
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
errSelectors: PropTypes.object.isRequired, errSelectors: PropTypes.object.isRequired,
authSelectors: PropTypes.object.isRequired
} }
constructor(props, context) { constructor(props, context) {
@@ -47,19 +48,20 @@ export default class BasicAuth extends React.Component {
} }
render() { render() {
let { schema, getComponent, name, errSelectors } = this.props let { schema, getComponent, name, errSelectors, authSelectors } = this.props
const Input = getComponent("Input") const Input = getComponent("Input")
const Row = getComponent("Row") const Row = getComponent("Row")
const Col = getComponent("Col") const Col = getComponent("Col")
const AuthError = getComponent("authError") const AuthError = getComponent("authError")
const JumpToPath = getComponent("JumpToPath", true) const JumpToPath = getComponent("JumpToPath", true)
const Markdown = getComponent("Markdown", true) const Markdown = getComponent("Markdown", true)
const path = authSelectors.selectAuthPath(name)
let username = this.getValue().username let username = this.getValue().username
let errors = errSelectors.allErrors().filter( err => err.get("authId") === name) let errors = errSelectors.allErrors().filter( err => err.get("authId") === name)
return ( return (
<div> <div>
<h4>Basic authorization<JumpToPath path={[ "securityDefinitions", name ]} /></h4> <h4>Basic authorization<JumpToPath path={path} /></h4>
{ username && <h6>Authorized</h6> } { username && <h6>Authorized</h6> }
<Row> <Row>
<Markdown source={ schema.get("description") } /> <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_ACCESS_CODE = isOAS3() ? (oidcUrl ? "authorization_code" : "authorizationCode") : "accessCode"
const AUTH_FLOW_APPLICATION = isOAS3() ? (oidcUrl ? "client_credentials" : "clientCredentials") : "application" const AUTH_FLOW_APPLICATION = isOAS3() ? (oidcUrl ? "client_credentials" : "clientCredentials") : "application"
const path = authSelectors.selectAuthPath(name)
let authConfigs = authSelectors.getConfigs() || {} let authConfigs = authSelectors.getConfigs() || {}
let isPkceCodeGrant = !!authConfigs.usePkceWithAuthorizationCodeGrant let isPkceCodeGrant = !!authConfigs.usePkceWithAuthorizationCodeGrant
@@ -144,7 +146,7 @@ export default class Oauth2 extends React.Component {
return ( return (
<div> <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> } { !this.state.appName ? null : <h5>Application: { this.state.appName } </h5> }
{ description && <Markdown source={ schema.get("description") } /> } { description && <Markdown source={ schema.get("description") } /> }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,8 @@ import React from "react"
import URL from "url-parse" import URL from "url-parse"
import PropTypes from "prop-types" 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" import win from "core/window"
export default class OnlineValidatorBadge extends React.Component { export default class OnlineValidatorBadge extends React.Component {

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ import { Map, List, fromJS } from "immutable"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes" import ImPropTypes from "react-immutable-proptypes"
import win from "core/window" 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" import getParameterSchema from "core/utils/get-parameter-schema.js"
export default class ParameterRow extends Component { export default class ParameterRow extends Component {
@@ -152,10 +152,13 @@ export default class ParameterRow extends Component {
//// Dispatch the initial value //// 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) { if(initialValue !== undefined) {
this.onChangeWrapper(initialValue) this.onChangeWrapper(initialValue)
} else if( } else if(
schema && schema.get("type") === "object" type === "object"
&& generatedSampleValue && generatedSampleValue
&& !paramWithMeta.get("examples") && !paramWithMeta.get("examples")
) { ) {
@@ -172,6 +175,20 @@ export default class ParameterRow extends Component {
) )
) )
} }
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 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 isFormData = inType === "formData"
let isFormDataSupported = "FormData" in win let isFormDataSupported = "FormData" in win
let required = param.get("required") 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 value = paramWithMeta ? paramWithMeta.get("value") : ""
let commonExt = showCommonExtensions ? getCommonExtensions(schema) : null let commonExt = showCommonExtensions ? getCommonExtensions(schema) : null
let extensions = showExtensions ? getExtensions(param) : 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 ( return (
<tr data-param-name={param.get("name")} data-param-in={param.get("in")}> <tr data-param-name={param.get("name")} data-param-in={param.get("in")}>
<td className="parameters-col_name"> <td className="parameters-col_name">
@@ -289,8 +322,7 @@ export default class ParameterRow extends Component {
{ !required ? null : <span>&nbsp;*</span> } { !required ? null : <span>&nbsp;*</span> }
</div> </div>
<div className="parameter__type"> <div className="parameter__type">
{ type } { typeLabel }
{ itemType && `[${itemType}]` }
{ format && <span className="prop-format">(${format})</span>} { format && <span className="prop-format">(${format})</span>}
</div> </div>
<div className="parameter__deprecated"> <div className="parameter__deprecated">
@@ -338,19 +370,19 @@ export default class ParameterRow extends Component {
) : null ) : null
} }
{ bodyParam ? null { (isObject || isArrayOfObjects) ? (
: <JsonSchemaForm fn={fn} <ModelExample
getComponent={getComponent} getComponent={getComponent}
value={ value } specPath={specPath.push("schema")}
required={ required } getConfigs={getConfigs}
disabled={!isExecute} isExecute={isExecute}
description={param.get("name")} specSelectors={specSelectors}
onChange={ this.onChangeWrapper } schema={schema}
errors={ paramWithMeta.get("errors") } example={jsonSchemaForm}
schema={ schema }/> />
) : jsonSchemaForm
} }
{ {
bodyParam && schema ? <ModelExample getComponent={ getComponent } bodyParam && schema ? <ModelExample getComponent={ getComponent }
specPath={specPath.push("schema")} 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 { getExtensions, fromJSOrdered, stringify } from "core/utils"
import { getKnownSyntaxHighlighterLanguage } from "core/utils/jsonParse" import { getKnownSyntaxHighlighterLanguage } from "core/utils/jsonParse"
/* eslint-disable react/jsx-no-bind */
const getExampleComponent = ( sampleResponse, HighlightCode ) => { const getExampleComponent = ( sampleResponse, HighlightCode ) => {
if (sampleResponse == null) return null if (sampleResponse == null) return null
@@ -139,7 +140,9 @@ export default class Response extends React.Component {
const targetExample = examplesForMediaType const targetExample = examplesForMediaType
.get(targetExamplesKey, Map({})) .get(targetExamplesKey, Map({}))
const getMediaTypeExample = (targetExample) => const getMediaTypeExample = (targetExample) =>
targetExample.get("value") Map.isMap(targetExample)
? targetExample.get("value")
: undefined
mediaTypeExample = getMediaTypeExample(targetExample) mediaTypeExample = getMediaTypeExample(targetExample)
if(mediaTypeExample === undefined) { if(mediaTypeExample === undefined) {
mediaTypeExample = getMediaTypeExample(examplesForMediaType.values().next().value) mediaTypeExample = getMediaTypeExample(examplesForMediaType.values().next().value)

View File

@@ -30,7 +30,7 @@ export default class VersionPragmaFilter extends React.PureComponent {
<div> <div>
<h3>Unable to render this definition</h3> <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><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> </div>
</div> </div>
@@ -43,7 +43,7 @@ export default class VersionPragmaFilter extends React.PureComponent {
<div> <div>
<h3>Unable to render this definition</h3> <h3>Unable to render this definition</h3>
<p>The provided definition does not specify a valid version field.</p> <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> </div>
</div> </div>

View File

@@ -1,6 +1,7 @@
import parseUrl from "url-parse" import parseUrl from "url-parse"
import Im from "immutable" 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 } ) { export default function authorize ( { auth, authActions, errActions, configs, authConfigs={}, currentServer } ) {
let { schema, scopes, name, clientId } = auth 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 SHOW_AUTH_POPUP = "show_popup"
export const AUTHORIZE = "authorize" export const AUTHORIZE = "authorize"
export const LOGOUT = "logout" export const LOGOUT = "logout"
export const PRE_AUTHORIZE_OAUTH2 = "pre_authorize_oauth2"
export const AUTHORIZE_OAUTH2 = "authorize_oauth2" export const AUTHORIZE_OAUTH2 = "authorize_oauth2"
export const VALIDATE = "validate"
export const CONFIGURE_AUTH = "configure_auth" export const CONFIGURE_AUTH = "configure_auth"
export const RESTORE_AUTHORIZATION = "restore_authorization" export const RESTORE_AUTHORIZATION = "restore_authorization"

View File

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

View File

@@ -18,7 +18,7 @@ const IconsPlugin = () => ({
CopyIcon, CopyIcon,
LockIcon, LockIcon,
UnlockIcon, 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 formatAPI } from "./api/formatAPI"
export { default as mediaTypeAPI } from "./api/mediaTypeAPI" export { default as mediaTypeAPI } from "./api/mediaTypeAPI"
export { default as mergeJsonSchema } from "./core/merge" export { default as mergeJsonSchema } from "./core/merge"
export { foldType } from "./core/type"

View File

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

View File

@@ -14,13 +14,13 @@
vertical-align: bottom; vertical-align: bottom;
&--expanded { &--expanded {
transition: transform .15s ease-in; transition: transform 0.15s ease-in;
transform: rotate(-90deg); transform: rotate(-90deg);
transform-origin: 50% 50%; transform-origin: 50% 50%;
} }
&--collapsed { &--collapsed {
transition: transform .15s ease-in; transition: transform 0.15s ease-in;
transform: rotate(0deg); transform: rotate(0deg);
transform-origin: 50% 50%; 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 { .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; font-size: 12px;
color: rgb(175, 174, 174); color: rgb(175, 174, 174);
border: none; border: none;

View File

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

View File

@@ -1,8 +1,14 @@
@use "./../../../../../style/variables" as *;
@use "./../../components/mixins";
.json-schema-2020-12 { .json-schema-2020-12 {
margin: 0 20px 15px 20px; margin: 0 20px 15px 20px;
border-radius: 4px; border-radius: 4px;
padding: 12px 0 12px 20px; 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 { &:first-of-type {
margin: 20px; margin: 20px;
@@ -18,7 +24,7 @@
} }
&-body { &-body {
@include expansion-border; @include mixins.expansion-border;
margin: 2px 0; margin: 2px 0;
&--collapsed { &--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 { @use "./JSONViewer/json-viewer";
margin: 0 0 0 20px; @use "./JSONSchema/json-schema";
border-left: 1px dashed rgba($section-models-model-container-background-color, 0.1); @use "./Accordion/accordion";
} @use "./ExpandDeepButton/expand-deep-button";
@import './JSONSchema/json-schema';
@import './Accordion/accordion';
@import './ExpandDeepButton/expand-deep-button';
@import './keywords/all';

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
/** /**
* @prettier * @prettier
*/ */
import React, { useCallback, useState } from "react" import React, { useCallback } from "react"
import classNames from "classnames" import classNames from "classnames"
import { schema } from "../../prop-types" import { schema } from "../../prop-types"
@@ -9,17 +9,18 @@ import {
useFn, useFn,
useComponent, useComponent,
useIsExpanded, useIsExpanded,
useIsExpandedDeeply, usePath,
useLevel,
} from "../../hooks" } from "../../hooks"
import { JSONSchemaDeepExpansionContext } from "../../context" import { JSONSchemaLevelContext, JSONSchemaPathContext } from "../../context"
const AnyOf = ({ schema }) => { const AnyOf = ({ schema }) => {
const anyOf = schema?.anyOf || [] const anyOf = schema?.anyOf || []
const fn = useFn() const fn = useFn()
const isExpanded = useIsExpanded() const pathToken = "anyOf"
const isExpandedDeeply = useIsExpandedDeeply() const { path } = usePath(pathToken)
const [expanded, setExpanded] = useState(isExpanded || isExpandedDeeply) const { isExpanded, setExpanded, setCollapsed } = useIsExpanded(pathToken)
const [expandedDeeply, setExpandedDeeply] = useState(false) const [level, nextLevel] = useLevel()
const Accordion = useComponent("Accordion") const Accordion = useComponent("Accordion")
const ExpandDeepButton = useComponent("ExpandDeepButton") const ExpandDeepButton = useComponent("ExpandDeepButton")
const JSONSchema = useComponent("JSONSchema") const JSONSchema = useComponent("JSONSchema")
@@ -29,12 +30,22 @@ const AnyOf = ({ schema }) => {
* Event handlers. * Event handlers.
*/ */
const handleExpansion = useCallback(() => { const handleExpansion = useCallback(() => {
setExpanded((prev) => !prev) if (isExpanded) {
}, []) setCollapsed()
const handleExpansionDeep = useCallback((e, expandedDeepNew) => { } else {
setExpanded(expandedDeepNew) setExpanded()
setExpandedDeeply(expandedDeepNew) }
}, []) }, [isExpanded, setExpanded, setCollapsed])
const handleExpansionDeep = useCallback(
(e, expandedDeepNew) => {
if (expandedDeepNew) {
setExpanded({ deep: true })
} else {
setCollapsed({ deep: true })
}
},
[setExpanded, setCollapsed]
)
/** /**
* Rendering. * Rendering.
@@ -44,24 +55,34 @@ const AnyOf = ({ schema }) => {
} }
return ( return (
<JSONSchemaDeepExpansionContext.Provider value={expandedDeeply}> <JSONSchemaPathContext.Provider value={path}>
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--anyOf"> <JSONSchemaLevelContext.Provider value={nextLevel}>
<Accordion expanded={expanded} onChange={handleExpansion}> <div
className="json-schema-2020-12-keyword json-schema-2020-12-keyword--anyOf"
data-json-schema-level={level}
>
<Accordion expanded={isExpanded} onChange={handleExpansion}>
<span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary"> <span className="json-schema-2020-12-keyword__name json-schema-2020-12-keyword__name--primary">
Any of Any of
</span> </span>
</Accordion> </Accordion>
<ExpandDeepButton expanded={expanded} onClick={handleExpansionDeep} /> <ExpandDeepButton
expanded={isExpanded}
onClick={handleExpansionDeep}
/>
<KeywordType schema={{ anyOf }} /> <KeywordType schema={{ anyOf }} />
<ul <ul
className={classNames("json-schema-2020-12-keyword__children", { className={classNames("json-schema-2020-12-keyword__children", {
"json-schema-2020-12-keyword__children--collapsed": !expanded, "json-schema-2020-12-keyword__children--collapsed": !isExpanded,
})} })}
> >
{expanded && ( {isExpanded && (
<> <>
{anyOf.map((schema, index) => ( {anyOf.map((schema, index) => (
<li key={`#${index}`} className="json-schema-2020-12-property"> <li
key={`#${index}`}
className="json-schema-2020-12-property"
>
<JSONSchema <JSONSchema
name={`#${index} ${fn.getTitle(schema)}`} name={`#${index} ${fn.getTitle(schema)}`}
schema={schema} schema={schema}
@@ -72,7 +93,8 @@ const AnyOf = ({ schema }) => {
)} )}
</ul> </ul>
</div> </div>
</JSONSchemaDeepExpansionContext.Provider> </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 { .json-schema-2020-12__constraint {
@include text_code(); @include type.text_code();
margin-left: 10px; margin-left: 10px;
line-height: 1.5; line-height: 1.5;
padding: 1px 3px; padding: 1px 3px;
color: white; color: white;
background-color: #805AD5; background-color: #805ad5;
border-radius: 4px; border-radius: 4px;
&--string { &--string {
color: white; color: white;
background-color: #D69E2E; background-color: #d69e2e;
} }
} }

View File

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

View File

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

View File

@@ -23,7 +23,7 @@ const Else = ({ schema }) => {
return ( return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--if"> <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> </div>
) )
} }

View File

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

View File

@@ -1,12 +1,11 @@
.json-schema-2020-12-keyword--enum { @use "./../../mixins";
& > ul {
display: inline-block;
padding: 0;
margin: 0;
li { .json-schema-2020-12-keyword--enum {
display: inline; .json-schema-2020-12-json-viewer__name {
list-style-type: none; @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 ( return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--if"> <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> </div>
) )
} }

View File

@@ -23,7 +23,7 @@ const Items = ({ schema }) => {
return ( return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--items"> <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> </div>
) )
} }

View File

@@ -23,7 +23,7 @@ const Not = ({ schema }) => {
return ( return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--not"> <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> </div>
) )
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,8 +11,11 @@
list-style-type: none; list-style-type: none;
&--required { &--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; color: red;
font-weight: bold; font-weight: bold;
} }

View File

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

View File

@@ -23,7 +23,7 @@ const Then = ({ schema }) => {
return ( return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--then"> <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> </div>
) )
} }

View File

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

View File

@@ -24,7 +24,11 @@ const UnevaluatedItems = ({ schema }) => {
return ( return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--unevaluatedItems"> <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> </div>
) )
} }

View File

@@ -24,7 +24,11 @@ const UnevaluatedProperties = ({ schema }) => {
return ( return (
<div className="json-schema-2020-12-keyword json-schema-2020-12-keyword--unevaluatedProperties"> <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> </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 { .json-schema-2020-12-keyword {
margin: 5px 0 5px 0; @include mixins.json-schema-2020-12-keyword;
&__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;
}
}
} }
.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 { .json-schema-2020-12__attribute {
@@ -84,12 +41,3 @@
color: red; 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) export const JSONSchemaLevelContext = createContext(0)
JSONSchemaLevelContext.displayName = "JSONSchemaLevelContext" JSONSchemaLevelContext.displayName = "JSONSchemaLevelContext"
export const JSONSchemaDeepExpansionContext = createContext(false)
JSONSchemaDeepExpansionContext.displayName = "JSONSchemaDeepExpansionContext"
export const JSONSchemaCyclesContext = createContext(new Set()) 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 * @prettier
*/ */
import { useFn } from "./hooks"
export const upperFirst = (value) => { export const upperFirst = (value) => {
if (typeof value === "string") { if (typeof value === "string") {
return `${value.charAt(0).toUpperCase()}${value.slice(1)}` return `${value.charAt(0).toUpperCase()}${value.slice(1)}`
@@ -13,8 +11,9 @@ export const upperFirst = (value) => {
/** /**
* Lookup can be `basic` or `extended`. By default the lookup is `extended`. * Lookup can be `basic` or `extended`. By default the lookup is `extended`.
*/ */
export const getTitle = (schema, { lookup = "extended" } = {}) => { export const makeGetTitle = (fnAccessor) => {
const fn = useFn() const getTitle = (schema, { lookup = "extended" } = {}) => {
const fn = fnAccessor()
if (schema?.title != null) return fn.upperFirst(String(schema.title)) if (schema?.title != null) return fn.upperFirst(String(schema.title))
if (lookup === "extended") { if (lookup === "extended") {
@@ -23,10 +22,14 @@ export const getTitle = (schema, { lookup = "extended" } = {}) => {
} }
return "" return ""
}
return getTitle
} }
export const getType = (schema, processedSchemas = new WeakSet()) => { export const makeGetType = (fnAccessor) => {
const fn = useFn() const getType = (schema, processedSchemas = new WeakSet()) => {
const fn = fnAccessor()
if (schema == null) { if (schema == null) {
return "any" return "any"
@@ -155,6 +158,9 @@ export const getType = (schema, processedSchemas = new WeakSet()) => {
processedSchemas.delete(schema) processedSchemas.delete(schema)
return combinedStrings || "any" return combinedStrings || "any"
}
return getType
} }
export const isBooleanJSONSchema = (schema) => typeof schema === "boolean" export const isBooleanJSONSchema = (schema) => typeof schema === "boolean"
@@ -164,8 +170,9 @@ export const hasKeyword = (schema, keyword) =>
typeof schema === "object" && typeof schema === "object" &&
Object.hasOwn(schema, keyword) Object.hasOwn(schema, keyword)
export const isExpandable = (schema) => { export const makeIsExpandable = (fnAccessor) => {
const fn = useFn() const isExpandable = (schema) => {
const fn = fnAccessor()
return ( return (
schema?.$schema || schema?.$schema ||
@@ -198,8 +205,13 @@ export const isExpandable = (schema) => {
schema?.enum || schema?.enum ||
fn.hasKeyword(schema, "const") || fn.hasKeyword(schema, "const") ||
fn.hasKeyword(schema, "contentSchema") || fn.hasKeyword(schema, "contentSchema") ||
fn.hasKeyword(schema, "default") fn.hasKeyword(schema, "default") ||
schema?.examples ||
fn.getExtensionKeywords(schema).length > 0
) )
}
return isExpandable
} }
export const stringify = (value) => { export const stringify = (value) => {
@@ -339,13 +351,16 @@ export const stringifyConstraints = (schema) => {
// validation Keywords for Arrays // validation Keywords for Arrays
const arrayRange = stringifyConstraintRange( const arrayRange = stringifyConstraintRange(
schema?.hasUniqueItems ? "unique items" : "items", schema?.uniqueItems ? "unique items" : "items",
schema?.minItems, schema?.minItems,
schema?.maxItems schema?.maxItems
) )
if (arrayRange !== null) { if (arrayRange !== null) {
constraints.push({ scope: "array", value: arrayRange }) constraints.push({ scope: "array", value: arrayRange })
} }
if (schema?.uniqueItems && !arrayRange) {
constraints.push({ scope: "array", value: "unique" })
}
const containsRange = stringifyConstraintRange( const containsRange = stringifyConstraintRange(
"contained items", "contained items",
schema?.minContains, schema?.minContains,
@@ -382,3 +397,108 @@ export const getDependentRequired = (propertyName, schema) => {
}, new Set()) }, 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 KeywordUnevaluatedProperties from "./components/keywords/UnevaluatedProperties"
import KeywordType from "./components/keywords/Type" import KeywordType from "./components/keywords/Type"
import KeywordEnum from "./components/keywords/Enum/Enum" 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 KeywordConstraint from "./components/keywords/Constraint/Constraint"
import KeywordDependentRequired from "./components/keywords/DependentRequired/DependentRequired" import KeywordDependentRequired from "./components/keywords/DependentRequired/DependentRequired"
import KeywordContentSchema from "./components/keywords/ContentSchema" import KeywordContentSchema from "./components/keywords/ContentSchema"
import KeywordTitle from "./components/keywords/Title/Title" import KeywordTitle from "./components/keywords/Title/Title"
import KeywordDescription from "./components/keywords/Description/Description" 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 KeywordDeprecated from "./components/keywords/Deprecated"
import KeywordReadOnly from "./components/keywords/ReadOnly" import KeywordReadOnly from "./components/keywords/ReadOnly"
import KeywordWriteOnly from "./components/keywords/WriteOnly" 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 Accordion from "./components/Accordion/Accordion"
import ExpandDeepButton from "./components/ExpandDeepButton/ExpandDeepButton" import ExpandDeepButton from "./components/ExpandDeepButton/ExpandDeepButton"
import ChevronRightIcon from "./components/icons/ChevronRight" import ChevronRightIcon from "./components/icons/ChevronRight"
import { JSONSchemaContext } from "./context" import { JSONSchemaContext } from "./context"
import { useFn } from "./hooks"
import { import {
getTitle, makeGetTitle,
isBooleanJSONSchema, isBooleanJSONSchema,
upperFirst, upperFirst,
getType, makeGetType,
hasKeyword, hasKeyword,
isExpandable, makeIsExpandable,
stringify, stringify,
stringifyConstraints, stringifyConstraints,
getDependentRequired, getDependentRequired,
getSchemaKeywords,
makeGetExtensionKeywords,
} from "./fn" } from "./fn"
export const withJSONSchemaContext = (Component, overrides = {}) => { export const withJSONSchemaContext = (Component, overrides = {}) => {
@@ -100,6 +106,9 @@ export const withJSONSchemaContext = (Component, overrides = {}) => {
KeywordDeprecated, KeywordDeprecated,
KeywordReadOnly, KeywordReadOnly,
KeywordWriteOnly, KeywordWriteOnly,
KeywordExamples,
ExtensionKeywords,
JSONViewer,
Accordion, Accordion,
ExpandDeepButton, ExpandDeepButton,
ChevronRightIcon, ChevronRightIcon,
@@ -116,20 +125,24 @@ export const withJSONSchemaContext = (Component, overrides = {}) => {
* 3 -> [0]...(3) * 3 -> [0]...(3)
*/ */
defaultExpandedLevels: 0, // 2 = 0...2 defaultExpandedLevels: 0, // 2 = 0...2
showExtensionKeywords: true,
...overrides.config, ...overrides.config,
}, },
fn: { fn: {
upperFirst, upperFirst,
getTitle, getTitle: makeGetTitle(useFn),
getType, getType: makeGetType(useFn),
isBooleanJSONSchema, isBooleanJSONSchema,
hasKeyword, hasKeyword,
isExpandable, isExpandable: makeIsExpandable(useFn),
stringify, stringify,
stringifyConstraints, stringifyConstraints,
getDependentRequired, getDependentRequired,
getSchemaKeywords,
getExtensionKeywords: makeGetExtensionKeywords(useFn),
...overrides.fn, ...overrides.fn,
}, },
state: { paths: {} },
} }
const HOC = (props) => ( const HOC = (props) => (
@@ -144,3 +157,140 @@ export const withJSONSchemaContext = (Component, overrides = {}) => {
return HOC 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 * @prettier
*/ */
import { useContext } from "react" import { useCallback, useContext, useEffect, useState } from "react"
import { import {
JSONSchemaContext, JSONSchemaContext,
JSONSchemaLevelContext, JSONSchemaLevelContext,
JSONSchemaDeepExpansionContext,
JSONSchemaCyclesContext, JSONSchemaCyclesContext,
JSONSchemaPathContext,
} from "./context" } from "./context"
import { JSONSchemaIsExpandedState } from "./enum"
export const useConfig = () => { export const useConfig = () => {
const { config } = useContext(JSONSchemaContext) const { config } = useContext(JSONSchemaContext)
@@ -26,6 +27,17 @@ export const useFn = (fnName = undefined) => {
return typeof fnName !== "undefined" ? fn[fnName] : fn 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 = () => { export const useLevel = () => {
const level = useContext(JSONSchemaLevelContext) const level = useContext(JSONSchemaLevelContext)
@@ -38,15 +50,84 @@ export const useIsEmbedded = () => {
return level > 0 return level > 0
} }
export const useIsExpanded = () => { export const usePath = (pathToken) => {
const [level] = useLevel() const path = useContext(JSONSchemaPathContext)
const { defaultExpandedLevels } = useConfig() 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 = () => { export const useIsExpanded = (name) => {
return useContext(JSONSchemaDeepExpansionContext) 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) => { export const useRenderedSchemas = (schema = undefined) => {

View File

@@ -30,25 +30,50 @@ import KeywordUnevaluatedItems from "./components/keywords/UnevaluatedItems"
import KeywordUnevaluatedProperties from "./components/keywords/UnevaluatedProperties" import KeywordUnevaluatedProperties from "./components/keywords/UnevaluatedProperties"
import KeywordType from "./components/keywords/Type" import KeywordType from "./components/keywords/Type"
import KeywordEnum from "./components/keywords/Enum/Enum" 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 KeywordConstraint from "./components/keywords/Constraint/Constraint"
import KeywordDependentRequired from "./components/keywords/DependentRequired/DependentRequired" import KeywordDependentRequired from "./components/keywords/DependentRequired/DependentRequired"
import KeywordContentSchema from "./components/keywords/ContentSchema" import KeywordContentSchema from "./components/keywords/ContentSchema"
import KeywordTitle from "./components/keywords/Title/Title" import KeywordTitle from "./components/keywords/Title/Title"
import KeywordDescription from "./components/keywords/Description/Description" 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 KeywordDeprecated from "./components/keywords/Deprecated"
import KeywordReadOnly from "./components/keywords/ReadOnly" import KeywordReadOnly from "./components/keywords/ReadOnly"
import KeywordWriteOnly from "./components/keywords/WriteOnly" 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 Accordion from "./components/Accordion/Accordion"
import ExpandDeepButton from "./components/ExpandDeepButton/ExpandDeepButton" import ExpandDeepButton from "./components/ExpandDeepButton/ExpandDeepButton"
import ChevronRightIcon from "./components/icons/ChevronRight" import ChevronRightIcon from "./components/icons/ChevronRight"
import { upperFirst, hasKeyword, isExpandable } from "./fn" import {
import { JSONSchemaDeepExpansionContext } from "./context" upperFirst,
import { useFn, useConfig, useComponent, useIsExpandedDeeply } from "./hooks" hasKeyword,
import { withJSONSchemaContext } from "./hoc" 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 = () => ({ const JSONSchema202012Plugin = ({ getSystem, fn }) => {
const fnAccessor = () => ({
upperFirst: fn.upperFirst,
...fn.jsonSchema202012,
})
return {
components: { components: {
JSONSchema202012: JSONSchema, JSONSchema202012: JSONSchema,
JSONSchema202012Keyword$schema: Keyword$schema, JSONSchema202012Keyword$schema: Keyword$schema,
@@ -76,7 +101,8 @@ const JSONSchema202012Plugin = () => ({
JSONSchema202012KeywordAdditionalProperties: KeywordAdditionalProperties, JSONSchema202012KeywordAdditionalProperties: KeywordAdditionalProperties,
JSONSchema202012KeywordPropertyNames: KeywordPropertyNames, JSONSchema202012KeywordPropertyNames: KeywordPropertyNames,
JSONSchema202012KeywordUnevaluatedItems: KeywordUnevaluatedItems, JSONSchema202012KeywordUnevaluatedItems: KeywordUnevaluatedItems,
JSONSchema202012KeywordUnevaluatedProperties: KeywordUnevaluatedProperties, JSONSchema202012KeywordUnevaluatedProperties:
KeywordUnevaluatedProperties,
JSONSchema202012KeywordType: KeywordType, JSONSchema202012KeywordType: KeywordType,
JSONSchema202012KeywordEnum: KeywordEnum, JSONSchema202012KeywordEnum: KeywordEnum,
JSONSchema202012KeywordConst: KeywordConst, JSONSchema202012KeywordConst: KeywordConst,
@@ -89,23 +115,37 @@ const JSONSchema202012Plugin = () => ({
JSONSchema202012KeywordDeprecated: KeywordDeprecated, JSONSchema202012KeywordDeprecated: KeywordDeprecated,
JSONSchema202012KeywordReadOnly: KeywordReadOnly, JSONSchema202012KeywordReadOnly: KeywordReadOnly,
JSONSchema202012KeywordWriteOnly: KeywordWriteOnly, JSONSchema202012KeywordWriteOnly: KeywordWriteOnly,
JSONSchema202012KeywordExamples: KeywordExamples,
JSONSchema202012ExtensionKeywords: ExtensionKeywords,
JSONSchema202012JSONViewer: JSONViewer,
JSONSchema202012Accordion: Accordion, JSONSchema202012Accordion: Accordion,
JSONSchema202012ExpandDeepButton: ExpandDeepButton, JSONSchema202012ExpandDeepButton: ExpandDeepButton,
JSONSchema202012ChevronRightIcon: ChevronRightIcon, JSONSchema202012ChevronRightIcon: ChevronRightIcon,
withJSONSchema202012Context: withJSONSchemaContext, withJSONSchema202012Context: withJSONSchemaContext,
JSONSchema202012DeepExpansionContext: () => JSONSchemaDeepExpansionContext, withJSONSchema202012SystemContext:
makeWithJSONSchemaSystemContext(getSystem()),
JSONSchema202012PathContext: () => JSONSchemaPathContext,
JSONSchema202012LevelContext: () => JSONSchemaLevelContext,
}, },
fn: { fn: {
upperFirst, upperFirst,
jsonSchema202012: { jsonSchema202012: {
isExpandable, getTitle: makeGetTitle(fnAccessor),
getType: makeGetType(fnAccessor),
isExpandable: makeIsExpandable(fnAccessor),
isBooleanJSONSchema,
hasKeyword, hasKeyword,
useFn, useFn,
useConfig, useConfig,
useComponent, useComponent,
useIsExpandedDeeply, useIsExpanded,
usePath,
useLevel,
getSchemaKeywords,
getExtensionKeywords: makeGetExtensionKeywords(fnAccessor),
}, },
}, },
}) }
}
export default JSONSchema202012Plugin export default JSONSchema202012Plugin

View File

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

View File

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

View File

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

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

View File

@@ -1,8 +1,13 @@
import React, { Component, } from "react" /**
* @prettier
*/
import React, { Component } from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import { List } from "immutable" import { List } from "immutable"
import ImPropTypes from "react-immutable-proptypes" import ImPropTypes from "react-immutable-proptypes"
import { sanitizeUrl } from "core/utils" import { sanitizeUrl } from "core/utils/url"
import classNames from "classnames"
import { getExtensions } from "../../../utils"
const braceOpen = "{" const braceOpen = "{"
const braceClose = "}" const braceClose = "}"
@@ -26,24 +31,43 @@ export default class ObjectModel extends Component {
includeWriteOnly: PropTypes.bool, includeWriteOnly: PropTypes.bool,
} }
render(){ render() {
let { schema, name, displayName, isRef, getComponent, getConfigs, depth, onToggle, expanded, specPath, ...otherProps } = this.props let {
let { specSelectors,expandDepth, includeReadOnly, includeWriteOnly} = otherProps schema,
name,
displayName,
isRef,
getComponent,
getConfigs,
depth,
onToggle,
expanded,
specPath,
...otherProps
} = this.props
let { specSelectors, expandDepth, includeReadOnly, includeWriteOnly } =
otherProps
const { isOAS3 } = specSelectors const { isOAS3 } = specSelectors
const isEmbedded = depth > 2 || (depth === 2 && specPath.last() !== "items")
if(!schema) { if (!schema) {
return null return null
} }
const { showExtensions } = getConfigs() const { showExtensions } = getConfigs()
const extensions = showExtensions ? getExtensions(schema) : List()
let description = schema.get("description") let description = schema.get("description")
let properties = schema.get("properties") let properties = schema.get("properties")
let additionalProperties = schema.get("additionalProperties") let additionalProperties = schema.get("additionalProperties")
let title = schema.get("title") || displayName || name let title = schema.get("title") || displayName || name
let requiredProperties = schema.get("required") let requiredProperties = schema.get("required")
let infoProperties = schema let infoProperties = schema.filter(
.filter( ( v, key) => ["maxProperties", "minProperties", "nullable", "example"].indexOf(key) !== -1 ) (v, key) =>
["maxProperties", "minProperties", "nullable", "example"].indexOf(
key
) !== -1
)
let deprecated = schema.get("deprecated") let deprecated = schema.get("deprecated")
let externalDocsUrl = schema.getIn(["externalDocs", "url"]) let externalDocsUrl = schema.getIn(["externalDocs", "url"])
let externalDocsDescription = schema.getIn(["externalDocs", "description"]) let externalDocsDescription = schema.getIn(["externalDocs", "description"])
@@ -54,82 +78,99 @@ export default class ObjectModel extends Component {
const ModelCollapse = getComponent("ModelCollapse") const ModelCollapse = getComponent("ModelCollapse")
const Property = getComponent("Property") const Property = getComponent("Property")
const Link = getComponent("Link") const Link = getComponent("Link")
const ModelExtensions = getComponent("ModelExtensions")
const JumpToPathSection = () => { const JumpToPathSection = () => {
return <span className="model-jump-to-path"><JumpToPath specPath={specPath} /></span> return (
<span className="model-jump-to-path">
<JumpToPath path={specPath} />
</span>
)
} }
const collapsedContent = (<span> const collapsedContent = (
<span>{ braceOpen }</span>...<span>{ braceClose }</span> <span>
{ <span>{braceOpen}</span>...<span>{braceClose}</span>
isRef ? <JumpToPathSection /> : "" {isRef ? <JumpToPathSection /> : ""}
} </span>
</span>) )
const allOf = specSelectors.isOAS3() ? schema.get("allOf") : null const allOf = specSelectors.isOAS3() ? schema.get("allOf") : null
const anyOf = specSelectors.isOAS3() ? schema.get("anyOf") : null const anyOf = specSelectors.isOAS3() ? schema.get("anyOf") : null
const oneOf = specSelectors.isOAS3() ? schema.get("oneOf") : null const oneOf = specSelectors.isOAS3() ? schema.get("oneOf") : null
const not = specSelectors.isOAS3() ? schema.get("not") : null const not = specSelectors.isOAS3() ? schema.get("not") : null
const titleEl = title && <span className="model-title"> const titleEl = title && (
{ isRef && schema.get("$$ref") && <span className="model-hint">{ schema.get("$$ref") }</span> } <span className="model-title">
<span className="model-title__text">{ title }</span> {isRef && schema.get("$$ref") && (
<span
className={classNames("model-hint", {
"model-hint--embedded": isEmbedded,
})}
>
{schema.get("$$ref")}
</span> </span>
)}
<span className="model-title__text">{title}</span>
</span>
)
return <span className="model"> return (
<span className="model">
<ModelCollapse <ModelCollapse
modelName={name} modelName={name}
title={titleEl} title={titleEl}
onToggle = {onToggle} onToggle={onToggle}
expanded={ expanded ? true : depth <= expandDepth } expanded={expanded ? true : depth <= expandDepth}
collapsedContent={ collapsedContent }> collapsedContent={collapsedContent}
>
<span className="brace-open object">{ braceOpen }</span> <span className="brace-open object">{braceOpen}</span>
{ {!isRef ? null : <JumpToPathSection />}
!isRef ? null : <JumpToPathSection />
}
<span className="inner-object"> <span className="inner-object">
{ {
<table className="model"><tbody> <table className="model">
{ <tbody>
!description ? null : <tr className="description"> {!description ? null : (
<tr className="description">
<td>description:</td> <td>description:</td>
<td> <td>
<Markdown source={ description } /> <Markdown source={description} />
</td> </td>
</tr> </tr>
} )}
{ {externalDocsUrl && (
externalDocsUrl &&
<tr className={"external-docs"}> <tr className={"external-docs"}>
<td>externalDocs:</td>
<td> <td>
externalDocs: <Link
</td> target="_blank"
<td> href={sanitizeUrl(externalDocsUrl)}
<Link target="_blank" href={sanitizeUrl(externalDocsUrl)}>{externalDocsDescription || externalDocsUrl}</Link> >
{externalDocsDescription || externalDocsUrl}
</Link>
</td> </td>
</tr> </tr>
} )}
{ {!deprecated ? null : (
!deprecated ? null :
<tr className={"property"}> <tr className={"property"}>
<td> <td>deprecated:</td>
deprecated: <td>true</td>
</td>
<td>
true
</td>
</tr> </tr>
} )}
{ {!(properties && properties.size)
!(properties && properties.size) ? null : properties.entrySeq().filter( ? null
([, value]) => { : properties
return (!value.get("readOnly") || includeReadOnly) && .entrySeq()
.filter(([, value]) => {
return (
(!value.get("readOnly") || includeReadOnly) &&
(!value.get("writeOnly") || includeWriteOnly) (!value.get("writeOnly") || includeWriteOnly)
} )
).map( })
([key, value]) => { .map(([key, value]) => {
let isDeprecated = isOAS3() && value.get("deprecated") let isDeprecated = isOAS3() && value.get("deprecated")
let isRequired = List.isList(requiredProperties) && requiredProperties.contains(key) let isRequired =
List.isList(requiredProperties) &&
requiredProperties.contains(key)
let classNames = ["property-row"] let classNames = ["property-row"]
@@ -141,133 +182,159 @@ export default class ObjectModel extends Component {
classNames.push("required") classNames.push("required")
} }
return (<tr key={key} className={classNames.join(" ")}> return (
<tr key={key} className={classNames.join(" ")}>
<td> <td>
{ key }{ isRequired && <span className="star">*</span> } {key}
{isRequired && <span className="star">*</span>}
</td> </td>
<td> <td>
<Model key={ `object-${name}-${key}_${value}` } { ...otherProps } <Model
required={ isRequired } key={`object-${name}-${key}_${value}`}
getComponent={ getComponent } {...otherProps}
required={isRequired}
getComponent={getComponent}
specPath={specPath.push("properties", key)} specPath={specPath.push("properties", key)}
getConfigs={ getConfigs } getConfigs={getConfigs}
schema={ value } schema={value}
depth={ depth + 1 } /> depth={depth + 1}
</td> />
</tr>)
}).toArray()
}
{
// empty row before extensions...
!showExtensions ? null : <tr><td>&nbsp;</td></tr>
}
{
!showExtensions ? null :
schema.entrySeq().map(
([key, value]) => {
if(key.slice(0,2) !== "x-") {
return
}
const normalizedValue = !value ? null : value.toJS ? value.toJS() : value
return (<tr key={key} className="extension">
<td>
{ key }
</td>
<td>
{ JSON.stringify(normalizedValue) }
</td>
</tr>)
}).toArray()
}
{
!additionalProperties || !additionalProperties.size ? null
: <tr>
<td>{ "< * >:" }</td>
<td>
<Model { ...otherProps } required={ false }
getComponent={ getComponent }
specPath={specPath.push("additionalProperties")}
getConfigs={ getConfigs }
schema={ additionalProperties }
depth={ depth + 1 } />
</td> </td>
</tr> </tr>
} )
{ })
!allOf ? null .toArray()}
: <tr> {extensions.size === 0 ? null : (
<td>{ "allOf ->" }</td> <>
<tr>
<td>&nbsp;</td>
</tr>
<ModelExtensions
extensions={extensions}
propClass="extension"
/>
</>
)}
{!additionalProperties ||
!additionalProperties.size ? null : (
<tr>
<td>{"< * >:"}</td>
<td>
<Model
{...otherProps}
required={false}
getComponent={getComponent}
specPath={specPath.push("additionalProperties")}
getConfigs={getConfigs}
schema={additionalProperties}
depth={depth + 1}
/>
</td>
</tr>
)}
{!allOf ? null : (
<tr>
<td>{"allOf ->"}</td>
<td> <td>
{allOf.map((schema, k) => { {allOf.map((schema, k) => {
return <div key={k}><Model { ...otherProps } required={ false } return (
getComponent={ getComponent } <div key={k}>
<Model
{...otherProps}
required={false}
getComponent={getComponent}
specPath={specPath.push("allOf", k)} specPath={specPath.push("allOf", k)}
getConfigs={ getConfigs } getConfigs={getConfigs}
schema={ schema } schema={schema}
depth={ depth + 1 } /></div> depth={depth + 1}
/>
</div>
)
})} })}
</td> </td>
</tr> </tr>
} )}
{ {!anyOf ? null : (
!anyOf ? null <tr>
: <tr> <td>{"anyOf ->"}</td>
<td>{ "anyOf ->" }</td>
<td> <td>
{anyOf.map((schema, k) => { {anyOf.map((schema, k) => {
return <div key={k}><Model { ...otherProps } required={ false } return (
getComponent={ getComponent } <div key={k}>
<Model
{...otherProps}
required={false}
getComponent={getComponent}
specPath={specPath.push("anyOf", k)} specPath={specPath.push("anyOf", k)}
getConfigs={ getConfigs } getConfigs={getConfigs}
schema={ schema } schema={schema}
depth={ depth + 1 } /></div> depth={depth + 1}
/>
</div>
)
})} })}
</td> </td>
</tr> </tr>
} )}
{ {!oneOf ? null : (
!oneOf ? null <tr>
: <tr> <td>{"oneOf ->"}</td>
<td>{ "oneOf ->" }</td>
<td> <td>
{oneOf.map((schema, k) => { {oneOf.map((schema, k) => {
return <div key={k}><Model { ...otherProps } required={ false } return (
getComponent={ getComponent } <div key={k}>
<Model
{...otherProps}
required={false}
getComponent={getComponent}
specPath={specPath.push("oneOf", k)} specPath={specPath.push("oneOf", k)}
getConfigs={ getConfigs } getConfigs={getConfigs}
schema={ schema } schema={schema}
depth={ depth + 1 } /></div> depth={depth + 1}
/>
</div>
)
})} })}
</td> </td>
</tr> </tr>
} )}
{ {!not ? null : (
!not ? null <tr>
: <tr> <td>{"not ->"}</td>
<td>{ "not ->" }</td>
<td> <td>
<div> <div>
<Model { ...otherProps } <Model
required={ false } {...otherProps}
getComponent={ getComponent } required={false}
getComponent={getComponent}
specPath={specPath.push("not")} specPath={specPath.push("not")}
getConfigs={ getConfigs } getConfigs={getConfigs}
schema={ not } schema={not}
depth={ depth + 1 } /> depth={depth + 1}
/>
</div> </div>
</td> </td>
</tr> </tr>
} )}
</tbody></table> </tbody>
</table>
} }
</span> </span>
<span className="brace-close">{ braceClose }</span> <span className="brace-close">{braceClose}</span>
</ModelCollapse> </ModelCollapse>
{ {infoProperties.size
infoProperties.size ? infoProperties.entrySeq().map( ( [ key, v ] ) => <Property key={`${key}-${v}`} propKey={ key } propVal={ v } propClass={ propClass } />) : null ? infoProperties
} .entrySeq()
.map(([key, v]) => (
<Property
key={`${key}-${v}`}
propKey={key}
propVal={v}
propClass={propClass}
/>
))
: null}
</span> </span>
)
} }
} }

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