Bläddra i källkod

in with the new

bubble
Ron 7 år sedan
förälder
incheckning
f22a628934
100 ändrade filer med 6857 tillägg och 0 borttagningar
  1. +38
    -0
      .babelrc
  2. +5
    -0
      .gitignore
  3. +0
    -0
     
  4. +11
    -0
      LICENSE
  5. +111
    -0
      README.md
  6. +28
    -0
      build-tools/loadersByExtension.js
  7. +92
    -0
      dist/index.html
  8. +105
    -0
      dist/swagger-ui-bundle.js
  9. +1
    -0
      dist/swagger-ui-bundle.js.map
  10. +20
    -0
      dist/swagger-ui-standalone-preset.js
  11. +1
    -0
      dist/swagger-ui-standalone-preset.js.map
  12. +2
    -0
      dist/swagger-ui.css
  13. +1
    -0
      dist/swagger-ui.css.map
  14. +9
    -0
      dist/swagger-ui.js
  15. +1
    -0
      dist/swagger-ui.js.map
  16. +145
    -0
      make-webpack-config.js
  17. +124
    -0
      package.json
  18. Binär
     
  19. Binär
     
  20. +93
    -0
      public/index.html
  21. +83
    -0
      public/oauth2-redirect.html
  22. +6
    -0
      src/core/brace-snippets-yaml.js
  23. +27
    -0
      src/core/components/app.jsx
  24. +82
    -0
      src/core/components/auth/api-key-auth.jsx
  25. +59
    -0
      src/core/components/auth/authorization-popup.jsx
  26. +42
    -0
      src/core/components/auth/authorize-btn.jsx
  27. +34
    -0
      src/core/components/auth/authorize-operation-btn.jsx
  28. +138
    -0
      src/core/components/auth/auths.jsx
  29. +97
    -0
      src/core/components/auth/basic-auth.jsx
  30. +23
    -0
      src/core/components/auth/error.jsx
  31. +218
    -0
      src/core/components/auth/oauth2.jsx
  32. +24
    -0
      src/core/components/clear.jsx
  33. +45
    -0
      src/core/components/content-type.jsx
  34. +28
    -0
      src/core/components/curl.jsx
  35. +51
    -0
      src/core/components/debug.jsx
  36. +113
    -0
      src/core/components/errors.jsx
  37. +41
    -0
      src/core/components/execute.jsx
  38. +9
    -0
      src/core/components/footer.jsx
  39. +46
    -0
      src/core/components/headers.jsx
  40. +24
    -0
      src/core/components/highlight-code.jsx
  41. +128
    -0
      src/core/components/info.jsx
  42. +252
    -0
      src/core/components/layout-utils.jsx
  43. +72
    -0
      src/core/components/layouts/xpane.jsx
  44. +91
    -0
      src/core/components/live-response.jsx
  45. +58
    -0
      src/core/components/model-example.jsx
  46. +300
    -0
      src/core/components/model.jsx
  47. +42
    -0
      src/core/components/models.jsx
  48. +40
    -0
      src/core/components/online-validator-badge.jsx
  49. +258
    -0
      src/core/components/operation.jsx
  50. +133
    -0
      src/core/components/operations.jsx
  51. +119
    -0
      src/core/components/overview.jsx
  52. +141
    -0
      src/core/components/param-body.jsx
  53. +119
    -0
      src/core/components/parameter-row.jsx
  54. +109
    -0
      src/core/components/parameters.jsx
  55. +95
    -0
      src/core/components/response-body.jsx
  56. +91
    -0
      src/core/components/response.jsx
  57. +93
    -0
      src/core/components/responses.jsx
  58. +45
    -0
      src/core/components/schemes.jsx
  59. +0
    -0
     
  60. +27
    -0
      src/core/components/try-it-out-button.jsx
  61. +34
    -0
      src/core/curlify.js
  62. +119
    -0
      src/core/index.js
  63. +187
    -0
      src/core/json-schema-components.js
  64. +47
    -0
      src/core/oauth2-authorize.js
  65. +67
    -0
      src/core/path-translator.js
  66. +17
    -0
      src/core/plugins/all.js
  67. +36
    -0
      src/core/plugins/allow-try-it-out-if-host.js
  68. +284
    -0
      src/core/plugins/ast/ast.js
  69. +9
    -0
      src/core/plugins/ast/index.js
  70. +9
    -0
      src/core/plugins/ast/jump-to-path.jsx
  71. +118
    -0
      src/core/plugins/auth/actions.js
  72. +19
    -0
      src/core/plugins/auth/index.js
  73. +63
    -0
      src/core/plugins/auth/reducers.js
  74. +78
    -0
      src/core/plugins/auth/selectors.js
  75. +13
    -0
      src/core/plugins/auth/spec-wrap-actions.js
  76. +67
    -0
      src/core/plugins/download-url.js
  77. +43
    -0
      src/core/plugins/err/actions.js
  78. +31
    -0
      src/core/plugins/err/error-transformers/README.md
  79. +57
    -0
      src/core/plugins/err/error-transformers/hook.js
  80. +29
    -0
      src/core/plugins/err/error-transformers/transformers/not-of-type.js
  81. +60
    -0
      src/core/plugins/err/error-transformers/transformers/parameter-oneof.js
  82. +10
    -0
      src/core/plugins/err/error-transformers/transformers/strip-instance.js
  83. +15
    -0
      src/core/plugins/err/index.js
  84. +68
    -0
      src/core/plugins/err/reducers.js
  85. +15
    -0
      src/core/plugins/err/selectors.js
  86. +45
    -0
      src/core/plugins/layout/actions.js
  87. +15
    -0
      src/core/plugins/layout/index.js
  88. +24
    -0
      src/core/plugins/layout/reducers.js
  89. +22
    -0
      src/core/plugins/layout/selectors.js
  90. +28
    -0
      src/core/plugins/logs/index.js
  91. +223
    -0
      src/core/plugins/samples/fn.js
  92. +5
    -0
      src/core/plugins/samples/index.js
  93. +234
    -0
      src/core/plugins/spec/actions.js
  94. +17
    -0
      src/core/plugins/spec/index.js
  95. +123
    -0
      src/core/plugins/spec/reducers.js
  96. +318
    -0
      src/core/plugins/spec/selectors.js
  97. +15
    -0
      src/core/plugins/spec/wrap-actions.js
  98. +17
    -0
      src/core/plugins/split-pane-mode/components/index.js
  99. +81
    -0
      src/core/plugins/split-pane-mode/components/split-pane-mode.jsx
  100. +5
    -0
      src/core/plugins/split-pane-mode/components/split-pane-mode.less

+ 38
- 0
.babelrc Visa fil

@@ -0,0 +1,38 @@
{
"presets": [
"es2015",
"react",
"stage-0"
],
"plugins": [
[
"module-alias",
[
{
"expose": "components",
"src": "src/core/components"
},
{
"expose": "core",
"src": "src/core"
},
{
"expose": "img",
"src": "src/img"
},
{
"expose": "corePlugins",
"src": "src/core/plugins"
},
{
"expose": "less",
"src": "src/less"
},
{
"expose": "base",
"src": "npm:getbase/src/less/base"
}
]
]
]
}

+ 5
- 0
.gitignore Visa fil

@@ -0,0 +1,5 @@
node_modules
.idea
.deps_check
.DS_Store
npm-debug.log

+ 0
- 0
Visa fil


+ 11
- 0
LICENSE Visa fil

@@ -0,0 +1,11 @@
Copyright 2017 SmartBear Software

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at [apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

+ 111
- 0
README.md Visa fil

@@ -0,0 +1,111 @@
# Swagger UI

[![NPM version](https://badge.fury.io/js/swagger-ui.svg)](http://badge.fury.io/js/swagger-ui)

## New!

This is the new version of swagger-ui, 3.x.

For the older version of swagger-ui, refer to the [*2.x branch*](https://github.com/swagger-api/swagger-ui/tree/2.x).

## Compatibility
The OpenAPI Specification has undergone 4 revisions since initial creation in 2010. Compatibility between swagger-ui and the OpenAPI Specification is as follows:

Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes | Status
------------------ | ------------ | -------------------------- | ----- | ------
3.0.0 | 2017-03-17 | 2.0 | [tag v3.0.0](https://github.com/swagger-api/swagger-ui/tree/v3.0.0) |
2.2.10 | 2017-01-04 | 1.1, 1.2, 2.0 | [tag v2.2.10](https://github.com/swagger-api/swagger-ui/tree/v2.2.10) |
2.1.5 | 2016-07-20 | 1.1, 1.2, 2.0 | [tag v2.1.5](https://github.com/swagger-api/swagger-ui/tree/v2.1.5) |
2.0.24 | 2014-09-12 | 1.1, 1.2 | [tag v2.0.24](https://github.com/swagger-api/swagger-ui/tree/v2.0.24) |
1.0.13 | 2013-03-08 | 1.1, 1.2 | [tag v1.0.13](https://github.com/swagger-api/swagger-ui/tree/v1.0.13) |
1.0.1 | 2011-10-11 | 1.0, 1.1 | [tag v1.0.1](https://github.com/swagger-api/swagger-ui/tree/v1.0.1) |


### How to run

##### Prerequisites
- Node 6.x
- NPM 3.x

If you just want to see your specs, open `public/index.html` in your browser directly from your filesystem.

If you'd like to make modifications to the codebase, run the dev server with: `npm run dev`.


##### Browser support
Swagger UI works in the latest versions of Chrome, Safari, Firefox, Edge and IE11.

## CORS Support

CORS is a technique to prevent websites from doing bad things with your personal data. Most browsers + JavaScript toolkits not only support CORS but enforce it, which has implications for your API server which supports Swagger.

You can read about CORS here: http://www.w3.org/TR/cors.

There are two cases where no action is needed for CORS support:

1. swagger-ui is hosted on the same server as the application itself (same host *and* port).
2. The application is located behind a proxy that enables the required CORS headers. This may already be covered within your organization.

Otherwise, CORS support needs to be enabled for:

1. Your Swagger docs. For Swagger 2.0 it's the `swagger.json`/`swagger.yaml` and any externally `$ref`ed docs.
2. For the `Try it now` button to work, CORS needs to be enabled on your API endpoints as well.

### Testing CORS Support

You can verify CORS support with one of three techniques:

- Curl your API and inspect the headers. For instance:

```bash
$ curl -I "http://petstore.swagger.io/v2/swagger.json"
HTTP/1.1 200 OK
Date: Sat, 31 Jan 2015 23:05:44 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, DELETE, PUT, PATCH, OPTIONS
Access-Control-Allow-Headers: Content-Type, api_key, Authorization
Content-Type: application/json
Content-Length: 0
```

This tells us that the petstore resource listing supports OPTIONS, and the following headers: `Content-Type`, `api_key`, `Authorization`.

- Try swagger-ui from your file system and look at the debug console. If CORS is not enabled, you'll see something like this:

```
XMLHttpRequest cannot load http://sad.server.com/v2/api-docs. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.
```

Swagger-UI cannot easily show this error state.

- Using the http://www.test-cors.org website. Keep in mind this will show a successful result even if `Access-Control-Allow-Headers` is not available, which is still required for Swagger-UI to function properly.

### Enabling CORS

The method of enabling CORS depends on the server and/or framework you use to host your application. http://enable-cors.org provides information on how to enable CORS in some common web servers.

Other servers/frameworks may provide you information on how to enable it specifically in their use case.

### CORS and Header Parameters

Swagger lets you easily send headers as parameters to requests. The name of these headers *MUST* be supported in your CORS configuration as well. From our example above:

```
Access-Control-Allow-Headers: Content-Type, api_key, Authorization
```

Only headers with these names will be allowed to be sent by Swagger-UI.

## License

Copyright 2017 SmartBear Software

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at [apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

+ 28
- 0
build-tools/loadersByExtension.js Visa fil

@@ -0,0 +1,28 @@
function extsToRegExp(exts) {
return new RegExp("\\.(" + exts.map(function(ext) {
return ext.replace(/\./g, "\\.");
}).join("|") + ")(\\?.*)?$");
}

module.exports = function loadersByExtension(obj) {
var loaders = [];
Object.keys(obj).forEach(function(key) {
var exts = key.split("|");
var value = obj[key];
var entry = {
extensions: exts,
test: extsToRegExp(exts)
};
if(Array.isArray(value)) {
entry.loaders = value;
} else if(typeof value === "string") {
entry.loader = value;
} else {
Object.keys(value).forEach(function(valueKey) {
entry[valueKey] = value[valueKey];
});
}
loaders.push(entry);
});
return loaders;
};

+ 92
- 0
dist/index.html Visa fil

@@ -0,0 +1,92 @@
<!DOCTYPE html>
<!-- this is for the dev server -->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}

body {
margin:0;
background: #fafafa;
}
</style>
</head>

<body>

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
<defs>
<symbol viewBox="0 0 20 20" id="unlocked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
</symbol>

<symbol viewBox="0 0 20 20" id="locked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
</symbol>

<symbol viewBox="0 0 20 20" id="close">
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
</symbol>

<symbol viewBox="0 0 20 20" id="large-arrow">
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
</symbol>

<symbol viewBox="0 0 20 20" id="large-arrow-down">
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
</symbol>


<symbol viewBox="0 0 24 24" id="jump-to">
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
</symbol>

<symbol viewBox="0 0 24 24" id="expand">
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
</symbol>

</defs>
</svg>

<div id="swagger-ui"></div>

<!-- don't be alarmed, these don't match what's in dist, because webpack-dev-server serves them in memory. -->
<script src="/dist/SwaggerUIBundle.js"> </script>
<script src="/dist/SwaggerUIStandalonePreset.js"> </script>
<script>
window.onload = function() {
// Build a system
const ui = SwaggerUIBundle({
url: "http://petstore.swagger.io/v2/swagger.json",
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
// yay ES6 modules ↘
Array.isArray(SwaggerUIStandalonePreset) ? SwaggerUIStandalonePreset : SwaggerUIStandalonePreset.default
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})

window.ui = ui
}
</script>
</body>

</html>

+ 105
- 0
dist/swagger-ui-bundle.js
Filskillnaden har hållits tillbaka eftersom den är för stor
Visa fil


+ 1
- 0
dist/swagger-ui-bundle.js.map Visa fil

@@ -0,0 +1 @@
{"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;AA6OA;;;;;;AAoIA;AAm7FA;AAwtCA;AAg0IA;;;;;AAkxBA;AAo8IA;AA41GA;AA23FA;AAmqFA;AA0nFA;AA49CA;AAwhDA;AAkrCA;AAumFA;AAmnHA;;;;;;;;;;;;;;AAqjHA;AAyoIA;AAkuJA;AAilHA;AA4kGA;AAwkEA;AAs3DA;AAovDA;AAotBA;AAoqGA;;;;;;AAueA;AAimGA;AA44EA;;;;;AAoGA;AA2qFA;AAo2CA;AAgvDA;AA8tCA;AAoiEA;AA69FA;;;;;;;;;AA20BA;AA2zIA;AAm4DA","sourceRoot":""}

+ 20
- 0
dist/swagger-ui-standalone-preset.js
Filskillnaden har hållits tillbaka eftersom den är för stor
Visa fil


+ 1
- 0
dist/swagger-ui-standalone-preset.js.map Visa fil

@@ -0,0 +1 @@
{"version":3,"file":"swagger-ui-standalone-preset.js","sources":["webpack:///swagger-ui-standalone-preset.js"],"mappings":"AAAA;;;;;AAwSA;AAyiGA;AAqwFA;;;;;;AA4eA;AAkvFA;AAu+CA;AAo+CA;AAgrCA;AAgyEA","sourceRoot":""}

+ 2
- 0
dist/swagger-ui.css
Filskillnaden har hållits tillbaka eftersom den är för stor
Visa fil


+ 1
- 0
dist/swagger-ui.css.map Visa fil

@@ -0,0 +1 @@
{"version":3,"file":"swagger-ui.css","sources":[],"mappings":"","sourceRoot":""}

+ 9
- 0
dist/swagger-ui.js
Filskillnaden har hållits tillbaka eftersom den är för stor
Visa fil


+ 1
- 0
dist/swagger-ui.js.map Visa fil

@@ -0,0 +1 @@
{"version":3,"file":"swagger-ui.js","sources":["webpack:///swagger-ui.js"],"mappings":"AAAA;AAooGA;AAw0HA;AAijGA;AA6lCA;AA29BA;AAmwCA;AAw4BA","sourceRoot":""}

+ 145
- 0
make-webpack-config.js Visa fil

@@ -0,0 +1,145 @@
var path = require('path')

var webpack = require('webpack')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var deepExtend = require('deep-extend')
var autoprefixer = require('autoprefixer')

var loadersByExtension = require('./build-tools/loadersByExtension')

module.exports = function(options) {

// Special options, that have logic in this file
// ...with defaults
var specialOptions = deepExtend({}, {
hot: false,
separateStylesheets: true,
minimize: false,
longTermCaching: false,
sourcemaps: false,
}, options._special)

var loadersMap = {
'js(x)?': {
loader: 'babel?retainLines=true',
include: [ path.join(__dirname, 'src') ],
},
'json': 'json-loader',
'txt|yaml': 'raw-loader',
'png|jpg|jpeg|gif|svg': specialOptions.disableAssets ? 'null-loader' : 'url-loader?limit=10000',
'woff|woff2': specialOptions.disableAssets ? 'null-loader' : 'url-loader?limit=100000',
'ttf|eot': specialOptions.disableAssets ? 'null-loader' : 'file-loader',
"worker.js": ["worker-loader?inline=true", "babel"]
}

var plugins = []

if( specialOptions.separateStylesheets ) {
plugins.push(new ExtractTextPlugin('[name].css' + (specialOptions.longTermCaching ? '?[contenthash]' : ''), {
allChunks: true
}))
}

if( specialOptions.minimize ) {

plugins.push(
new webpack.optimize.UglifyJsPlugin({
compressor: {
warnings: false
}
}),
new webpack.optimize.DedupePlugin()
)

plugins.push( new webpack.NoErrorsPlugin())

}

plugins.push(
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: specialOptions.minimize ? JSON.stringify('production') : null,
WEBPACK_INLINE_STYLES: !Boolean(specialOptions.separateStylesheets)
},
}))

var cssLoader = 'css-loader!postcss-loader'

var completeStylesheetLoaders = deepExtend({
'css': cssLoader,
'scss': cssLoader + '!' + 'sass-loader?outputStyle=expanded&sourceMap=true&sourceMapContents=true',
'less': cssLoader + '!' + 'less-loader',
}, specialOptions.stylesheetLoaders)

if(specialOptions.cssModules) {
cssLoader = cssLoader + '?module' + (specialOptions.minimize ? '' : '&localIdentName=[path][name]---[local]---[hash:base64:5]')
}

Object.keys(completeStylesheetLoaders).forEach(function(ext) {
var ori = completeStylesheetLoaders[ext]
if(specialOptions.separateStylesheets) {
completeStylesheetLoaders[ext] = ExtractTextPlugin.extract('style-loader', ori)
} else {
completeStylesheetLoaders[ext] = 'style-loader!' + ori
}
})

var loaders = loadersByExtension(deepExtend({}, loadersMap, specialOptions.loaders, completeStylesheetLoaders))
var extraLoaders = (options.module || {} ).loaders

if(Array.isArray(extraLoaders)) {
loaders = loaders.concat(extraLoaders)
delete options.module.loaders
}

var completeConfig = deepExtend({
entry: {},

output: {
path: path.join(__dirname, 'dist'),
publicPath: '/',
filename: '[name].js',
chunkFilename: '[name].js'
},

target: 'web',

// yaml-js has a reference to `fs`, this is a workaround
node: {
fs: 'empty'
},

module: {
loaders: loaders,
},

resolveLoader: {
root: path.join(__dirname, 'node_modules'),
},

externals: {
'buffertools': true // json-react-schema/deeper depends on buffertools, which fails.
},

resolve: {
root: path.join(__dirname, './src'),
modulesDirectories: ['node_modules'],
extensions: ["", ".web.js", ".js", ".jsx", ".json", ".less"],
packageAlias: 'browser',
alias: {
base: "getbase/src/less/base"
}
},

postcss: function() {
return [autoprefixer]
},

devtool: specialOptions.sourcemaps ? 'cheap-module-source-map' : null,

plugins,

}, options)

return completeConfig
}

+ 124
- 0
package.json Visa fil

@@ -0,0 +1,124 @@
{
"name": "swagger-ui",
"version": "3.0.0",
"main": "dist/swagger-ui.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",
"scripts": {
"start": "http-server -i -a 0.0.0.0 -p 3001",
"build": "npm run build-core && npm run build-bundle && npm run build-standalone",
"build-bundle": "webpack --config webpack-dist-bundle.config.js --colors",
"build-core": "webpack --config webpack-dist.config.js --colors",
"build-standalone": "webpack --config webpack-dist-standalone.config.js --colors",
"predev": "npm install",
"dev": "npm-run-all --parallel hot-server watch open-localhost",
"watch": "webpack --config webpack-watch.config.js --watch --progress",
"open-localhost": "node -e 'require(\"open\")(\"http://localhost:3200\")'",
"hot-server": "webpack-dev-server --host 0.0.0.0 --config webpack-hot-dev-server.config.js --inline --hot --progress --content-base dist/",
"deps-license": "license-checker --production --csv --out $npm_package_config_deps_check_dir/licenses.csv && license-checker --development --csv --out $npm_package_config_deps_check_dir/licenses-dev.csv",
"deps-size": "webpack -p --config webpack.check.js --json | webpack-bundle-size-analyzer >| $npm_package_config_deps_check_dir/sizes.txt",
"deps-check": "npm run deps-license && npm run deps-size",
"just-test-in-node": "mocha --recursive --compilers js:babel-core/register test/core"
},
"dependencies": {
"brace": "0.7.0",
"btoa": "^1.1.2",
"debounce": "1.0.0",
"deep-extend": "0.4.1",
"expect": "1.20.2",
"getbase": "^2.8.2",
"immutable": "^3.x.x",
"js-yaml": "^3.5.5",
"jsonschema": "^1.1.0",
"less": "2.7.1",
"lodash": "4.17.2",
"matcher": "^0.1.2",
"memoizee": "0.4.1",
"promise-worker": "^1.1.1",
"react": "^15.4.0",
"react-addons-perf": "0.14.8",
"react-addons-shallow-compare": "0.14.8",
"react-addons-test-utils": "0.14.8",
"react-collapse": "2.3.1",
"react-dom": "^15.4.0",
"react-height": "^2.0.0",
"react-hot-loader": "1.3.1",
"react-immutable-proptypes": "2.1.0",
"react-motion": "0.4.4",
"react-object-inspector": "0.2.1",
"react-redux": "^4.x.x",
"react-remarkable": "1.1.1",
"react-split-pane": "0.1.57",
"redux": "^3.x.x",
"redux-immutable": "3.0.8",
"redux-logger": "*",
"reselect": "2.5.3",
"serialize-error": "2.0.0",
"swagger-client": "^3.0.1",
"whatwg-fetch": "0.11.1",
"worker-loader": "^0.7.1",
"xml": "1.0.1",
"yaml-js": "^0.1.3",
"yaml-worker": "^2.1.0"
},
"devDependencies": {
"autoprefixer": "6.6.1",
"babel-core": "^6.23.1",
"babel-eslint": "^7.1.1",
"babel-loader": "^6.3.2",
"babel-plugin-module-alias": "^1.6.0",
"babel-preset-es2015": "^6.22.0",
"babel-preset-es2015-ie": "^6.6.2",
"babel-preset-react": "^6.23.0",
"babel-preset-stage-0": "^6.22.0",
"babel-runtime": "^6.23.0",
"css-loader": "0.22.0",
"deep-extend": "^0.4.1",
"deepmerge": "^1.3.2",
"extract-text-webpack-plugin": "0.8.2",
"file-loader": "0.8.4",
"html-webpack-plugin": "^2.28.0",
"imports-loader": "0.6.5",
"json-loader": "0.5.3",
"less": "2.5.3",
"less-loader": "2.2.1",
"license-checker": "^8.0.4",
"mocha": "^2.5.3",
"node-sass": "^4.5.0",
"npm-run-all": "3.1.1",
"null-loader": "0.1.1",
"open": "0.0.5",
"postcss-loader": "0.7.0",
"raw-loader": "0.5.1",
"react-hot-loader": "^1.3.1",
"rimraf": "^2.6.0",
"sass-loader": "^6.0.2",
"shallowequal": "0.2.2",
"standard": "^8.6.0",
"standard-loader": "^5.0.0",
"style-loader": "0.13.0",
"url-loader": "0.5.6",
"webpack": "^1.14.0",
"webpack-bundle-size-analyzer": "^2.5.0"
},
"config": {
"deps_check_dir": ".deps_check"
},
"browserslist": [
"> 1%",
"last 2 versions",
"IE 10"
],
"optionalDependencies": {
"webpack-dev-server": "1.14.0"
}
}

Binär
Visa fil


Binär
Visa fil


+ 93
- 0
public/index.html Visa fil

@@ -0,0 +1,93 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="../dist/swagger-ui.css" >
<link rel="icon" type="image/png" href="favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}

body {
margin:0;
background: #fafafa;
}
</style>
</head>

<body>

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
<defs>
<symbol viewBox="0 0 20 20" id="unlocked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
</symbol>

<symbol viewBox="0 0 20 20" id="locked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
</symbol>

<symbol viewBox="0 0 20 20" id="close">
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
</symbol>

<symbol viewBox="0 0 20 20" id="large-arrow">
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
</symbol>

<symbol viewBox="0 0 20 20" id="large-arrow-down">
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
</symbol>


<symbol viewBox="0 0 24 24" id="jump-to">
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
</symbol>

<symbol viewBox="0 0 24 24" id="expand">
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
</symbol>

</defs>
</svg>

<div id="swagger-ui"></div>

<script src="../dist/swagger-ui-bundle.js"> </script>
<script src="../dist/swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Build a system
const ui = SwaggerUIBundle({
url: "http://petstore.swagger.io/v2/swagger.json",
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
// yay ES6 modules ↘
Array.isArray(SwaggerUIStandalonePreset) ? SwaggerUIStandalonePreset : SwaggerUIStandalonePreset.default
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})

window.ui = ui
}
</script>
</body>

</html>

+ 83
- 0
public/oauth2-redirect.html Visa fil

@@ -0,0 +1,83 @@
<!doctype html>
<html lang="en-US">
<body onload="run()">
</body>
</html>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var isValid, qp;

qp = (window.location.hash || location.search).substring(1);

qp = qp ? JSON.parse('{"' + qp.replace(/&/g, '","').replace(/=/g, '":"') + '"}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value)
}
) : {}

isValid = qp.state === sentState

if (oauth2.auth.schema.get("flow") === "accessCode" && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
});
}

if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
createForm(oauth2.auth, qp).submit();
} else {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: "Authorization failed: no accessCode came from the server"
});
window.close();
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid});
window.close();
}
}

function createForm(auth, qp) {
var form = document.createElement("form");
var schema = auth.schema;
var action = schema.get("tokenUrl");
var name, input;

var fields = {
code: qp.code,
"redirect_uri": location.protocol + "//" + location.host + location.pathname,
"grant_type": "authorization_code",
"client_secret": auth.clientSecret,
"client_id": auth.clientId
}

for ( name in fields ) {
input = document.createElement("input");
input.name = name;
input.value = fields[name];
input.type = "hidden";
form.appendChild(input);
}


form.method = "POST";
form.action = action;

document.body.appendChild(form);

return form;
}

</script>

+ 6
- 0
src/core/brace-snippets-yaml.js Visa fil

@@ -0,0 +1,6 @@
/* global ace */
ace.define("ace/snippets/yaml",
["require","exports","module"], function(e,t,n){ // eslint-disable-line no-unused-vars
t.snippetText=undefined
t.scope="yaml"
})

+ 27
- 0
src/core/components/app.jsx Visa fil

@@ -0,0 +1,27 @@
import React, { PropTypes } from "react"

export default class App extends React.Component {

getLayout() {
let { getComponent, layoutSelectors } = this.props
const layoutName = layoutSelectors.current()
const Component = getComponent(layoutName, true)
return Component ? Component : ()=> <h1> No layout defined for "{layoutName}" </h1>
}

render() {
const Layout = this.getLayout()

return (
<Layout/>
)
}
}

App.propTypes = {
getComponent: PropTypes.func.isRequired,
layoutSelectors: PropTypes.object.isRequired,
}

App.defaultProps = {
}

+ 82
- 0
src/core/components/auth/api-key-auth.jsx Visa fil

@@ -0,0 +1,82 @@
import React, { PropTypes } from "react"

export default class ApiKeyAuth extends React.Component {
static propTypes = {
authorized: PropTypes.object,
getComponent: PropTypes.func.isRequired,
errSelectors: PropTypes.object.isRequired,
schema: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
onChange: PropTypes.func
}

constructor(props, context) {
super(props, context)
let { name, schema } = this.props
let value = this.getValue()

this.state = {
name: name,
schema: schema,
value: value
}
}

getValue () {
let { name, authorized } = this.props

return authorized && authorized.getIn([name, "value"])
}

onChange =(e) => {
let { onChange } = this.props
let value = e.target.value
let newState = Object.assign({}, this.state, { value: value })

this.setState(newState)
onChange(newState)
}

render() {
let { schema, getComponent, errSelectors, name } = this.props
const Input = getComponent("Input")
const Row = getComponent("Row")
const Col = getComponent("Col")
const AuthError = getComponent("authError")
const Markdown = getComponent( "Markdown" )
const JumpToPath = getComponent("JumpToPath", true)
let value = this.getValue()
let errors = errSelectors.allErrors().filter( err => err.get("authId") === name)

return (
<div>
<h4>Api key authorization<JumpToPath path={[ "securityDefinitions", name ]} /></h4>
{ value && <h6>Authorized</h6>}
<Row>
<Markdown options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}}
source={ schema.get("description") } />
</Row>
<Row>
<p>Name: <code>{ schema.get("name") }</code></p>
</Row>
<Row>
<p>In: <code>{ schema.get("in") }</code></p>
</Row>
<Row>
<label>Value:</label>
<Col>
{
value || <Input type="text" onChange={ this.onChange }/>
}
</Col>
</Row>
{
errors.valueSeq().map( (error, key) => {
return <AuthError error={ error }
key={ key }/>
} )
}
</div>
)
}
}

+ 59
- 0
src/core/components/auth/authorization-popup.jsx Visa fil

@@ -0,0 +1,59 @@
import React, { PropTypes } from "react"

export default class AuthorizationPopup extends React.Component {
close =() => {
let { authActions } = this.props

authActions.showDefinitions(false)
}

render() {
let { authSelectors, authActions, getComponent, errSelectors, specSelectors, fn: { AST } } = this.props
let definitions = authSelectors.shownDefinitions()
const Auths = getComponent("auths")

return (
<div className="dialog-ux">
<div className="backdrop-ux"></div>
<div className="modal-ux">
<div className="modal-dialog-ux">
<div className="modal-ux-inner">
<div className="modal-ux-header">
<h3>Available authorizations</h3>
<button type="button" className="close-modal" onClick={ this.close }>
<svg width="20" height="20">
<use xlinkHref="#close" />
</svg>
</button>
</div>
<div className="modal-ux-content">

{
definitions.valueSeq().map(( definition, key ) => {
return <Auths key={ key }
AST={AST}
definitions={ definition }
getComponent={ getComponent }
errSelectors={ errSelectors }
authSelectors={ authSelectors }
authActions={ authActions }
specSelectors={ specSelectors }/>
})
}
</div>
</div>
</div>
</div>
</div>
)
}

static propTypes = {
fn: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
authSelectors: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
errSelectors: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
}
}

+ 42
- 0
src/core/components/auth/authorize-btn.jsx Visa fil

@@ -0,0 +1,42 @@
import React, { PropTypes } from "react"

export default class AuthorizeBtn extends React.Component {
static propTypes = {
className: PropTypes.string
}

onClick =() => {
let { authActions, authSelectors, errActions} = this.props
let definitions = authSelectors.definitionsToAuthorize()

authActions.showDefinitions(definitions)
}

render() {
let { authSelectors, getComponent } = this.props
//must be moved out of button component
const AuthorizationPopup = getComponent("authorizationPopup", true)
let showPopup = !!authSelectors.shownDefinitions()
let isAuthorized = !!authSelectors.authorized().size

return (
<div className="auth-wrapper">
<button className={isAuthorized ? "btn authorize locked" : "btn authorize unlocked"} onClick={ this.onClick }>
<span>Authorize</span>
<svg width="20" height="20">
<use xlinkHref={ isAuthorized ? "#locked" : "#unlocked" } />
</svg>
</button>
{ showPopup && <AuthorizationPopup /> }
</div>
)
}


static propTypes = {
getComponent: PropTypes.func.isRequired,
authSelectors: PropTypes.object.isRequired,
errActions: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
}
}

+ 34
- 0
src/core/components/auth/authorize-operation-btn.jsx Visa fil

@@ -0,0 +1,34 @@
import React, { PropTypes } from "react"
import ImPropTypes from "react-immutable-proptypes"

export default class AuthorizeOperationBtn extends React.Component {
onClick =(e) => {
e.stopPropagation()

let { security, authActions, authSelectors } = this.props
let definitions = authSelectors.getDefinitionsByNames(security)

authActions.showDefinitions(definitions)
}

render() {
let { security, authSelectors } = this.props

let isAuthorized = authSelectors.isAuthorized(security)

return (
<button className={isAuthorized ? "authorization__btn locked" : "authorization__btn unlocked"} onClick={ this.onClick }>
<svg width="20" height="20">
<use xlinkHref={ isAuthorized ? "#locked" : "#unlocked" } />
</svg>
</button>

)
}

static propTypes = {
authSelectors: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
security: ImPropTypes.iterable.isRequired
}
}

+ 138
- 0
src/core/components/auth/auths.jsx Visa fil

@@ -0,0 +1,138 @@
import React, { PropTypes } from "react"
import ImPropTypes from "react-immutable-proptypes"

export default class Auths extends React.Component {
static propTypes = {
definitions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
authSelectors: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired
}

constructor(props, context) {
super(props, context)

this.state = {}
}

onAuthChange =(auth) => {
let { name } = auth

this.setState({ [name]: auth })
}

submitAuth =(e) => {
e.preventDefault()

let { authActions } = this.props

authActions.authorize(this.state)
}

logoutClick =(e) => {
e.preventDefault()

let { authActions, definitions } = this.props
let auths = definitions.map( (val, key) => {
return key
}).toArray()

authActions.logout(auths)
}

render() {
let { definitions, getComponent, authSelectors, errSelectors, specSelectors } = this.props
const ApiKeyAuth = getComponent("apiKeyAuth")
const BasicAuth = getComponent("basicAuth")
const Oauth2 = getComponent("oauth2", true)
const Button = getComponent("Button")
const JumpToPath = getComponent("JumpToPath", true)

let specStr = specSelectors.specStr()
let authorized = authSelectors.authorized()

let authorizedAuth = definitions.filter( (definition, key) => {
return !!authorized.get(key)
})

let nonOauthDefinitions = definitions.filter( schema => schema.get("type") !== "oauth2")
let oauthDefinitions = definitions.filter( schema => schema.get("type") === "oauth2")

return (
<div className="auth-container">
{
!!nonOauthDefinitions.size && <form onSubmit={ this.submitAuth }>
{
nonOauthDefinitions.map( (schema, name) => {
let type = schema.get("type")
let authEl

switch(type) {
case "apiKey": authEl = <ApiKeyAuth key={ name }
schema={ schema }
name={ name }
errSelectors={ errSelectors }
authorized={ authorized }
getComponent={ getComponent }
onChange={ this.onAuthChange } />
break
case "basic": authEl = <BasicAuth key={ name }
schema={ schema }
name={ name }
errSelectors={ errSelectors }
authorized={ authorized }
getComponent={ getComponent }
onChange={ this.onAuthChange } />
break
default: authEl = <div key={ name }>Unknown security definition type { type }</div>
}

return (<div key={`${name}-jump`}>
{ authEl }
</div>)

}).toArray()
}
<div className="auth-btn-wrapper">
{
nonOauthDefinitions.size === authorizedAuth.size ? <Button className="btn modal-btn auth" onClick={ this.logoutClick }>Logout</Button>
: <Button type="submit" className="btn modal-btn auth authorize">Authorize</Button>
}
</div>
</form>
}

{
oauthDefinitions && oauthDefinitions.size ? <div>
<div className="scope-def">
<p>Scopes are used to grant an application different levels of access to data on behalf of the end user. Each API may declare one or more scopes.</p>
<p>API requires the following scopes. Select which ones you want to grant to Swagger UI.</p>
</div>
{
definitions.filter( schema => schema.get("type") === "oauth2")
.map( (schema, name) =>{
return (<div key={ name }>
<Oauth2 authorized={ authorized }
schema={ schema }
name={ name } />
</div>)
}
).toArray()
}
</div> : null
}

</div>
)
}

static propTypes = {
errSelectors: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
authSelectors: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
definitions: ImPropTypes.iterable.isRequired
}
}

+ 97
- 0
src/core/components/auth/basic-auth.jsx Visa fil

@@ -0,0 +1,97 @@
import React, { PropTypes } from "react"
import ImPropTypes from "react-immutable-proptypes"

export default class BasicAuth extends React.Component {
static propTypes = {
authorized: PropTypes.object,
getComponent: PropTypes.func.isRequired,
schema: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired
}

constructor(props, context) {
super(props, context)
let { schema, name } = this.props

let value = this.getValue()
let username = value.username

this.state = {
name: name,
schema: schema,
value: !username ? {} : {
username: username
}
}
}

getValue () {
let { authorized, name } = this.props

return authorized && authorized.getIn([name, "value"]) || {}
}

onChange =(e) => {
let { onChange } = this.props
let { value, name } = e.target

let newValue = this.state.value
newValue[name] = value

this.setState({ value: newValue })

onChange(this.state)
}

render() {
let { schema, getComponent, name, errSelectors } = this.props
const Input = getComponent("Input")
const Row = getComponent("Row")
const Col = getComponent("Col")
const AuthError = getComponent("authError")
const JumpToPath = getComponent("JumpToPath", true)
const Markdown = getComponent( "Markdown" )
let username = this.getValue().username
let errors = errSelectors.allErrors().filter( err => err.get("authId") === name)

return (
<div>
<h4>Basic authorization<JumpToPath path={[ "securityDefinitions", name ]} /></h4>
{ username && <h6>Authorized</h6> }
<Row>
<Markdown options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}}
source={ schema.get("description") } />
</Row>
<Row>
<Col tablet={2} desktop={2}>username:</Col>
<Col tablet={10} desktop={10}>
{
username || <Input type="text" required="required" name="username" onChange={ this.onChange }/>
}
</Col>
</Row>
{
!username && <Row>
<Col tablet={2} desktop={2}>password:</Col>
<Col tablet={10} desktop={10}><Input required="required" autoComplete="new-password" name="password" type="password" onChange={ this.onChange }/></Col>
</Row>
}
{
errors.valueSeq().map( (error, key) => {
return <AuthError error={ error }
key={ key }/>
} )
}
</div>
)
}

static propTypes = {
name: PropTypes.string.isRequired,
errSelectors: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
onChange: PropTypes.func,
schema: ImPropTypes.map,
authorized: ImPropTypes.map
}
}

+ 23
- 0
src/core/components/auth/error.jsx Visa fil

@@ -0,0 +1,23 @@
import React, { PropTypes } from "react"

export default class AuthError extends React.Component {

static propTypes = {
error: PropTypes.object.isRequired
}

render() {
let { error } = this.props

let level = error.get("level")
let message = error.get("message")
let source = error.get("source")

return (
<div className="errors" style={{ backgroundColor: "#ffeeee", color: "red", margin: "1em" }}>
<b style={{ textTransform: "capitalize", marginRight: "1em"}} >{ source } { level }</b>
<span>{ message }</span>
</div>
)
}
}

+ 218
- 0
src/core/components/auth/oauth2.jsx Visa fil

@@ -0,0 +1,218 @@
import React, { PropTypes } from "react"
import oauth2Authorize from "core/oauth2-authorize"

const IMPLICIT = "implicit"
const ACCESS_CODE = "accessCode"
const PASSWORD = "password"
const APPLICATION = "application"

export default class Oauth2 extends React.Component {
static propTypes = {
name: PropTypes.string,
authorized: PropTypes.object,
configs: PropTypes.object,
getComponent: PropTypes.func.isRequired,
schema: PropTypes.object.isRequired,
authSelectors: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
errSelectors: PropTypes.object.isRequired,
errActions: PropTypes.object.isRequired
}

constructor(props, context) {
super(props, context)
let { name, schema, authorized } = this.props
let auth = authorized && authorized.get(name)
let username = auth && auth.get("username") || ""
let clientId = auth && auth.get("clientId") || ""
let clientSecret = auth && auth.get("clientSecret") || ""
let passwordType = auth && auth.get("passwordType") || "none"

this.state = {
name: name,
schema: schema,
scopes: [],
clientId: clientId,
clientSecret: clientSecret,
username: username,
password: "",
passwordType: passwordType
}
}

authorize =() => {
let { authActions, errActions, getConfigs } = this.props
let configs = getConfigs()

errActions.clear({authId: name,type: "auth", source: "auth"})
oauth2Authorize(this.state, authActions, errActions, configs)
}

onScopeChange =(e) => {
let { target } = e
let { checked } = target
let scope = target.dataset.value

if ( checked && this.state.scopes.indexOf(scope) === -1 ) {
let newScopes = this.state.scopes.concat([scope])
this.setState({ scopes: newScopes })
} else if ( !checked && this.state.scopes.indexOf(scope) > -1) {
this.setState({ scopes: this.state.scopes.filter((val) => val !== scope) })
}
}

onInputChange =(e) => {
let { target : { dataset : { name }, value } } = e
let state = {
[name]: value
}

this.setState(state)
}

logout =(e) => {
e.preventDefault()
let { authActions, errActions, name } = this.props

errActions.clear({authId: name, type: "auth", source: "auth"})
authActions.logout([ name ])
}

render() {
let { schema, getComponent, authSelectors, errSelectors, name } = this.props
const Input = getComponent("Input")
const Row = getComponent("Row")
const Col = getComponent("Col")
const Button = getComponent("Button")
const AuthError = getComponent("authError")
const JumpToPath = getComponent("JumpToPath", true)
const Markdown = getComponent( "Markdown" )

let flow = schema.get("flow")
let scopes = schema.get("allowedScopes") || schema.get("scopes")
let authorizedAuth = authSelectors.authorized().get(name)
let isAuthorized = !!authorizedAuth
let errors = errSelectors.allErrors().filter( err => err.get("authId") === name)
let isValid = !errors.filter( err => err.get("source") === "validation").size

return (
<div>
<h4>OAuth2.0 <JumpToPath path={[ "securityDefinitions", name ]} /></h4>
<Markdown options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}}
source={ schema.get("description") } />

{ isAuthorized && <h6>Authorized</h6> }

{ ( flow === IMPLICIT || flow === ACCESS_CODE ) && <p>Authorization URL: <code>{ schema.get("authorizationUrl") }</code></p> }
{ ( flow === PASSWORD || flow === ACCESS_CODE || flow === APPLICATION ) && <p>Token URL:<code> { schema.get("tokenUrl") }</code></p> }
<p className="flow">Flow: <code>{ schema.get("flow") }</code></p>

{
flow === PASSWORD && ( !isAuthorized || isAuthorized && this.state.username) && <Row>
<Col tablet={2} desktop={2}>username:</Col>
<Col tablet={10} desktop={10}>
{
isAuthorized ? <span>{ this.state.username }</span>
: <input type="text" data-name="username" onChange={ this.onInputChange }/>
}
</Col>
</Row>
}

{
flow === PASSWORD && !isAuthorized && <Row>
<Col tablet={2} desktop={2}>password:</Col>
<Col tablet={10} desktop={10}>
<input type="password" data-name="password" onChange={ this.onInputChange }/>
</Col>
</Row>
}

{
flow === PASSWORD && <Row>
<Col tablet={2} desktop={2}>type:</Col>
<Col tablet={10} desktop={10}>
{
isAuthorized ? <span>{ this.state.passwordType }</span>
: <select data-name="passwordType" onChange={ this.onInputChange }>
<option value="none">None or other</option>
<option value="basic">Basic auth</option>
<option value="request">Request body</option>
</select>
}
</Col>
</Row>
}

{
( flow === IMPLICIT || flow === ACCESS_CODE || ( flow === PASSWORD && this.state.passwordType!== "none") ) &&
( !isAuthorized || isAuthorized && this.state.clientId) && <Row>
<label htmlFor="client_id">client_id:</label>
<Col tablet={10} desktop={10}>
{
isAuthorized ? <span>{ this.state.clientId }</span>
: <input id="client_id" type="text" required={ flow === PASSWORD } data-name="clientId"
onChange={ this.onInputChange }/>
}
</Col>
</Row>
}

{
( flow === ACCESS_CODE || ( flow === PASSWORD && this.state.passwordType!== "none") ) && <Row>
<label htmlFor="client_secret">client_secret:</label>
<Col tablet={10} desktop={10}>
{
isAuthorized ? <span>{ this.state.clientSecret }</span>
: <input id="client_secret" type="text" data-name="clientSecret"
onChange={ this.onInputChange }/>
}
</Col>
</Row>
}

{
!isAuthorized && flow !== PASSWORD && scopes && scopes.size ? <div className="scopes">
<h2>Scopes:</h2>
{ scopes.map((description, name) => {
return (
<Row key={ name }>
<div className="checkbox">
<Input data-value={ name }
id={`${name}-checkbox`}
disabled={ isAuthorized }
type="checkbox"
onChange={ this.onScopeChange }/>
<label htmlFor={`${name}-checkbox`}>
<span className="item"></span>
<div className="text">
<p className="name">{name}</p>
<p className="description">{description}</p>
</div>
</label>
</div>
</Row>
)
}).toArray()
}
</div> : null
}

{
errors.valueSeq().map( (error, key) => {
return <AuthError error={ error }
key={ key }/>
} )
}
<div className="auth-btn-wrapper">
{ isValid && flow !== APPLICATION &&
( isAuthorized ? <Button className="btn modal-btn auth authorize" onClick={ this.logout }>Logout</Button>
: <Button className="btn modal-btn auth authorize" onClick={ this.authorize }>Authorize</Button>
)
}
</div>

</div>
)
}
}

+ 24
- 0
src/core/components/clear.jsx Visa fil

@@ -0,0 +1,24 @@
import React, { Component, PropTypes } from "react"

export default class Clear extends Component {

onClick =() => {
let { specActions, path, method } = this.props
specActions.clearResponse( path, method )
specActions.clearRequest( path, method )
}

render(){
return (
<button className="btn btn-clear opblock-control__btn" onClick={ this.onClick }>
Clear
</button>
)
}

static propTypes = {
specActions: PropTypes.object.isRequired,
path: PropTypes.string.isRequired,
method: PropTypes.string.isRequired,
}
}

+ 45
- 0
src/core/components/content-type.jsx Visa fil

@@ -0,0 +1,45 @@
import React, { PropTypes } from "react"
import ImPropTypes from "react-immutable-proptypes"
import { fromJS } from 'immutable'

const noop = ()=>{}

export default class ContentType extends React.Component {

static propTypes = {
contentTypes: PropTypes.oneOfType([ImPropTypes.list, ImPropTypes.set]),
value: PropTypes.string,
onChange: PropTypes.func,
className: PropTypes.string
}

static defaultProps = {
onChange: noop,
value: null,
contentTypes: fromJS(["application/json"]),
}

componentDidMount() {
// Needed to populate the form, initially
this.props.onChange(this.props.contentTypes.first())
}

onChangeWrapper = e => this.props.onChange(e.target.value)

render() {
let { contentTypes, className, value } = this.props

if ( !contentTypes || !contentTypes.size )
return null

return (
<div className={ "content-type-wrapper " + ( className || "" ) }>
<select className="content-type" value={value} onChange={this.onChangeWrapper} >
{ contentTypes.map( (val) => {
return <option key={ val } value={ val }>{ val }</option>
}).toArray()}
</select>
</div>
)
}
}

+ 28
- 0
src/core/components/curl.jsx Visa fil

@@ -0,0 +1,28 @@
import React, { PropTypes } from "react"
import curlify from "core/curlify"

export default class Curl extends React.Component {
static propTypes = {
request: PropTypes.object.isRequired
}

handleFocus(e) {
e.target.select()
document.execCommand("copy")
}

render() {
let { request } = this.props
let curl = curlify(request)

return (
<div>
<h4>Curl</h4>
<div className="copy-paste">
<textarea onFocus={this.handleFocus} className="curl" style={{ whiteSpace: "normal" }} defaultValue={curl}></textarea>
</div>
</div>
)
}

}

+ 51
- 0
src/core/components/debug.jsx Visa fil

@@ -0,0 +1,51 @@
import React, { PropTypes } from "react"
import Collapse from "react-collapse"
import { presets } from "react-motion"
import ObjectInspector from "react-object-inspector"
import Perf from "react-addons-perf"

export default class Debug extends React.Component {

constructor() {
super()
this.state = {
jsonDumpOpen: false
}
this.toggleJsonDump = (e) => {
e.preventDefault()
this.setState({jsonDumpOpen: !this.state.jsonDumpOpen})
}
window.Perf = Perf
}

plusOrMinus(bool) {
return bool ? "-" : "+"
}

render() {

let { getState } = this.props

window.props = this.props

return (
<div className="info">
<h3><a onClick={this.toggleJsonDump}> {this.plusOrMinus(this.state.jsonDumpOpen)} App </a></h3>

<Collapse isOpened={this.state.jsonDumpOpen} springConfig={presets.noWobble}>

<ObjectInspector data={getState().toJS() || {}} name="state" initialExpandedPaths={["state"]}/>

</Collapse>


</div>
)
}

}

Debug.propTypes = {
getState: PropTypes.func.isRequired
}


+ 113
- 0
src/core/components/errors.jsx Visa fil

@@ -0,0 +1,113 @@
import React, { PropTypes } from "react"
import Im, { List } from "immutable"
import Collapse from "react-collapse"
import sortBy from "lodash/sortBy"

export default class Errors extends React.Component {

static propTypes = {
jumpToLine: PropTypes.func,
errSelectors: PropTypes.object.isRequired,
layoutSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired
}

render() {
let { jumpToLine, errSelectors, layoutSelectors, layoutActions } = this.props

let errors = errSelectors.allErrors()

// all thrown errors, plus error-level everything else
let allErrorsToDisplay = errors.filter(err => err.get("type") === "thrown" ? true :err.get("level") === "error")

if(!allErrorsToDisplay || allErrorsToDisplay.count() < 1) {
return null
}

let isVisible = layoutSelectors.isShown(["errorPane"], true)
let toggleVisibility = () => layoutActions.show(["errorPane"], !isVisible)

let sortedJSErrors = allErrorsToDisplay.sortBy(err => err.get("line"))

return (
<pre className="errors-wrapper">
<hgroup className="error">
<h4 className="errors__title">Errors</h4>
<button className="btn errors__clear-btn" onClick={ toggleVisibility }>{ isVisible ? "Hide" : "Show" }</button>
</hgroup>
<Collapse isOpened={ isVisible } animated >
<div className="errors">
{ sortedJSErrors.map((err, i) => {
if(err.get("type") === "thrown") {
return <ThrownErrorItem key={ i } error={ err.get("error") || err } jumpToLine={jumpToLine} />
}
if(err.get("type") === "spec") {
return <SpecErrorItem key={ i } error={ err } jumpToLine={jumpToLine} />
}
}) }
</div>
</Collapse>
</pre>
)
}
}

const ThrownErrorItem = ( { error, jumpToLine } ) => {
if(!error) {
return null
}
let errorLine = error.get("line")

return (
<div className="error-wrapper">
{ !error ? null :
<div>
<h4>{ (error.get("source") && error.get("level")) ?
toTitleCase(error.get("source")) + " " + error.get("level") : "" }
{ error.get("path") ? <small> at {error.get("path")}</small>: null }</h4>
<span style={{ whiteSpace: "pre-line", "maxWidth": "100%" }}>
{ error.get("message") }
</span>
<div>
{ errorLine ? <a onClick={jumpToLine.bind(null, errorLine)}>Jump to line { errorLine }</a> : null }
</div>
</div>
}
</div>
)
}

const SpecErrorItem = ( { error, jumpToLine } ) => {
return (
<div className="error-wrapper">
{ !error ? null :
<div>
<h4>{ toTitleCase(error.get("source")) + " " + error.get("level") }{ error.get("path") ? <small> at {List.isList(error.get("path")) ? error.get("path").join(".") : error.get("path")}</small>: null }</h4>
<span style={{ whiteSpace: "pre-line"}}>{ error.get("message") }</span>
<div>
{ jumpToLine ? (
<a onClick={jumpToLine.bind(null, error.get("line"))}>Jump to line { error.get("line") }</a>
) : null }
</div>
</div>
}
</div>
)
}

function toTitleCase(str) {
return str
.split(" ")
.map(substr => substr[0].toUpperCase() + substr.slice(1))
.join(" ")
}

ThrownErrorItem.propTypes = {
error: PropTypes.object.isRequired,
jumpToLine: PropTypes.func
}

SpecErrorItem.propTypes = {
error: PropTypes.object.isRequired,
jumpToLine: PropTypes.func
}

+ 41
- 0
src/core/components/execute.jsx Visa fil

@@ -0,0 +1,41 @@
import React, { Component, PropTypes } from "react"
import { fromJS } from "immutable"

export default class Execute extends Component {

static propTypes = {
specSelectors: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired,
operation: PropTypes.object.isRequired,
path: PropTypes.string.isRequired,
getComponent: PropTypes.func.isRequired,
method: PropTypes.string.isRequired,
onExecute: PropTypes.func
}

onClick=()=>{
let { specSelectors, specActions, operation, path, method } = this.props

specActions.validateParams( [path, method] )

if ( specSelectors.validateBeforeExecute([path, method]) ) {
if(this.props.onExecute) {
this.props.onExecute()
}
specActions.execute( { operation, path, method } )
}
}

onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue([this.props.path, this.props.method], val)

render(){
let { getComponent, operation, specActions, path, method } = this.props
const ContentType = getComponent( "contentType" )

return (
<button className="btn execute opblock-control__btn" onClick={ this.onClick }>
Execute
</button>
)
}
}

+ 9
- 0
src/core/components/footer.jsx Visa fil

@@ -0,0 +1,9 @@
import React from "react"

export default class Footer extends React.Component {
render() {
return (
<div className="footer"></div>
)
}
}

+ 46
- 0
src/core/components/headers.jsx Visa fil

@@ -0,0 +1,46 @@
import React, { PropTypes } from "react"
import Im from "immutable"

export default class Headers extends React.Component {

static propTypes = {
headers: PropTypes.object.isRequired
};

render() {

let { headers } = this.props

if ( !headers || !headers.size )
return null

return (
<div className="headers-wrapper">
<h4 className="headers__title">Headers:</h4>
<table className="headers">
<thead>
<tr className="header-row">
<th className="header-col">Name</th>
<th className="header-col">Description</th>
<th className="header-col">Type</th>
</tr>
</thead>
<tbody>
{
headers.entrySeq().map( ([ key, header ]) => {
if(!Im.Map.isMap(header)) {
return null
}
return (<tr key={ key }>
<td className="header-col">{ key }</td>
<td className="header-col">{ header.get( "description" ) }</td>
<td className="header-col">{ header.get( "type" ) }</td>
</tr>)
}).toArray()
}
</tbody>
</table>
</div>
)
}
}

+ 24
- 0
src/core/components/highlight-code.jsx Visa fil

@@ -0,0 +1,24 @@
import React, { Component, PropTypes } from "react"
import { highlight } from "core/utils"

export default class HighlightCode extends Component {
static propTypes = {
value: PropTypes.string.isRequired,
className: PropTypes.string
}

componentDidMount() {
highlight(this.refs.el)
}

componentDidUpdate() {
highlight(this.refs.el)
}

render () {
let { value, className } = this.props
className = className || ""

return <pre ref="el" className={className + " microlight"}>{ value }</pre>
}
}

+ 128
- 0
src/core/components/info.jsx Visa fil

@@ -0,0 +1,128 @@
import React, { PropTypes } from "react"
import { fromJS } from "immutable"
import ImPropTypes from "react-immutable-proptypes"


class Path extends React.Component {
static propTypes = {
host: PropTypes.string,
basePath: PropTypes.string
}

render() {
let { host, basePath } = this.props

return (
<pre className="base-url">
[ Base url: {host}{basePath}]
</pre>
)
}
}


class Contact extends React.Component {
static propTypes = {
data: PropTypes.object
}

render(){
let { data } = this.props
let name = data.get("name") || "the developer"
let url = data.get("url")
let email = data.get("email")

return (
<div>
{ url && <div><a href={ url } target="_blank">{ name } - Website</a></div> }
{ email &&
<a href={`mailto:${email}`}>
{ url ? `Send email to ${name}` : `Contact ${name}`}
</a>
}
</div>
)
}
}

class License extends React.Component {
static propTypes = {
license: PropTypes.object
}

render(){
let { license } = this.props
let name = license.get("name") || "License"
let url = license.get("url")

return (
<div>
{
url ? <a href={ url }>{ name }</a>
: <span>{ name }</span>
}
</div>
)
}
}

export default class Info extends React.Component {
static propTypes = {
info: PropTypes.object,
url: PropTypes.string,
host: PropTypes.string,
basePath: PropTypes.string,
externalDocs: ImPropTypes.map,
getComponent: PropTypes.func.isRequired,
}

render() {
let { info, url, host, basePath, getComponent, externalDocs } = this.props
let version = info.get("version")
let description = info.get("description")
let title = info.get("title")
let termsOfService = info.get("termsOfService")
let contact = info.get("contact")
let license = info.get("license")
const { url:externalDocsUrl, description:externalDocsDescription } = (externalDocs || fromJS({})).toJS()

const Markdown = getComponent("Markdown")

return (
<div className="info">
<hgroup className="main">
<h2 className="title" >{ title }
{ version && <small><pre className="version"> { version } </pre></small> }
</h2>
{ host || basePath ? <Path host={ host } basePath={ basePath } /> : null }
{ url && <a href={ url }><span className="url"> { url } </span></a> }
</hgroup>

<div className="description">
<Markdown options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}} source={ description } />
</div>

{
termsOfService && <div>
<a href={ termsOfService }>Terms of service</a>
</div>
}

{ contact && contact.size ? <Contact data={ contact } /> : null }
{ license && license.size ? <License license={ license } /> : null }
{ externalDocsUrl ?
<a target="_blank" href={externalDocsUrl}>{externalDocsDescription || externalDocsUrl}</a>
: null }

</div>
)
}

}

Info.propTypes = {
title: PropTypes.any,
description: PropTypes.any,
version: PropTypes.any,
url: PropTypes.string
}

+ 252
- 0
src/core/components/layout-utils.jsx Visa fil

@@ -0,0 +1,252 @@
import React, { PropTypes } from "react"
import OriCollapse from "react-collapse"
import _Markdown from "react-remarkable"

const noop = () => {}

function xclass(...args) {
return args.filter(a => !!a).join(" ").trim()
}

export const Markdown = _Markdown

export class Container extends React.Component {
render() {
let { fullscreen, full, ...rest } = this.props
// Normal element

if(fullscreen)
return <section {...rest}/>

let containerClass = "container" + (full ? "-full" : "")
return (
<section {...rest} className={xclass(rest.className, containerClass)}/>
)
}
}

Container.propTypes = {
fullscreen: PropTypes.bool,
full: PropTypes.bool,
className: PropTypes.string
}

const DEVICES = {
"mobile": "",
"tablet": "-tablet",
"desktop": "-desktop",
"large": "-hd"
}

export class Col extends React.Component {

render() {
const {
hide,
keepContents,

mobile, /* we don't want these in the final component, since React now complains. So we extract them */
tablet,
desktop,
large,

...rest
} = this.props

if(hide && !keepContents)
return <span/>

let classesAr = []

for (let device in DEVICES) {
let deviceClass = DEVICES[device]
if(device in this.props) {
let val = this.props[device]

if(val < 1) {
classesAr.push("none" + deviceClass)
continue
}

classesAr.push("block" + deviceClass)
classesAr.push("col-" + val + deviceClass)
}
}

let classes = xclass(rest.className, "clear", ...classesAr)

return (
<section {...rest} style={{display: hide ? "none": null}} className={classes}/>
)
}

}

Col.propTypes = {
hide: PropTypes.bool,
keepContents: PropTypes.bool,
mobile: PropTypes.number,
tablet: PropTypes.number,
desktop: PropTypes.number,
large: PropTypes.number,
className: PropTypes.string
}

export class Row extends React.Component {

render() {
return <div {...this.props} className={xclass(this.props.className, "wrapper")} />
}

}

Row.propTypes = {
className: PropTypes.string
}

export class Button extends React.Component {

static propTypes = {
className: PropTypes.string
}

static defaultProps = {
className: ""
}

render() {
return <button {...this.props} className={xclass(this.props.className, "button")} />
}

}


export const TextArea = (props) => <textarea {...props} />

export const Input = (props) => <input {...props} />

export class Select extends React.Component {
static propTypes = {
allowedValues: PropTypes.object,
value: PropTypes.any,
onChange: PropTypes.func,
multiple: PropTypes.bool,
allowEmptyValue: PropTypes.bool
}

static defaultProps = {
multiple: false,
allowEmptyValue: true
}

constructor(props, context) {
super(props, context)

let value

if (props.value !== undefined) {
value = props.value
} else {
value = props.multiple ? [""] : ""
}

this.state = { value: value }
}

onChange = (e) => {
let { onChange, multiple } = this.props
let options = [].slice.call(e.target.options)
let value


if (multiple) {
value = options.filter(function (option) {
return option.selected
})
.map(function (option){
return option.value
})
} else {
value = e.target.value
}

this.setState({value: value})

onChange && onChange(value)
}

render(){
let { allowedValues, multiple, allowEmptyValue } = this.props
let value = this.state.value.toJS ? this.state.value.toJS() : this.state.value

return (
<select multiple={ multiple } value={ value } onChange={ this.onChange } >
{ allowEmptyValue ? <option value="">--</option> : null }
{
allowedValues.map(function (item, key) {
return <option key={ key } value={ String(item) }>{ item }</option>
})
}
</select>
)
}
}

export class Link extends React.Component {

render() {
return <a {...this.props} className={xclass(this.props.className, "link")}/>
}

}

Link.propTypes = {
className: PropTypes.string
}

const NoMargin = ({children}) => <div style={{height: "auto", border: "none", margin: 0, padding: 0}}> {children} </div>

NoMargin.propTypes = {
children: PropTypes.node
}

export class Collapse extends React.Component {

static propTypes = {
isOpened: PropTypes.bool,
children: PropTypes.node.isRequired,
animated: PropTypes.bool
}

static defaultProps = {
isOpened: false,
animated: false
}

renderNotAnimated() {
if(!this.props.isOpened)
return <noscript/>
return (
<NoMargin>
{this.props.children}
</NoMargin>
)
}

render() {
let { animated, isOpened, children } = this.props

if(!animated)
return this.renderNotAnimated()

children = isOpened ? children : null
return (
<OriCollapse isOpened={isOpened}>
<NoMargin>
{children}
</NoMargin>
</OriCollapse>
)
}

}

+ 72
- 0
src/core/components/layouts/xpane.jsx Visa fil

@@ -0,0 +1,72 @@
import React, { PropTypes } from "react"

export default class XPane extends React.Component {

render() {
let { getComponent, specSelectors, specActions, layoutSelectors, layoutActions } = this.props
let info = specSelectors.info()
let url = specSelectors.url()
let showEditor = layoutSelectors.isShown("editor")

let Info = getComponent("info")
let Operations = getComponent("operations", true)
let Overview = getComponent("overview", true)
let Editor = getComponent("editor", true)
let Footer = getComponent("footer", true)
let Header = getComponent("header", true)

let Container = getComponent("Container")
let Row = getComponent("Row")
let Col = getComponent("Col")
let Button = getComponent("Button")

let showEditorAction = ()=> layoutActions.show("editor", !showEditor)

return (
<Container fullscreen>

<Header/>

{
info && info.size ? <Info version={info.get("version")}
description={info.get("description")}
title={info.get("title")}
url={url}/>
: null
}
<Button onClick={showEditorAction}>{showEditor ? "Hide" : "Show"} Editor</Button>
<Button onClick={specActions.formatIntoYaml}>Format contents</Button>

<Row>

<Col desktop={3} >
<Overview/>
</Col>

<Col hide={!showEditor} keepContents={true} desktop={5} >
<Editor/>
</Col>

<Col desktop={showEditor ? 4 : 9} >
<Operations/>
</Col>

</Row>

<Footer></Footer>

</Container>
)
}

}

XPane.propTypes = {
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired,
layoutSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired
}



+ 91
- 0
src/core/components/live-response.jsx Visa fil

@@ -0,0 +1,91 @@
import React, { PropTypes } from "react"
import ImPropTypes from "react-immutable-proptypes"

const Headers = ( { headers } )=>{
return (
<div>
<h5>Response headers</h5>
<pre>{headers}</pre>
</div>)
}

Headers.propTypes = {
headers: PropTypes.array.isRequired
}

export default class LiveResponse extends React.Component {
static propTypes = {
response: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired
}

render() {
let { request, response, getComponent } = this.props
const Curl = getComponent("curl")
let body = response.get("text")
let status = response.get("status")
let url = response.get("url")
let originalHeaders = response.get("headers")
let headers = originalHeaders && originalHeaders.toJS()
let headersKeys = Object.keys(headers)
let returnObject = headersKeys.map(key => {
return <span className="headerline" key={key}> {key}: {headers[key]} </span>
})
let notDocumented = response.get("notDocumented")
let ResponseBody = getComponent("responseBody")
let contentType = headers && headers["content-type"]
let isError = response.get("error")

return (
<div>
{ request && <Curl request={ request }/> }
<h4>Server response</h4>
<table className="responses-table">
<thead>
<tr className="responses-header">
<td className="col col_header response-col_status">Code</td>
<td className="col col_header response-col_description">Details</td>
</tr>
</thead>
<tbody>
<tr className="response">
<td className="col response-col_status">
{ status }
{
!notDocumented ? null :
<div className="response-undocumented">
<i> Undocumented </i>
</div>
}
</td>
<td className="col response-col_description">
{
!isError ? null : <span>
{`${response.get("name")}: ${response.get("message")}`}
</span>
}
{
!body || isError ? null
: <ResponseBody content={ body }
contentType={ contentType }
url={ url }
headers={ headers }
getComponent={ getComponent }/>
}
{
!headers ? null : <Headers headers={ returnObject }/>
}
</td>
</tr>
</tbody>
</table>
</div>
)
}

static propTypes = {
getComponent: PropTypes.func.isRequired,
request: ImPropTypes.map,
response: ImPropTypes.map
}
}

+ 58
- 0
src/core/components/model-example.jsx Visa fil

@@ -0,0 +1,58 @@
import React, { PropTypes } from "react"


export default class ModelExample extends React.Component {
static propTypes = {
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
schema: PropTypes.object.isRequired,
example: PropTypes.any.isRequired,
isExecute: PropTypes.bool
}

constructor(props, context) {
super(props, context)

this.state = {
activeTab: "example"
}
}

activeTab =( e ) => {
let { target : { dataset : { name } } } = e

this.setState({
activeTab: name
})
}

render() {
let { getComponent, specSelectors, schema, example, isExecute } = this.props
const Model = getComponent("model")

return <div>
<ul className="tab">
<li className={ "tabitem" + ( isExecute || this.state.activeTab === "example" ? " active" : "") }>
<a className="tablinks" data-name="example" onClick={ this.activeTab }>Example Value</a>
</li>
<li className={ "tabitem" + ( !isExecute && this.state.activeTab === "model" ? " active" : "") }>
<a className={ "tablinks" + ( isExecute ? " inactive" : "" )} data-name="model" onClick={ this.activeTab }>Model</a>
</li>
</ul>
<div>
{
(isExecute || this.state.activeTab === "example") && example
}
{
!isExecute && this.state.activeTab === "model" && <Model schema={ schema }
getComponent={ getComponent }
specSelectors={ specSelectors }
expandDepth={ 1 } />


}
</div>
</div>
}

}

+ 300
- 0
src/core/components/model.jsx Visa fil

@@ -0,0 +1,300 @@
import React, { Component, PropTypes } from "react"
import ImPropTypes from "react-immutable-proptypes"
import isObject from "lodash/isObject"
import { List } from "immutable"
const braceOpen = "{"
const braceClose = "}"

const EnumModel = ({ value }) => {
let collapsedContent = <span>Array [ { value.count() } ]</span>
return <span className="prop-enum">
Enum:<br />
<Collapse collapsedContent={ collapsedContent }>
[ { value.join(", ") } ]
</Collapse>
</span>
}

EnumModel.propTypes = {
value: ImPropTypes.iterable
}

class ObjectModel extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
name: PropTypes.string,
isRef: PropTypes.bool,
expandDepth: PropTypes.number,
depth: PropTypes.number
}

render(){
let { schema, name, isRef, getComponent, depth, ...props } = this.props
let { expandDepth } = this.props
const JumpToPath = getComponent("JumpToPath", true)
let description = schema.get("description")
let properties = schema.get("properties")
let additionalProperties = schema.get("additionalProperties")
let title = schema.get("title") || name
let required = schema.get("required")
const JumpToPathSection = ({ name }) => <span className="model-jump-to-path"><JumpToPath path={`definitions.${name}`} /></span>
let collapsedContent = (<span>
<span>{ braceOpen }</span>...<span>{ braceClose }</span>
{
isRef ? <JumpToPathSection name={ name }/> : ""
}
</span>)

return <span className="model">
{
title && <span className="model-title">
{ isRef && schema.get("$$ref") && <span className="model-hint">{ schema.get("$$ref") }</span> }
<span className="model-title__text">{ title }</span>
</span>
}
<Collapse collapsed={ depth > expandDepth } collapsedContent={ collapsedContent }>
<span className="brace-open object">{ braceOpen }</span>
{
!isRef ? null : <JumpToPathSection name={ name }/>
}
<span className="inner-object">
{
<table className="model" style={{ marginLeft: "2em" }}><tbody>
{
!description ? null : <tr style={{ color: "#999", fontStyle: "italic" }}>
<td>description:</td>
<td>{ description }</td>
</tr>
}
{
!(properties && properties.size) ? null : properties.entrySeq().map(
([key, value]) => {
let isRequired = List.isList(required) && required.contains(key)
let propertyStyle = { verticalAlign: "top", paddingRight: "0.2em" }
if ( isRequired ) {
propertyStyle.fontWeight = "bold"
}

return (<tr key={key}>
<td style={ propertyStyle }>{ key }:</td>
<td style={{ verticalAlign: "top" }}>
<Model key={ `object-${name}-${key}_${value}` } { ...props }
required={ isRequired }
getComponent={ getComponent }
schema={ value }
depth={ depth + 1 } />
</td>
</tr>)
}).toArray()
}
{
!additionalProperties || !additionalProperties.size ? null
: <tr>
<td>{ "< * >:" }</td>
<td>
<Model { ...props } required={ false }
getComponent={ getComponent }
schema={ additionalProperties }
depth={ depth + 1 } />
</td>
</tr>
}
</tbody></table>
}
</span>
<span className="brace-close">{ braceClose }</span>
</Collapse>
</span>
}
}

class Primitive extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
required: PropTypes.bool
}

render(){
let { schema, required } = this.props

if(!schema || !schema.get) {
// don't render if schema isn't correctly formed
return <div></div>
}

let type = schema.get("type")
let format = schema.get("format")
let xml = schema.get("xml")
let enumArray = schema.get("enum")
let description = schema.get("description")
let properties = schema.filter( ( v, key) => ["enum", "type", "format", "$$ref"].indexOf(key) === -1 )
let style = required ? { fontWeight: "bold" } : {}
let propStyle = { color: "#999", fontStyle: "italic" }

return <span className="prop">
<span className="prop-type" style={ style }>{ type }</span> { required && <span style={{ color: "red" }}>*</span>}
{ format && <span className="prop-format">(${format})</span>}
{
properties.size ? properties.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={ propStyle }>
<br />{ key !== "description" && key + ": " }{ String(v) }</span>)
: null
}
{
xml && xml.size ? (<span><br /><span style={ propStyle }>xml:</span>
{
xml.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={ propStyle }><br/>&nbsp;&nbsp;&nbsp;{key}: { String(v) }</span>).toArray()
}
</span>): null
}
{
enumArray && <EnumModel value={ enumArray } />
}
</span>
}
}

class ArrayModel extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
name: PropTypes.string,
required: PropTypes.bool,
expandDepth: PropTypes.number,
depth: PropTypes.number
}

render(){
let { required, schema, depth, expandDepth } = this.props
let items = schema.get("items")

return <span>
<Collapse collapsed={ depth > expandDepth } collapsedContent="[...]">
[
<span><Model { ...this.props } schema={ items } required={ false }/></span>
]
</Collapse>
{ required && <span style={{ color: "red" }}>*</span>}
</span>
}
}


class Model extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
name: PropTypes.string,
isRef: PropTypes.bool,
required: PropTypes.bool,
expandDepth: PropTypes.number,
depth: PropTypes.number
}

getModelName =( ref )=> {
if ( ref.indexOf("#/definitions/") !== -1 ) {
return ref.replace(/^.*#\/definitions\//, "")
}
}

getRefSchema =( model )=> {
let { specSelectors } = this.props

return specSelectors.findDefinition(model)
}

render () {
let { schema, required, name, isRef } = this.props
let $$ref = schema && schema.get("$$ref")
let modelName = $$ref && this.getModelName( $$ref )
let modelSchema, type

if ( schema && (schema.get("type") || schema.get("properties")) ) {
modelSchema = schema
} else if ( $$ref ) {
modelSchema = this.getRefSchema( modelName )
}

type = modelSchema && modelSchema.get("type")
if ( !type && modelSchema && modelSchema.get("properties") ) {
type = "object"
}

switch(type) {
case "object":
return <ObjectModel className="object" { ...this.props } schema={ modelSchema }
name={ modelName || name }
isRef={ isRef!== undefined ? isRef : !!$$ref }/>
case "array":
return <ArrayModel className="array" { ...this.props } schema={ modelSchema } required={ required } />
case "string":
case "number":
case "integer":
case "boolean":
default:
return <Primitive schema={ modelSchema } required={ required }/>
}
}
}


export default class ModelComponent extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
name: PropTypes.string,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
expandDepth: PropTypes.number
}

render(){
let { name, schema } = this.props
let title = schema.get("title") || name

return <div className="model-box">
<Model { ...this.props } depth={ 1 } expandDepth={ this.props.expandDepth || 0 }/>
</div>
}
}

class Collapse extends Component {
static propTypes = {
collapsedContent: PropTypes.any,
collapsed: PropTypes.bool,
children: PropTypes.any
}

static defaultProps = {
collapsedContent: "{...}",
collapsed: true,
}

constructor(props, context) {
super(props, context)

let { collapsed, collapsedContent } = this.props

this.state = {
collapsed: collapsed !== undefined ? collapsed : Collapse.defaultProps.collapsed,
collapsedContent: collapsedContent || Collapse.defaultProps.collapsedContent
}
}

toggleCollapsed=()=>{
this.setState({
collapsed: !this.state.collapsed
})
}

render () {
return (<span>
<span onClick={ this.toggleCollapsed } style={{ "cursor": "pointer" }}>
<span className={ "model-toggle" + ( this.state.collapsed ? " collapsed" : "" ) }></span>
</span>
{ this.state.collapsed ? this.state.collapsedContent : this.props.children }
</span>)
}
}

+ 42
- 0
src/core/components/models.jsx Visa fil

@@ -0,0 +1,42 @@
import React, { Component, PropTypes } from "react"


export default class Models extends Component {
static propTypes = {
getComponent: PropTypes.func,
specSelectors: PropTypes.object
}

render(){
let { specSelectors, getComponent, layoutSelectors, layoutActions } = this.props
let definitions = specSelectors.definitions()
let showModels = layoutSelectors.isShown('models', true)

const Model = getComponent("model")
const Collapse = getComponent("Collapse")

if (!definitions.size) return null

return <section className={ showModels ? "models is-open" : "models"}>
<h4 onClick={() => layoutActions.show('models', !showModels)}>
<span>Models</span>
<svg width="20" height="20">
<use xlinkHref="#large-arrow" />
</svg>
</h4>
<Collapse isOpened={showModels} animated>
{
definitions.entrySeq().map( ( [ name, model ])=>{
return <div className="model-container" key={ `models-section-${name}` }>
<Model name={ name }
schema={ model }
isRef={ true }
getComponent={ getComponent }
specSelectors={ specSelectors }/>
</div>
}).toArray()
}
</Collapse>
</section>
}
}

+ 40
- 0
src/core/components/online-validator-badge.jsx Visa fil

@@ -0,0 +1,40 @@
import React from "react"

export default class OnlineValidatorBadge extends React.Component {
constructor(props, context) {
super(props, context)
let { specSelectors, getConfigs } = props
let { validatorUrl } = getConfigs()
this.state = {
url: specSelectors.url(),
validatorUrl: validatorUrl
}
}

componentWillReceiveProps(nextProps) {
let { specSelectors, getConfigs } = nextProps
let { validatorUrl } = getConfigs()

this.setState({
url: specSelectors.url(),
validatorUrl: validatorUrl
})
}

render() {
let { getConfigs } = this.props
let { spec } = getConfigs()

if ( typeof spec === "object" && Object.keys(spec).length) return null

if (!this.state.url) {
return null
}

return (<span style={{ float: "right"}}>
<a target="_blank" href={`${ this.state.validatorUrl }/debug?url=${ this.state.url }`}>
<img alt="Online validator badge" src={`${ this.state.validatorUrl }?url=${ this.state.url }`} />
</a>
</span>)
}
}

+ 258
- 0
src/core/components/operation.jsx Visa fil

@@ -0,0 +1,258 @@
import React, { PropTypes } from "react"
import { Map, fromJS } from "immutable"
import shallowCompare from "react-addons-shallow-compare"
import { getList } from "core/utils"
import * as CustomPropTypes from "core/proptypes"

//import "less/opblock"

export default class Operation extends React.Component {
static propTypes = {
path: PropTypes.string.isRequired,
method: PropTypes.string.isRequired,
operation: PropTypes.object.isRequired,
showSummary: PropTypes.bool,

isShownKey: CustomPropTypes.arrayOrString.isRequired,
jumpToKey: CustomPropTypes.arrayOrString.isRequired,

allowTryItOut: PropTypes.bool,

response: PropTypes.object,
request: PropTypes.object,

getComponent: PropTypes.func.isRequired,
authActions: PropTypes.object,
authSelectors: PropTypes.object,
specActions: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired,
layoutSelectors: PropTypes.object.isRequired,
fn: PropTypes.object.isRequired
}

static defaultProps = {
showSummary: true,
response: null,
allowTryItOut: true,
}

constructor(props, context) {
super(props, context)
this.state = {
tryItOutEnabled: false
}
}

componentWillReceiveProps(nextProps) {
const defaultContentType = "application/json"
let { specActions, path, method, operation } = nextProps
let producesValue = operation.get("produces_value")
let produces = operation.get("produces")
let consumes = operation.get("consumes")
let consumesValue = operation.get("consumes_value")

if(nextProps.response !== this.props.response) {
this.setState({ executeInProgress: false })
}

if (producesValue === undefined) {
producesValue = produces && produces.size ? produces.first() : defaultContentType
specActions.changeProducesValue([path, method], producesValue)
}

if (consumesValue === undefined) {
consumesValue = consumes && consumes.size ? consumes.first() : defaultContentType
specActions.changeConsumesValue([path, method], consumesValue)
}
}

shouldComponentUpdate(props, state) {
return shallowCompare(this, props, state)
}

toggleShown =() => {
let { layoutActions, isShownKey } = this.props
layoutActions.show(isShownKey, !this.isShown())
}

isShown =() => {
let { layoutSelectors, isShownKey } = this.props
return layoutSelectors.isShown(isShownKey, false ) // Here is where we set the default
}

onTryoutClick =() => {
this.setState({tryItOutEnabled: !this.state.tryItOutEnabled})
}

onCancelClick =() => {
let { specActions, path, method } = this.props
this.setState({tryItOutEnabled: !this.state.tryItOutEnabled})
specActions.clearValidateParams([path, method])
}

onExecute = () => {
this.setState({ executeInProgress: true })
}

render() {
let {
isShownKey,
jumpToKey,
path,
method,
operation,
showSummary,
response,
request,
allowTryItOut,

fn,
getComponent,
specActions,
specSelectors,
authActions,
authSelectors,
layoutSelectors,
layoutActions,
} = this.props

let summary = operation.get("summary")
let description = operation.get("description")
let deprecated = operation.get("deprecated")
let externalDocs = operation.get("externalDocs")
let responses = operation.get("responses")
let security = operation.get("security") || specSelectors.security()
let produces = operation.get("produces")
let schemes = operation.get("schemes")
let parameters = getList(operation, ["parameters"])

const Responses = getComponent("responses")
const Parameters = getComponent( "parameters" )
const Execute = getComponent( "execute" )
const Clear = getComponent( "clear" )
const AuthorizeOperationBtn = getComponent( "authorizeOperationBtn" )
const JumpToPath = getComponent("JumpToPath", true)
const Collapse = getComponent( "Collapse" )
const Markdown = getComponent( "Markdown" )
const Schemes = getComponent( "schemes" )

// Merge in Live Response
if(response && response.size > 0) {
let notDocumented = !responses.get(String(response.get("status")))
response = response.set("notDocumented", notDocumented)
}

let { tryItOutEnabled } = this.state
let shown = this.isShown()
let onChangeKey = [ path, method ] // Used to add values to _this_ operation ( indexed by path and method )

return (
<div className={deprecated ? "opblock opblock-deprecated" : shown ? `opblock opblock-${method} is-open` : `opblock opblock-${method}`} id={isShownKey} >
<div className={`opblock-summary opblock-summary-${method}`} onClick={this.toggleShown} >
<span className="opblock-summary-method">{method.toUpperCase()}</span>
<span className={ deprecated ? "opblock-summary-path__deprecated" : "opblock-summary-path" } >
<span>{path}</span>
<JumpToPath path={jumpToKey} />
</span>

{ !showSummary ? null :
<div className="opblock-summary-description">
{ summary }
</div>
}

{
(!security || !security.count()) ? null :
<AuthorizeOperationBtn authActions={ authActions }
security={ security }
authSelectors={ authSelectors }/>
}
</div>

<Collapse isOpened={shown} animated>
<div className="opblock-body">
{ deprecated && <h4 className="opblock-title_normal"> Warning: Deprecated</h4>}
{ description &&
<div className="opblock-description-wrapper">
<div className="opblock-description">
<Markdown options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}} source={ description } />
</div>
</div>
}
{
externalDocs && externalDocs.get("url") ?
<div className="opblock-external-docs-wrapper">
<h4 className="opblock-title_normal">Find more details</h4>
<div className="opblock-external-docs">
<span className="opblock-external-docs__description">{ externalDocs.get("description") }</span>
<a className="opblock-external-docs__link" href={ externalDocs.get("url") }>{ externalDocs.get("url") }</a>
</div>
</div> : null
}
<Parameters
parameters={parameters}
onChangeKey={onChangeKey}
onTryoutClick = { this.onTryoutClick }
onCancelClick = { this.onCancelClick }
tryItOutEnabled = { tryItOutEnabled }
allowTryItOut={allowTryItOut}

fn={fn}
getComponent={ getComponent }
specActions={ specActions }
specSelectors={ specSelectors }
pathMethod={ [path, method] }
/>

{!tryItOutEnabled || !allowTryItOut ? null : schemes && schemes.size ? <Schemes schemes={ schemes }
path={ path }
method={ method }
specActions={ specActions }/>
: null
}

<div className={(!tryItOutEnabled || !response || !allowTryItOut) ? "execute-wrapper" : "btn-group"}>
{ !tryItOutEnabled || !allowTryItOut ? null :

<Execute
getComponent={getComponent}
operation={ operation }
specActions={ specActions }
specSelectors={ specSelectors }
path={ path }
method={ method }
onExecute={ this.onExecute } />
}

{ (!tryItOutEnabled || !response || !allowTryItOut) ? null :
<Clear
onClick={ this.onClearClick }
specActions={ specActions }
path={ path }
method={ method }/>
}
</div>

{this.state.executeInProgress ? <div className="loading-container"><div className="loading"></div></div> : null}

{ !responses ? null :
<Responses
responses={ responses }
request={ request }
tryItOutResponse={ response }
getComponent={ getComponent }
specSelectors={ specSelectors }
specActions={ specActions }
produces={ produces }
producesValue={ operation.get("produces_value") }
pathMethod={ [path, method] }
fn={fn} />
}
</div>
</Collapse>
</div>
)
}

}

+ 133
- 0
src/core/components/operations.jsx Visa fil

@@ -0,0 +1,133 @@
import React, { PropTypes } from "react"
import {presets} from "react-motion"

export default class Operations extends React.Component {

static propTypes = {
specSelectors: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
layoutSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
authSelectors: PropTypes.object.isRequired,
};

static defaultProps = {

};

render() {
let {
specSelectors,
specActions,
getComponent,
layoutSelectors,
layoutActions,
authActions,
authSelectors,
fn
} = this.props

let taggedOps = specSelectors.taggedOperations()

const Operation = getComponent("operation")
const Collapse = getComponent("Collapse")
const Schemes = getComponent("schemes")

let showSummary = layoutSelectors.showSummary()

return (
<div>
{
taggedOps.map( (tagObj, tag) => {
let operations = tagObj.get("operations")
let tagDescription = tagObj.getIn(["tagDetails", "description"], null)

let isShownKey = ["operations-tag", tag]
let showTag = layoutSelectors.isShown(isShownKey, true)

return (
<div className={showTag ? "opblock-tag-section is-open" : "opblock-tag-section"} key={"operation-" + tag}>

<h4 className={!tagDescription ? "opblock-tag no-desc" : "opblock-tag" }>
<span onClick={() => layoutActions.show(isShownKey, !showTag)}>{tag}</span>

{ !tagDescription ? null :
<small onClick={() => layoutActions.show(isShownKey, !showTag)} >
{ tagDescription }
</small>
}

<button className="expand-methods" title="Expand all methods">
<svg className="expand" width="20" height="20">
<use xlinkHref="#expand" />
</svg>
</button>

<button className="expand-operation" title="Expand operation" onClick={() => layoutActions.show(isShownKey, !showTag)}>
<svg className="arrow" width="20" height="20">
<use xlinkHref={showTag ? "#large-arrow-down" : "#large-arrow"} />
</svg>
</button>
</h4>

<Collapse isOpened={showTag}>
{
operations.map( op => {

const isShownKey = ["operations", op.get("id"), tag]
const path = op.get("path", "")
const method = op.get("method", "")
const jumpToKey = `paths.${path}.${method}`

const allowTryItOut = specSelectors.allowTryItOutFor(op.get("path"), op.get("method"))
const response = specSelectors.responseFor(op.get("path"), op.get("method"))
const request = specSelectors.requestFor(op.get("path"), op.get("method"))

return <Operation
{...op.toObject()}

isShownKey={isShownKey}
jumpToKey={jumpToKey}
showSummary={showSummary}
key={isShownKey}
response={ response }
request={ request }
allowTryItOut={allowTryItOut}

specActions={ specActions }
specSelectors={ specSelectors }

layoutActions={ layoutActions }
layoutSelectors={ layoutSelectors }

authActions={ authActions }
authSelectors={ authSelectors }

getComponent={ getComponent }
fn={fn}
/>
}).toArray()
}
</Collapse>
</div>
)
}).toArray()
}

{ taggedOps.size < 1 ? <h3> No operations defined in spec! </h3> : null }
</div>
)
}

}

Operations.propTypes = {
layoutActions: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired,
layoutSelectors: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
fn: PropTypes.object.isRequired
}

+ 119
- 0
src/core/components/overview.jsx Visa fil

@@ -0,0 +1,119 @@
import React, { PropTypes } from "react"
import { Link } from "core/components/layout-utils"

export default class Overview extends React.Component {

constructor(...args) {
super(...args)
this.setTagShown = this._setTagShown.bind(this)
}

_setTagShown(showTagId, shown) {
this.props.layoutActions.show(showTagId, shown)
}

showOp(key, shown) {
let { layoutActions } = this.props
layoutActions.show(key, shown)
}

render() {
let { specSelectors, layoutSelectors, layoutActions, getComponent } = this.props
let taggedOps = specSelectors.taggedOperations()

const Collapse = getComponent("Collapse")

return (
<div>
<h4 className="overview-title">Overview</h4>

{
taggedOps.map( (tagObj, tag) => {
let operations = tagObj.get("operations")
let tagDetails = tagObj.get("tagDetails")

let showTagId = ["overview-tags", tag]
let showTag = layoutSelectors.isShown(showTagId, true)
let toggleShow = ()=> layoutActions.show(showTagId, !showTag)

return (
<div key={"overview-"+tag}>


<h4 onClick={toggleShow} className="link overview-tag"> {showTag ? "-" : "+"}{tag}</h4>

<Collapse isOpened={showTag} animated>
{
operations.map( op => {
let { path, method, operation, id } = op.toObject() // toObject is shallow
let showOpIdPrefix = "operations"
let showOpId = id
let shown = layoutSelectors.isShown([showOpIdPrefix, showOpId])
return <OperationLink key={id}
path={path}
method={method}
id={path + "-" + method}
shown={shown}
showOpId={showOpId}
showOpIdPrefix={showOpIdPrefix}
href={`#operation-${showOpId}`}
onClick={layoutActions.show} />
}).toArray()
}
</Collapse>

</div>
)
}).toArray()
}

{ taggedOps.size < 1 && <h3> No operations defined in spec! </h3> }
</div>
)
}

}

Overview.propTypes = {
layoutSelectors: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired
}

export class OperationLink extends React.Component {

constructor(props) {
super(props)
this.onClick = this._onClick.bind(this)
}

_onClick() {
let { showOpId, showOpIdPrefix, onClick, shown } = this.props
onClick([showOpIdPrefix, showOpId], !shown)
}

render() {
let { id, method, shown, href } = this.props

return (
<Link href={ href } style={{fontWeight: shown ? "bold" : "normal"}} onClick={this.onClick} className="block opblock-link">
<div>
<small className={`bold-label-${method}`}>{method.toUpperCase()}</small>
<span className="bold-label" >{id}</span>
</div>
</Link>
)
}

}

OperationLink.propTypes = {
href: PropTypes.string,
onClick: PropTypes.func,
id: PropTypes.string.isRequired,
method: PropTypes.string.isRequired,
shown: PropTypes.bool.isRequired,
showOpId: PropTypes.string.isRequired,
showOpIdPrefix: PropTypes.string.isRequired
}

+ 141
- 0
src/core/components/param-body.jsx Visa fil

@@ -0,0 +1,141 @@
import React, { Component, PropTypes } from "react"
import shallowCompare from "react-addons-shallow-compare"
import { Set, fromJS, List } from "immutable"
import { getSampleSchema } from "core/utils"

const NOOP = Function.prototype

export default class ParamBody extends Component {

static propTypes = {
param: PropTypes.object,
onChange: PropTypes.func,
onChangeConsumes: PropTypes.func,
consumes: PropTypes.object,
consumesValue: PropTypes.string,
fn: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
isExecute: PropTypes.bool,
specSelectors: PropTypes.object.isRequired,
pathMethod: PropTypes.array.isRequired
};

static defaultProp = {
consumes: fromJS(["application/json"]),
param: fromJS({}),
onChange: NOOP,
onChangeConsumes: NOOP,
};

constructor(props, context) {
super(props, context)

this.state = {
isEditBox: false,
value: ""
}

}

componentDidMount() {
this.updateValues.call(this, this.props)
}

shouldComponentUpdate(props, state) {
return shallowCompare(this, props, state)
}

componentWillReceiveProps(nextProps) {
this.updateValues.call(this, nextProps)
}

updateValues = (props) => {
let { specSelectors, pathMethod, param, isExecute, consumesValue="", onChangeConsumes } = props
let parameter = specSelectors ? specSelectors.getParameter(pathMethod, param.get("name")) : {}
let isXml = /xml/i.test(consumesValue)
let paramValue = isXml ? parameter.get("value_xml") : parameter.get("value")

if ( paramValue ) {
this.setState({ value: paramValue })
this.onChange(paramValue, {isXml: isXml, isEditBox: isExecute})
} else {
if (isXml) {
this.onChange(this.sample("xml"), {isXml: isXml, isEditBox: isExecute})
} else {
this.onChange(this.sample(), {isEditBox: isExecute})
}
}
}

sample = (xml) => {
let { param, fn:{inferSchema} } = this.props
let schema = inferSchema(param.toJS())

return getSampleSchema(schema, xml)
}

onChange = (value, { isEditBox, isXml }) => {
this.setState({value, isEditBox})
this._onChange(value, isXml)
}

_onChange = (val, isXml) => { (this.props.onChange || NOOP)(this.props.param, val, isXml) }

handleOnChange = e => {
let {consumesValue} = this.props
this.onChange(e.target.value.trim(), {isXml: /xml/i.test(consumesValue)})
}

toggleIsEditBox = () => this.setState( state => ({isEditBox: !state.isEditBox}))

render() {
let {
onChangeConsumes,
param,
isExecute,
specSelectors,
pathMethod,

getComponent,
} = this.props

const Button = getComponent("Button")
const TextArea = getComponent("TextArea")
const HighlightCode = getComponent("highlightCode")
const ContentType = getComponent("contentType")
// for domains where specSelectors not passed
let parameter = specSelectors ? specSelectors.getParameter(pathMethod, param.get("name")) : param
let errors = parameter.get("errors", List())
let consumesValue = specSelectors.contentTypeValues(pathMethod).get("requestContentType")
let consumes = this.props.consumes && this.props.consumes.size ? this.props.consumes : ParamBody.defaultProp.consumes

let { value, isEditBox } = this.state

return (
<div className="body-param">
{
isEditBox && isExecute
? <TextArea className={ "body-param__text" + ( errors.count() ? " invalid" : "")} value={value} onChange={ this.handleOnChange }/>
: (value && <HighlightCode className="body-param__example"
value={ value }/>)
}
<div className="body-param-options">
{
!isExecute ? null
: <div className="body-param-edit">
<Button className={isEditBox ? "btn cancel body-param__example-edit" : "btn edit body-param__example-edit"}
onClick={this.toggleIsEditBox}>{ isEditBox ? "Cancel" : "Edit"}
</Button>
</div>
}
<label htmlFor="">
<span>Parameter content type</span>
<ContentType value={ consumesValue } contentTypes={ consumes } onChange={onChangeConsumes} className="body-param-content-type" />
</label>
</div>

</div>
)

}
}

+ 119
- 0
src/core/components/parameter-row.jsx Visa fil

@@ -0,0 +1,119 @@
import React, { Component, PropTypes } from "react"
import win from "core/window"


export default class ParameterRow extends Component {
static propTypes = {
onChange: PropTypes.func.isRequired,
param: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
fn: PropTypes.object.isRequired,
isExecute: PropTypes.bool,
onChangeConsumes: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
pathMethod: PropTypes.array.isRequired
}

constructor(props, context) {
super(props, context)

let { specSelectors, pathMethod, param } = props
let defaultValue = param.get("default")
let parameter = specSelectors.getParameter(pathMethod, param.get("name"))
let value = parameter ? parameter.get("value") : ""
if ( defaultValue !== undefined && value === undefined ) {
this.onChangeWrapper(defaultValue)
}
}

componentWillReceiveProps(props) {
let { specSelectors, pathMethod, param } = props
let defaultValue = param.get("default")
let parameter = specSelectors.getParameter(pathMethod, param.get("name"))
let value = parameter ? parameter.get("value") : ""
if ( defaultValue !== undefined && value === undefined ) {
this.onChangeWrapper(defaultValue)
}
}

onChangeWrapper = (value) => {
let { onChange, param } = this.props
return onChange(param, value)
}

render() {
let {param, onChange, getComponent, isExecute, fn, onChangeConsumes, specSelectors, pathMethod} = this.props

// const onChangeWrapper = (value) => onChange(param, value)
const JsonSchemaForm = getComponent("JsonSchemaForm")
const ParamBody = getComponent("ParamBody")
let inType = param.get("in")
let bodyParam = inType !== "body" ? null
: <ParamBody getComponent={getComponent}
fn={fn}
param={param}
consumes={ specSelectors.operationConsumes(pathMethod) }
consumesValue={ specSelectors.contentTypeValues(pathMethod).get("requestContentType") }
onChange={onChange}
onChangeConsumes={onChangeConsumes}
isExecute={ isExecute }
specSelectors={ specSelectors }
pathMethod={ pathMethod }
/>

const ModelExample = getComponent("modelExample")
const Markdown = getComponent("Markdown")

let schema = param.get("schema")

let isFormData = inType === "formData"
let isFormDataSupported = "FormData" in win
let required = param.get("required")
let itemType = param.getIn(["items", "type"])
let parameter = specSelectors.getParameter(pathMethod, param.get("name"))
let value = parameter ? parameter.get("value") : ""

return (
<tr>
<td className="col parameters-col_name">
<div className={required ? "parameter__name required" : "parameter__name"}>
{ param.get("name") }
{ !required ? null : <span style={{color: "red"}}>&nbsp;*</span> }
</div>
<div className="parаmeter__type">{ param.get("type") } { itemType && `[${itemType}]` }</div>
<div className="parameter__in">({ param.get("in") })</div>
</td>

<td className="col parameters-col_description">
<Markdown options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}}
source={ param.get("description") }/>
{(isFormData && !isFormDataSupported) && <div>Error: your browser does not support FormData</div>}

{ bodyParam || !isExecute ? null
: <JsonSchemaForm fn={fn}
getComponent={getComponent}
value={ value }
required={ required }
description={param.get("description") ? `${param.get("name")} - ${param.get("description")}` : `${param.get("name")}`}
onChange={ this.onChangeWrapper }
schema={ param }/>
}


{
bodyParam && schema ? <ModelExample getComponent={ getComponent }
isExecute={ isExecute }
specSelectors={ specSelectors }
schema={ schema }
example={ bodyParam }/>
: null
}

</td>

</tr>
)

}

}

+ 109
- 0
src/core/components/parameters.jsx Visa fil

@@ -0,0 +1,109 @@
import React, { Component, PropTypes } from "react"
import ImPropTypes from "react-immutable-proptypes"
import Im, { fromJS } from "immutable"

// More readable, just iterate over maps, only
const eachMap = (iterable, fn) => iterable.valueSeq().filter(Im.Map.isMap).map(fn)

export default class Parameters extends Component {

static propTypes = {
parameters: ImPropTypes.list.isRequired,
specActions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
fn: PropTypes.object.isRequired,
tryItOutEnabled: PropTypes.bool,
allowTryItOut: PropTypes.bool,
onTryoutClick: PropTypes.func,
onCancelClick: PropTypes.func,
onChangeKey: PropTypes.array,
pathMethod: PropTypes.array.isRequired
}


static defaultProps = {
onTryoutClick: Function.prototype,
onCancelClick: Function.prototype,
tryItOutEnabled: false,
allowTryItOut: true,
onChangeKey: [],
}

onChange = ( param, value, isXml ) => {
let {
specActions: { changeParam },
onChangeKey,
} = this.props

changeParam( onChangeKey, param.get("name"), value, isXml)
}

onChangeConsumesWrapper = ( val ) => {
let {
specActions: { changeConsumesValue },
onChangeKey
} = this.props

changeConsumesValue(onChangeKey, val)
}

render(){

let {
onTryoutClick,
onCancelClick,
parameters,
allowTryItOut,
tryItOutEnabled,

fn,
getComponent,
specSelectors,
pathMethod
} = this.props

const ParameterRow = getComponent("parameterRow")
const TryItOutButton = getComponent("TryItOutButton")

const isExecute = tryItOutEnabled && allowTryItOut

return (
<div className="opblock-section">
<div className="opblock-section-header">
<h4 className="opblock-title">Parameters</h4>
{ allowTryItOut ? (
<TryItOutButton enabled={ tryItOutEnabled } onCancelClick={ onCancelClick } onTryoutClick={ onTryoutClick } />
) : null }
</div>
{ !parameters.count() ? <div className="opblock-description-wrapper"><p>No parameters</p></div> :
<div className="table-container">
<table className="parameters">
<thead>
<tr>
<th className="col col_header parameters-col_name">Name</th>
<th className="col col_header parameters-col_description">Description</th>
</tr>
</thead>
<tbody>
{
eachMap(parameters, (parameter, k) => (
<ParameterRow fn={ fn }
getComponent={ getComponent }
param={ parameter }
key={ parameter.get( "name" ) }
onChange={ this.onChange }
onChangeConsumes={this.onChangeConsumesWrapper}
specSelectors={ specSelectors }
pathMethod={ pathMethod }
isExecute={ isExecute }/>
)).toArray()
}
</tbody>
</table>
</div>
}
</div>
)
}
}

+ 95
- 0
src/core/components/response-body.jsx Visa fil

@@ -0,0 +1,95 @@
import React, { PropTypes } from "react"
import { formatXml } from "core/utils"
import lowerCase from "lodash/lowerCase"

export default class ResponseBody extends React.Component {

static propTypes = {
content: PropTypes.any.isRequired,
contentType: PropTypes.string.isRequired,
getComponent: PropTypes.func.isRequired,
headers: PropTypes.object,
url: PropTypes.string
}

render() {
let { content, contentType, url, headers={}, getComponent } = this.props
const HighlightCode = getComponent("highlightCode")
let body, bodyEl
url = url || ""

// JSON
if (/json/i.test(contentType)) {
try {
body = JSON.stringify(JSON.parse(content), null, " ")
} catch (error) {
body = "can't parse JSON. Raw result:\n\n" + content
}

bodyEl = <HighlightCode value={ body } />

// XML
} else if (/xml/i.test(contentType)) {
body = formatXml(content)
bodyEl = <HighlightCode value={ body } />

// HTML or Plain Text
} else if (lowerCase(contentType) === "text/html" || /text\/plain/.test(contentType)) {
bodyEl = <HighlightCode value={ content } />

// Image
} else if (/^image\//i.test(contentType)) {
bodyEl = <img src={ url } />

// Audio
} else if (/^audio\//i.test(contentType)) {
bodyEl = <pre><audio controls><source src={ url } type={ contentType } /></audio></pre>

// Download
} else if (
/^application\/octet-stream/i.test(contentType) ||
headers["Content-Disposition"] && (/attachment/i).test(headers["Content-Disposition"]) ||
headers["content-disposition"] && (/attachment/i).test(headers["content-disposition"]) ||
headers["Content-Description"] && (/File Transfer/i).test(headers["Content-Description"]) ||
headers["content-description"] && (/File Transfer/i).test(headers["content-description"])) {

let contentLength = headers["content-length"] || headers["Content-Length"]
if ( !(+contentLength) ) return null

const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)

if (!isSafari && "Blob" in window) {
let type = contentType || "text/html"
let blob = (content instanceof Blob) ? content : new Blob([content], {type: type})
let href = window.URL.createObjectURL(blob)
let fileName = url.substr(url.lastIndexOf("/") + 1)
let download = [type, fileName, href].join(":")

// Use filename from response header
let disposition = headers["content-disposition"] || headers["Content-Disposition"]
if (typeof disposition !== "undefined") {
let responseFilename = /filename=([^;]*);?/i.exec(disposition)
if (responseFilename !== null && responseFilename.length > 1) {
download = responseFilename[1]
}
}

bodyEl = <div><a href={ href } download={ download }>{ "Download file" }</a></div>
} else {
bodyEl = <pre>Download headers detected but your browser does not support downloading binary via XHR (Blob).</pre>
}

// Anything else (CORS)
} else if (typeof content === "string") {
bodyEl = <HighlightCode value={ content } />
} else {
bodyEl = <div>Unknown response type</div>
}

return ( !bodyEl ? null : <div>
<h5>Response body</h5>
{ bodyEl }
</div>
)
}
}

+ 91
- 0
src/core/components/response.jsx Visa fil

@@ -0,0 +1,91 @@
import React, { PropTypes } from "react"
import { fromJS } from 'immutable'
import { getSampleSchema } from "core/utils"

const getExampleComponent = ( sampleResponse, examples, HighlightCode ) => {
if ( examples && examples.size ) {
return examples.entrySeq().map( ([ key, example ]) => {
return (<div key={ key }>
<h5>{ key }</h5>
<HighlightCode className="example" value={ example } />
</div>)
}).toArray()
}

if ( sampleResponse ) { return <div>
<HighlightCode className="example" value={ sampleResponse } />
</div>
}
return null
}

export default class Response extends React.Component {

static propTypes = {
code: PropTypes.string.isRequired,
response: PropTypes.object,
className: PropTypes.string,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
fn: PropTypes.object.isRequired,
contentType: PropTypes.string
}

static defaultProps = {
response: fromJS({}),
};

render() {
let {
code,
response,
className,

fn,
getComponent,
specSelectors,
contentType
} = this.props

let { inferSchema } = fn

let schema = inferSchema(response.toJS())
let headers = response.get("headers")
let examples = response.get("examples")
const Headers = getComponent("headers")
const HighlightCode = getComponent("highlightCode")
const ModelExample = getComponent("modelExample")
const Markdown = getComponent( "Markdown" )

let sampleResponse = schema ? getSampleSchema(schema, contentType, { includeReadOnly: true }) : null
let example = getExampleComponent( sampleResponse, examples, HighlightCode )

return (
<tr className={ "response " + ( className || "") }>
<td className="col response-col_status">
{ code }
</td>
<td className="col response-col_description">

<div className="response-col_description__inner">
<Markdown options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}} source={ response.get( "description" ) } />
</div>

{ example ? (
<ModelExample
getComponent={ getComponent }
specSelectors={ specSelectors }
schema={ fromJS(schema) }
example={ example }/>
) : null}

{ headers ? (
<Headers headers={ headers }/>
) : null}

</td>

</tr>
)
}
}

+ 93
- 0
src/core/components/responses.jsx Visa fil

@@ -0,0 +1,93 @@
import React, { PropTypes } from "react"
import { fromJS } from "immutable"
import { defaultStatusCode } from "core/utils"

export default class Responses extends React.Component {

static propTypes = {
request: PropTypes.object,
tryItOutResponse: PropTypes.object,
responses: PropTypes.object.isRequired,
produces: PropTypes.object,
producesValue: PropTypes.any,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired,
pathMethod: PropTypes.array.isRequired,
fn: PropTypes.object.isRequired
}

static defaultProps = {
request: null,
tryItOutResponse: null,
produces: fromJS(["application/json"])
}

onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue(this.props.pathMethod, val)

render() {
let { responses, request, tryItOutResponse, getComponent, specSelectors, fn, producesValue } = this.props
let defaultCode = defaultStatusCode( responses )

const ContentType = getComponent( "contentType" )
const LiveResponse = getComponent( "liveResponse" )
const Response = getComponent( "response" )

let produces = this.props.produces && this.props.produces.size ? this.props.produces : Responses.defaultProps.produces

return (
<div className="responses-wrapper">
<div className="opblock-section-header">
<h4>Responses</h4>
<label>
<span>Response content type</span>
<ContentType value={producesValue}
onChange={this.onChangeProducesWrapper}
contentTypes={produces}
className="execute-content-type"/>
</label>
</div>
<div className="responses-inner">
{
!tryItOutResponse ? null
: <div>
<LiveResponse request={ request }
response={ tryItOutResponse }
getComponent={ getComponent } />
<h4>Responses</h4>
</div>

}

<table className="responses-table">
<thead>
<tr className="responses-header">
<td className="col col_header response-col_status">Code</td>
<td className="col col_header response-col_description">Description</td>
</tr>
</thead>
<tbody>
{
responses.entrySeq().map( ([code, response]) => {

let className = tryItOutResponse && tryItOutResponse.get("status") == code ? "response_current" : ""
return (
<Response key={ code }
isDefault={defaultCode === code}
fn={fn}
className={ className }
code={ code }
response={ response }
specSelectors={ specSelectors }
contentType={ producesValue }
getComponent={ getComponent }/>
)
}).toArray()
}
</tbody>
</table>
</div>
</div>
)
}
}

+ 45
- 0
src/core/components/schemes.jsx Visa fil

@@ -0,0 +1,45 @@
import React, { PropTypes } from "react"

export default class Schemes extends React.Component {

static propTypes = {
specActions: PropTypes.object.isRequired,
schemes: PropTypes.object.isRequired,
path: PropTypes.string,
method: PropTypes.string
}

componentWillMount() {
let { schemes } = this.props

//fire 'change' event to set default 'value' of select
this.setScheme(schemes.first())
}

onChange =( e ) => {
let { path, method, specActions } = this.props

this.setScheme( e.target.value )
}

setScheme =( value ) => {
let { path, method, specActions } = this.props

specActions.setScheme( value, path, method )
}

render() {
let { schemes } = this.props

return (
<label htmlFor="schemes">
<span>Schemes</span>
<select onChange={ this.onChange }>
{ schemes.valueSeq().map(
( scheme ) => <option value={ scheme } key={ scheme }>{ scheme }</option>
).toArray()}
</select>
</label>
)
}
}

+ 0
- 0
Visa fil


+ 27
- 0
src/core/components/try-it-out-button.jsx Visa fil

@@ -0,0 +1,27 @@
import React, { PropTypes } from "react"

export default class TryItOutButton extends React.Component {

static propTypes = {
onTryoutClick: PropTypes.func,
enabled: PropTypes.bool, // Try it out is enabled, ie: the user has access to the form
};

static defaultProps = {
onTryoutClick: Function.prototype,
enabled: false,
};

render() {
const { onTryoutClick, onCancelClick, enabled } = this.props

return (
<div className="try-out">
{
enabled ? <button className="btn try-out__btn cancel" onClick={ onTryoutClick }>Cancel</button>
: <button className="btn try-out__btn" onClick={ onCancelClick }>Try it out </button>
}
</div>
)
}
}

+ 34
- 0
src/core/curlify.js Visa fil

@@ -0,0 +1,34 @@
export default function curl( request ){
let curlified = []
let type = ""
let headers = request.get("headers")
curlified.push( "curl" )
curlified.push( "-X", request.get("method") )
curlified.push( request.get("url") )

if ( headers && headers.size ) {
for( let p of request.get("headers").entries() ){
let [ h,v ] = p
type = v
curlified.push( "-H " )
curlified.push( `"${h}: ${v}"` )
}
}

if ( request.get("body") ){

if(type === "multipart/form-data" && request.get("method") === "POST") {
let formDataBody = request.get("body").split("&")

for(var data in formDataBody) {
curlified.push( "-F" )
curlified.push(formDataBody[data])
}
} else {
curlified.push( "-d" )
curlified.push( JSON.stringify( request.get("body") ).replace(/\\n/g, "") )
}
}

return curlified.join( " " )
}

+ 119
- 0
src/core/index.js Visa fil

@@ -0,0 +1,119 @@
import deepExtend from "deep-extend"

import System from "core/system"
import ApisPreset from "core/presets/apis"
import * as AllPlugins from "core/plugins/all"
import { filterConfigs } from "plugins/configs"

module.exports = function SwaggerUI(opts) {

const defaults = {
// Some general settings, that we floated to the top
dom_id: null,
spec: {},
url: "",
layout: "Layout",
configs: {
validatorUrl: "https://online.swagger.io/validator"
},

// Initial set of plugins ( TODO rename this, or refactor - we don't need presets _and_ plugins. Its just there for performance.
// Instead, we can compile the first plugin ( it can be a collection of plugins ), then batch the rest.
presets: [
],

// Plugins; ( loaded after presets )
plugins: [
],

// Inline Plugin
fn: { },
components: { },
state: { },

// Override some core configs... at your own risk
store: { },
}

const config = deepExtend({}, defaults, opts)

const storeConfigs = deepExtend({}, config.store, {
system: {
configs: config.configs
},
plugins: config.presets,
state: {
layout: {
layout: config.layout
},
spec: {
spec: "",
url: config.url
}
}
})

let inlinePlugin = ()=> {
return {
fn: config.fn,
components: config.components,
state: config.state,
}
}

var store = new System(storeConfigs)
store.register([config.plugins, inlinePlugin])

var system = store.getSystem()

const downloadSpec = (configs) => {
if(typeof config !== "object") {
return system
}

let localConfig = system.specSelectors.getLocalConfig ? system.specSelectors.getLocalConfig() : {}
let mergedConfig = deepExtend({}, config, configs, localConfig)
store.setConfigs(filterConfigs(mergedConfig))

if(typeof mergedConfig.spec === "object" && Object.keys(mergedConfig.spec).length) {
system.specActions.updateUrl("")
system.specActions.updateLoadingStatus("success");
system.specActions.updateSpec(JSON.stringify(mergedConfig.spec))
} else if(mergedConfig.url) {
system.specActions.updateUrl(mergedConfig.url)
system.specActions.download(mergedConfig.url)
}

if(mergedConfig.dom_id)
system.render(mergedConfig.dom_id, "App")

return system
}

if (system.specActions.getConfigByUrl && !system.specActions.getConfigByUrl(downloadSpec)) {
return downloadSpec(config)
}

if (system.specActions.download && config.url) {
system.specActions.download(config.url)
}

if(config.spec && typeof config.spec === "string")
system.specActions.updateSpec(config.spec)

if(config.dom_id) {
system.render(config.dom_id, "App")
} else {
console.error("Skipped rendering: no `dom_id` was specified")
}

return system
}

// Add presets
module.exports.presets = {
apis: ApisPreset,
}

// All Plugins
module.exports.plugins = AllPlugins

+ 187
- 0
src/core/json-schema-components.js Visa fil

@@ -0,0 +1,187 @@
import React, { PropTypes, Component } from "react"
import { arrayify } from "core/utils"
import shallowCompare from "react-addons-shallow-compare"
import { List, fromJS } from "immutable"
import assign from "object-assign"
//import "less/json-schema-form"

const noop = ()=> {}
const JsonSchemaPropShape = {
getComponent: PropTypes.func.isRequired,
value: PropTypes.any,
onChange: PropTypes.func,
keyName: PropTypes.any,
fn: PropTypes.object.isRequired,
schema: PropTypes.object,
required: PropTypes.bool,
description: PropTypes.any
}

const JsonSchemaDefaultProps = {
value: "",
onChange: noop,
schema: {},
keyName: "",
required: false
}

export class JsonSchemaForm extends Component {

static propTypes = JsonSchemaPropShape
static defaultProps = JsonSchemaDefaultProps

render() {
let { schema, value, onChange, getComponent, fn } = this.props

if(schema.toJS)
schema = schema.toJS()

let { type, format="" } = schema

let Comp = getComponent(`JsonSchema_${type}_${format}`) || getComponent(`JsonSchema_${type}`) || getComponent("JsonSchema_string")
return <Comp { ...this.props } fn={fn} getComponent={getComponent} value={value} onChange={onChange} schema={schema}/>
}

}

export class JsonSchema_string extends Component {
static propTypes = JsonSchemaPropShape
static defaultProps = JsonSchemaDefaultProps
onChange = (e) => {
const value = this.props.schema["type"] === "file" ? e.target.files[0] : e.target.value
this.props.onChange(value, this.props.keyName)
}
onEnumChange = (val) => this.props.onChange(val)
render() {
let { getComponent, value, schema, fn, required, description } = this.props
let enumValue = schema["enum"]
let errors = schema.errors || []

if ( enumValue ) {
const Select = getComponent("Select")
return (<Select allowedValues={ enumValue }
value={ value }
allowEmptyValue={ !required }
onChange={ this.onEnumChange }/>)
}

const isDisabled = schema["in"] === "formData" && !("FormData" in window)
const Input = getComponent("Input")
if (schema["type"] === "file") {
return <Input type="file" className={ errors.length ? "invalid" : ""} onChange={ this.onChange } disabled={isDisabled}/>
}
else {
return <Input type="text" className={ errors.length ? "invalid" : ""} value={value} placeholder={description} onChange={ this.onChange } disabled={isDisabled}/>
}
}
}

export class JsonSchema_array extends Component {

static propTypes = JsonSchemaPropShape
static defaultProps = JsonSchemaDefaultProps

constructor(props, context) {
super(props, context)
this.state = {value: props.value}
}

componentWillReceiveProps(props) {
if(props.value !== this.state.value)
this.setState({value: props.value})
}

shouldComponentUpdate(props, state) {
return shallowCompare(this, props, state)
}

onChange = () => this.props.onChange(this.state.value)

onItemChange = (itemVal, i) => {
this.setState(state => ({
value: state.value.set(i, itemVal)
}), this.onChange)
}

removeItem = (i) => {
this.setState(state => ({
value: state.value.remove(i)
}), this.onChange)
}

addItem = () => {
this.setState(state => {
state.value = state.value || List()
return {
value: state.value.push("")
}
}, this.onChange)
}

onEnumChange = (value) => {
this.setState(state => ({
value: value
}), this.onChange)
}

render() {
let { getComponent, onChange, required, schema, fn } = this.props

let itemSchema = fn.inferSchema(schema.items)

const JsonSchemaForm = getComponent("JsonSchemaForm")
const Button = getComponent("Button")

let enumValue = itemSchema["enum"]
let value = this.state.value

if ( enumValue ) {
const Select = getComponent("Select")
return (<Select multiple={ true }
value={ value }
allowedValues={ enumValue }
allowEmptyValue={ !required }
onChange={ this.onEnumChange }/>)
}

let errors = schema.errors || []

return (
<div>
{ !value || value.count() < 1 ?
(errors.length ? <span style={{ color: "red", fortWeight: "bold" }}>{ errors[0] }</span> : null) :
value.map( (item,i) => {
let schema = Object.assign({}, itemSchema)
let err = errors.filter((err) => err.index === i)
if ( err.length ) {
schema.errors = [ err[0].error + i ]
}
return (
<div key={i} className="json-schema-form-item">
<JsonSchemaForm fn={fn} getComponent={getComponent} value={item} onChange={(val) => this.onItemChange(val, i)} schema={schema} />
<Button className="json-schema-form-item-remove" onClick={()=> this.removeItem(i)} > - </Button>
</div>
)
}).toArray()
}
<Button className="json-schema-form-item-add" onClick={this.addItem}> Add item </Button>
</div>
)
}
}

export class JsonSchema_boolean extends Component {
static propTypes = JsonSchemaPropShape
static defaultProps = JsonSchemaDefaultProps

onEnumChange = (val) => this.props.onChange(val)
render() {
let { getComponent, required, value } = this.props
const Select = getComponent("Select")

return (<Select value={ String(value) }
allowedValues={ fromJS(["true", "false"]) }
allowEmptyValue={ !required }
onChange={ this.onEnumChange }/>)
}
}

+ 47
- 0
src/core/oauth2-authorize.js Visa fil

@@ -0,0 +1,47 @@
import win from "core/window"

export default function authorize ( auth, authActions, errActions, configs ) {
let { schema, scopes, name, clientId } = auth

let redirectUrl = configs.oauth2RedirectUrl
let scopeSeparator = " "
let state = name
let flow = schema.get("flow")
let url

if (flow === "password") {
authActions.authorizePassword(auth)
return
}

// todo move to parser
if ( !redirectUrl ) {
errActions.newAuthErr( {
authId: name,
source: "validation",
level: "error",
message: "oauth2RedirectUri configuration is not passed. Oauth2 authorization cannot be performed."
})
return
}

if (flow === "implicit" || flow === "accessCode") {
url = schema.get("authorizationUrl") + "?response_type=" + (flow === "implicit" ? "token" : "code")
}

url += "&redirect_uri=" + encodeURIComponent(redirectUrl)
+ "&scope=" + encodeURIComponent(scopes.join(scopeSeparator))
+ "&state=" + encodeURIComponent(state)
+ "&client_id=" + encodeURIComponent(clientId)

// pass action authorizeOauth2 and authentication data through window
// to authorize with oauth2
win.swaggerUIRedirectOauth2 = {
auth: auth,
state: state,
callback: authActions.preAuthorizeOauth2,
errCb: errActions.newAuthErr
}

win.open(url)
}

+ 67
- 0
src/core/path-translator.js Visa fil

@@ -0,0 +1,67 @@
import get from "lodash/get"

export function transformPathToArray(property, jsSpec) {
if(property.slice(0,9) === "instance.") {
var str = property.slice(9)
} else { // eslint-disable-next-line no-redeclare
var str = property
}

var pathArr = []

str
.split(".")
.map(item => {
// "key[0]" becomes ["key", "0"]
if(item.includes("[")) {
let index = parseInt(item.match(/\[(.*)\]/)[1])
let keyName = item.slice(0, item.indexOf("["))
return [keyName, index.toString()]
} else {
return item
}
})
.reduce(function(a, b) {
// flatten!
return a.concat(b)
}, [])
.concat([""]) // add an empty item into the array, so we don't get stuck with something in our buffer below
.reduce((buffer, curr, i, arr) => {
let obj = pathArr.length ? get(jsSpec, pathArr) : jsSpec

if(get(obj, makeAccessArray(buffer, curr))) {
if(buffer.length) {
pathArr.push(buffer)
}
if(curr.length) {
pathArr.push(curr)
}
return ""
} else {
// attach key to buffer
return `${buffer}${buffer.length ? "." : ""}${curr}`
}
}, "")

if(typeof get(jsSpec, pathArr) !== "undefined") {
return pathArr
} else {
// if our path is not correct (there is no value at the path),
// return null
return null
}
}

function makeAccessArray(buffer, curr) {
let arr = []

if(buffer.length) {
arr.push(buffer)
}

if(curr.length) {
arr.push(curr)
}

return arr
}

+ 17
- 0
src/core/plugins/all.js Visa fil

@@ -0,0 +1,17 @@
import { pascalCaseFilename } from "core/utils"

const request = require.context(".", true, /\.jsx?$/)

request.keys().forEach( function( key ){
if( key === "./index.js" ) {
return
}

// if( key.slice(2).indexOf("/") > -1) {
// // skip files in subdirs
// return
// }

let mod = request(key)
module.exports[pascalCaseFilename(key)] = mod.default ? mod.default : mod
})

+ 36
- 0
src/core/plugins/allow-try-it-out-if-host.js Visa fil

@@ -0,0 +1,36 @@
import React, { PropTypes } from "react"

export default function (system) {
return {
components: {
NoHostWarning,
},
statePlugins: {
spec: {
selectors: {
allowTryItOutFor,
}
}
}
}
}

// This is a quick style. How do we improve this?
const style = {
backgroundColor: "#e7f0f7",
padding: "1rem",
borderRadius: "3px",
}

function NoHostWarning() {
return (
<div style={style}>Note: The interactive forms are disabled, as no `host` property was found in the specification. Please see: <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#swagger-object" target="_blank">OAI 2.0/#swagger-object</a></div>
)
}

// Only allow if, there is a host field
function allowTryItOutFor(state) {
return ({specSelectors}) => {
return specSelectors.hasHost(state)
}
}

+ 284
- 0
src/core/plugins/ast/ast.js Visa fil

@@ -0,0 +1,284 @@
import YAML from "yaml-js"
import isArray from "lodash/isArray"
import lodashFind from "lodash/find"
import { memoize } from "core/utils"

let cachedCompose = memoize(YAML.compose) // TODO: build a custom cache based on content

var MAP_TAG = "tag:yaml.org,2002:map"
var SEQ_TAG = "tag:yaml.org,2002:seq"

export function getLineNumberForPath(yaml, path) {

// Type check
if (typeof yaml !== "string") {
throw new TypeError("yaml should be a string")
}
if (!isArray(path)) {
throw new TypeError("path should be an array of strings")
}

var i = 0

let ast = cachedCompose(yaml)

// simply walks the tree using current path recursively to the point that
// path is empty

return find(ast, path)

function find(current, path, last) {
if(!current) {
// something has gone quite wrong
// return the last start_mark as a best-effort
if(last && last.start_mark)
return last.start_mark.line
return 0
}

if (path.length && current.tag === MAP_TAG) {
for (i = 0; i < current.value.length; i++) {
var pair = current.value[i]
var key = pair[0]
var value = pair[1]

if (key.value === path[0]) {
return find(value, path.slice(1), current)
}

if (key.value === path[0].replace(/\[.*/, "")) {
// access the array at the index in the path (example: grab the 2 in "tags[2]")
var index = parseInt(path[0].match(/\[(.*)\]/)[1])
if(value.value.length === 1 && index !== 0 && !!index) {
var nextVal = lodashFind(value.value[0], { value: index.toString() })
} else { // eslint-disable-next-line no-redeclare
var nextVal = value.value[index]
}
return find(nextVal, path.slice(1), value.value)
}
}
}

if (path.length && current.tag === SEQ_TAG) {
var item = current.value[path[0]]

if (item && item.tag) {
return find(item, path.slice(1), current.value)
}
}

if (current.tag === MAP_TAG && !Array.isArray(last)) {
return current.start_mark.line
} else {
return current.start_mark.line + 1
}
}
}

/**
* Get a position object with given
* @param {string} yaml
* YAML or JSON string
* @param {array} path
* an array of stings that constructs a
* JSON Path similiar to JSON Pointers(RFC 6901). The difference is, each
* component of path is an item of the array intead of beinf seperated with
* slash(/) in a string
*/
export function positionRangeForPath(yaml, path) {

// Type check
if (typeof yaml !== "string") {
throw new TypeError("yaml should be a string")
}
if (!isArray(path)) {
throw new TypeError("path should be an array of strings")
}

var invalidRange = {
start: {line: -1, column: -1},
end: {line: -1, column: -1}
}
var i = 0

let ast = cachedCompose(yaml)

// simply walks the tree using current path recursively to the point that
// path is empty.
return find(ast)

function find(current) {
if (current.tag === MAP_TAG) {
for (i = 0; i < current.value.length; i++) {
var pair = current.value[i]
var key = pair[0]
var value = pair[1]

if (key.value === path[0]) {
path.shift()
return find(value)
}
}
}

if (current.tag === SEQ_TAG) {
var item = current.value[path[0]]

if (item && item.tag) {
path.shift()
return find(item)
}
}

// if path is still not empty we were not able to find the node
if (path.length) {
return invalidRange
}

return {
/* jshint camelcase: false */
start: {
line: current.start_mark.line,
column: current.start_mark.column
},
end: {
line: current.end_mark.line,
column: current.end_mark.column
}
}
}
}

/**
* Get a JSON Path for position object in the spec
* @param {string} yaml
* YAML or JSON string
* @param {object} position
* position in the YAML or JSON string with `line` and `column` properties.
* `line` and `column` number are zero indexed
*/
export function pathForPosition(yaml, position) {

// Type check
if (typeof yaml !== "string") {
throw new TypeError("yaml should be a string")
}
if (typeof position !== "object" || typeof position.line !== "number" ||
typeof position.column !== "number") {
throw new TypeError("position should be an object with line and column" +
" properties")
}

try {
var ast = cachedCompose(yaml)
} catch (e) {
console.error("Error composing AST", e)
console.error(`Problem area:\n`, yaml.split("\n").slice(position.line - 5, position.line + 5).join("\n"))
return null
}


var path = []

return find(ast)

/**
* recursive find function that finds the node matching the position
* @param {object} current - AST object to serach into
*/
function find(current) {

// algorythm:
// is current a promitive?
// // finish recursion without modifying the path
// is current a hash?
// // find a key or value that position is in their range
// // if key is in range, terminate recursion with exisiting path
// // if a value is in range push the corresponding key to the path
// // andcontinue recursion
// is current an array
// // find the item that position is in it"s range and push the index
// // of the item to the path and continue recursion with that item.

var i = 0

if (!current || [MAP_TAG, SEQ_TAG].indexOf(current.tag) === -1) {
return path
}

if (current.tag === MAP_TAG) {
for (i = 0; i < current.value.length; i++) {
var pair = current.value[i]
var key = pair[0]
var value = pair[1]

if (isInRange(key)) {
return path
} else if (isInRange(value)) {
path.push(key.value)
return find(value)
}
}
}

if (current.tag === SEQ_TAG) {
for (i = 0; i < current.value.length; i++) {
var item = current.value[i]

if (isInRange(item)) {
path.push(i.toString())
return find(item)
}
}
}

return path

/**
* Determines if position is in node"s range
* @param {object} node - AST node
* @return {Boolean} true if position is in node"s range
*/
function isInRange(node) {
/* jshint camelcase: false */

// if node is in a single line
if (node.start_mark.line === node.end_mark.line) {

return (position.line === node.start_mark.line) &&
(node.start_mark.column <= position.column) &&
(node.end_mark.column >= position.column)
}

// if position is in the same line as start_mark
if (position.line === node.start_mark.line) {
return position.column >= node.start_mark.column
}

// if position is in the same line as end_mark
if (position.line === node.end_mark.line) {
return position.column <= node.end_mark.column
}

// if position is between start and end lines return true, otherwise
// return false.
return (node.start_mark.line < position.line) &&
(node.end_mark.line > position.line)
}
}
}

// utility fns


export let pathForPositionAsync = promisifySyncFn(pathForPosition)
export let positionRangeForPathAsync = promisifySyncFn(positionRangeForPath)
export let getLineNumberForPathAsync = promisifySyncFn(getLineNumberForPath)

function promisifySyncFn(fn) {
return function(...args) {
return new Promise(function(resolve, reject) {
resolve(fn(...args))
})
}
}

+ 9
- 0
src/core/plugins/ast/index.js Visa fil

@@ -0,0 +1,9 @@
import * as AST from "./ast"
import JumpToPath from "./jump-to-path"

export default function() {
return {
fn: { AST },
components: { JumpToPath }
}
}

+ 9
- 0
src/core/plugins/ast/jump-to-path.jsx Visa fil

@@ -0,0 +1,9 @@
import React from "react"

// Nothing by default- component can be overriden by another plugin.

export default class JumpToPath extends React.Component {
render() {
return null
}
}

+ 118
- 0
src/core/plugins/auth/actions.js Visa fil

@@ -0,0 +1,118 @@
import win from "core/window"
import btoa from "btoa"

export const SHOW_AUTH_POPUP = "show_popup"
export const AUTHORIZE = "authorize"
export const LOGOUT = "logout"
export const PRE_AUTHORIZE_OAUTH2 = "pre_authorize_oauth2"
export const AUTHORIZE_OAUTH2 = "authorize_oauth2"
export const VALIDATE = "validate"

export function showDefinitions(payload) {
return {
type: SHOW_AUTH_POPUP,
payload: payload
}
}

export function authorize(payload) {
return {
type: AUTHORIZE,
payload: payload
}
}

export function logout(payload) {
return {
type: LOGOUT,
payload: payload
}
}

export const preAuthorizeOauth2 = (payload) => ( { authActions, errActions } ) => {
let { auth , token, isValid } = payload
let { schema, name } = auth
let flow = schema.get("flow")

// remove oauth2 property from window after redirect from authentication
delete win.swaggerUIRedirectOauth2

if ( flow !== "accessCode" && !isValid ) {
errActions.newAuthErr( {
authId: name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
})
}

if ( token.error ) {
errActions.newAuthErr({
authId: name,
source: "auth",
level: "error",
message: JSON.stringify(token)
})
return
}

authActions.authorizeOauth2({ auth, token })
}

export function authorizeOauth2(payload) {
return {
type: AUTHORIZE_OAUTH2,
payload: payload
}
}

export const authorizePassword = ( auth ) => ( { fn, authActions, errActions } ) => {
let { schema, name, username, password, passwordType, clientId, clientSecret } = auth
let req = {
url: schema.get("tokenUrl"),
method: "post",
headers: {
"content-type": "application/x-www-form-urlencoded"
},
query: {
grant_type: "password",
username,
password
}
}

if ( passwordType === "basic") {
req.headers.authorization = "Basic " + btoa(clientId + ":" + clientSecret)
} else if ( passwordType === "request") {
req.query = Object.assign(req.query, { client_id: clientId, client_secret: clientSecret })
}
return fn.fetch(req)
.then(( response ) => {
let token = JSON.parse(response.data)
let error = token && ( token.error || "" )
let parseError = token && ( token.parseError || "" )

if ( !response.ok ) {
errActions.newAuthErr( {
authId: name,
level: "error",
source: "auth",
message: response.statusText
} )
return
}

if ( error || parseError ) {
errActions.newAuthErr({
authId: name,
level: "error",
source: "auth",
message: JSON.stringify(token)
})
return
}

authActions.authorizeOauth2({ auth, token })
})
.catch(err => { errActions.newAuthErr( err ) })
}

+ 19
- 0
src/core/plugins/auth/index.js Visa fil

@@ -0,0 +1,19 @@
import reducers from "./reducers"
import * as actions from "./actions"
import * as selectors from "./selectors"
import * as specWrapActionReplacements from "./spec-wrap-actions"

export default function() {
return {
statePlugins: {
auth: {
reducers,
actions,
selectors
},
spec: {
wrapActions: specWrapActionReplacements
}
}
}
}

+ 63
- 0
src/core/plugins/auth/reducers.js Visa fil

@@ -0,0 +1,63 @@
import { fromJS, Map } from "immutable"
import btoa from "btoa"

import {
SHOW_AUTH_POPUP,
AUTHORIZE,
PRE_AUTHORIZE_OAUTH2,
AUTHORIZE_OAUTH2,
LOGOUT
} from "./actions"

export default {
[SHOW_AUTH_POPUP]: (state, { payload } ) =>{
return state.set( "showDefinitions", payload )
},

[AUTHORIZE]: (state, { payload } ) =>{
let securities = fromJS(payload)
let map = state.get("authorized") || Map()

// refactor withMutations
securities.entrySeq().forEach( ([ key, security ]) => {
let type = security.getIn(["schema", "type"])
let name = security.get("name")

if ( type === "apiKey" ) {
map = map.set(key, security)
} else if ( type === "basic" ) {
let username = security.getIn(["value", "username"])
let password = security.getIn(["value", "password"])

map = map.setIn([key, "value"], {
username: username,
header: "Basic " + btoa(username + ":" + password)
})

map = map.setIn([key, "schema"], security.get("schema"))
}
})

return state.set( "authorized", map )
},

[AUTHORIZE_OAUTH2]: (state, { payload } ) =>{
let { auth, token } = payload
let parsedAuth

auth.token = token
parsedAuth = fromJS(auth)

return state.setIn( [ "authorized", parsedAuth.get("name") ], parsedAuth )
},

[LOGOUT]: (state, { payload } ) =>{
let result = state.get("authorized").withMutations((authorized) => {
payload.forEach((auth) => {
authorized.delete(auth)
})
})

return state.set("authorized", result)
}
}

+ 78
- 0
src/core/plugins/auth/selectors.js Visa fil

@@ -0,0 +1,78 @@
import { createSelector } from "reselect"
import { List, Map } from "immutable"

const state = state => state

export const shownDefinitions = createSelector(
state,
auth => auth.get( "showDefinitions" )
)

export const definitionsToAuthorize = createSelector(
state,
auth =>( { specSelectors } ) => {
let definitions = specSelectors.securityDefinitions()
let list = List()

//todo refactor
definitions.entrySeq().forEach( ([ key, val ]) => {
let map = Map()

map = map.set(key, val)
list = list.push(map)
})

return list
}
)


export const getDefinitionsByNames = ( state, securities ) =>( { specSelectors } ) => {
let securityDefinitions = specSelectors.securityDefinitions()
let result = List()

securities.valueSeq().forEach( (names) => {
let map = Map()
names.entrySeq().forEach( ([name, scopes]) => {
let definition = securityDefinitions.get(name)
let allowedScopes

if ( definition.get("type") === "oauth2" && scopes.size ) {
allowedScopes = definition.get("scopes")

allowedScopes.keySeq().forEach( (key) => {
if ( !scopes.contains(key) ) {
allowedScopes = allowedScopes.delete(key)
}
})

definition = definition.set("allowedScopes", allowedScopes)
}

map = map.set(name, definition)
})

result = result.push(map)
})

return result
}

export const authorized = createSelector(
state,
auth => auth.get("authorized") || Map()
)


export const isAuthorized = ( state, securities ) =>( { authSelectors } ) => {
let authorized = authSelectors.authorized()
let isAuth = false

return !!securities.toJS().filter( ( security ) => {
let isAuthorized = true

return Object.keys(security).map((key) => {
return !isAuthorized || !!authorized.get(key)
}).indexOf(false) === -1
}).length
}

+ 13
- 0
src/core/plugins/auth/spec-wrap-actions.js Visa fil

@@ -0,0 +1,13 @@
import { Map } from "immutable"

// Add security to the final `execute` call ( via `extras` )
export const execute = ( oriAction, { authSelectors, specSelectors }) => ({ path, method, operation, extras }) => {
let securities = {
authorized: authSelectors.authorized() && authSelectors.authorized().toJS(),
definitions: specSelectors.securityDefinitions() && specSelectors.securityDefinitions().toJS(),
specSecurity: specSelectors.security() && specSelectors.security().toJS()
}

return oriAction({ path, method, operation, securities, ...extras })
}


+ 67
- 0
src/core/plugins/download-url.js Visa fil

@@ -0,0 +1,67 @@
/* global Promise */

import { createSelector } from "reselect"
import { Map } from "immutable"

export default function downloadUrlPlugin (toolbox) {
let { fn, Im } = toolbox

const actions = {
download: (url)=> ({ errActions, specSelectors, specActions }) => {
let { fetch } = fn
url = url || specSelectors.url()
specActions.updateLoadingStatus("loading")
fetch(url, {
headers: {
"Accept": "application/json"
}
}).then(next,next)

function next(res) {
if(res instanceof Error || res.status >= 400) {
specActions.updateLoadingStatus("failed")
return errActions.newThrownErr( new Error(res.statusText + " " + url) )
}
specActions.updateLoadingStatus("success")
specActions.updateSpec(res.text)
specActions.updateUrl(url)
}

},

updateLoadingStatus: (status) => {
let enums = [null, "loading", "failed", "success"]
if(enums.indexOf(status) === -1) {
console.error(`Error: ${status} is not one of ${JSON.stringify(enums)}`)
}

return {
type: "spec_update_loading_status",
payload: status
}
}
}

let reducers = {
"spec_update_loading_status": (state, action) => {
return (typeof action.payload === "string")
? state.set("loadingStatus", action.payload)
: state
}
}

let selectors = {
loadingStatus: createSelector(
state => {
return state || Map()
},
spec => spec.get("loadingStatus") || null
)
}

return {
statePlugins: {
spec: { actions, reducers, selectors }
}
}
}

+ 43
- 0
src/core/plugins/err/actions.js Visa fil

@@ -0,0 +1,43 @@
import serializeError from "serialize-error"

export const NEW_THROWN_ERR = "err_new_thrown_err"
export const NEW_THROWN_ERR_BATCH = "err_new_thrown_err_batch"
export const NEW_SPEC_ERR = "err_new_spec_err"
export const NEW_AUTH_ERR = "err_new_auth_err"
export const CLEAR = "err_clear"

export function newThrownErr(err, action) {
return {
type: NEW_THROWN_ERR,
payload: { action, error: serializeError(err) }
}
}

export function newThrownErrBatch(errors) {
return {
type: NEW_THROWN_ERR_BATCH,
payload: errors
}
}

export function newSpecErr(err, action) {
return {
type: NEW_SPEC_ERR,
payload: err
}
}

export function newAuthErr(err, action) {
return {
type: NEW_AUTH_ERR,
payload: err
}
}

export function clear(filter = {}) {
// filter looks like: {type: 'spec'}, {source: 'parser'}
return {
type: CLEAR,
payload: filter
}
}

+ 31
- 0
src/core/plugins/err/error-transformers/README.md Visa fil

@@ -0,0 +1,31 @@
# Error transformers

Error transformers provide a standard interface for making generated error messages more useful to end users.

### Inputs & outputs

Each transformer's `transform` function is given an Immutable List of Immutable Maps as its first argument.

It is expected that each `transform` function returns a List of similarly-formed Maps.

These errors originate from the Redux error actions that add errors to state. Errors are transformed before being passed into the reducer.

It's important that all the keys present in each error (specifically, `line`, `level`, `message`, `source`, and `type`) are present when the transformer is finished.

##### Deleting an error

If you want to delete an error completely, you can overwrite it with `null`. The null value will be filtered out of the transformed error array before the errors are returned.
å

### Example transformer

This transformer will increase all your line numbers by 10.

```
export function transform(errors) {
return errors.map(err => {
err.line += 10
return err
})
}
```

+ 57
- 0
src/core/plugins/err/error-transformers/hook.js Visa fil

@@ -0,0 +1,57 @@
import concat from "lodash/concat"
import reduce from "lodash/reduce"
let request = require.context("./transformers/", true, /\.js$/)
let errorTransformers = []

request.keys().forEach( function( key ){
if( key === "./hook.js" ) {
return
}

if( !key.match(/js$/) ) {
return
}

if( key.slice(2).indexOf("/") > -1) {
// skip files in subdirs
return
}

errorTransformers.push({
name: toTitleCase(key).replace(".js", "").replace("./", ""),
transform: request(key).transform
})
})

export default function transformErrors (errors, system) {
let inputs = {
jsSpec: system.specSelectors.specJson().toJS()
}

let transformedErrors = reduce(errorTransformers, (result, transformer) => {
try {
let newlyTransformedErrors = transformer.transform(result, inputs)
return newlyTransformedErrors.filter(err => !!err) // filter removed errors
} catch(e) {
console.error("Transformer error:", e)
return result
}
}, errors)

return transformedErrors
.filter(err => !!err) // filter removed errors
.map(err => {
if(!err.get("line") && err.get("path")) {
// TODO: re-resolve line number if we've transformed it away
}
return err
})

}

function toTitleCase(str) {
return str
.split("-")
.map(substr => substr[0].toUpperCase() + substr.slice(1))
.join("")
}

+ 29
- 0
src/core/plugins/err/error-transformers/transformers/not-of-type.js Visa fil

@@ -0,0 +1,29 @@
export function transform(errors) {
// JSONSchema refers to the current object being validated
// as 'instance'. This isn't helpful to users, so we remove it.
return errors
.map(err => {
let seekStr = "is not of a type(s)"
let i = err.get("message").indexOf(seekStr)
if(i > -1) {
let types = err.get("message").slice(i + seekStr.length).split(",")
return err.set("message", err.get("message").slice(0, i) + makeNewMessage(types))
} else {
return err
}
})
}

function makeNewMessage(types) {
return types.reduce((p, c, i, arr) => {
if(i === arr.length - 1 && arr.length > 1) {
return p + "or " + c
} else if(arr[i+1] && arr.length > 2) {
return p + c + ", "
} else if(arr[i+1]) {
return p + c + " "
} else {
return p + c
}
}, "should be a")
}

+ 60
- 0
src/core/plugins/err/error-transformers/transformers/parameter-oneof.js Visa fil

@@ -0,0 +1,60 @@
import get from "lodash/get"
import last from "lodash/get"
import { fromJS, List } from "immutable"

export function transform(errors, { jsSpec }) {
// LOOK HERE THIS TRANSFORMER IS CURRENTLY DISABLED 😃
// TODO: finish implementing, fix flattening problem
/* eslint-disable no-unreachable */
return errors


// JSONSchema gives us very little to go on
let searchStr = "is not exactly one from <#/definitions/parameter>,<#/definitions/jsonReference>"
return errors
.map(err => {
let message = err.get("message")
let isParameterOneOfError = message.indexOf(searchStr) > -1
if(isParameterOneOfError) {
// try to find what's wrong
return createTailoredParameterError(err, jsSpec)
} else {
return err
}
})
.flatten(true) // shallow Immutable flatten
}

const VALID_IN_VALUES = ["path", "query", "header", "body", "formData"]
const VALID_COLLECTIONFORMAT_VALUES = ["csv", "ssv", "tsv", "pipes", "multi"]

function createTailoredParameterError(err, jsSpec) {
let newErrs = []
let parameter = get(jsSpec, err.get("path"))

// find addressable cases
if(parameter.in && VALID_IN_VALUES.indexOf(parameter.in) === -1) {
let message = `Wrong value for the "in" keyword. Expected one of: ${VALID_IN_VALUES.join(", ")}.`
newErrs.push({
message,
path: err.get("path") + ".in",
type: "spec",
source: "schema",
level: "error"
})
}

if(parameter.collectionFormat && VALID_COLLECTIONFORMAT_VALUES.indexOf(parameter.collectionFormat) === -1) {
let message = `Wrong value for the "collectionFormat" keyword. Expected one of: ${VALID_COLLECTIONFORMAT_VALUES.join(", ")}.`
newErrs.push({
message,
path: err.get("path") + ".collectionFormat",
type: "spec",
source: "schema",
level: "error"
})
}

return newErrs.length ? fromJS(newErrs) : err // fall back to making no changes

}

+ 10
- 0
src/core/plugins/err/error-transformers/transformers/strip-instance.js Visa fil

@@ -0,0 +1,10 @@
export function transform(errors) {
return errors
.map(err => {
return err.set("message", removeSubstring(err.get("message"), "instance."))
})
}

function removeSubstring(str, substr) {
return str.replace(new RegExp(substr, "g"), "")
}

+ 15
- 0
src/core/plugins/err/index.js Visa fil

@@ -0,0 +1,15 @@
import makeReducers from "./reducers"
import * as actions from "./actions"
import * as selectors from "./selectors"

export default function(system) {
return {
statePlugins: {
err: {
reducers: makeReducers(system),
actions,
selectors
}
}
}
}

+ 68
- 0
src/core/plugins/err/reducers.js Visa fil

@@ -0,0 +1,68 @@
import {
NEW_THROWN_ERR,
NEW_THROWN_ERR_BATCH,
NEW_SPEC_ERR,
NEW_AUTH_ERR,
CLEAR
} from "./actions"

import reject from "lodash/reject"

import Im, { fromJS, List } from "immutable"

import transformErrors from "./error-transformers/hook"

let DEFAULT_ERROR_STRUCTURE = {
// defaults
line: 0,
level: "error",
message: "Unknown error"
}

export default function(system) {
return {
[NEW_THROWN_ERR]: (state, { payload }) => {
let error = Object.assign(DEFAULT_ERROR_STRUCTURE, payload, {type: "thrown"})
return state
.update("errors", errors => (errors || List()).push( fromJS( error )) )
.update("errors", errors => transformErrors(errors, system.getSystem()))
},

[NEW_THROWN_ERR_BATCH]: (state, { payload }) => {
payload = payload.map(err => {
return fromJS(Object.assign(DEFAULT_ERROR_STRUCTURE, err, { type: "thrown" }))
})
return state
.update("errors", errors => (errors || List()).concat( fromJS( payload )) )
.update("errors", errors => transformErrors(errors, system.getSystem()))
},

[NEW_SPEC_ERR]: (state, { payload }) => {
let error = fromJS(payload)
error = error.set("type", "spec")
return state
.update("errors", errors => (errors || List()).push( fromJS(error)).sortBy(err => err.get("line")) )
.update("errors", errors => transformErrors(errors, system.getSystem()))
},

[NEW_AUTH_ERR]: (state, { payload }) => {
let error = fromJS(Object.assign({}, payload))

error = error.set("type", "auth")
return state
.update("errors", errors => (errors || List()).push( fromJS(error)) )
.update("errors", errors => transformErrors(errors, system.getSystem()))
},

[CLEAR]: (state, { payload }) => {
if(!payload) {
return
}
// TODO: Rework, to use immutable only, no need for lodash
let newErrors = Im.fromJS(reject((state.get("errors") || List()).toJS(), payload))
return state.merge({
errors: newErrors
})
}
}
}

+ 15
- 0
src/core/plugins/err/selectors.js Visa fil

@@ -0,0 +1,15 @@
import { List } from "immutable"
import { createSelector } from "reselect"

const state = state => state

export const allErrors = createSelector(
state,
err => err.get("errors", List())
)

export const lastError = createSelector(
allErrors,
all => all.last()
)


+ 45
- 0
src/core/plugins/layout/actions.js Visa fil

@@ -0,0 +1,45 @@
import { normalizeArray } from "core/utils"

export const UPDATE_LAYOUT = "layout_update_layout"
export const UPDATE_MODE = "layout_update_mode"
export const SHOW = "layout_show"

// export const ONLY_SHOW = "layout_only_show"

export function updateLayout(layout) {
return {
type: UPDATE_LAYOUT,
payload: layout
}
}

export function show(thing, shown=true) {
thing = normalizeArray(thing)
return {
type: SHOW,
payload: {thing, shown}
}
}

// Simple string key-store, used for
export function changeMode(thing, mode="") {
thing = normalizeArray(thing)
return {
type: UPDATE_MODE,
payload: {thing, mode}
}
}


// export function onlyShow(thing, shown=true) {
// thing = normalizeArray(thing)
// if(thing.length < 2)
// throw new Error("layoutActions.onlyShow only works, when `thing` is an array with length > 1")
// return {
// type: ONLY_SHOW,
// payload: {thing, shown}
// }
// }




+ 15
- 0
src/core/plugins/layout/index.js Visa fil

@@ -0,0 +1,15 @@
import reducers from "./reducers"
import * as actions from "./actions"
import * as selectors from "./selectors"

export default function() {
return {
statePlugins: {
layout: {
reducers,
actions,
selectors
}
}
}
}

+ 24
- 0
src/core/plugins/layout/reducers.js Visa fil

@@ -0,0 +1,24 @@
import {
UPDATE_LAYOUT,
UPDATE_MODE,
SHOW
} from "./actions"

export default {

[UPDATE_LAYOUT]: (state, action) => state.set("layout", action.payload),

[SHOW]: (state, action) => {
let thing = action.payload.thing
let shown = action.payload.shown
return state.setIn(["shown"].concat(thing), shown)
},

[UPDATE_MODE]: (state, action) => {
let thing = action.payload.thing
let mode = action.payload.mode
return state.setIn(["modes"].concat(thing), (mode || "") + "")
}

}


+ 22
- 0
src/core/plugins/layout/selectors.js Visa fil

@@ -0,0 +1,22 @@
import { createSelector } from "reselect"
import { normalizeArray } from "core/utils"

const state = state => state

export const current = state => state.get("layout")

export const isShown = (state, thing, def) => {
thing = normalizeArray(thing)
return Boolean(state.getIn(["shown", ...thing], def))
}

export const whatMode = (state, thing, def="") => {
thing = normalizeArray(thing)
return state.getIn(["modes", ...thing], def)
}

export const showSummary = createSelector(
state,
state => !isShown(state, "editor")
)


+ 28
- 0
src/core/plugins/logs/index.js Visa fil

@@ -0,0 +1,28 @@
export default function ({configs}) {

const levels = {
"debug": 0,
"info": 1,
"log": 2,
"warn": 3,
"error": 4
}

const getLevel = (level) => levels[level] || -1

let { logLevel } = configs
let logLevelInt = getLevel(logLevel)

function log(level, ...args) {
if(getLevel(level) >= logLevelInt)
// eslint-disable-next-line no-console
console[level](...args)
}

log.warn = log.bind(null, "warn")
log.error = log.bind(null, "error")
log.info = log.bind(null, "info")
log.debug = log.bind(null, "debug")

return { rootInjects: { log } }
}

+ 223
- 0
src/core/plugins/samples/fn.js Visa fil

@@ -0,0 +1,223 @@
import { objectify, isFunc, normalizeArray } from "core/utils"
import XML from "xml"
import memoizee from "memoizee"

const primitives = {
"string": () => "string",
"string_email": () => "user@example.com",
"string_date-time": () => new Date().toISOString(),
"number": () => 0,
"number_float": () => 0.0,
"integer": () => 0,
"boolean": () => true
}

const primitive = (schema) => {
schema = objectify(schema)
let { type, format } = schema

let fn = primitives[`${type}_${format}`] || primitives[type]

if(isFunc(fn))
return fn(schema)

return "Unknown Type: " + schema.type
}


export const sampleFromSchema = (schema, config={}) => {
let { type, example, properties, additionalProperties, items } = objectify(schema)
let { includeReadOnly } = config

if(example !== undefined)
return example

if(!type) {
if(properties) {
type = "object"
} else if(items) {
type = "array"
} else {
return
}
}

if(type === "object") {
let props = objectify(properties)
let obj = {}
for (var name in props) {
if ( !props[name].readOnly || includeReadOnly ) {
obj[name] = sampleFromSchema(props[name])
}
}

if ( additionalProperties === true ) {
obj.additionalProp1 = {}
} else if ( additionalProperties ) {
let additionalProps = objectify(additionalProperties)
let additionalPropVal = sampleFromSchema(additionalProps)

for (let i = 1; i < 4; i++) {
obj["additionalProp" + i] = additionalPropVal
}
}
return obj
}

if(type === "array") {
return [ sampleFromSchema(items) ]
}

if(schema["enum"]) {
if(schema["default"])
return schema["default"]
return normalizeArray(schema["enum"])[0]
}

return primitive(schema)
}

export const inferSchema = (thing) => {
if(thing.schema)
thing = thing.schema

if(thing.properties) {
thing.type = "object"
}

return thing // Hopefully this will have something schema like in it... `type` for example
}


export const sampleXmlFromSchema = (schema, config={}) => {
let objectifySchema = objectify(schema)
let { type, properties, additionalProperties, items, example } = objectifySchema
let { includeReadOnly } = config
let defaultValue = objectifySchema.default
let res = {}
let _attr = {}
let { xml } = schema
let { name, prefix, namespace } = xml
let enumValue = objectifySchema.enum
let displayName, value

if(!type) {
if(properties || additionalProperties) {
type = "object"
} else if(items) {
type = "array"
} else {
return
}
}

name = name || "notagname"
// add prefix to name if exists
displayName = (prefix ? prefix + ":" : "") + name
if ( namespace ) {
//add prefix to namespace if exists
let namespacePrefix = prefix ? ( "xmlns:" + prefix ) : "xmlns"
_attr[namespacePrefix] = namespace
}

if (type === "array") {
if (items) {
items.xml = items.xml || xml || {}
items.xml.name = items.xml.name || xml.name

if (xml.wrapped) {
res[displayName] = []
if (Array.isArray(defaultValue)) {

defaultValue.forEach((v)=>{
items.default = v
res[displayName].push(sampleXmlFromSchema(items, config))
})
} else {
res[displayName] = [sampleXmlFromSchema(items, config)]
}

if (_attr) {
res[displayName].push({_attr: _attr})
}
return res
}

let _res = []

if (Array.isArray(defaultValue)) {
defaultValue.forEach((v)=>{
items.default = v
_res.push(sampleXmlFromSchema(items, config))
})
return _res

}
return sampleXmlFromSchema(items, config)
}
}

if (type === "object") {
let props = objectify(properties)
res[displayName] = []
example = example || {}

for (let propName in props) {
if ( !props[propName].readOnly || includeReadOnly ) {
props[propName].xml = props[propName].xml || {}

if (props[propName].xml.attribute) {
let enumAttrVal = Array.isArray(props[propName].enum) && props[propName].enum[0]
let attrExample = props[propName].example
let attrDefault = props[propName].default
_attr[props[propName].xml.name || propName] = attrExample!== undefined && attrExample
|| example[propName] !== undefined && example[propName] || attrDefault !== undefined && attrDefault
|| enumAttrVal || primitive(props[propName])
} else {
props[propName].xml.name = props[propName].xml.name || propName
props[propName].example = props[propName].example !== undefined ? props[propName].example : example[propName]
res[displayName].push(sampleXmlFromSchema(props[propName]))
}
}
}

if (additionalProperties === true) {
res[displayName].push({additionalProp: "Anything can be here"})
} else if (additionalProperties) {
res[displayName].push({additionalProp: primitive(additionalProperties)})
}

if (_attr) {
res[displayName].push({_attr: _attr})
}
return res
}

if (example !== undefined) {
value = example
} else if (defaultValue !== undefined) {
//display example if exists
value = defaultValue
} else if (Array.isArray(enumValue)) {
//display enum first value
value = enumValue[0]
} else {
//set default value
value = primitive(schema)
}

res[displayName] = _attr ? [{_attr: _attr}, value] : value

return res
}

export function createXMLExample(schema, config) {
let json = sampleXmlFromSchema(schema, config)
if (!json) { return }

return XML(json, { declaration: true, indent: "\t" })
}

export const memoizedCreateXMLExample = memoizee(createXMLExample)

export const memoizedSampleFromSchema = memoizee(sampleFromSchema)

+ 5
- 0
src/core/plugins/samples/index.js Visa fil

@@ -0,0 +1,5 @@
import * as fn from "./fn"

export default function () {
return { fn }
}

+ 234
- 0
src/core/plugins/spec/actions.js Visa fil

@@ -0,0 +1,234 @@
import YAML from "js-yaml"
import serializeError from "serialize-error"

// Actions conform to FSA (flux-standard-actions)
// {type: string,payload: Any|Error, meta: obj, error: bool}

export const UPDATE_SPEC = "spec_update_spec"
export const UPDATE_URL = "spec_update_url"
export const UPDATE_JSON = "spec_update_json"
export const UPDATE_PARAM = "spec_update_param"
export const VALIDATE_PARAMS = "spec_validate_param"
export const SET_RESPONSE = "spec_set_response"
export const SET_REQUEST = "spec_set_request"
export const LOG_REQUEST = "spec_log_request"
export const CLEAR_RESPONSE = "spec_clear_response"
export const CLEAR_REQUEST = "spec_clear_request"
export const ClEAR_VALIDATE_PARAMS = "spec_clear_validate_param"
export const UPDATE_OPERATION_VALUE = "spec_update_operation_value"
export const UPDATE_RESOLVED = "spec_update_resolved"
export const SET_SCHEME = "set_scheme"

export function updateSpec(spec) {
if(spec instanceof Error) {
return {type: UPDATE_SPEC, error: true, payload: spec}
}

if(typeof spec === "string") {
return {
type: UPDATE_SPEC,
payload: spec.replace(/\t/g, " ") || ""
}
}

return {
type: UPDATE_SPEC,
payload: ""
}
}

export function updateResolved(spec) {
return {
type: UPDATE_RESOLVED,
payload: spec
}
}

export function updateUrl(url) {
return {type: UPDATE_URL, payload: url}
}

export function updateJsonSpec(json) {
if(!json || typeof json !== "object") {
throw new Error("updateJson must only accept a simple JSON object")
}
return {type: UPDATE_JSON, payload: json}
}

export const parseToJson = (str) => ({specActions, specSelectors, errActions}) => {
let { specStr } = specSelectors

let json = null
try {
str = str || specStr()
errActions.clear({ source: "parser" })
json = YAML.safeLoad(str)
} catch(e) {
// TODO: push error to state
console.error(e)
return errActions.newSpecErr({
source: "parser",
level: "error",
message: e.reason,
line: e.mark && e.mark.line ? e.mark.line + 1 : undefined
})
}
return specActions.updateJsonSpec(json)
}

export const resolveSpec = (json, url) => ({specActions, specSelectors, errActions, fn: { fetch, resolve, AST }}) => {
if(typeof(json) === "undefined") {
json = specSelectors.specJson()
}
if(typeof(url) === "undefined") {
url = specSelectors.url()
}

let { getLineNumberForPath } = AST

let specStr = specSelectors.specStr()

return resolve({fetch, spec: json, baseDoc: url})
.then( ({spec, errors}) => {
errActions.clear({
type: "thrown"
})

if(errors.length > 0) {
let preparedErrors = errors
.map(err => {
console.error(err)
err.line = err.fullPath ? getLineNumberForPath(specStr, err.fullPath) : null
err.path = err.fullPath ? err.fullPath.join(".") : null
err.level = "error"
err.type = "thrown"
err.source = "resolver"
Object.defineProperty(err, "message", { enumerable: true, value: err.message })
return err
})
errActions.newThrownErrBatch(preparedErrors)
}

return specActions.updateResolved(spec)
})
}

export const formatIntoYaml = () => ({specActions, specSelectors}) => {
let { specStr } = specSelectors
let { updateSpec } = specActions

try {
let yaml = YAML.safeDump(YAML.safeLoad(specStr()), {indent: 2})
updateSpec(yaml)
} catch(e) {
updateSpec(e)
}
}

export function changeParam( path, paramName, value, isXml ){
return {
type: UPDATE_PARAM,
payload:{ path, value, paramName, isXml }
}
}

export function validateParams( payload ){
return {
type: VALIDATE_PARAMS,
payload:{ pathMethod: payload }
}
}

export function clearValidateParams( payload ){
return {
type: ClEAR_VALIDATE_PARAMS,
payload:{ pathMethod: payload }
}
}

export function changeConsumesValue(path, value) {
return {
type: UPDATE_OPERATION_VALUE,
payload:{ path, value, key: "consumes_value" }
}
}

export function changeProducesValue(path, value) {
return {
type: UPDATE_OPERATION_VALUE,
payload:{ path, value, key: "produces_value" }
}
}

export const setResponse = ( path, method, res ) => {
return {
payload: { path, method, res },
type: SET_RESPONSE
}
}

export const setRequest = ( path, method, req ) => {
return {
payload: { path, method, req },
type: SET_REQUEST
}
}

// This is for debugging, remove this comment if you depend on this action
export const logRequest = (req) => {
return {
payload: req,
type: LOG_REQUEST
}
}

// Actually fire the request via fn.execute
// (For debugging) and ease of testing
export const executeRequest = (req) => ({fn, specActions, errActions}) => {
let { pathName, method } = req
let parsedRequest = Object.assign({}, req)
if ( pathName && method ) {
parsedRequest.operationId = method.toLowerCase() + "-" + pathName
}
parsedRequest = fn.buildRequest(parsedRequest)
specActions.setRequest(req.pathName, req.method, parsedRequest)

return fn.execute(req)
.then( fn.serializeRes )
.then( res => specActions.setResponse(req.pathName, req.method, res))
.catch( err => specActions.setResponse(req.pathName, req.method, { error: true, err: serializeError(err) } ) )
}


// I'm using extras as a way to inject properties into the final, `execute` method - It's not great. Anyone have a better idea? @ponelat
export const execute = ( { path, method, ...extras }={} ) => (system) => {
let { fn:{fetch}, specSelectors, specActions } = system
let spec = specSelectors.spec().toJS()
let scheme = specSelectors.operationScheme(path, method)
let { requestContentType, responseContentType } = specSelectors.contentTypeValues([path, method]).toJS()
let isXml = /xml/i.test(requestContentType)
let parameters = specSelectors.parameterValues([path, method], isXml).toJS()

return specActions.executeRequest({fetch, spec, pathName: path, method, parameters, requestContentType, scheme, responseContentType, ...extras })
}

export function clearResponse (path, method) {
return {
type: CLEAR_RESPONSE,
payload:{ path, method }
}
}

export function clearRequest (path, method) {
return {
type: CLEAR_REQUEST,
payload:{ path, method }
}
}

export function setScheme (scheme, path, method) {
return {
type: SET_SCHEME,
payload: { scheme, path, method }
}
}

+ 17
- 0
src/core/plugins/spec/index.js Visa fil

@@ -0,0 +1,17 @@
import reducers from "./reducers"
import * as actions from "./actions"
import * as selectors from "./selectors"
import * as wrapActions from "./wrap-actions"

export default function() {
return {
statePlugins: {
spec: {
wrapActions,
reducers,
actions,
selectors
}
}
}
}

+ 123
- 0
src/core/plugins/spec/reducers.js Visa fil

@@ -0,0 +1,123 @@
import { fromJS } from "immutable"
import { fromJSOrdered, validateParam } from "core/utils"
import win from "../../window"

import {
UPDATE_SPEC,
UPDATE_URL,
UPDATE_JSON,
UPDATE_PARAM,
VALIDATE_PARAMS,
SET_RESPONSE,
SET_REQUEST,
UPDATE_RESOLVED,
UPDATE_OPERATION_VALUE,
CLEAR_RESPONSE,
CLEAR_REQUEST,
ClEAR_VALIDATE_PARAMS,
SET_SCHEME
} from "./actions"

export default {

[UPDATE_SPEC]: (state, action) => {
return (typeof action.payload === "string")
? state.set("spec", action.payload)
: state
},

[UPDATE_URL]: (state, action) => {
return state.set("url", action.payload+"")
},

[UPDATE_JSON]: (state, action) => {
return state.set("json", fromJSOrdered(action.payload))
},

[UPDATE_RESOLVED]: (state, action) => {
return state.setIn(["resolved"], fromJSOrdered(action.payload))
},

[UPDATE_PARAM]: ( state, {payload} ) => {
let { path, paramName, value, isXml } = payload
return state.updateIn( [ "resolved", "paths", ...path, "parameters" ], fromJS([]), parameters => {
let index = parameters.findIndex( p => p.get( "name" ) === paramName )
if (!(value instanceof win.File)) {
value = fromJSOrdered( value )
}
return parameters.setIn( [ index, isXml ? "value_xml" : "value" ], value)
})
},

[VALIDATE_PARAMS]: ( state, { payload: { pathMethod } } ) => {
let operation = state.getIn( [ "resolved", "paths", ...pathMethod ] )
let parameters = operation.get("parameters")
let isXml = /xml/i.test(operation.get("consumes_value"))

return state.updateIn( [ "resolved", "paths", ...pathMethod, "parameters" ], fromJS([]), parameters => {
return parameters.withMutations( parameters => {
for ( let i = 0, len = parameters.count(); i < len; i++ ) {
let errors = validateParam(parameters.get(i), isXml)
parameters.setIn([i, "errors"], fromJS(errors))
}
})
})
},
[ClEAR_VALIDATE_PARAMS]: ( state, { payload: { pathMethod } } ) => {
let operation = state.getIn( [ "resolved", "paths", ...pathMethod ] )
let parameters = operation.get("parameters")

return state.updateIn( [ "resolved", "paths", ...pathMethod, "parameters" ], fromJS([]), parameters => {
return parameters.withMutations( parameters => {
for ( let i = 0, len = parameters.count(); i < len; i++ ) {
parameters.setIn([i, "errors"], fromJS({}))
}
})
})
},

[SET_RESPONSE]: (state, { payload: { res, path, method } } ) =>{
let result
if ( res.error ) {
result = Object.assign({error: true}, res.err)
} else {
result = res
}

let newState = state.setIn( [ "responses", path, method ], fromJSOrdered(result) )

// ImmutableJS messes up Blob. Needs to reset its value.
if (res.data instanceof win.Blob) {
newState = newState.setIn( [ "responses", path, method, "text" ], res.data)
}
return newState
},

[SET_REQUEST]: (state, { payload: { req, path, method } } ) =>{
return state.setIn( [ "requests", path, method ], fromJSOrdered(req))
},

[UPDATE_OPERATION_VALUE]: (state, { payload: { path, value, key } }) => {
return state.setIn(["resolved", "paths", ...path, key], fromJS(value))
},

[CLEAR_RESPONSE]: (state, { payload: { path, method } } ) =>{
return state.deleteIn( [ "responses", path, method ])
},

[CLEAR_REQUEST]: (state, { payload: { path, method } } ) =>{
return state.deleteIn( [ "requests", path, method ])
},

[SET_SCHEME]: (state, { payload: { scheme, path, method } } ) =>{
if ( path && method ) {
return state.setIn( [ "scheme", path, method ], scheme)
}

if (!path && !method) {
return state.setIn( [ "scheme", "_defaultScheme" ], scheme)
}

}

}

+ 318
- 0
src/core/plugins/spec/selectors.js Visa fil

@@ -0,0 +1,318 @@
import { createSelector } from "reselect"
import { fromJS, Set, Map, List } from "immutable"

const DEFAULT_TAG = "default"

const OPERATION_METHODS = ["get", "put", "post", "delete", "options", "head", "patch"]

const state = state => {
return state || Map()
}

export const lastError = createSelector(
state,
spec => spec.get("lastError")
)

export const url = createSelector(
state,
spec => spec.get("url")
)

export const specStr = createSelector(
state,
spec => spec.get("spec") || ""
)

export const specSource = createSelector(
state,
spec => spec.get("specSource") || "not-editor"
)

export const specJson = createSelector(
state,
spec => spec.get("json", Map())
)

export const specResolved = createSelector(
state,
spec => spec.get("resolved", Map())
)

// Default Spec ( as an object )
export const spec = state => {
let res = specResolved(state)
if(res.count() < 1)
res = specJson(state)
return res
}

export const info = createSelector(
spec,
spec => returnSelfOrNewMap(spec && spec.get("info"))
)

export const externalDocs = createSelector(
spec,
spec => returnSelfOrNewMap(spec && spec.get("externalDocs"))
)

export const version = createSelector(
info,
info => info && info.get("version")
)

export const semver = createSelector(
version,
version => /v?([0-9]*)\.([0-9]*)\.([0-9]*)/i.exec(version).slice(1)
)

export const paths = createSelector(
spec,
spec => spec.get("paths")
)

export const operations = createSelector(
paths,
paths => {
if(!paths || paths.size < 1)
return List()

let list = List()

if(!paths || !paths.forEach) {
return List()
}

paths.forEach((path, pathName) => {
if(!path || !path.forEach) {
return {}
}
path.forEach((operation, method) => {
if(OPERATION_METHODS.indexOf(method) === -1) {
return
}
list = list.push(fromJS({
path: pathName,
method,
operation,
id: `${method}-${pathName}`
}))
})
})

return list
}
)

export const consumes = createSelector(
spec,
spec => Set(spec.get("consumes"))
)

export const produces = createSelector(
spec,
spec => Set(spec.get("produces"))
)

export const security = createSelector(
spec,
spec => spec.get("security", List())
)

export const securityDefinitions = createSelector(
spec,
spec => spec.get("securityDefinitions")
)


export const findDefinition = ( state, name ) => {
return specResolved(state).getIn(["definitions", name], null)
}

export const definitions = createSelector(
spec,
spec => spec.get("definitions") || Map()
)

export const basePath = createSelector(
spec,
spec => spec.get("basePath")
)

export const host = createSelector(
spec,
spec => spec.get("host")
)

export const schemes = createSelector(
spec,
spec => spec.get("schemes", Map())
)

export const operationsWithRootInherited = createSelector(
operations,
consumes,
produces,
(operations, consumes, produces) => {
return operations.map( ops => ops.update("operation", op => {
if(op) {
if(!Map.isMap(op)) { return }
return op.withMutations( op => {
if ( !op.get("consumes") ) {
op.update("consumes", a => Set(a).merge(consumes))
}
if ( !op.get("produces") ) {
op.update("produces", a => Set(a).merge(produces))
}
return op
})
} else {
// return something with Immutable methods
return Map()
}

}))
}
)

export const tags = createSelector(
spec,
json => json.get("tags", List())
)

export const tagDetails = (state, tag) => {
let currentTags = tags(state) || List()
return currentTags.filter(Map.isMap).find(t => t.get("name") === tag, Map())
}

export const operationsWithTags = createSelector(
operationsWithRootInherited,
operations => {
return operations.reduce( (taggedMap, op) => {
let tags = Set(op.getIn(["operation","tags"]))
if(tags.count() < 1)
return taggedMap.update(DEFAULT_TAG, List(), ar => ar.push(op))
return tags.reduce( (res, tag) => res.update(tag, List(), (ar) => ar.push(op)), taggedMap )
}, Map())
}
)

export const taggedOperations = createSelector(
state,
operationsWithTags,
(state, tagMap) => {
return tagMap.map((ops, tag) => Map({tagDetails: tagDetails(state, tag), operations: ops}))
}
)

export const responses = createSelector(
state,
state => state.get( "responses", Map() )
)

export const requests = createSelector(
state,
state => state.get( "requests", Map() )
)

export const responseFor = (state, path, method) => {
return responses(state).getIn([path, method], null)
}

export const requestFor = (state, path, method) => {
return requests(state).getIn([path, method], null)
}

export const allowTryItOutFor = (state, path, method ) => {
// This is just a hook for now.
return true
}

// Get the parameter value by parameter name
export function getParameter(state, pathMethod, name) {
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([]))
return params.filter( (p) => {
return Map.isMap(p) && p.get("name") === name
}).first()
}

export const hasHost = createSelector(
spec,
spec => {
const host = spec.get("host")
return typeof host === "string" && host.length > 0 && host[0] !== "/"
}
)

// Get the parameter values, that the user filled out
export function parameterValues(state, pathMethod, isXml) {
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([]))
return params.reduce( (hash, p) => {
let value = isXml && p.get("in") === "body" ? p.get("value_xml") : p.get("value")
return hash.set(p.get("name"), value)
}, fromJS({}))
}

// True if any parameter includes `in: ?`
export function parametersIncludeIn(parameters, inValue="") {
if(List.isList(parameters)) {
return parameters.some( p => Map.isMap(p) && p.get("in") === inValue )
}
}

// True if any parameter includes `type: ?`
export function parametersIncludeType(parameters, typeValue="") {
if(List.isList(parameters)) {
return parameters.some( p => Map.isMap(p) && p.get("type") === typeValue )
}
}

// Get the consumes/produces value that the user selected
export function contentTypeValues(state, pathMethod) {
let op = spec(state).getIn(["paths", ...pathMethod], fromJS({}))
const parameters = op.get("parameters") || new List()
const requestContentType = (
parametersIncludeType(parameters, "file") ? "multipart/form-data"
: parametersIncludeIn(parameters, "formData") ? "application/x-www-form-urlencoded"
: op.get("consumes_value")
)


return fromJS({
requestContentType,
responseContentType: op.get("produces_value")
})
}

// Get the consumes/produces by path
export function operationConsumes(state, pathMethod) {
return spec(state).getIn(["paths", ...pathMethod, "consumes"], fromJS({}))
}

export const operationScheme = ( state, path, method ) => {
return state.getIn(["scheme", path, method]) || state.getIn(["scheme", "_defaultScheme"]) || "http"
}

export const canExecuteScheme = ( state, path, method ) => {
return ["http", "https"].indexOf(operationScheme(state, path, method)) > -1
}

export const validateBeforeExecute = ( state, pathMethod ) => {
let params = spec(state).getIn(["paths", ...pathMethod, "parameters"], fromJS([]))
let isValid = true

params.forEach( (p) => {
let errors = p.get("errors")
if ( errors && errors.count() ) {
isValid = false
}
})

return isValid
}

function returnSelfOrNewMap(obj) {
// returns obj if obj is an Immutable map, else returns a new Map
return Map.isMap(obj) ? obj : new Map()
}

+ 15
- 0
src/core/plugins/spec/wrap-actions.js Visa fil

@@ -0,0 +1,15 @@
export const updateSpec = (ori, {specActions}) => (...args) => {
ori(...args)
specActions.parseToJson(...args)
}

export const updateJsonSpec = (ori, {specActions}) => (...args) => {
ori(...args)
specActions.resolveSpec(...args)
}

// Log the request ( just for debugging, shouldn't affect prod )
export const executeRequest = (ori, { specActions }) => (req) => {
specActions.logRequest(req)
return ori(req)
}

+ 17
- 0
src/core/plugins/split-pane-mode/components/index.js Visa fil

@@ -0,0 +1,17 @@
import { pascalCaseFilename } from "core/utils"

const request = require.context(".", true, /\.jsx?$/)

request.keys().forEach( function( key ){
if( key === "./index.js" ) {
return
}

// if( key.slice(2).indexOf("/") > -1) {
// // skip files in subdirs
// return
// }

let mod = request(key)
module.exports[pascalCaseFilename(key)] = mod.default ? mod.default : mod
})

+ 81
- 0
src/core/plugins/split-pane-mode/components/split-pane-mode.jsx Visa fil

@@ -0,0 +1,81 @@
import React, { PropTypes } from "react"
import SplitPane from "react-split-pane"
import "./split-pane-mode.less"

const MODE_KEY = ["split-pane-mode"]
const MODE_LEFT = "left"
const MODE_RIGHT = "right"
const MODE_BOTH = "both" // or anything other than left/right

export default class SplitPaneMode extends React.Component {

static propTypes = {
threshold: PropTypes.number,

children: PropTypes.array,

layoutSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired,
};

static defaultProps = {
threshold: 100, // in pixels
children: [],
};

onDragFinished = () => {
let { threshold, layoutActions } = this.props
let { position, draggedSize } = this.refs.splitPane.state
this.draggedSize = draggedSize

let nearLeftEdge = position <= threshold
let nearRightEdge = draggedSize <= threshold

layoutActions
.changeMode(MODE_KEY, (
nearLeftEdge
? MODE_RIGHT : nearRightEdge
? MODE_LEFT : MODE_BOTH
))
}

sizeFromMode = (mode, defaultSize) => {
if(mode === MODE_LEFT) {
this.draggedSize = null
return "0px"
} else if (mode === MODE_RIGHT) {
this.draggedSize = null
return "100%"
}
// mode === "both"
return this.draggedSize || defaultSize
}

render() {
let { children, layoutSelectors } = this.props

const mode = layoutSelectors.whatMode(MODE_KEY)
const left = mode === MODE_RIGHT ? <noscript/> : children[0]
const right = mode === MODE_LEFT ? <noscript/> : children[1]
const size = this.sizeFromMode(mode, '50%')

return (
<SplitPane
disabledClass={''}
ref={'splitPane'}
split='vertical'
defaultSize={'50%'}
primary="second"
minSize={0}
size={size}
onDragFinished={this.onDragFinished}
allowResize={mode !== MODE_LEFT && mode !== MODE_RIGHT }
resizerStyle={{"flex": "0 0 auto", "position": "relative"}}
>
{ left }
{ right }
</SplitPane>
)
}

}

+ 5
- 0
src/core/plugins/split-pane-mode/components/split-pane-mode.less Visa fil

@@ -0,0 +1,5 @@
.swagger-ui {
.Resizer.vertical.disabled {
display: none;
}
}

Vissa filer visades inte eftersom för många filer har ändrats

Laddar…
Avbryt
Spara