UNPKG

rapidoc

Version:

RapiDoc - Open API spec viewer with built in console

377 lines (345 loc) 13.5 kB
import { LitElement, html, css} from 'lit-element'; import {unsafeHTML} from 'lit-html/directives/unsafe-html.js'; import MLogo from '@/components/m-logo'; import EndPoints from '@/components/end-points'; import SecuritySchemes from '@/components/security-schemes'; import FontStyles from '@/styles/font-styles'; import InputStyles from '@/styles/input-styles'; import FlexStyles from '@/styles/flex-styles'; import TableStyles from '@/styles/table-styles'; import vars from '@/styles/vars'; import ProcessSpec from '@/utils/parse-utils'; import marked from 'marked'; export default class RapiDoc extends LitElement { render() { return html` ${FontStyles} ${InputStyles} ${FlexStyles} ${TableStyles} ${this.theme==='dark'? html`<style> :host{ --bg:#333; --bg2:#444; --fg:#bbb; --fg2:#aaa; --light-fg:#777; --very-light-fg:#666; --pre-border-color:#666; --pre-fg:#fff; --pre-bg:#222; --code-fg:#ccc; --code-bg:transparent; --border-color:#666; --input-bg:#303030; --input-border-color:#297aa2; --placeholder-color:#666; --light-border-color:#444; --light-get-color:#2a2a2a; --light-put-color:#2a2a2a; --light-post-color:#2a2a2a; --light-delete-color:#2a2a2a; --light-patch-color:#2a2a2a; --hover-color:#2a2a2a; } </style>` :html`<style> :host{ --bg:#fff; --bg2:#fafafa; --fg:#333; --fg2:#565656; --light-fg:#999; --very-light-fg:#bbb; --pre-border-color:#000; --pre-fg:#ccc; --pre-bg:#263238; --code-fg:#ccc; --code-bg:transparent; --border-color:#ccc; --input-bg:#fff; --input-border-color:#C5D9E8; --placeholder-color:#dedede; --light-border-color:#eee; --light-get-color:#eff8fd; --light-put-color:#fff5e6; --light-post-color:#fbfff0; --light-delete-color:#fff0f0; --light-patch-color:#fff5cc; --hover-color:#f7f7f7; } </style>`} <style> :host{ --error-color:#ff3333; --success-color:#47AFE8; --hover-bg:#f7f7f7; --get-color:#47AFE8; --put-color:#FF9900; --post-color:#99CC00; --delete-color:#F06560; --patch-color:#fc0; --link-color:#47AFE8; --primary-color:${this.primaryColor?`${this.primaryColor}`:`#FF791A`}; --dark-primary-color:${vars.color.brightness(this.primaryColor?this.primaryColor:'#FF791A', -30)}; --primary-text:${this.primaryColor?`${vars.color.invert(this.primaryColor)}`:`#ffffff`}; --header-bg:${this.headerColor?`${this.headerColor}`:`#444`}; --header-fg:${this.headerColor?`${vars.color.invert(this.headerColor)}`:`#ccc`}; --layout:${this.layout?`${this.layout}`:`row`}; --font-mono:${this.monoFont?`${this.monoFont}`:`Monaco, 'Andale Mono', 'Roboto Mono', Consolas`}; --font-regular:${this.regularFont?`${this.regularFont}`:`rapidoc, Helvetica, Arial`}; --title-font-size:16px; --border-radius:2px; display:block; min-width:375px; width:100%; height:100%; margin:0; padding:0; overflow: auto; letter-spacing:normal; color:var(--fg); background-color:var(--bg); font-family:var(--font-regular); } .body-container{ margin:0; } .section-gap { padding: 24px 8px 8px 8px; } .logo { height:36px; width:36px; margin-left:5px; } .only-large-screen-flex, .only-large-screen{ display:none; } .header-title{ font-size:24px; padding:0 8px; } .tag{ text-transform: uppercase; } .header{ background-color:var(--header-bg); color:var(--header-fg); } input.header-input{ background:${this.headerColor?vars.color.brightness(this.headerColor, -20):vars.color.inputReverseBg}; color:var(--header-fg); border:1px solid var(--dark-primary-color); flex:1; padding-right:24px; border-radius:3px; } input.header-input::placeholder { opacity:0.4; } @media only screen and (min-width: 768px){ .only-large-screen{ display:block; } .only-large-screen-flex{ display:flex; } .body-container{ margin:0 16px; } .section-gap { padding: 24px 24px 8px 24px; } } </style> ${this.showHeader==='false'?'':html` <div class="row header regular-font" style="padding:8px 4px 8px 4px;min-height:48px;position:sticky;top:0;flex:1"> <div class="only-large-screen-flex" style="align-items: center;"> <slot name="logo" class="logo"> <m-logo style="height:36px;width:36px;margin-left:5px"></m-logo> </slot> <div class="header-title">${this.headingText}</div> </div> <div style="margin: 0px 8px;display:flex;flex:1"> ${ (this.allowSpecUrlLoad==='false') ?``:html` <input id="spec-url" type="text" class="header-input" placeholder="Spec URL" value="${this.specUrl?this.specUrl:''}" @change="${this.onSepcUrlChange}"> <div style="margin: 6px 5px 0 -24px; font-size:18px; cursor:pointer;">&#x23ce;</div> `} ${ (this.allowSpecFileLoad==='false') ?``:html` <input id="spec-file" type="file" style="display:none" value="${this.specFile?this.specFile:''}" @change="${this.onSepcFileChange}" > <button class="m-btn only-large-screen" style="margin-left:10px;" @click="${this.onFileLoadClick}"> LOCAL JSON FILE </button> `} ${ (this.allowSearch==='false') ?``:html` <input id="search" class="header-input" type="text" placeholder="search" @change="${this.onSearchChange}" style="max-width:130px;margin-left:10px;"> <div style="margin: 6px 5px 0 -24px; font-size:18px; cursor:pointer;">&#x23ce;</div> `} </div> </div>`} <div class="body-container regular-font"> <slot></slot> ${this.loading===true?html`<div style="text-align: center;margin: 16px;">Loading ... </div>`:''} ${ (this.showInfo==='false' || !this.resolvedSpec || !this.resolvedSpec.info) ?``:html` <div class="section-gap"> <div class="title"> ${this.resolvedSpec.info.title} ${!this.resolvedSpec.info.version?"":html` <span style="font-size:14px;font-weight:bold"> ${this.resolvedSpec.info.version} </span>` } </div> ${this.resolvedSpec.info.description?html` ${unsafeHTML(`<div class='m-markdown regular-font'>${marked(this.resolvedSpec.info.description)}</div>`)} `:``} </div>` } ${(this.allowTry==='false' || !this.resolvedSpec || !this.resolvedSpec.servers || this.resolvedSpec.servers.length===0) ?``:html` <div class="sub-title regular-font section-gap"> <a id="api_server_options"> API SERVER: </a> <div style="margin: 8px 0; font-size:12px"> ${this.resolvedSpec.servers.map(server => html` <input type='radio' name='api_server' value='${server.url}' @change="${this.onApiServerChange}" checked style='margin:0 0 5px 8px'/> ${server.url}<br/> `)} </div> </div> `} ${(this.allowAuthentication==='false' || !this.resolvedSpec || !this.resolvedSpec.securitySchemes)?'':html` <div class="sub-title regular-font section-gap"> <security-schemes .schemes="${this.resolvedSpec.securitySchemes}" @change="${this.onSecurityChange}" ></security-schemes> </div> `} ${this.resolvedSpec && this.resolvedSpec.tags ?html` ${this.resolvedSpec.tags.map(tag => html` <div class="sub-title tag regular-font section-gap">${tag.name}</div> <div style="margin:4px 20px"> ${unsafeHTML(`<div class='m-markdown regular-font'>${marked(tag.description?tag.description:'')}</div>`)} </div> <end-points server = "${this.server?this.server:''}" api-key-name = "${this.apiKeyName?this.apiKeyName:''}" api-key-value = "${this.apiKeyValue?this.apiKeyValue:''}" api-key-location = "${this.apiKeyLocation?this.apiKeyLocation:''}" layout = "${this.layout?this.layout:'row'}" .paths = "${tag.paths}" allow-try = "${this.allowTry}" match-paths = "${this.matchPaths}" ></end-points> `)}` :''} <slot name="footer"></slot> </div> `} static get properties() { return { specUrl : { type: String, attribute: 'spec-url' }, specFile: { type: String, attribute: false }, server : { type: String }, matchPaths : { type: String, attribute: 'match-paths' }, headingText : { type: String, attribute: 'heading-text' }, headerColor : { type: String, attribute: 'header-color' }, primaryColor: { type: String, attribute: 'primary-color' }, regularFont : { type: String, attribute: 'regular-font' }, monoFont : { type: String, attribute: 'mono-font' }, showHeader : { type: String, attribute: 'show-header' }, showInfo : { type: String, attribute: 'show-info' }, allowAuthentication: { type: String, attribute: 'allow-authentication' }, allowTry : { type: String, attribute: 'allow-try' }, allowSpecUrlLoad: { type: String, attribute: 'allow-spec-url-load' }, allowSpecFileLoad: { type: String, attribute: 'allow-spec-file-load' }, allowSearch : { type: String, attribute: 'allow-search' }, layout : { type: String }, theme : { type: String }, logoUrl : { type: String , attribute: 'logo-url' }, apiKeyName : { type: String, attribute: 'api-key-name' }, apiKeyValue : { type: String, attribute: 'api-key-value' }, apiKeyLocation: { type: String, attribute: 'api-key-location'}, resolveCircularRefs: { type: String, attribute: 'resolve-circular-refs' }, }; } attributeChangedCallback(name, oldVal, newVal) { if (name=='spec-url'){ if (oldVal !== newVal){ this.loadSpec(newVal); } } super.attributeChangedCallback(name, oldVal, newVal); } onSepcUrlChange(e){ this.setAttribute('spec-url', this.shadowRoot.getElementById('spec-url').value); } onSepcFileChange(e){ let me = this; this.setAttribute('spec-file', this.shadowRoot.getElementById('spec-file').value); let specFile = e.target.files[0]; let reader = new FileReader(); reader.onload = function(e) { try{ let specObj = JSON.parse(reader.result); me.loadSpec(specObj); me.shadowRoot.getElementById('spec-url').value=""; } catch{ alert("Unable to read or parse json"); console.log("Unable to read or parse json") } } // Read the Text file reader.readAsText(specFile); } onFileLoadClick(){ this.shadowRoot.getElementById('spec-file').click(); } onApiServerChange(){ let apiServerRadioEl = this.shadowRoot.querySelector("input[name='api_server']:checked"); if (apiServerRadioEl !== null){ this.server = apiServerRadioEl.value; } } onSecurityChange(e){ this.apiKeyName = e.detail.keyName this.apiKeyValue = e.detail.keyValue this.apiKeyLocation= e.detail.keyLocation; } onSearchChange(e){ this.matchPaths = e.target.value; } loadSpec(specUrl) { let me = this; if (!specUrl){ return; } this.loading = true; this.apiKeyName = ""; this.apiKeyValue = ""; this.apiKeyLocation = ""; this.server = ""; this.matchPaths = ""; ProcessSpec(specUrl, this.resolveCircularRefs).then(function(spec){ me.loading = false; if (spec===undefined || spec === null){ console.error('Unable to resolve the API spec. '); } console.log(spec); me.afterSpecParsedAndValidated(spec); }) .catch(function(err) { me.loading=false; console.error('Unable to resolve the API spec.. ' + err.message); }); } afterSpecParsedAndValidated(spec, isReloadingSpec=false){ let me = this; this.resolvedSpec = spec; this.requestUpdate(); window.setTimeout(function(){ me.onApiServerChange() },0); } } customElements.define('rapi-doc', RapiDoc);