diff --git a/README.md b/README.md index 8f849d27..79247b7b 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,7 @@ ReDoc makes use of the following [vendor extensions](http://swagger.io/specifica * **selector**: selector of the element to be used for specifying the offset. The distance from the top of the page to the element's bottom will be used as offset; * **function**: A getter function. Must return a number representing the offset (in pixels); * `suppress-warnings` - if set, warnings are not rendered at the top of documentation (they still are logged to the console). +* `lazy-rendering` - if set, enables lazy rendering mode in ReDoc. This mode is useful for APIs with big number of operations (e.g. > 50). In this mode ReDoc shows initial screen ASAP and then renders the rest operations asynchronously while showing progress bar on the top. Check out the [demo](\\rebilly.github.io/ReDoc) for the example. * `hide-hostname` - if set, the protocol and hostname is not shown in the method definition. ## Advanced usage diff --git a/demo/index-gh.html b/demo/index-gh.html index 6d3a7c50..17724a03 100644 --- a/demo/index-gh.html +++ b/demo/index-gh.html @@ -22,7 +22,7 @@ frameborder="0" scrolling="0" width="130px" height="30px"> - + diff --git a/demo/index.html b/demo/index.html index 8cb4b1d5..55ce64f3 100644 --- a/demo/index.html +++ b/demo/index.html @@ -22,7 +22,7 @@ frameborder="0" scrolling="0" width="130px" height="30px"> - + diff --git a/lib/components/ApiInfo/api-info.spec.ts b/lib/components/ApiInfo/api-info.spec.ts index 4d0e6612..c2c80bdc 100644 --- a/lib/components/ApiInfo/api-info.spec.ts +++ b/lib/components/ApiInfo/api-info.spec.ts @@ -18,23 +18,28 @@ describe('Redoc components', () => { let component; let fixture; let opts; + let specMgr; beforeEach(() => { TestBed.configureTestingModule({ declarations: [ TestAppComponent ] }); }); - beforeEach(async(inject([SpecManager, OptionsService], (specMgr, _opts) => { + beforeEach(async(inject([SpecManager, OptionsService], (_specMgr, _opts) => { opts = _opts; opts.options = { scrollYOffset: () => 0, $scrollParent: window }; - return specMgr.load('/tests/schemas/api-info-test.json'); + specMgr = _specMgr; }))); - beforeEach(() => { + beforeEach(done => { + specMgr.load('/tests/schemas/api-info-test.json').then(done, done.fail); + }); + + beforeEach(async(() => { fixture = TestBed.createComponent(TestAppComponent); component = getChildDebugElement(fixture.debugElement, 'api-info').componentInstance; fixture.detectChanges(); - }); + })); it('should init component data', () => { diff --git a/lib/components/ApiLogo/api-logo.spec.ts b/lib/components/ApiLogo/api-logo.spec.ts index 5ef98c1f..f9ba3cd2 100644 --- a/lib/components/ApiLogo/api-logo.spec.ts +++ b/lib/components/ApiLogo/api-logo.spec.ts @@ -24,10 +24,15 @@ describe('Redoc components', () => { beforeEach(() => { TestBed.configureTestingModule({ declarations: [ TestAppComponent ] }); }); + beforeEach(async(inject([SpecManager], ( _specMgr) => { specMgr = _specMgr; - return specMgr.load(schemaUrl); }))); + + beforeEach(done => { + specMgr.load(schemaUrl).then(done, done.fail); + }); + beforeEach(() => { fixture = TestBed.createComponent(TestAppComponent); component = getChildDebugElement(fixture.debugElement, 'api-logo').componentInstance; @@ -36,6 +41,7 @@ describe('Redoc components', () => { it('should init component data', () => { + if (specMgr.a) return; expect(component).not.toBeNull(); expect(component.logo).not.toBeNull(); }); @@ -61,7 +67,6 @@ describe('Redoc components', () => { /** Test component that contains an ApiInfo. */ @Component({ selector: 'test-app', - providers: [SpecManager], template: `` }) diff --git a/lib/components/JsonSchema/_json-schema-common.scss b/lib/components/JsonSchema/_json-schema-common.scss index dcdf57bb..8cefa4df 100644 --- a/lib/components/JsonSchema/_json-schema-common.scss +++ b/lib/components/JsonSchema/_json-schema-common.scss @@ -27,6 +27,10 @@ $sub-schema-offset: ($bullet-size / 2) + $bullet-margin; width: 75%; box-sizing: border-box; + + > div { + line-height: 1; + } } .param-range { @@ -41,7 +45,7 @@ $sub-schema-offset: ($bullet-size / 2) + $bullet-margin; } .param-description { - font-size: 13px; + //font-size: 14px; } .param-required { @@ -220,7 +224,6 @@ $sub-schema-offset: ($bullet-size / 2) + $bullet-margin; margin: 0 3px; font-size: 1.2em; font-weight: bold; - vertical-align: bottom; } } diff --git a/lib/components/JsonSchema/json-schema-lazy.ts b/lib/components/JsonSchema/json-schema-lazy.ts index 4cfe9143..2427b701 100644 --- a/lib/components/JsonSchema/json-schema-lazy.ts +++ b/lib/components/JsonSchema/json-schema-lazy.ts @@ -12,7 +12,8 @@ var cache = {}; @Component({ selector: 'json-schema-lazy', entryComponents: [ JsonSchema ], - template: '' + template: '', + styles: [':host { display:none }'] }) export class JsonSchemaLazy implements OnDestroy, AfterViewInit { @Input() pointer: string; @@ -66,7 +67,7 @@ export class JsonSchemaLazy implements OnDestroy, AfterViewInit { this._loadAfterSelf(); return; } - insertAfter($element.cloneNode(true), this.elementRef.nativeElement); + //insertAfter($element.cloneNode(true), this.elementRef.nativeElement); this.loaded = true; } else { cache[this.pointer] = this._loadAfterSelf(); diff --git a/lib/components/JsonSchema/json-schema.spec.ts b/lib/components/JsonSchema/json-schema.spec.ts index 53b77cad..5c985048 100644 --- a/lib/components/JsonSchema/json-schema.spec.ts +++ b/lib/components/JsonSchema/json-schema.spec.ts @@ -62,7 +62,6 @@ describe('Redoc components', () => { /** Test component that contains a Method. */ @Component({ selector: 'test-app', - providers: [SpecManager], template: `` }) diff --git a/lib/components/LoadingBar/loading-bar.ts b/lib/components/LoadingBar/loading-bar.ts new file mode 100644 index 00000000..8666deb7 --- /dev/null +++ b/lib/components/LoadingBar/loading-bar.ts @@ -0,0 +1,49 @@ +'use strict'; +import { Input, HostBinding, Component, OnInit, ChangeDetectionStrategy, ElementRef, ChangeDetectorRef } from '@angular/core'; +import JsonPointer from '../../utils/JsonPointer'; +import { BaseComponent, SpecManager } from '../base'; +import { SchemaHelper } from '../../services/schema-helper.service'; +import { OptionsService, AppStateService } from '../../services/'; + +@Component({ + selector: 'loading-bar', + template: ` + + `, + styles: [` + :host { + position: fixed; + top: 0; + left: 0; + right: 0; + display: block; + + height: 5px; + z-index: 100; + } + + span { + display: block; + position: absolute; + left: 0; + top: 0; + bottom: 0; + right: attr(progress percentage); + background-color: #5f7fc3; + transition: right 0.2s linear; + } + `], + //changeDetection: ChangeDetectionStrategy.OnPush +}) +export class LoadingBar { + @Input() progress:number = 0; + @HostBinding('style.display') display = 'block'; + + ngOnChanges(ch) { + if (ch.progress.currentValue === 100) { + setTimeout(() => { + this.display = 'none'; + }, 500); + } + } +} diff --git a/lib/components/Method/method.html b/lib/components/Method/method.html index 861ff11a..d9b1afe6 100644 --- a/lib/components/Method/method.html +++ b/lib/components/Method/method.html @@ -1,18 +1,18 @@ -
-
-

- {{method.summary}} -

- -

+

+
+

+ {{method.summary}} +

+ +

-
-
+
+

Definition

@@ -30,5 +30,5 @@
+
-
diff --git a/lib/components/Method/method.scss b/lib/components/Method/method.scss index 8d644a94..0306b61e 100644 --- a/lib/components/Method/method.scss +++ b/lib/components/Method/method.scss @@ -6,18 +6,20 @@ display: block; border-bottom: 1px solid rgba(127, 127, 127, 0.25); margin-top: 1em; + transform: translateZ(0); + z-index: 2; } -:host:last-of-type { - border-bottom: 0; -} +// :host:last-of-type { +// border-bottom: 0; +// } .method-header { - margin-bottom: .9em; + margin-bottom: calc(1em - 6px); } .method-endpoint { - margin: 0 0 2em 0; + //margin: 0 0 2px 0; padding: 10px 20px; border-radius: $border-radius*2; background-color: darken($black, 2%); @@ -31,8 +33,8 @@ padding-top: 1px; padding-bottom: 0; margin: 0; - font-size: .8em; - color: $black; + font-size: 12/14em; + color: $black; vertical-align: middle; display: inline-block; border-radius: $border-radius; diff --git a/lib/components/Method/method.spec.ts b/lib/components/Method/method.spec.ts index c35508a0..d754067c 100644 --- a/lib/components/Method/method.spec.ts +++ b/lib/components/Method/method.spec.ts @@ -11,6 +11,7 @@ import { getChildDebugElement } from '../../../tests/helpers'; import { Method } from './method'; import { SpecManager } from '../../utils/spec-manager';; +import { LazyTasksService } from '../../shared/components/LazyFor/lazy-for';; describe('Redoc components', () => { beforeEach(() => { @@ -19,12 +20,17 @@ describe('Redoc components', () => { describe('Method Component', () => { let builder; let component; + let specMgr; - beforeEach(async(inject([SpecManager], ( specMgr) => { - - return specMgr.load('/tests/schemas/extended-petstore.yml'); + beforeEach(async(inject([SpecManager, LazyTasksService], (_specMgr, lazyTasks) => { + lazyTasks.sync = true; + specMgr = _specMgr; }))); + beforeEach(done => { + specMgr.load('/tests/schemas/extended-petstore.yml').then(done, done.fail); + }); + beforeEach(() => { let fixture = TestBed.createComponent(TestAppComponent); component = getChildDebugElement(fixture.debugElement, 'method').componentInstance; @@ -53,7 +59,6 @@ describe('Redoc components', () => { /** Test component that contains a Method. */ @Component({ selector: 'test-app', - providers: [SpecManager], template: `` }) diff --git a/lib/components/Method/method.ts b/lib/components/Method/method.ts index 80f23ce0..b846684f 100644 --- a/lib/components/Method/method.ts +++ b/lib/components/Method/method.ts @@ -1,9 +1,9 @@ 'use strict'; -import { Input, Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; +import { Input, Component, OnInit, ChangeDetectionStrategy, ElementRef, ChangeDetectorRef } from '@angular/core'; import JsonPointer from '../../utils/JsonPointer'; import { BaseComponent, SpecManager } from '../base'; import { SchemaHelper } from '../../services/schema-helper.service'; -import { OptionsService } from '../../services/options.service'; +import { OptionsService, AppStateService } from '../../services/'; @Component({ selector: 'method', @@ -14,10 +14,14 @@ import { OptionsService } from '../../services/options.service'; export class Method extends BaseComponent implements OnInit { @Input() pointer:string; @Input() tag:string; + @Input() posInfo: any; + + hidden = true; method:any; - constructor(specMgr:SpecManager, private optionsService: OptionsService) { + constructor(specMgr:SpecManager, private optionsService: OptionsService, private chDetector: ChangeDetectorRef, + private appState: AppStateService, private el: ElementRef) { super(specMgr); } @@ -53,6 +57,14 @@ export class Method extends BaseComponent implements OnInit { return bodyParam; } + show(res) { + if (res) { + this.el.nativeElement.firstElementChild.removeAttribute('hidden'); + } else { + this.el.nativeElement.firstElementChild.setAttribute('hidden', 'hidden'); + } + } + ngOnInit() { this.preinit(); } diff --git a/lib/components/MethodsList/methods-list.html b/lib/components/MethodsList/methods-list.html index 11f4f859..225a1930 100644 --- a/lib/components/MethodsList/methods-list.html +++ b/lib/components/MethodsList/methods-list.html @@ -1,10 +1,10 @@
-
+

{{tag.name}}

-
diff --git a/lib/components/MethodsList/methods-list.scss b/lib/components/MethodsList/methods-list.scss index 0cfbfa50..fe57d8c2 100644 --- a/lib/components/MethodsList/methods-list.scss +++ b/lib/components/MethodsList/methods-list.scss @@ -4,6 +4,11 @@ display: block; overflow: hidden; } + +:host [hidden] { + display: none; +} + .tag-info { padding: $section-spacing; box-sizing: border-box; diff --git a/lib/components/MethodsList/methods-list.spec.ts b/lib/components/MethodsList/methods-list.spec.ts index ca59e157..330cb754 100644 --- a/lib/components/MethodsList/methods-list.spec.ts +++ b/lib/components/MethodsList/methods-list.spec.ts @@ -13,7 +13,7 @@ import { getChildDebugElement } from '../../../tests/helpers'; import { MethodsList } from './methods-list'; import { SpecManager } from '../../utils/spec-manager'; -describe('Redoc components', () => { +describe('Redoc components', () => { beforeEach(() => { TestBed.configureTestingModule({ declarations: [ TestAppComponent ] }); }); @@ -21,11 +21,16 @@ describe('Redoc components', () => { let builder; let component; let fixture; + let specMgr; - beforeEach(async(inject([SpecManager], ( specMgr) => { - - return specMgr.load('/tests/schemas/methods-list-component.json'); + beforeEach(async(inject([SpecManager], (_specMgr) => { + specMgr = _specMgr; }))); + + beforeEach(done => { + specMgr.load('/tests/schemas/methods-list-component.json').then(done, done.fail); + }); + beforeEach(() => { fixture = TestBed.createComponent(TestAppComponent); component = getChildDebugElement(fixture.debugElement, 'methods-list').componentInstance; diff --git a/lib/components/ParamsList/params-list.scss b/lib/components/ParamsList/params-list.scss index 239c8c83..70123065 100644 --- a/lib/components/ParamsList/params-list.scss +++ b/lib/components/ParamsList/params-list.scss @@ -2,10 +2,14 @@ $hint-color: #999999; +:host { + display: block; +} + .param-list-header { border-bottom: 1px solid rgba($text-color, .3); - padding: 0.2em 0; - margin: 3.5em 0 .8em 0; +// padding: 0.2em 0; + margin: 3em 0 1em 0; color: rgba($text-color, .5); font-weight: normal; text-transform: uppercase; diff --git a/lib/components/Redoc/redoc.html b/lib/components/Redoc/redoc.html index 89bacc5c..a688d51a 100644 --- a/lib/components/Redoc/redoc.html +++ b/lib/components/Redoc/redoc.html @@ -2,7 +2,11 @@

Oops... ReDoc failed to render this spec

{{error.message}}
+
+
+
+
diff --git a/lib/components/SchemaSample/schema-sample.scss b/lib/components/SchemaSample/schema-sample.scss index 714eac34..45fdf161 100644 --- a/lib/components/SchemaSample/schema-sample.scss +++ b/lib/components/SchemaSample/schema-sample.scss @@ -13,46 +13,26 @@ pre { } .action-buttons { - display: block; opacity: 0; transition: opacity 0.3s ease; transform: translateY(100%); - z-index: 1; + z-index: 3; position: relative; - - > span { - float: right; - - &:last-child > a:before { - display: none; - } - } + height: 2em; + line-height: 2em; + padding-right: 10px; + text-align: right; + margin-top: -1em; > span > a { padding: 2px 10px; color: #ffffff; cursor: pointer; - &:before { - content: '|'; - display: inline-block; - transform: translateX(-10px); - } - - &:first-child { - margin-right: 0; - } - &:hover { - background-color: $black; + background-color: lighten($black, 15%); } } - - &:after { - display: block; - content: ''; - clear: both; - } } .snippet:hover .action-buttons { @@ -135,6 +115,7 @@ pre { li { position: relative; + display: block; } .hoverable { diff --git a/lib/components/SideMenu/side-menu.html b/lib/components/SideMenu/side-menu.html index 72f569c8..9124c6f4 100644 --- a/lib/components/SideMenu/side-menu.html +++ b/lib/components/SideMenu/side-menu.html @@ -10,10 +10,10 @@ `, styles: [` + :host { + display: block; + } .tab-wrap { display: none; } diff --git a/lib/shared/components/Zippy/zippy.scss b/lib/shared/components/Zippy/zippy.scss index a93ab00b..a72b7ef9 100644 --- a/lib/shared/components/Zippy/zippy.scss +++ b/lib/shared/components/Zippy/zippy.scss @@ -12,8 +12,8 @@ $zippy-redirect-bg-color: rgba($zippy-redirect-color, .08); :host { // performance optimization - transform: translate3d(0, 0, 0); - backface-visibility: hidden; + // transform: translate3d(0, 0, 0); + // backface-visibility: hidden; overflow: hidden; display: block; } diff --git a/lib/shared/components/index.ts b/lib/shared/components/index.ts index b273f1a5..fe8a1392 100644 --- a/lib/shared/components/index.ts +++ b/lib/shared/components/index.ts @@ -6,9 +6,11 @@ import { Zippy } from './Zippy/zippy'; import { CopyButton } from './CopyButton/copy-button.directive'; import { SelectOnClick } from './SelectOnClick/select-on-click.directive'; import { DynamicNg2Viewer, DynamicNg2Wrapper } from './DynamicNg2Viewer/dynamic-ng2-viewer.component'; +import { LazyFor, LazyTasksService, LazyTasksServiceSync } from './LazyFor/lazy-for'; export const REDOC_COMMON_DIRECTIVES = [ - DropDown, StickySidebar, Tabs, Tab, Zippy, CopyButton, SelectOnClick, DynamicNg2Viewer, DynamicNg2Wrapper + DropDown, StickySidebar, Tabs, Tab, Zippy, CopyButton, SelectOnClick, DynamicNg2Viewer, DynamicNg2Wrapper, LazyFor ]; -export { DropDown, StickySidebar, Tabs, Tab, Zippy, CopyButton, SelectOnClick, DynamicNg2Viewer, DynamicNg2Wrapper } +export { DropDown, StickySidebar, Tabs, Tab, Zippy, CopyButton, SelectOnClick, DynamicNg2Viewer, DynamicNg2Wrapper, LazyFor } +export { LazyTasksService, LazyTasksServiceSync } diff --git a/lib/shared/styles/_variables.scss b/lib/shared/styles/_variables.scss index 95ff58d8..e31b8a65 100644 --- a/lib/shared/styles/_variables.scss +++ b/lib/shared/styles/_variables.scss @@ -22,7 +22,7 @@ $base-font: Roboto; $base-font-family: sans-serif; $base-font-weight: $light; $base-font-size: 1em; -$base-line-height: 1.55em; +$base-line-height: 1.5em; $text-color: $black; // Heading Font diff --git a/lib/utils/helpers.ts b/lib/utils/helpers.ts index 46f1ed66..4366388e 100644 --- a/lib/utils/helpers.ts +++ b/lib/utils/helpers.ts @@ -74,3 +74,7 @@ export function throttle(fn, threshhold, scope) { } }; } + +export const isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0 + || (function (p) { return p.toString() === '[object SafariRemoteNotification]'; })(!window['safari'] + || safari.pushNotification); diff --git a/lib/utils/spec-manager.ts b/lib/utils/spec-manager.ts index 5a464de7..7a9e3c94 100644 --- a/lib/utils/spec-manager.ts +++ b/lib/utils/spec-manager.ts @@ -13,24 +13,10 @@ export class SpecManager { public basePath: string; public spec = new BehaviorSubject(null); - private _instance: any; private _url: string; private parser: any; - static instance() { - return new SpecManager(); - } - - constructor() { - if (SpecManager.prototype._instance) { - return SpecManager.prototype._instance; - } - - SpecManager.prototype._instance = this; - } - load(urlOrObject: string|Object) { - this.schema = null; let promise = new Promise((resolve, reject) => { this.parser = new JsonSchemaRefParser(); this.parser.bundle(urlOrObject, {http: {withCredentials: false}}) diff --git a/manual-types/index.d.ts b/manual-types/index.d.ts index faecbd1c..8d37c8e6 100644 --- a/manual-types/index.d.ts +++ b/manual-types/index.d.ts @@ -19,4 +19,11 @@ declare var AOT: any; interface ErrorStackTraceLimit { stackTraceLimit: number; } +interface History { + scrollRestoration: "auto"|"manual"; +} +interface Window { + HTMLElement: any +} +declare var safari: any; interface ErrorConstructor extends ErrorStackTraceLimit {} diff --git a/tests/spec-bundle.js b/tests/spec-bundle.js index e82fbfb0..4b5637d4 100644 --- a/tests/spec-bundle.js +++ b/tests/spec-bundle.js @@ -48,6 +48,7 @@ beforeEach(function() { services.OptionsService, services.ComponentParser, services.ContentProjector, + { provide: sharedComponents.LazyTasksService, useClass: sharedComponents.LazyTasksServiceSync }, { provide: ErrorHandler, useClass: services.CustomErrorHandler }, { provide: services.COMPONENT_PARSER_ALLOWED, useValue: { 'security-definitions': components.SecurityDefinitions }} ], @@ -60,6 +61,14 @@ beforeEach(function() { }); }); +// afterEach(function() { +// TestBed.resetTestingModule(); +// }); + +// afterEach(function() { +// TestBed.resetTestEnvironment(); +// }) + var testContext = require.context('..', true, /\.spec\.ts/); diff --git a/tests/unit/SpecManager.spec.ts b/tests/unit/SpecManager.spec.ts index c5bc8f63..0ac1a03f 100644 --- a/tests/unit/SpecManager.spec.ts +++ b/tests/unit/SpecManager.spec.ts @@ -9,11 +9,6 @@ describe('Utils', () => { specMgr = new SpecManager(); }); - it('Should be a singleton', ()=> { - (new SpecManager()).should.be.equal(specMgr); - SpecManager.instance().should.be.equal(specMgr); - }); - it('load should return a promise', ()=> { specMgr.load('/tests/schemas/extended-petstore.yml').should.be.instanceof(Promise); }); @@ -35,15 +30,10 @@ describe('Utils', () => { }); describe('Schema manager basic functionality', ()=> { - beforeAll(function (done) { - specMgr.load('/tests/schemas/extended-petstore.yml').then(() => { - done(); - }, () => { - throw new Error('Error handler should not be called'); - }); + beforeEach(function (done) { + specMgr.load('/tests/schemas/extended-petstore.yml').then(done, done.fail); }); - it('should contain non-empty schema', ()=> { specMgr.schema.should.be.an.Object(); specMgr.schema.should.be.not.empty(); @@ -68,9 +58,9 @@ describe('Utils', () => { it('should substitute api host when spec host is undefined', () => { specMgr._schema.host = undefined; - specMgr._url = 'https://petstore.swagger.io/v2'; + specMgr._url = 'http://petstore.swagger.io/v2'; specMgr.init(); - specMgr.apiUrl.should.be.equal('https://petstore.swagger.io/v2'); + specMgr.apiUrl.should.be.equal('http://petstore.swagger.io/v2'); }); describe('byPointer method', () => { @@ -88,7 +78,7 @@ describe('Utils', () => { }); describe('getTagsMap method', () => { - beforeAll(function () { + beforeEach(function () { specMgr._schema = { tags: [ {name: 'tag1', description: 'info1'}, @@ -114,12 +104,8 @@ describe('Utils', () => { }); describe('getMethodParams method', () => { - beforeAll((done:any) => { - specMgr.load('/tests/schemas/schema-mgr-methodparams.json').then(() => { - done(); - }, () => { - done(new Error('Error handler should not be called')); - }); + beforeEach((done:any) => { + specMgr.load('/tests/schemas/schema-mgr-methodparams.json').then(done, done.fail); }); it('should propagate path parameters', () => { @@ -163,12 +149,8 @@ describe('Utils', () => { }); describe('findDerivedDefinitions method', () => { - beforeAll((done:any) => { - specMgr.load('/tests/schemas/extended-petstore.yml').then(() => { - done(); - }, () => { - done(new Error('Error handler should not be called')); - }); + beforeEach((done) => { + specMgr.load('/tests/schemas/extended-petstore.yml').then(done, done.fail); }); it('should find derived definitions for Pet', () => {