feature: swagger-ui-react module (via #5207)
* swagger-ui-react alpha.0 * alpha.1 * alpha.2 * alpha.3 * begin updating README * alpha.4 * WIP: `displayOperationId` support * move loading error readouts to BaseLayout * add `url` prop * export React component as default * add interceptor support * modify docs markup * add `onComplete` prop * add `spec` prop * Update README.md * alpha.6 * remove independent manifest; build releasable exclusively from template * ensure dist is present; drop config field in manifest * drop alpha field this script is now able to release to npm! * remove unused selector references * Update README.md
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -11,6 +11,8 @@ selenium-debug.log
|
|||||||
test/e2e/db.json
|
test/e2e/db.json
|
||||||
docs/_book
|
docs/_book
|
||||||
|
|
||||||
|
flavors/**/dist/*
|
||||||
|
|
||||||
# Cypress
|
# Cypress
|
||||||
test/e2e-cypress/screenshots
|
test/e2e-cypress/screenshots
|
||||||
test/e2e-cypress/videos
|
test/e2e-cypress/videos
|
||||||
@@ -7,10 +7,11 @@
|
|||||||
**🕰️ Looking for the older version of Swagger UI?** Refer to the [*2.x* branch](https://github.com/swagger-api/swagger-ui/tree/2.x).
|
**🕰️ Looking for the older version of Swagger UI?** Refer to the [*2.x* branch](https://github.com/swagger-api/swagger-ui/tree/2.x).
|
||||||
|
|
||||||
|
|
||||||
This repository publishes to two different NPM modules:
|
This repository publishes to three different NPM modules:
|
||||||
|
|
||||||
* [swagger-ui](https://www.npmjs.com/package/swagger-ui) is a traditional npm module intended for use in single-page applications that are capable of resolving dependencies (via Webpack, Browserify, etc).
|
* [swagger-ui](https://www.npmjs.com/package/swagger-ui) is a traditional npm module intended for use in single-page applications that are capable of resolving dependencies (via Webpack, Browserify, etc).
|
||||||
* [swagger-ui-dist](https://www.npmjs.com/package/swagger-ui-dist) is a dependency-free module that includes everything you need to serve Swagger UI in a server-side project, or a single-page application that can't resolve npm module dependencies.
|
* [swagger-ui-dist](https://www.npmjs.com/package/swagger-ui-dist) is a dependency-free module that includes everything you need to serve Swagger UI in a server-side project, or a single-page application that can't resolve npm module dependencies.
|
||||||
|
* [swagger-ui-react](https://www.npmjs.com/package/swagger-ui-react) is Swagger UI packaged as a React component for use in React applciations.
|
||||||
|
|
||||||
We strongly suggest that you use `swagger-ui` instead of `swagger-ui-dist` if you're building a single-page application, since `swagger-ui-dist` is significantly larger.
|
We strongly suggest that you use `swagger-ui` instead of `swagger-ui-dist` if you're building a single-page application, since `swagger-ui-dist` is significantly larger.
|
||||||
|
|
||||||
@@ -67,7 +68,6 @@ To help with the migration, here are the currently known issues with 3.X. This l
|
|||||||
- Support for `collectionFormat` is partial.
|
- Support for `collectionFormat` is partial.
|
||||||
- l10n (translations) is not implemented.
|
- l10n (translations) is not implemented.
|
||||||
- Relative path support for external files is not implemented.
|
- Relative path support for external files is not implemented.
|
||||||
- There are compatibility [issues](https://github.com/swagger-api/swagger-ui/labels/epic%3A%20usage%20in%20react%4016) with React 16.x.
|
|
||||||
|
|
||||||
## Security contact
|
## Security contact
|
||||||
|
|
||||||
|
|||||||
79
flavors/swagger-ui-react/README.md
Normal file
79
flavors/swagger-ui-react/README.md
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# `swagger-ui-react`
|
||||||
|
|
||||||
|
[](http://badge.fury.io/js/swagger-ui-react)
|
||||||
|
|
||||||
|
`swagger-ui-react` is a flavor of Swagger UI suitable for use in React applications.
|
||||||
|
|
||||||
|
It has a few differences from the main version of Swagger UI:
|
||||||
|
* Declares `react` and `react-dom` as peerDependencies instead of production dependencies
|
||||||
|
* Exports a component instead of a constructor function
|
||||||
|
|
||||||
|
Versions of this module mirror the version of Swagger UI included in the distribution.
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
Install `swagger-ui-react`:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ npm i --save swagger-ui-react
|
||||||
|
```
|
||||||
|
|
||||||
|
Use it in your React application:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import SwaggerUI from "swagger-ui-react"
|
||||||
|
import "swagger-ui-react/swagger-ui.css"
|
||||||
|
|
||||||
|
export default App = () => <SwaggerUI url="https://petstore.swagger.io/v2/swagger.json" />
|
||||||
|
```
|
||||||
|
|
||||||
|
## Props
|
||||||
|
|
||||||
|
These props map to [Swagger UI configuration options](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md) of the same name.
|
||||||
|
|
||||||
|
#### `spec`: PropTypes.object
|
||||||
|
|
||||||
|
An OpenAPI document respresented as a JavaScript object, JSON string, or YAML string for Swagger UI to display.
|
||||||
|
|
||||||
|
⚠️ Don't use this in conjunction with `url` - unpredictable behavior may occur.
|
||||||
|
|
||||||
|
#### `url`: PropTypes.string
|
||||||
|
|
||||||
|
Remote URL to an OpenAPI document that Swagger UI will fetch, parse, and display.
|
||||||
|
|
||||||
|
⚠️ Don't use this in conjunction with `spec` - unpredictable behavior may occur.
|
||||||
|
|
||||||
|
#### `onComplete`: PropTypes.func
|
||||||
|
|
||||||
|
A callback function that is triggered when Swagger-UI finishes rendering an OpenAPI document.
|
||||||
|
|
||||||
|
#### `requestInterceptor`: PropTypes.func
|
||||||
|
|
||||||
|
> `req => req` or `req => Promise<req>`.
|
||||||
|
|
||||||
|
A function that accepts a request object, and returns either a request object
|
||||||
|
or a Promise that resolves to a request object.
|
||||||
|
|
||||||
|
#### `responseInterceptor`: PropTypes.func
|
||||||
|
|
||||||
|
> `res => res` or `res => Promise<res>`.
|
||||||
|
|
||||||
|
A function that accepts a response object, and returns either a response object
|
||||||
|
or a Promise that resolves to a response object.
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
* Not all configuration bindings are available.
|
||||||
|
* OAuth redirection handling is not supported.
|
||||||
|
* Topbar/Standalone mode is not supported.
|
||||||
|
* Custom plugins are not supported.
|
||||||
|
|
||||||
|
We intend to address these limitations based on user demand, so please open an issue or pull request if you have a specific request.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
* The `package.json` in the same folder as this README is _not_ the manifest that should be used for releases - another manifest is generated at build-time and can be found in `./dist/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
For anything else, check the [Swagger-UI](https://github.com/swagger-api/swagger-ui) repository.
|
||||||
83
flavors/swagger-ui-react/index.js
Normal file
83
flavors/swagger-ui-react/index.js
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import React from "react"
|
||||||
|
import PropTypes from "prop-types"
|
||||||
|
import swaggerUIConstructor from "./swagger-ui"
|
||||||
|
|
||||||
|
export default class SwaggerUI extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
this.SwaggerUIComponent = null
|
||||||
|
this.system = null
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const ui = swaggerUIConstructor({
|
||||||
|
spec: this.props.spec,
|
||||||
|
url: this.props.url,
|
||||||
|
requestInterceptor: this.requestInterceptor,
|
||||||
|
responseInterceptor: this.responseInterceptor,
|
||||||
|
onComplete: this.onComplete,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.system = ui
|
||||||
|
this.SwaggerUIComponent = ui.getComponent("App", "root")
|
||||||
|
|
||||||
|
this.forceUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return this.SwaggerUIComponent ? <this.SwaggerUIComponent /> : null
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
if(this.props.url !== prevProps.url) {
|
||||||
|
// flush current content
|
||||||
|
this.system.specActions.updateSpec("")
|
||||||
|
|
||||||
|
if(this.props.url) {
|
||||||
|
// update the internal URL
|
||||||
|
this.system.specActions.updateUrl(this.props.url)
|
||||||
|
// trigger remote definition fetch
|
||||||
|
this.system.specActions.download(this.props.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.props.spec !== prevProps.spec && this.props.spec) {
|
||||||
|
if(typeof this.props.spec === "object") {
|
||||||
|
this.system.specActions.updateSpec(JSON.stringify(this.props.spec))
|
||||||
|
} else {
|
||||||
|
this.system.specActions.updateSpec(this.props.spec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestInterceptor = (req) => {
|
||||||
|
if (typeof this.props.requestInterceptor === "function") {
|
||||||
|
return this.props.requestInterceptor(req)
|
||||||
|
}
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
responseInterceptor = (res) => {
|
||||||
|
if (typeof this.props.responseInterceptor === "function") {
|
||||||
|
return this.props.responseInterceptor(res)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
onComplete = () => {
|
||||||
|
if (typeof this.props.onComplete === "function") {
|
||||||
|
return this.props.onComplete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SwaggerUI.propTypes = {
|
||||||
|
spec: PropTypes.oneOf([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.object,
|
||||||
|
]),
|
||||||
|
url: PropTypes.string,
|
||||||
|
requestInterceptor: PropTypes.func,
|
||||||
|
responseInterceptor: PropTypes.func,
|
||||||
|
onComplete: PropTypes.func,
|
||||||
|
}
|
||||||
5
flavors/swagger-ui-react/release/create-manifest.js
Normal file
5
flavors/swagger-ui-react/release/create-manifest.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
var jsonMerger = require("json-merger")
|
||||||
|
var fs = require("fs")
|
||||||
|
var result = jsonMerger.mergeFiles(["../../../package.json", "template.json"])
|
||||||
|
|
||||||
|
process.stdout.write(JSON.stringify(result, null, 2))
|
||||||
28
flavors/swagger-ui-react/release/run.sh
Executable file
28
flavors/swagger-ui-react/release/run.sh
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
# Deploy `swagger-ui-react` to npm.
|
||||||
|
|
||||||
|
# Parameter Expansion: http://stackoverflow.com/questions/6393551/what-is-the-meaning-of-0-in-a-bash-script
|
||||||
|
cd "${0%/*}"
|
||||||
|
|
||||||
|
mkdir ../dist
|
||||||
|
|
||||||
|
# Copy UI's dist files to our directory
|
||||||
|
cp ../../../dist/swagger-ui.js ../dist
|
||||||
|
cp ../../../dist/swagger-ui.css ../dist
|
||||||
|
|
||||||
|
# Create a releasable package manifest
|
||||||
|
node create-manifest.js > ../dist/package.json
|
||||||
|
|
||||||
|
# Transpile our top-level component
|
||||||
|
../../../node_modules/.bin/babel ../index.js > ../dist/index.js
|
||||||
|
|
||||||
|
# Copy our README into the dist folder for npm
|
||||||
|
cp ../README.md ../dist
|
||||||
|
|
||||||
|
# Run the release from the dist folder
|
||||||
|
cd ../dist
|
||||||
|
|
||||||
|
if [ "$PUBLISH_FLAVOR_REACT" = "true" ] ; then
|
||||||
|
npm publish .
|
||||||
|
else
|
||||||
|
npm pack .
|
||||||
|
fi
|
||||||
45
flavors/swagger-ui-react/release/template.json
Normal file
45
flavors/swagger-ui-react/release/template.json
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"react": {
|
||||||
|
"$remove": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"$remove": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"$remove": true
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"$remove": true
|
||||||
|
},
|
||||||
|
"bundlesize": {
|
||||||
|
"$remove": true
|
||||||
|
},
|
||||||
|
"nyc": {
|
||||||
|
"$remove": true
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"$remove": true
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"$remove": true
|
||||||
|
},
|
||||||
|
"name": "swagger-ui-react",
|
||||||
|
"main": "index.js",
|
||||||
|
"repository": "git@github.com:swagger-api/swagger-ui.git",
|
||||||
|
"contributors": [
|
||||||
|
"(in alphabetical order)",
|
||||||
|
"Anna Bodnia <anna.bodnia@gmail.com>",
|
||||||
|
"Buu Nguyen <buunguyen@gmail.com>",
|
||||||
|
"Josh Ponelat <jponelat@gmail.com>",
|
||||||
|
"Kyle Shockey <kyleshockey1@gmail.com>",
|
||||||
|
"Robert Barnwell <robert@robertismy.name>",
|
||||||
|
"Sahar Jafari <shr.jafari@gmail.com>"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=15.6.2",
|
||||||
|
"react-dom": ">=15.6.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
991
package-lock.json
generated
991
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -83,6 +83,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "^8.4.1",
|
"autoprefixer": "^8.4.1",
|
||||||
|
"babel-cli": "^6.26.0",
|
||||||
"babel-core": "^6.23.1",
|
"babel-core": "^6.23.1",
|
||||||
"babel-eslint": "^7.1.1",
|
"babel-eslint": "^7.1.1",
|
||||||
"babel-loader": "^7.1.0",
|
"babel-loader": "^7.1.0",
|
||||||
@@ -118,6 +119,7 @@
|
|||||||
"imports-loader": "^0.8.0",
|
"imports-loader": "^0.8.0",
|
||||||
"jsdom": "^11.10.0",
|
"jsdom": "^11.10.0",
|
||||||
"json-loader": "^0.5.7",
|
"json-loader": "^0.5.7",
|
||||||
|
"json-merger": "^1.1.0",
|
||||||
"json-server": "^0.12.2",
|
"json-server": "^0.12.2",
|
||||||
"less": "^3.0.2",
|
"less": "^3.0.2",
|
||||||
"license-checker": "^19.0.0",
|
"license-checker": "^19.0.0",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export default class BaseLayout extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let {specSelectors, getComponent} = this.props
|
let {errSelectors, specSelectors, getComponent} = this.props
|
||||||
|
|
||||||
let SvgAssets = getComponent("SvgAssets")
|
let SvgAssets = getComponent("SvgAssets")
|
||||||
let InfoContainer = getComponent("InfoContainer", true)
|
let InfoContainer = getComponent("InfoContainer", true)
|
||||||
@@ -33,15 +33,43 @@ export default class BaseLayout extends React.Component {
|
|||||||
|
|
||||||
const isSpecEmpty = !specSelectors.specStr()
|
const isSpecEmpty = !specSelectors.specStr()
|
||||||
|
|
||||||
if(isSpecEmpty) {
|
const loadingStatus = specSelectors.loadingStatus()
|
||||||
let loadingMessage
|
|
||||||
let isLoading = specSelectors.loadingStatus() === "loading"
|
|
||||||
if(isLoading) {
|
|
||||||
loadingMessage = <div className="loading"></div>
|
|
||||||
} else {
|
|
||||||
loadingMessage = <h4>No API definition provided.</h4>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
let loadingMessage = null
|
||||||
|
|
||||||
|
if(loadingStatus === "loading") {
|
||||||
|
loadingMessage = <div className="info">
|
||||||
|
<div className="loading-container">
|
||||||
|
<div className="loading"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
if(loadingStatus === "failed") {
|
||||||
|
loadingMessage = <div className="info">
|
||||||
|
<div className="loading-container">
|
||||||
|
<h4 className="title">Failed to load API definition.</h4>
|
||||||
|
<Errors />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadingStatus === "failedConfig") {
|
||||||
|
const lastErr = errSelectors.lastError()
|
||||||
|
const lastErrMsg = lastErr ? lastErr.get("message") : ""
|
||||||
|
loadingMessage = <div className="info" style={{ maxWidth: "880px", marginLeft: "auto", marginRight: "auto", textAlign: "center" }}>
|
||||||
|
<div className="loading-container">
|
||||||
|
<h4 className="title">Failed to load remote configuration.</h4>
|
||||||
|
<p>{lastErrMsg}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!loadingMessage && isSpecEmpty) {
|
||||||
|
loadingMessage = <h4>No API definition provided.</h4>
|
||||||
|
}
|
||||||
|
|
||||||
|
if(loadingMessage) {
|
||||||
return <div className="swagger-ui">
|
return <div className="swagger-ui">
|
||||||
<div className="loading-container">
|
<div className="loading-container">
|
||||||
{loadingMessage}
|
{loadingMessage}
|
||||||
|
|||||||
@@ -16,49 +16,22 @@ export default class StandaloneLayout extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let { getComponent, specSelectors, errSelectors } = this.props
|
let { getComponent } = this.props
|
||||||
|
|
||||||
let Container = getComponent("Container")
|
let Container = getComponent("Container")
|
||||||
let Row = getComponent("Row")
|
let Row = getComponent("Row")
|
||||||
let Col = getComponent("Col")
|
let Col = getComponent("Col")
|
||||||
let Errors = getComponent("errors", true)
|
|
||||||
|
|
||||||
const Topbar = getComponent("Topbar", true)
|
const Topbar = getComponent("Topbar", true)
|
||||||
const BaseLayout = getComponent("BaseLayout", true)
|
const BaseLayout = getComponent("BaseLayout", true)
|
||||||
const OnlineValidatorBadge = getComponent("onlineValidatorBadge", true)
|
const OnlineValidatorBadge = getComponent("onlineValidatorBadge", true)
|
||||||
|
|
||||||
const loadingStatus = specSelectors.loadingStatus()
|
|
||||||
const lastErr = errSelectors.lastError()
|
|
||||||
const lastErrMsg = lastErr ? lastErr.get("message") : ""
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<Container className='swagger-ui'>
|
<Container className='swagger-ui'>
|
||||||
{ Topbar ? <Topbar /> : null }
|
{Topbar ? <Topbar /> : null}
|
||||||
{ loadingStatus === "loading" &&
|
<BaseLayout />
|
||||||
<div className="info">
|
|
||||||
<div className="loading-container">
|
|
||||||
<div className="loading"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
{ loadingStatus === "failed" &&
|
|
||||||
<div className="info">
|
|
||||||
<div className="loading-container">
|
|
||||||
<h4 className="title">Failed to load API definition.</h4>
|
|
||||||
<Errors/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
{ loadingStatus === "failedConfig" &&
|
|
||||||
<div className="info" style={{ maxWidth: "880px", marginLeft: "auto", marginRight: "auto", textAlign: "center" }}>
|
|
||||||
<div className="loading-container">
|
|
||||||
<h4 className="title">Failed to load remote configuration.</h4>
|
|
||||||
<p>{lastErrMsg}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
{ !loadingStatus || loadingStatus === "success" && <BaseLayout /> }
|
|
||||||
<Row>
|
<Row>
|
||||||
<Col>
|
<Col>
|
||||||
<OnlineValidatorBadge />
|
<OnlineValidatorBadge />
|
||||||
|
|||||||
Reference in New Issue
Block a user