* 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.mdbubble
@@ -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 | ||||
@@ -0,0 +1,79 @@ | |||||
# `swagger-ui-react` | |||||
[![NPM version](https://badge.fury.io/js/swagger-ui-react.svg)](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. |
@@ -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, | |||||
} |
@@ -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)) |
@@ -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 |
@@ -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" | |||||
} | |||||
} |
@@ -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) { | |||||
let loadingMessage | |||||
let isLoading = specSelectors.loadingStatus() === "loading" | |||||
if(isLoading) { | |||||
loadingMessage = <div className="loading"></div> | |||||
} else { | |||||
loadingMessage = <h4>No API definition provided.</h4> | |||||
} | |||||
const loadingStatus = specSelectors.loadingStatus() | |||||
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 } | |||||
{ loadingStatus === "loading" && | |||||
<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 /> } | |||||
{Topbar ? <Topbar /> : null} | |||||
<BaseLayout /> | |||||
<Row> | <Row> | ||||
<Col> | <Col> | ||||
<OnlineValidatorBadge /> | <OnlineValidatorBadge /> | ||||