mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-27 02:53:43 +03:00
Merge branch 'performance-fix'🎉
This commit is contained in:
commit
6bb521e1d8
|
@ -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;
|
* **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);
|
* **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).
|
* `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.
|
* `hide-hostname` - if set, the protocol and hostname is not shown in the method definition.
|
||||||
|
|
||||||
## Advanced usage
|
## Advanced usage
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
frameborder="0" scrolling="0" width="130px" height="30px"></iframe>
|
frameborder="0" scrolling="0" width="130px" height="30px"></iframe>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<redoc scroll-y-offset="body > nav" spec-url='swagger.yaml'></redoc>
|
<redoc scroll-y-offset="body > nav" spec-url='swagger.yaml' lazy-rendering></redoc>
|
||||||
|
|
||||||
<script src="main.js"> </script>
|
<script src="main.js"> </script>
|
||||||
<script src="./dist/redoc.min.js"> </script>
|
<script src="./dist/redoc.min.js"> </script>
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
frameborder="0" scrolling="0" width="130px" height="30px"></iframe>
|
frameborder="0" scrolling="0" width="130px" height="30px"></iframe>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<redoc scroll-y-offset="body > nav" spec-url='swagger.yaml'></redoc>
|
<redoc scroll-y-offset="body > nav" spec-url='swagger.yaml' lazy-rendering></redoc>
|
||||||
|
|
||||||
<script src="main.js"> </script>
|
<script src="main.js"> </script>
|
||||||
<script src="/webpack-dev-server.js"></script>
|
<script src="/webpack-dev-server.js"></script>
|
||||||
|
|
|
@ -18,23 +18,28 @@ describe('Redoc components', () => {
|
||||||
let component;
|
let component;
|
||||||
let fixture;
|
let fixture;
|
||||||
let opts;
|
let opts;
|
||||||
|
let specMgr;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
|
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
|
||||||
});
|
});
|
||||||
beforeEach(async(inject([SpecManager, OptionsService], (specMgr, _opts) => {
|
beforeEach(async(inject([SpecManager, OptionsService], (_specMgr, _opts) => {
|
||||||
opts = _opts;
|
opts = _opts;
|
||||||
opts.options = {
|
opts.options = {
|
||||||
scrollYOffset: () => 0,
|
scrollYOffset: () => 0,
|
||||||
$scrollParent: window
|
$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);
|
fixture = TestBed.createComponent(TestAppComponent);
|
||||||
component = getChildDebugElement(fixture.debugElement, 'api-info').componentInstance;
|
component = getChildDebugElement(fixture.debugElement, 'api-info').componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
}));
|
||||||
|
|
||||||
|
|
||||||
it('should init component data', () => {
|
it('should init component data', () => {
|
||||||
|
|
|
@ -24,10 +24,15 @@ describe('Redoc components', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
|
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(async(inject([SpecManager], ( _specMgr) => {
|
beforeEach(async(inject([SpecManager], ( _specMgr) => {
|
||||||
specMgr = _specMgr;
|
specMgr = _specMgr;
|
||||||
return specMgr.load(schemaUrl);
|
|
||||||
})));
|
})));
|
||||||
|
|
||||||
|
beforeEach(done => {
|
||||||
|
specMgr.load(schemaUrl).then(done, done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(TestAppComponent);
|
fixture = TestBed.createComponent(TestAppComponent);
|
||||||
component = getChildDebugElement(fixture.debugElement, 'api-logo').componentInstance;
|
component = getChildDebugElement(fixture.debugElement, 'api-logo').componentInstance;
|
||||||
|
@ -36,6 +41,7 @@ describe('Redoc components', () => {
|
||||||
|
|
||||||
|
|
||||||
it('should init component data', () => {
|
it('should init component data', () => {
|
||||||
|
if (specMgr.a) return;
|
||||||
expect(component).not.toBeNull();
|
expect(component).not.toBeNull();
|
||||||
expect(component.logo).not.toBeNull();
|
expect(component.logo).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
@ -61,7 +67,6 @@ describe('Redoc components', () => {
|
||||||
/** Test component that contains an ApiInfo. */
|
/** Test component that contains an ApiInfo. */
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'test-app',
|
selector: 'test-app',
|
||||||
providers: [SpecManager],
|
|
||||||
template:
|
template:
|
||||||
`<api-logo></api-logo>`
|
`<api-logo></api-logo>`
|
||||||
})
|
})
|
||||||
|
|
|
@ -27,6 +27,10 @@ $sub-schema-offset: ($bullet-size / 2) + $bullet-margin;
|
||||||
width: 75%;
|
width: 75%;
|
||||||
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.param-range {
|
.param-range {
|
||||||
|
@ -41,7 +45,7 @@ $sub-schema-offset: ($bullet-size / 2) + $bullet-margin;
|
||||||
}
|
}
|
||||||
|
|
||||||
.param-description {
|
.param-description {
|
||||||
font-size: 13px;
|
//font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.param-required {
|
.param-required {
|
||||||
|
@ -220,7 +224,6 @@ $sub-schema-offset: ($bullet-size / 2) + $bullet-margin;
|
||||||
margin: 0 3px;
|
margin: 0 3px;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
vertical-align: bottom;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,8 @@ var cache = {};
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'json-schema-lazy',
|
selector: 'json-schema-lazy',
|
||||||
entryComponents: [ JsonSchema ],
|
entryComponents: [ JsonSchema ],
|
||||||
template: ''
|
template: '',
|
||||||
|
styles: [':host { display:none }']
|
||||||
})
|
})
|
||||||
export class JsonSchemaLazy implements OnDestroy, AfterViewInit {
|
export class JsonSchemaLazy implements OnDestroy, AfterViewInit {
|
||||||
@Input() pointer: string;
|
@Input() pointer: string;
|
||||||
|
@ -66,7 +67,7 @@ export class JsonSchemaLazy implements OnDestroy, AfterViewInit {
|
||||||
this._loadAfterSelf();
|
this._loadAfterSelf();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
insertAfter($element.cloneNode(true), this.elementRef.nativeElement);
|
//insertAfter($element.cloneNode(true), this.elementRef.nativeElement);
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
} else {
|
} else {
|
||||||
cache[this.pointer] = this._loadAfterSelf();
|
cache[this.pointer] = this._loadAfterSelf();
|
||||||
|
|
|
@ -62,7 +62,6 @@ describe('Redoc components', () => {
|
||||||
/** Test component that contains a Method. */
|
/** Test component that contains a Method. */
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'test-app',
|
selector: 'test-app',
|
||||||
providers: [SpecManager],
|
|
||||||
template:
|
template:
|
||||||
`<json-schema></json-schema>`
|
`<json-schema></json-schema>`
|
||||||
})
|
})
|
||||||
|
|
49
lib/components/LoadingBar/loading-bar.ts
Normal file
49
lib/components/LoadingBar/loading-bar.ts
Normal file
|
@ -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: `
|
||||||
|
<span [style.width]='progress + "%"'> </span>
|
||||||
|
`,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,18 @@
|
||||||
<div class="method">
|
<div class="method" *ngIf="method">
|
||||||
<div class="method-content">
|
<div class="method-content">
|
||||||
<h2 class="method-header sharable-header">
|
<h2 class="method-header sharable-header">
|
||||||
<a class="share-link" href="#{{method.anchor}}"></a>{{method.summary}}
|
<a class="share-link" href="#{{method.anchor}}"></a>{{method.summary}}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="method-tags" *ngIf="method.info.tags.length">
|
<div class="method-tags" *ngIf="method.info.tags.length">
|
||||||
<a *ngFor="let tag of method.info.tags" attr.href="#tag/{{tag}}"> {{tag}} </a>
|
<a *ngFor="let tag of method.info.tags" attr.href="#tag/{{tag}}"> {{tag}} </a>
|
||||||
</div>
|
</div>
|
||||||
<p *ngIf="method.info.description" class="method-description"
|
<p *ngIf="method.info.description" class="method-description"
|
||||||
[innerHtml]="method.info.description | marked">
|
[innerHtml]="method.info.description | marked">
|
||||||
</p>
|
</p>
|
||||||
<params-list pointer="{{pointer}}/parameters"> </params-list>
|
<params-list pointer="{{pointer}}/parameters"> </params-list>
|
||||||
<responses-list pointer="{{pointer}}/responses"> </responses-list>
|
<responses-list pointer="{{pointer}}/responses"> </responses-list>
|
||||||
</div>
|
</div>
|
||||||
<div class="method-samples">
|
<div class="method-samples">
|
||||||
<h4 class="method-params-subheader">Definition</h4>
|
<h4 class="method-params-subheader">Definition</h4>
|
||||||
|
|
||||||
<div class="method-endpoint">
|
<div class="method-endpoint">
|
||||||
|
@ -30,5 +30,5 @@
|
||||||
<br>
|
<br>
|
||||||
<responses-samples pointer="{{pointer}}/responses"> </responses-samples>
|
<responses-samples pointer="{{pointer}}/responses"> </responses-samples>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
|
|
|
@ -6,18 +6,20 @@
|
||||||
display: block;
|
display: block;
|
||||||
border-bottom: 1px solid rgba(127, 127, 127, 0.25);
|
border-bottom: 1px solid rgba(127, 127, 127, 0.25);
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
|
transform: translateZ(0);
|
||||||
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host:last-of-type {
|
// :host:last-of-type {
|
||||||
border-bottom: 0;
|
// border-bottom: 0;
|
||||||
}
|
// }
|
||||||
|
|
||||||
.method-header {
|
.method-header {
|
||||||
margin-bottom: .9em;
|
margin-bottom: calc(1em - 6px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.method-endpoint {
|
.method-endpoint {
|
||||||
margin: 0 0 2em 0;
|
//margin: 0 0 2px 0;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
border-radius: $border-radius*2;
|
border-radius: $border-radius*2;
|
||||||
background-color: darken($black, 2%);
|
background-color: darken($black, 2%);
|
||||||
|
@ -31,8 +33,8 @@
|
||||||
padding-top: 1px;
|
padding-top: 1px;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: .8em;
|
font-size: 12/14em;
|
||||||
color: $black;
|
color: $black;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border-radius: $border-radius;
|
border-radius: $border-radius;
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { getChildDebugElement } from '../../../tests/helpers';
|
||||||
|
|
||||||
import { Method } from './method';
|
import { Method } from './method';
|
||||||
import { SpecManager } from '../../utils/spec-manager';;
|
import { SpecManager } from '../../utils/spec-manager';;
|
||||||
|
import { LazyTasksService } from '../../shared/components/LazyFor/lazy-for';;
|
||||||
|
|
||||||
describe('Redoc components', () => {
|
describe('Redoc components', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -19,12 +20,17 @@ describe('Redoc components', () => {
|
||||||
describe('Method Component', () => {
|
describe('Method Component', () => {
|
||||||
let builder;
|
let builder;
|
||||||
let component;
|
let component;
|
||||||
|
let specMgr;
|
||||||
|
|
||||||
beforeEach(async(inject([SpecManager], ( specMgr) => {
|
beforeEach(async(inject([SpecManager, LazyTasksService], (_specMgr, lazyTasks) => {
|
||||||
|
lazyTasks.sync = true;
|
||||||
return specMgr.load('/tests/schemas/extended-petstore.yml');
|
specMgr = _specMgr;
|
||||||
})));
|
})));
|
||||||
|
|
||||||
|
beforeEach(done => {
|
||||||
|
specMgr.load('/tests/schemas/extended-petstore.yml').then(done, done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
let fixture = TestBed.createComponent(TestAppComponent);
|
let fixture = TestBed.createComponent(TestAppComponent);
|
||||||
component = getChildDebugElement(fixture.debugElement, 'method').componentInstance;
|
component = getChildDebugElement(fixture.debugElement, 'method').componentInstance;
|
||||||
|
@ -53,7 +59,6 @@ describe('Redoc components', () => {
|
||||||
/** Test component that contains a Method. */
|
/** Test component that contains a Method. */
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'test-app',
|
selector: 'test-app',
|
||||||
providers: [SpecManager],
|
|
||||||
template:
|
template:
|
||||||
`<method pointer='#/paths/~1user~1{username}/put'></method>`
|
`<method pointer='#/paths/~1user~1{username}/put'></method>`
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
'use strict';
|
'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 JsonPointer from '../../utils/JsonPointer';
|
||||||
import { BaseComponent, SpecManager } from '../base';
|
import { BaseComponent, SpecManager } from '../base';
|
||||||
import { SchemaHelper } from '../../services/schema-helper.service';
|
import { SchemaHelper } from '../../services/schema-helper.service';
|
||||||
import { OptionsService } from '../../services/options.service';
|
import { OptionsService, AppStateService } from '../../services/';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'method',
|
selector: 'method',
|
||||||
|
@ -14,10 +14,14 @@ import { OptionsService } from '../../services/options.service';
|
||||||
export class Method extends BaseComponent implements OnInit {
|
export class Method extends BaseComponent implements OnInit {
|
||||||
@Input() pointer:string;
|
@Input() pointer:string;
|
||||||
@Input() tag:string;
|
@Input() tag:string;
|
||||||
|
@Input() posInfo: any;
|
||||||
|
|
||||||
|
hidden = true;
|
||||||
|
|
||||||
method:any;
|
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);
|
super(specMgr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +57,14 @@ export class Method extends BaseComponent implements OnInit {
|
||||||
return bodyParam;
|
return bodyParam;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
show(res) {
|
||||||
|
if (res) {
|
||||||
|
this.el.nativeElement.firstElementChild.removeAttribute('hidden');
|
||||||
|
} else {
|
||||||
|
this.el.nativeElement.firstElementChild.setAttribute('hidden', 'hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.preinit();
|
this.preinit();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<div class="methods">
|
<div class="methods">
|
||||||
<div class="tag" *ngFor="let tag of tags;trackBy:trackByTagName">
|
<div class="tag" *ngFor="let tag of tags;let catIdx = index; trackBy:trackByTagName">
|
||||||
<div class="tag-info" [attr.section]="tag.id" *ngIf="!tag.headless">
|
<div class="tag-info" [attr.section]="tag.id" *ngIf="!tag.headless">
|
||||||
<h1 class="sharable-header"> <a class="share-link" href="#tag/{{tag.name | encodeURIComponent}}"></a>{{tag.name}} </h1>
|
<h1 class="sharable-header"> <a class="share-link" href="#tag/{{tag.name | encodeURIComponent}}"></a>{{tag.name}} </h1>
|
||||||
<p *ngIf="tag.description" [innerHtml]="tag.description | marked"> </p>
|
<p *ngIf="tag.description" [innerHtml]="tag.description | marked"> </p>
|
||||||
</div>
|
</div>
|
||||||
<method *ngFor="let method of tag.methods;trackBy:trackByPointer" [pointer]="method.pointer" [attr.pointer]="method.pointer"
|
<method *lazyFor="let method of tag.methods;let show = show;" [hidden]="!show" [pointer]="method.pointer" [attr.pointer]="method.pointer"
|
||||||
[attr.section]="method.tag" [tag]="method.tag" [attr.operation-id]="method.operationId"></method>
|
[attr.section]="method.tag" [tag]="method.tag" [attr.operation-id]="method.operationId"></method>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,6 +4,11 @@
|
||||||
display: block;
|
display: block;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host [hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.tag-info {
|
.tag-info {
|
||||||
padding: $section-spacing;
|
padding: $section-spacing;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
|
@ -21,11 +21,16 @@ describe('Redoc components', () => {
|
||||||
let builder;
|
let builder;
|
||||||
let component;
|
let component;
|
||||||
let fixture;
|
let fixture;
|
||||||
|
let specMgr;
|
||||||
|
|
||||||
beforeEach(async(inject([SpecManager], ( specMgr) => {
|
beforeEach(async(inject([SpecManager], (_specMgr) => {
|
||||||
|
specMgr = _specMgr;
|
||||||
return specMgr.load('/tests/schemas/methods-list-component.json');
|
|
||||||
})));
|
})));
|
||||||
|
|
||||||
|
beforeEach(done => {
|
||||||
|
specMgr.load('/tests/schemas/methods-list-component.json').then(done, done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(TestAppComponent);
|
fixture = TestBed.createComponent(TestAppComponent);
|
||||||
component = getChildDebugElement(fixture.debugElement, 'methods-list').componentInstance;
|
component = getChildDebugElement(fixture.debugElement, 'methods-list').componentInstance;
|
||||||
|
|
|
@ -2,10 +2,14 @@
|
||||||
|
|
||||||
$hint-color: #999999;
|
$hint-color: #999999;
|
||||||
|
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.param-list-header {
|
.param-list-header {
|
||||||
border-bottom: 1px solid rgba($text-color, .3);
|
border-bottom: 1px solid rgba($text-color, .3);
|
||||||
padding: 0.2em 0;
|
// padding: 0.2em 0;
|
||||||
margin: 3.5em 0 .8em 0;
|
margin: 3em 0 1em 0;
|
||||||
color: rgba($text-color, .5);
|
color: rgba($text-color, .5);
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
|
|
@ -2,7 +2,11 @@
|
||||||
<h1>Oops... ReDoc failed to render this spec</h1>
|
<h1>Oops... ReDoc failed to render this spec</h1>
|
||||||
<div class='redoc-error-details'>{{error.message}}</div>
|
<div class='redoc-error-details'>{{error.message}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<loading-bar *ngIf="options.lazyRendering" [progress]="loadingProgress"> </loading-bar>
|
||||||
<div class="redoc-wrap" *ngIf="specLoaded && !error">
|
<div class="redoc-wrap" *ngIf="specLoaded && !error">
|
||||||
|
<div class="background">
|
||||||
|
<div class="background-actual"> </div>
|
||||||
|
</div>
|
||||||
<div class="menu-content" sticky-sidebar [scrollParent]="options.$scrollParent" [scrollYOffset]="options.scrollYOffset">
|
<div class="menu-content" sticky-sidebar [scrollParent]="options.$scrollParent" [scrollYOffset]="options.scrollYOffset">
|
||||||
<api-logo> </api-logo>
|
<api-logo> </api-logo>
|
||||||
<side-menu> </side-menu>
|
<side-menu> </side-menu>
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.redoc-wrap {
|
.redoc-wrap {
|
||||||
|
z-index: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
font-family: $base-font, $base-font-family;
|
font-family: $base-font, $base-font-family;
|
||||||
font-size: $em-size;
|
font-size: $em-size;
|
||||||
|
@ -36,11 +37,17 @@
|
||||||
color: $text-color;
|
color: $text-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menu-content {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
[sticky-sidebar] {
|
[sticky-sidebar] {
|
||||||
width: $side-bar-width;
|
width: $side-bar-width;
|
||||||
background-color: $side-bar-bg-color;
|
background-color: $side-bar-bg-color;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
transform: translateZ(0);
|
||||||
|
z-index: 75;
|
||||||
|
|
||||||
@media (max-width: $side-menu-mobile-breakpoint) {
|
@media (max-width: $side-menu-mobile-breakpoint) {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
@ -51,22 +58,33 @@
|
||||||
|
|
||||||
.api-content {
|
.api-content {
|
||||||
margin-left: $side-bar-width;
|
margin-left: $side-bar-width;
|
||||||
|
z-index: 50;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
// height: 100vh;
|
||||||
|
// overflow: scroll;
|
||||||
|
top: 0;
|
||||||
@media (max-width: $side-menu-mobile-breakpoint) {
|
@media (max-width: $side-menu-mobile-breakpoint) {
|
||||||
padding-top: 3em;
|
padding-top: 3em;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.api-content:before {
|
.background {
|
||||||
content: "";
|
position: fixed;
|
||||||
background: $samples-panel-bg-color;
|
|
||||||
height: 100%;
|
|
||||||
width: $samples-panel-width;
|
|
||||||
top: 0;
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
position: absolute;
|
left: $side-bar-width;
|
||||||
z-index: -1;
|
z-index: 1;
|
||||||
|
|
||||||
|
&-actual {
|
||||||
|
background: $samples-panel-bg-color;
|
||||||
|
left: 100% - $samples-panel-width;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: $right-panel-squash-breakpoint) {
|
@media (max-width: $right-panel-squash-breakpoint) {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -98,7 +116,8 @@
|
||||||
font-family: $headers-font, $headers-font-family;
|
font-family: $headers-font, $headers-font-family;
|
||||||
color: $secondary-color;
|
color: $secondary-color;
|
||||||
font-weight: $headers-font-weight;
|
font-weight: $headers-font-weight;
|
||||||
line-height: 1.4em;
|
line-height: 1.5;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +126,7 @@
|
||||||
h2 { font-size: $h2; }
|
h2 { font-size: $h2; }
|
||||||
h3 { font-size: $h3; }
|
h3 { font-size: $h3; }
|
||||||
h4 { font-size: $h4; }
|
h4 { font-size: $h4; }
|
||||||
h5 { font-size: $h5; }
|
h5 { font-size: $h5; line-height: 20px; }
|
||||||
|
|
||||||
p {
|
p {
|
||||||
font-family: $base-font, $base-font-family;
|
font-family: $base-font, $base-font-family;
|
||||||
|
|
|
@ -29,9 +29,11 @@ describe('Redoc components', () => {
|
||||||
optsMgr = _optsMgr;
|
optsMgr = _optsMgr;
|
||||||
|
|
||||||
specMgr = _specMgr;
|
specMgr = _specMgr;
|
||||||
return specMgr.load('/tests/schemas/extended-petstore.yml');
|
|
||||||
})));
|
})));
|
||||||
|
|
||||||
|
beforeEach(done => {
|
||||||
|
specMgr.load('/tests/schemas/extended-petstore.yml').then(done, done.fail);
|
||||||
|
})
|
||||||
|
|
||||||
it('should init component', () => {
|
it('should init component', () => {
|
||||||
let fixture = TestBed.createComponent(TestAppComponent);
|
let fixture = TestBed.createComponent(TestAppComponent);
|
||||||
|
|
|
@ -16,14 +16,15 @@ import { BaseComponent } from '../base';
|
||||||
import * as detectScollParent from 'scrollparent';
|
import * as detectScollParent from 'scrollparent';
|
||||||
|
|
||||||
import { SpecManager } from '../../utils/spec-manager';
|
import { SpecManager } from '../../utils/spec-manager';
|
||||||
import { OptionsService, Hash, MenuService, AppStateService } from '../../services/index';
|
import { OptionsService, Hash, AppStateService, SchemaHelper } from '../../services/index';
|
||||||
|
import { LazyTasksService } from '../../shared/components/LazyFor/lazy-for';
|
||||||
import { CustomErrorHandler } from '../../utils/';
|
import { CustomErrorHandler } from '../../utils/';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redoc',
|
selector: 'redoc',
|
||||||
templateUrl: './redoc.html',
|
templateUrl: './redoc.html',
|
||||||
styleUrls: ['./redoc.css'],
|
styleUrls: ['./redoc.css'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
//changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class Redoc extends BaseComponent implements OnInit {
|
export class Redoc extends BaseComponent implements OnInit {
|
||||||
static _preOptions: any;
|
static _preOptions: any;
|
||||||
|
@ -34,6 +35,8 @@ export class Redoc extends BaseComponent implements OnInit {
|
||||||
specLoaded: boolean;
|
specLoaded: boolean;
|
||||||
options: any;
|
options: any;
|
||||||
|
|
||||||
|
loadingProgress: number;
|
||||||
|
|
||||||
@Input() specUrl: string;
|
@Input() specUrl: string;
|
||||||
@HostBinding('class.loading') specLoading: boolean = false;
|
@HostBinding('class.loading') specLoading: boolean = false;
|
||||||
@HostBinding('class.loading-remove') specLoadingRemove: boolean = false;
|
@HostBinding('class.loading-remove') specLoadingRemove: boolean = false;
|
||||||
|
@ -43,9 +46,12 @@ export class Redoc extends BaseComponent implements OnInit {
|
||||||
optionsMgr: OptionsService,
|
optionsMgr: OptionsService,
|
||||||
elementRef: ElementRef,
|
elementRef: ElementRef,
|
||||||
private changeDetector: ChangeDetectorRef,
|
private changeDetector: ChangeDetectorRef,
|
||||||
private appState: AppStateService
|
private appState: AppStateService,
|
||||||
|
private lazyTasksService: LazyTasksService,
|
||||||
|
private hash: Hash
|
||||||
) {
|
) {
|
||||||
super(specMgr);
|
super(specMgr);
|
||||||
|
SchemaHelper.setSpecManager(specMgr);
|
||||||
// merge options passed before init
|
// merge options passed before init
|
||||||
optionsMgr.options = Redoc._preOptions || {};
|
optionsMgr.options = Redoc._preOptions || {};
|
||||||
|
|
||||||
|
@ -56,14 +62,22 @@ export class Redoc extends BaseComponent implements OnInit {
|
||||||
if (scrollParent === DOM.defaultDoc().body) scrollParent = window;
|
if (scrollParent === DOM.defaultDoc().body) scrollParent = window;
|
||||||
optionsMgr.options.$scrollParent = scrollParent;
|
optionsMgr.options.$scrollParent = scrollParent;
|
||||||
this.options = optionsMgr.options;
|
this.options = optionsMgr.options;
|
||||||
|
this.lazyTasksService.allSync = !this.options.lazyRendering;
|
||||||
}
|
}
|
||||||
|
|
||||||
hideLoadingAnimation() {
|
hideLoadingAnimation() {
|
||||||
this.specLoadingRemove = true;
|
requestAnimationFrame(() => {
|
||||||
setTimeout(() => {
|
|
||||||
this.specLoadingRemove = true;
|
this.specLoadingRemove = true;
|
||||||
this.specLoading = false;
|
setTimeout(() => {
|
||||||
}, 400);
|
this.specLoadingRemove = false;
|
||||||
|
this.specLoading = false;
|
||||||
|
}, 400);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoadingAnimation() {
|
||||||
|
this.specLoading = true;
|
||||||
|
this.specLoadingRemove = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
load() {
|
load() {
|
||||||
|
@ -71,19 +85,30 @@ export class Redoc extends BaseComponent implements OnInit {
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.appState.loading.subscribe(loading => {
|
||||||
|
if (loading) {
|
||||||
|
this.showLoadingAnimation();
|
||||||
|
} else {
|
||||||
|
this.hideLoadingAnimation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.specMgr.spec.subscribe((spec) => {
|
this.specMgr.spec.subscribe((spec) => {
|
||||||
if (!spec) {
|
if (!spec) {
|
||||||
this.specLoading = true;
|
this.appState.startLoading();
|
||||||
this.specLoaded = false;
|
|
||||||
} else {
|
} else {
|
||||||
this.specLoaded = true;
|
|
||||||
this.hideLoadingAnimation();
|
|
||||||
this.changeDetector.markForCheck();
|
this.changeDetector.markForCheck();
|
||||||
|
this.changeDetector.detectChanges();
|
||||||
|
this.specLoaded = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.hash.start();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.lazyTasksService.loadProgress.subscribe(progress => this.loadingProgress = progress)
|
||||||
this.appState.error.subscribe(_err => {
|
this.appState.error.subscribe(_err => {
|
||||||
if (!_err) return;
|
if (!_err) return;
|
||||||
|
|
||||||
|
|
|
@ -6,31 +6,26 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
display: block;
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.3s ease;
|
transition: opacity 0.3s ease;
|
||||||
transform: translateY(100%);
|
transform: translateY(100%);
|
||||||
|
z-index: 3;
|
||||||
> span {
|
position: relative;
|
||||||
float: right;
|
height: 2em;
|
||||||
}
|
line-height: 2em;
|
||||||
|
padding-right: 10px;
|
||||||
|
text-align: right;
|
||||||
|
margin-top: -1em;
|
||||||
|
|
||||||
> span > a {
|
> span > a {
|
||||||
padding: 2px 10px;
|
padding: 2px 10px;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: darken($black, 4%);
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $black;
|
background-color: lighten($black, 15%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:after {
|
|
||||||
display: block;
|
|
||||||
content: '';
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-sample:hover > .action-buttons {
|
.code-sample:hover > .action-buttons {
|
||||||
|
@ -45,6 +40,7 @@ header {
|
||||||
color: $sample-panel-headers-color;
|
color: $sample-panel-headers-color;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host /deep/ > tabs > ul li {
|
:host /deep/ > tabs > ul li {
|
||||||
|
@ -53,7 +49,7 @@ header {
|
||||||
border-radius: $border-radius;
|
border-radius: $border-radius;
|
||||||
margin: 2px 0;
|
margin: 2px 0;
|
||||||
padding: 3px 10px 2px 10px;
|
padding: 3px 10px 2px 10px;
|
||||||
line-height: 1.25;
|
line-height: 16px;
|
||||||
color: $sample-panel-headers-color;
|
color: $sample-panel-headers-color;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|
|
@ -22,7 +22,7 @@ header {
|
||||||
margin: 2px 0;
|
margin: 2px 0;
|
||||||
padding: 2px 8px 3px 8px;
|
padding: 2px 8px 3px 8px;
|
||||||
color: $sample-panel-headers-color;
|
color: $sample-panel-headers-color;
|
||||||
line-height: 1.25;
|
line-height: 16px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
<!-- in case sample is not available for some reason -->
|
<!-- in case sample is not available for some reason -->
|
||||||
<pre *ngIf="sample == undefined"> Sample unavailable </pre>
|
<pre *ngIf="sample == undefined"> Sample unavailable </pre>
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<span> <a *ngIf="enableButtons" (click)="collapseAll()">Collapse all</a> </span>
|
<span copy-button [copyText]="sample" class="hint--top-left hint--inversed"> <a>Copy</a> </span>
|
||||||
<span> <a *ngIf="enableButtons" (click)="expandAll()">Expand all</a> </span>
|
<span> <a *ngIf="enableButtons" (click)="expandAll()">Expand all</a> </span>
|
||||||
<span copy-button [copyText]="sample | json" class="hint--top hint--inversed"> <a>Copy</a> </span>
|
<span> <a *ngIf="enableButtons" (click)="collapseAll()">Collapse all</a> </span>
|
||||||
</div>
|
</div>
|
||||||
<pre [innerHtml]="sample | jsonFormatter"></pre>
|
<pre [innerHtml]="sample | jsonFormatter"></pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,46 +13,26 @@ pre {
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
display: block;
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.3s ease;
|
transition: opacity 0.3s ease;
|
||||||
transform: translateY(100%);
|
transform: translateY(100%);
|
||||||
z-index: 1;
|
z-index: 3;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
height: 2em;
|
||||||
> span {
|
line-height: 2em;
|
||||||
float: right;
|
padding-right: 10px;
|
||||||
|
text-align: right;
|
||||||
&:last-child > a:before {
|
margin-top: -1em;
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> span > a {
|
> span > a {
|
||||||
padding: 2px 10px;
|
padding: 2px 10px;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:before {
|
|
||||||
content: '|';
|
|
||||||
display: inline-block;
|
|
||||||
transform: translateX(-10px);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $black;
|
background-color: lighten($black, 15%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:after {
|
|
||||||
display: block;
|
|
||||||
content: '';
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.snippet:hover .action-buttons {
|
.snippet:hover .action-buttons {
|
||||||
|
@ -135,6 +115,7 @@ pre {
|
||||||
|
|
||||||
li {
|
li {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hoverable {
|
.hoverable {
|
||||||
|
|
|
@ -10,10 +10,10 @@
|
||||||
<div *ngFor="let cat of categories; let idx = index" class="menu-cat">
|
<div *ngFor="let cat of categories; let idx = index" class="menu-cat">
|
||||||
|
|
||||||
<label class="menu-cat-header" (click)="activateAndScroll(idx, -1)" [hidden]="cat.headless"
|
<label class="menu-cat-header" (click)="activateAndScroll(idx, -1)" [hidden]="cat.headless"
|
||||||
[ngClass]="{active: cat.active}"> {{cat.name}}</label>
|
[ngClass]="{active: cat.active, disabled: !cat.ready }"> {{cat.name}}</label>
|
||||||
<ul class="menu-subitems" [@itemAnimation]="cat.active ? 'expanded' : 'collapsed'">
|
<ul class="menu-subitems" [@itemAnimation]="cat.active ? 'expanded' : 'collapsed'">
|
||||||
<li *ngFor="let method of cat.methods; trackBy:summary; let methIdx = index"
|
<li *ngFor="let method of cat.methods; trackBy:summary; let methIdx = index"
|
||||||
[ngClass]="{active: method.active}"
|
[ngClass]="{active: method.active, disabled: !method.ready}"
|
||||||
(click)="activateAndScroll(idx, methIdx)">
|
(click)="activateAndScroll(idx, methIdx)">
|
||||||
{{method.summary}}
|
{{method.summary}}
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -38,6 +38,11 @@ $mobile-menu-compact-breakpoint: 550px;
|
||||||
&[hidden] {
|
&[hidden] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.disabled, &.disabled:hover {
|
||||||
|
cursor: default;
|
||||||
|
color: lighten($text-color, 60%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-subitems {
|
.menu-subitems {
|
||||||
|
@ -72,6 +77,11 @@ $mobile-menu-compact-breakpoint: 550px;
|
||||||
& li.active {
|
& li.active {
|
||||||
background: darken($side-menu-active-bg-color, 6%);
|
background: darken($side-menu-active-bg-color, 6%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.disabled, &.disabled:hover {
|
||||||
|
cursor: default;
|
||||||
|
color: lighten($text-color, 60%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -19,23 +19,28 @@ let testOptions;
|
||||||
|
|
||||||
describe('Redoc components', () => {
|
describe('Redoc components', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
|
TestBed.configureTestingModule({ declarations: [ TestAppComponent, MethodsList ] });
|
||||||
});
|
});
|
||||||
describe('SideMenu Component', () => {
|
describe('SideMenu Component', () => {
|
||||||
let builder;
|
let builder;
|
||||||
let component;
|
let component;
|
||||||
let fixture;
|
let fixture;
|
||||||
|
let specMgr;
|
||||||
|
|
||||||
beforeEach(async(inject([SpecManager, OptionsService],
|
beforeEach(inject([SpecManager, OptionsService],
|
||||||
( specMgr, opts) => {
|
(_specMgr, opts) => {
|
||||||
|
|
||||||
testOptions = opts;
|
testOptions = opts;
|
||||||
testOptions.options = {
|
testOptions.options = {
|
||||||
scrollYOffset: () => 0,
|
scrollYOffset: () => 0,
|
||||||
$scrollParent: window
|
$scrollParent: window
|
||||||
};
|
};
|
||||||
return specMgr.load('/tests/schemas/extended-petstore.yml');
|
specMgr = _specMgr;
|
||||||
})));
|
}));
|
||||||
|
|
||||||
|
beforeEach(done => {
|
||||||
|
specMgr.load('/tests/schemas/extended-petstore.yml').then(done, done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(TestAppComponent);
|
fixture = TestBed.createComponent(TestAppComponent);
|
||||||
|
|
|
@ -38,6 +38,8 @@ export class SideMenu extends BaseComponent implements OnInit {
|
||||||
private $resourcesNav: any;
|
private $resourcesNav: any;
|
||||||
private $scrollParent: any;
|
private $scrollParent: any;
|
||||||
|
|
||||||
|
private firstChange = true;
|
||||||
|
|
||||||
constructor(specMgr:SpecManager, elementRef:ElementRef,
|
constructor(specMgr:SpecManager, elementRef:ElementRef,
|
||||||
private scrollService:ScrollService, private menuService:MenuService, private hash:Hash,
|
private scrollService:ScrollService, private menuService:MenuService, private hash:Hash,
|
||||||
optionsService:OptionsService, private detectorRef:ChangeDetectorRef) {
|
optionsService:OptionsService, private detectorRef:ChangeDetectorRef) {
|
||||||
|
@ -52,20 +54,37 @@ export class SideMenu extends BaseComponent implements OnInit {
|
||||||
this.menuService.changed.subscribe((evt) => this.changed(evt));
|
this.menuService.changed.subscribe((evt) => this.changed(evt));
|
||||||
}
|
}
|
||||||
|
|
||||||
changed({cat, item}) {
|
changed(newItem) {
|
||||||
this.activeCatCaption = cat.name || '';
|
if (newItem) {
|
||||||
this.activeItemCaption = item && item.summary || '';
|
let {cat, item} = newItem;
|
||||||
|
this.activeCatCaption = cat.name || '';
|
||||||
|
this.activeItemCaption = item && item.summary || '';
|
||||||
|
}
|
||||||
|
|
||||||
//safari doesn't update bindings if not run changeDetector manually :(
|
//safari doesn't update bindings if not run changeDetector manually :(
|
||||||
|
|
||||||
this.detectorRef.detectChanges();
|
this.detectorRef.detectChanges();
|
||||||
|
if (this.firstChange) {
|
||||||
|
this.scrollActiveIntoView();
|
||||||
|
this.firstChange = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
activateAndScroll(idx, methodIdx) {
|
scrollActiveIntoView() {
|
||||||
|
let $item = this.$element.querySelector('li.active, label.active');
|
||||||
|
if ($item) $item.scrollIntoView();
|
||||||
|
}
|
||||||
|
|
||||||
|
activateAndScroll(catIdx, methodIdx) {
|
||||||
if (this.mobileMode()) {
|
if (this.mobileMode()) {
|
||||||
this.toggleMobileNav();
|
this.toggleMobileNav();
|
||||||
}
|
}
|
||||||
this.menuService.activate(idx, methodIdx);
|
let menu = this.categories;
|
||||||
|
|
||||||
|
if (!menu[catIdx].ready) return;
|
||||||
|
if (menu[catIdx].methods && menu[catIdx].methods.length && (methodIdx >= 0) &&
|
||||||
|
!menu[catIdx].methods[methodIdx].ready) return;
|
||||||
|
|
||||||
|
this.menuService.activate(catIdx, methodIdx);
|
||||||
this.menuService.scrollToActive();
|
this.menuService.scrollToActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,13 +14,16 @@ import { MethodsList } from './MethodsList/methods-list';
|
||||||
import { Method } from './Method/method';
|
import { Method } from './Method/method';
|
||||||
import { Warnings } from './Warnings/warnings';
|
import { Warnings } from './Warnings/warnings';
|
||||||
import { SecurityDefinitions } from './SecurityDefinitions/security-definitions';
|
import { SecurityDefinitions } from './SecurityDefinitions/security-definitions';
|
||||||
|
import { LoadingBar } from './LoadingBar/loading-bar';
|
||||||
|
|
||||||
import { Redoc } from './Redoc/redoc';
|
import { Redoc } from './Redoc/redoc';
|
||||||
|
|
||||||
export const REDOC_DIRECTIVES = [
|
export const REDOC_DIRECTIVES = [
|
||||||
ApiInfo, ApiLogo, JsonSchema, JsonSchemaLazy, ParamsList, RequestSamples, ResponsesList,
|
ApiInfo, ApiLogo, JsonSchema, JsonSchemaLazy, ParamsList, RequestSamples, ResponsesList,
|
||||||
ResponsesSamples, SchemaSample, SideMenu, MethodsList, Method, Warnings, Redoc, SecurityDefinitions
|
ResponsesSamples, SchemaSample, SideMenu, MethodsList, Method, Warnings, Redoc, SecurityDefinitions,
|
||||||
|
LoadingBar
|
||||||
];
|
];
|
||||||
|
|
||||||
export { ApiInfo, ApiLogo, JsonSchema, JsonSchemaLazy, ParamsList, RequestSamples, ResponsesList,
|
export { ApiInfo, ApiLogo, JsonSchema, JsonSchemaLazy, ParamsList, RequestSamples, ResponsesList,
|
||||||
ResponsesSamples, SchemaSample, SideMenu, MethodsList, Method, Warnings, Redoc, SecurityDefinitions }
|
ResponsesSamples, SchemaSample, SideMenu, MethodsList, Method, Warnings, Redoc, SecurityDefinitions,
|
||||||
|
LoadingBar }
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import 'core-js/es7/reflect';
|
||||||
|
import 'zone.js/dist/zone';
|
||||||
|
|
||||||
import 'core-js/es6/symbol';
|
import 'core-js/es6/symbol';
|
||||||
import 'core-js/es6/object';
|
import 'core-js/es6/object';
|
||||||
import 'core-js/es6/function';
|
import 'core-js/es6/function';
|
||||||
|
@ -18,9 +21,6 @@ import 'core-js/es6/reflect';
|
||||||
// see issue https://github.com/AngularClass/angular2-webpack-starter/issues/709
|
// see issue https://github.com/AngularClass/angular2-webpack-starter/issues/709
|
||||||
// import 'core-js/es6/promise';
|
// import 'core-js/es6/promise';
|
||||||
|
|
||||||
import 'core-js/es7/reflect';
|
|
||||||
import 'zone.js/dist/zone';
|
|
||||||
|
|
||||||
// Typescript emit helpers polyfill
|
// Typescript emit helpers polyfill
|
||||||
import 'ts-helpers';
|
import 'ts-helpers';
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { NgModule, ErrorHandler } from '@angular/core';
|
import { NgModule, ErrorHandler } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
import { Redoc, SecurityDefinitions, REDOC_DIRECTIVES } from './components/index';
|
import { Redoc, SecurityDefinitions, Method, REDOC_DIRECTIVES } from './components/index';
|
||||||
import { REDOC_COMMON_DIRECTIVES, DynamicNg2Wrapper } from './shared/components/index';
|
import { REDOC_COMMON_DIRECTIVES, DynamicNg2Wrapper } from './shared/components/index';
|
||||||
import { REDOC_PIPES, KeysPipe } from './utils/pipes';
|
import { REDOC_PIPES, KeysPipe } from './utils/pipes';
|
||||||
import { CustomErrorHandler } from './utils/'
|
import { CustomErrorHandler } from './utils/'
|
||||||
|
import { LazyTasksService } from './shared/components/LazyFor/lazy-for';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
OptionsService,
|
OptionsService,
|
||||||
|
@ -22,7 +23,7 @@ import { SpecManager } from './utils/spec-manager';
|
||||||
imports: [ CommonModule ],
|
imports: [ CommonModule ],
|
||||||
declarations: [ REDOC_DIRECTIVES, REDOC_COMMON_DIRECTIVES, REDOC_PIPES ],
|
declarations: [ REDOC_DIRECTIVES, REDOC_COMMON_DIRECTIVES, REDOC_PIPES ],
|
||||||
bootstrap: [ Redoc ],
|
bootstrap: [ Redoc ],
|
||||||
entryComponents: [ SecurityDefinitions, DynamicNg2Wrapper ],
|
entryComponents: [ SecurityDefinitions, DynamicNg2Wrapper, Method ],
|
||||||
providers: [
|
providers: [
|
||||||
SpecManager,
|
SpecManager,
|
||||||
ScrollService,
|
ScrollService,
|
||||||
|
@ -33,6 +34,7 @@ import { SpecManager } from './utils/spec-manager';
|
||||||
AppStateService,
|
AppStateService,
|
||||||
ComponentParser,
|
ComponentParser,
|
||||||
ContentProjector,
|
ContentProjector,
|
||||||
|
LazyTasksService,
|
||||||
{ provide: ErrorHandler, useClass: CustomErrorHandler },
|
{ provide: ErrorHandler, useClass: CustomErrorHandler },
|
||||||
{ provide: COMPONENT_PARSER_ALLOWED, useValue: { 'security-definitions': SecurityDefinitions} }
|
{ provide: COMPONENT_PARSER_ALLOWED, useValue: { 'security-definitions': SecurityDefinitions} }
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,11 +1,24 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable, NgZone } from '@angular/core';
|
||||||
import { Subject } from 'rxjs/Subject';
|
import { Subject } from 'rxjs/Subject';
|
||||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||||
|
import { Injector } from '@angular/core';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppStateService {
|
export class AppStateService {
|
||||||
samplesLanguage = new Subject<string>();
|
samplesLanguage = new Subject<string>();
|
||||||
error = new BehaviorSubject<any>(null);
|
error = new BehaviorSubject<any>(null);
|
||||||
|
loading = new Subject<boolean>();
|
||||||
|
|
||||||
|
startLoading() {
|
||||||
|
this.loading.next(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopLoading() {
|
||||||
|
this.loading.next(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private injector: Injector, private zone: NgZone) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,20 +4,18 @@ import {
|
||||||
} from '@angular/core/testing';
|
} from '@angular/core/testing';
|
||||||
|
|
||||||
import { Hash } from './hash.service';
|
import { Hash } from './hash.service';
|
||||||
import { SpecManager } from '../utils/spec-manager';
|
|
||||||
|
|
||||||
describe('Hash Service', () => {
|
describe('Hash Service', () => {
|
||||||
let specMgr = new SpecManager();
|
|
||||||
let hashService;
|
let hashService;
|
||||||
|
|
||||||
beforeEach(inject([Hash], (_hash) => hashService = _hash));
|
beforeEach(inject([Hash], (_hash) => {
|
||||||
|
hashService = _hash;
|
||||||
|
}));
|
||||||
|
|
||||||
it('should trigger changed event after ReDoc bootstrapped', (done) => {
|
it('should trigger changed event when method start is called', () => {
|
||||||
spyOn(hashService.value, 'next').and.callThrough();
|
spyOn(hashService.value, 'next').and.stub();
|
||||||
specMgr.spec.next({});
|
hashService.start();
|
||||||
setTimeout(() => {
|
expect(hashService.value.next).toHaveBeenCalled();
|
||||||
expect(hashService.value.next).toHaveBeenCalled();
|
hashService.value.next.and.callThrough();
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,20 +3,16 @@ import { Injectable } from '@angular/core';
|
||||||
import { PlatformLocation } from '@angular/common';
|
import { PlatformLocation } from '@angular/common';
|
||||||
|
|
||||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||||
import { SpecManager } from '../utils/spec-manager';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class Hash {
|
export class Hash {
|
||||||
public value = new BehaviorSubject<string>('');
|
public value = new BehaviorSubject<string | null>(null);
|
||||||
constructor(private specMgr: SpecManager, private location: PlatformLocation) {
|
constructor(private location: PlatformLocation) {
|
||||||
this.bind();
|
this.bind();
|
||||||
|
}
|
||||||
|
|
||||||
this.specMgr.spec.subscribe((spec) => {
|
start() {
|
||||||
if (!spec) return;
|
this.value.next(this.hash);
|
||||||
setTimeout(() => {
|
|
||||||
this.value.next(this.hash);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get hash() {
|
get hash() {
|
||||||
|
|
|
@ -1,57 +1,52 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
inject,
|
inject,
|
||||||
async,
|
|
||||||
TestBed
|
TestBed
|
||||||
} from '@angular/core/testing';
|
} from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { MethodsList } from '../components/MethodsList/methods-list';
|
||||||
import { MenuService } from './menu.service';
|
import { MenuService } from './menu.service';
|
||||||
import { Hash } from './hash.service';
|
import { Hash } from './hash.service';
|
||||||
import { ScrollService } from './scroll.service';
|
import { LazyTasksService } from '../shared/components/LazyFor/lazy-for';
|
||||||
|
import { ScrollService } from './scroll.service';
|
||||||
|
import { SchemaHelper } from './schema-helper.service';
|
||||||
import { SpecManager } from '../utils/spec-manager';;
|
import { SpecManager } from '../utils/spec-manager';;
|
||||||
|
|
||||||
describe('Menu service', () => {
|
describe('Menu service', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
|
TestBed.configureTestingModule({ declarations: [ TestAppComponent, MethodsList ] });
|
||||||
});
|
});
|
||||||
|
|
||||||
let menu, hashService, scroll;
|
let menu, hashService, scroll, tasks;
|
||||||
let specMgr;
|
let specMgr;
|
||||||
|
|
||||||
beforeEach(async(inject([SpecManager, Hash, ScrollService],
|
beforeEach(inject([SpecManager, Hash, ScrollService, LazyTasksService],
|
||||||
( _specMgr, _hash, _scroll, _menu) => {
|
( _specMgr, _hash, _scroll, _tasks) => {
|
||||||
hashService = _hash;
|
hashService = _hash;
|
||||||
scroll = _scroll;
|
scroll = _scroll;
|
||||||
|
tasks = _tasks;
|
||||||
specMgr = _specMgr;
|
specMgr = _specMgr;
|
||||||
return specMgr.load('/tests/schemas/extended-petstore.yml');
|
SchemaHelper.setSpecManager(specMgr);
|
||||||
})));
|
}));
|
||||||
|
|
||||||
|
beforeEach(done => {
|
||||||
|
specMgr.load('/tests/schemas/extended-petstore.yml').then(done, done.fail);
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
menu = new MenuService(hashService, scroll, specMgr);
|
menu = TestBed.get(MenuService);
|
||||||
let fixture = TestBed.createComponent(TestAppComponent);
|
let fixture = TestBed.createComponent(TestAppComponent);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should run hashScroll when hash changed', (done) => {
|
|
||||||
spyOn(menu, 'hashScroll').and.callThrough();
|
|
||||||
hashService.value.subscribe((hash) => {
|
|
||||||
if (!hash) return;
|
|
||||||
expect(menu.hashScroll).toHaveBeenCalled();
|
|
||||||
menu.hashScroll.and.callThrough();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
hashService.value.next('nonFalsy');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should scroll to method when location hash is present [jp]', (done) => {
|
it('should scroll to method when location hash is present [jp]', (done) => {
|
||||||
let hash = '#tag/pet/paths/~1pet~1findByStatus/get';
|
let hash = '#tag/pet/paths/~1pet~1findByStatus/get';
|
||||||
spyOn(menu, 'hashScroll').and.callThrough();
|
spyOn(menu, 'scrollToActive').and.callThrough();
|
||||||
spyOn(window, 'scrollTo').and.stub();
|
spyOn(window, 'scrollTo').and.stub();
|
||||||
hashService.value.subscribe((hash) => {
|
hashService.value.subscribe((hash) => {
|
||||||
if (!hash) return;
|
if (!hash) return;
|
||||||
expect(menu.hashScroll).toHaveBeenCalled();
|
expect(menu.scrollToActive).toHaveBeenCalled();
|
||||||
let scrollY = (<jasmine.Spy>window.scrollTo).calls.argsFor(0)[1];
|
let scrollY = (<jasmine.Spy>window.scrollTo).calls.argsFor(0)[1];
|
||||||
expect(scrollY).toBeGreaterThan(0);
|
expect(scrollY).toBeGreaterThan(0);
|
||||||
(<jasmine.Spy>window.scrollTo).and.callThrough();
|
(<jasmine.Spy>window.scrollTo).and.callThrough();
|
||||||
|
@ -59,14 +54,14 @@ describe('Menu service', () => {
|
||||||
});
|
});
|
||||||
hashService.value.next(hash);
|
hashService.value.next(hash);
|
||||||
});
|
});
|
||||||
|
//
|
||||||
it('should scroll to method when location hash is present [operation]', (done) => {
|
it('should scroll to method when location hash is present [operation]', (done) => {
|
||||||
let hash = '#operation/getPetById';
|
let hash = '#operation/getPetById';
|
||||||
spyOn(menu, 'hashScroll').and.callThrough();
|
spyOn(menu, 'scrollToActive').and.callThrough();
|
||||||
spyOn(window, 'scrollTo').and.stub();
|
spyOn(window, 'scrollTo').and.stub();
|
||||||
hashService.value.subscribe((hash) => {
|
hashService.value.subscribe((hash) => {
|
||||||
if (!hash) return;
|
if (!hash) return;
|
||||||
expect(menu.hashScroll).toHaveBeenCalled();
|
expect(menu.scrollToActive).toHaveBeenCalled();
|
||||||
let scrollY = (<jasmine.Spy>window.scrollTo).calls.argsFor(0)[1];
|
let scrollY = (<jasmine.Spy>window.scrollTo).calls.argsFor(0)[1];
|
||||||
expect(scrollY).toBeGreaterThan(0);
|
expect(scrollY).toBeGreaterThan(0);
|
||||||
done();
|
done();
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
import { Injectable, EventEmitter } from '@angular/core';
|
import { Injectable, EventEmitter } from '@angular/core';
|
||||||
|
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||||
import { ScrollService, INVIEW_POSITION } from './scroll.service';
|
import { ScrollService, INVIEW_POSITION } from './scroll.service';
|
||||||
import { Hash } from './hash.service';
|
import { Hash } from './hash.service';
|
||||||
import { SpecManager } from '../utils/spec-manager';
|
import { SpecManager } from '../utils/spec-manager';
|
||||||
import { SchemaHelper, MenuCategory } from './schema-helper.service';
|
import { SchemaHelper, MenuCategory } from './schema-helper.service';
|
||||||
|
import { AppStateService } from './app-state.service';
|
||||||
|
import { LazyTasksService } from '../shared/components/LazyFor/lazy-for';
|
||||||
|
|
||||||
const CHANGE = {
|
const CHANGE = {
|
||||||
NEXT : 1,
|
NEXT : 1,
|
||||||
|
@ -14,13 +17,19 @@ const CHANGE = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MenuService {
|
export class MenuService {
|
||||||
changed: EventEmitter<any> = new EventEmitter();
|
changed: EventEmitter<any> = new EventEmitter();
|
||||||
|
ready: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||||
categories: Array<MenuCategory>;
|
categories: Array<MenuCategory>;
|
||||||
|
|
||||||
activeCatIdx: number = 0;
|
activeCatIdx: number = 0;
|
||||||
activeMethodIdx: number = -1;
|
activeMethodIdx: number = -1;
|
||||||
activeMethodPtr: string;
|
|
||||||
|
|
||||||
constructor(private hash:Hash, private scrollService:ScrollService, specMgr:SpecManager) {
|
constructor(
|
||||||
|
private hash:Hash,
|
||||||
|
private tasks: LazyTasksService,
|
||||||
|
private scrollService: ScrollService,
|
||||||
|
private appState: AppStateService,
|
||||||
|
specMgr:SpecManager
|
||||||
|
) {
|
||||||
this.hash = hash;
|
this.hash = hash;
|
||||||
this.categories = SchemaHelper.buildMenuTree(specMgr.schema);
|
this.categories = SchemaHelper.buildMenuTree(specMgr.schema);
|
||||||
|
|
||||||
|
@ -28,13 +37,45 @@ export class MenuService {
|
||||||
this.scrollUpdate(evt.isScrolledDown);
|
this.scrollUpdate(evt.isScrolledDown);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.changeActive(CHANGE.INITIAL);
|
//this.changeActive(CHANGE.INITIAL);
|
||||||
|
|
||||||
this.hash.value.subscribe((hash) => {
|
this.hash.value.subscribe((hash) => {
|
||||||
this.hashScroll(hash);
|
if (hash == undefined) return;
|
||||||
|
this.setActiveByHash(hash);
|
||||||
|
if (!this.tasks.empty) {
|
||||||
|
this.tasks.start(this.activeCatIdx, this.activeMethodIdx, this);
|
||||||
|
this.scrollService.setStickElement(this.getCurrentMethodEl());
|
||||||
|
if (hash) this.scrollToActive();
|
||||||
|
this.appState.stopLoading();
|
||||||
|
} else {
|
||||||
|
if (hash) this.scrollToActive();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enableItem(catIdx, methodIdx, skipUpdate = false) {
|
||||||
|
let cat = this.categories[catIdx];
|
||||||
|
cat.ready = true;
|
||||||
|
if (cat.methods.length) cat.methods[methodIdx].ready = true;
|
||||||
|
let prevCat = this.categories[catIdx - 1];
|
||||||
|
if (prevCat && !prevCat.ready && (prevCat.virtual || !prevCat.methods.length)) {
|
||||||
|
this.enableItem(catIdx - 1, -1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipUpdate) return;
|
||||||
|
this.changed.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
get activeMethodPtr() {
|
||||||
|
let cat = this.categories[this.activeCatIdx];
|
||||||
|
let ptr = null;
|
||||||
|
if (cat && cat.methods.length) {
|
||||||
|
let mtd = cat.methods[this.activeMethodIdx];
|
||||||
|
ptr = mtd && mtd.pointer || null;
|
||||||
|
}
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
scrollUpdate(isScrolledDown) {
|
scrollUpdate(isScrolledDown) {
|
||||||
let stable = false;
|
let stable = false;
|
||||||
while(!stable) {
|
while(!stable) {
|
||||||
|
@ -44,6 +85,7 @@ export class MenuService {
|
||||||
if(isScrolledDown) {
|
if(isScrolledDown) {
|
||||||
//&& elementInViewPos === INVIEW_POSITION.BELLOW
|
//&& elementInViewPos === INVIEW_POSITION.BELLOW
|
||||||
let $nextEl = this.getRelativeCatOrItem(1);
|
let $nextEl = this.getRelativeCatOrItem(1);
|
||||||
|
if (!$nextEl) return;
|
||||||
let nextInViewPos = this.scrollService.getElementPos($nextEl, true);
|
let nextInViewPos = this.scrollService.getElementPos($nextEl, true);
|
||||||
if (elementInViewPos === INVIEW_POSITION.BELLOW && nextInViewPos === INVIEW_POSITION.ABOVE) {
|
if (elementInViewPos === INVIEW_POSITION.BELLOW && nextInViewPos === INVIEW_POSITION.ABOVE) {
|
||||||
stable = this.changeActive(CHANGE.NEXT);
|
stable = this.changeActive(CHANGE.NEXT);
|
||||||
|
@ -93,6 +135,8 @@ export class MenuService {
|
||||||
}
|
}
|
||||||
|
|
||||||
activate(catIdx, methodIdx) {
|
activate(catIdx, methodIdx) {
|
||||||
|
if (catIdx < 0) return;
|
||||||
|
|
||||||
let menu = this.categories;
|
let menu = this.categories;
|
||||||
|
|
||||||
menu[this.activeCatIdx].active = false;
|
menu[this.activeCatIdx].active = false;
|
||||||
|
@ -105,12 +149,10 @@ export class MenuService {
|
||||||
this.activeCatIdx = catIdx;
|
this.activeCatIdx = catIdx;
|
||||||
this.activeMethodIdx = methodIdx;
|
this.activeMethodIdx = methodIdx;
|
||||||
menu[catIdx].active = true;
|
menu[catIdx].active = true;
|
||||||
this.activeMethodPtr = null;
|
|
||||||
let currentItem;
|
let currentItem;
|
||||||
if (menu[catIdx].methods.length && (methodIdx > -1)) {
|
if (menu[catIdx].methods.length && (methodIdx > -1)) {
|
||||||
currentItem = menu[catIdx].methods[methodIdx];
|
currentItem = menu[catIdx].methods[methodIdx];
|
||||||
currentItem.active = true;
|
currentItem.active = true;
|
||||||
this.activeMethodPtr = currentItem.pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.changed.next({cat: menu[catIdx], item: currentItem});
|
this.changed.next({cat: menu[catIdx], item: currentItem});
|
||||||
|
@ -156,24 +198,31 @@ export class MenuService {
|
||||||
this.scrollService.scrollTo(this.getCurrentMethodEl());
|
this.scrollService.scrollTo(this.getCurrentMethodEl());
|
||||||
}
|
}
|
||||||
|
|
||||||
hashScroll(hash) {
|
setActiveByHash(hash) {
|
||||||
if (!hash) return;
|
if (!hash) {
|
||||||
|
return;
|
||||||
let $el;
|
}
|
||||||
|
let catIdx, methodIdx;
|
||||||
hash = hash.substr(1);
|
hash = hash.substr(1);
|
||||||
let namespace = hash.split('/')[0];
|
let namespace = hash.split('/')[0];
|
||||||
let ptr = decodeURIComponent(hash.substr(namespace.length + 1));
|
let ptr = decodeURIComponent(hash.substr(namespace.length + 1));
|
||||||
if (namespace === 'operation') {
|
if (namespace === 'section' || namespace === 'tag') {
|
||||||
$el = this.getMethodElByOperId(ptr);
|
|
||||||
} else if (namespace === 'tag') {
|
|
||||||
let sectionId = ptr.split('/')[0];
|
let sectionId = ptr.split('/')[0];
|
||||||
|
catIdx = this.categories.findIndex(cat => cat.id === namespace + '/' + sectionId);
|
||||||
|
let cat = this.categories[catIdx];
|
||||||
ptr = ptr.substr(sectionId.length) || null;
|
ptr = ptr.substr(sectionId.length) || null;
|
||||||
sectionId = namespace + (sectionId ? '/' + sectionId : '');
|
methodIdx = cat.methods.findIndex(method => method.pointer === ptr);
|
||||||
$el = this.getMethodElByPtr(ptr, sectionId);
|
|
||||||
} else {
|
} else {
|
||||||
$el = this.getMethodElByPtr(null, namespace + '/' + ptr);
|
catIdx = this.categories.findIndex(cat => {
|
||||||
|
if (!cat.methods.length) return false;
|
||||||
|
methodIdx = cat.methods.findIndex(method => method.operationId === ptr || method.pointer === ptr);
|
||||||
|
if (methodIdx >= 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
this.activate(catIdx, methodIdx);
|
||||||
if ($el) this.scrollService.scrollTo($el);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,25 +8,43 @@ const defaults = {
|
||||||
disableLazySchemas: false
|
disableLazySchemas: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const OPTION_NAMES = new Set(['scrollYOffset', 'disableLazySchemas', 'specUrl', 'suppressWarnings', 'hideHostname']);
|
const OPTION_NAMES = new Set([
|
||||||
|
'scrollYOffset',
|
||||||
|
'disableLazySchemas',
|
||||||
|
'specUrl',
|
||||||
|
'suppressWarnings',
|
||||||
|
'hideHostname',
|
||||||
|
'lazyRendering'
|
||||||
|
]);
|
||||||
|
|
||||||
|
interface Options {
|
||||||
|
scrollYOffset?: any;
|
||||||
|
disableLazySchemas?: boolean;
|
||||||
|
specUrl?: string;
|
||||||
|
suppressWarnings?: boolean;
|
||||||
|
hideHostname?: boolean;
|
||||||
|
lazyRendering?: boolean;
|
||||||
|
$scrollParent?: HTMLElement | Window;
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OptionsService {
|
export class OptionsService {
|
||||||
private _options: any;
|
private _options: Options;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this._options = defaults;
|
this._options = defaults;
|
||||||
|
this._normalizeOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
get options() {
|
get options():Options {
|
||||||
return this._options;
|
return this._options;
|
||||||
}
|
}
|
||||||
|
|
||||||
set options(opts) {
|
set options(opts:Options) {
|
||||||
this._options = Object.assign(this._options, opts);
|
this._options = Object.assign(this._options, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
parseOptions(el) {
|
parseOptions(el:HTMLElement):void {
|
||||||
let parsedOpts;
|
let parsedOpts;
|
||||||
let attributesMap = DOM.attributeMap(el);
|
let attributesMap = DOM.attributeMap(el);
|
||||||
parsedOpts = {};
|
parsedOpts = {};
|
||||||
|
@ -46,7 +64,7 @@ export class OptionsService {
|
||||||
this._normalizeOptions();
|
this._normalizeOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
_normalizeOptions() {
|
_normalizeOptions():void {
|
||||||
// modify scrollYOffset to always be a function
|
// modify scrollYOffset to always be a function
|
||||||
if (!isFunction(this._options.scrollYOffset)) {
|
if (!isFunction(this._options.scrollYOffset)) {
|
||||||
if (isFinite(this._options.scrollYOffset)) {
|
if (isFinite(this._options.scrollYOffset)) {
|
||||||
|
@ -70,5 +88,6 @@ export class OptionsService {
|
||||||
if (isString(this._options.disableLazySchemas)) this._options.disableLazySchemas = true;
|
if (isString(this._options.disableLazySchemas)) this._options.disableLazySchemas = true;
|
||||||
if (isString(this._options.suppressWarnings)) this._options.suppressWarnings = true;
|
if (isString(this._options.suppressWarnings)) this._options.suppressWarnings = true;
|
||||||
if (isString(this._options.hideHostname)) this._options.hideHostname = true;
|
if (isString(this._options.hideHostname)) this._options.hideHostname = true;
|
||||||
|
if (isString(this._options.lazyRendering)) this._options.lazyRendering = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
import { JsonPointer } from '../utils/JsonPointer';
|
import { JsonPointer } from '../utils/JsonPointer';
|
||||||
import { SpecManager } from '../utils/spec-manager';
|
|
||||||
import { methods as swaggerMethods, keywordTypes } from '../utils/swagger-defs';
|
import { methods as swaggerMethods, keywordTypes } from '../utils/swagger-defs';
|
||||||
import { WarningsService } from './warnings.service';
|
import { WarningsService } from './warnings.service';
|
||||||
import * as slugify from 'slugify';
|
import * as slugify from 'slugify';
|
||||||
|
@ -15,6 +14,8 @@ export interface MenuMethod {
|
||||||
summary: string;
|
summary: string;
|
||||||
tag: string;
|
tag: string;
|
||||||
pointer: string;
|
pointer: string;
|
||||||
|
operationId: string;
|
||||||
|
ready: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MenuCategory {
|
export interface MenuCategory {
|
||||||
|
@ -26,8 +27,12 @@ export interface MenuCategory {
|
||||||
description?: string;
|
description?: string;
|
||||||
empty?: string;
|
empty?: string;
|
||||||
virtual?: boolean;
|
virtual?: boolean;
|
||||||
|
ready: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// global var for this module
|
||||||
|
var specMgrInstance;
|
||||||
|
|
||||||
const injectors = {
|
const injectors = {
|
||||||
notype: {
|
notype: {
|
||||||
check: (propertySchema) => !propertySchema.type,
|
check: (propertySchema) => !propertySchema.type,
|
||||||
|
@ -186,8 +191,8 @@ const injectors = {
|
||||||
parentPtr = JsonPointer.dirName(hostPointer, 3);
|
parentPtr = JsonPointer.dirName(hostPointer, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
let parentParam = SpecManager.instance().byPointer(parentPtr);
|
let parentParam = specMgrInstance.byPointer(parentPtr);
|
||||||
let root = SpecManager.instance().schema;
|
let root =specMgrInstance.schema;
|
||||||
injectTo._produces = parentParam && parentParam.produces || root.produces;
|
injectTo._produces = parentParam && parentParam.produces || root.produces;
|
||||||
injectTo._consumes = parentParam && parentParam.consumes || root.consumes;
|
injectTo._consumes = parentParam && parentParam.consumes || root.consumes;
|
||||||
injectTo._widgetType = 'file';
|
injectTo._widgetType = 'file';
|
||||||
|
@ -196,6 +201,10 @@ const injectors = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export class SchemaHelper {
|
export class SchemaHelper {
|
||||||
|
static setSpecManager(specMgr) {
|
||||||
|
specMgrInstance = specMgr;
|
||||||
|
}
|
||||||
|
|
||||||
static preprocess(schema, pointer, hostPointer?) {
|
static preprocess(schema, pointer, hostPointer?) {
|
||||||
//propertySchema = Object.assign({}, propertySchema);
|
//propertySchema = Object.assign({}, propertySchema);
|
||||||
if (schema['x-redoc-schema-precompiled']) {
|
if (schema['x-redoc-schema-precompiled']) {
|
||||||
|
@ -291,13 +300,15 @@ export class SchemaHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
static buildMenuTree(schema):Array<MenuCategory> {
|
static buildMenuTree(schema):Array<MenuCategory> {
|
||||||
|
var catIdx = 0;
|
||||||
let tag2MethodMapping = {};
|
let tag2MethodMapping = {};
|
||||||
|
|
||||||
for (let header of (<Array<string>>(schema.info && schema.info['x-redoc-markdown-headers'] || []))) {
|
for (let header of (<Array<string>>(schema.info && schema.info['x-redoc-markdown-headers'] || []))) {
|
||||||
let id = 'section/' + slugify(header);
|
let id = 'section/' + slugify(header);
|
||||||
tag2MethodMapping[id] = {
|
tag2MethodMapping[id] = {
|
||||||
name: header, id: id, virtual: true, methods: []
|
name: header, id: id, virtual: true, methods: [], idx: catIdx
|
||||||
};
|
};
|
||||||
|
catIdx++;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let tag of schema.tags || []) {
|
for (let tag of schema.tags || []) {
|
||||||
|
@ -309,7 +320,9 @@ export class SchemaHelper {
|
||||||
headless: tag.name === '',
|
headless: tag.name === '',
|
||||||
empty: !!tag['x-traitTag'],
|
empty: !!tag['x-traitTag'],
|
||||||
methods: [],
|
methods: [],
|
||||||
|
idx: catIdx
|
||||||
};
|
};
|
||||||
|
catIdx++;
|
||||||
}
|
}
|
||||||
|
|
||||||
let paths = schema.paths;
|
let paths = schema.paths;
|
||||||
|
@ -331,9 +344,11 @@ export class SchemaHelper {
|
||||||
tagDetails = {
|
tagDetails = {
|
||||||
name: tag,
|
name: tag,
|
||||||
id: id,
|
id: id,
|
||||||
headless: tag === ''
|
headless: tag === '',
|
||||||
|
idx: catIdx
|
||||||
};
|
};
|
||||||
tag2MethodMapping[id] = tagDetails;
|
tag2MethodMapping[id] = tagDetails;
|
||||||
|
catIdx++;
|
||||||
}
|
}
|
||||||
if (tagDetails.empty) continue;
|
if (tagDetails.empty) continue;
|
||||||
if (!tagDetails.methods) tagDetails.methods = [];
|
if (!tagDetails.methods) tagDetails.methods = [];
|
||||||
|
@ -341,7 +356,9 @@ export class SchemaHelper {
|
||||||
pointer: methodPointer,
|
pointer: methodPointer,
|
||||||
summary: methodSummary,
|
summary: methodSummary,
|
||||||
operationId: methodInfo.operationId,
|
operationId: methodInfo.operationId,
|
||||||
tag: tag
|
tag: tag,
|
||||||
|
idx: tagDetails.methods.length,
|
||||||
|
catIdx: tagDetails.idx
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
import { Injectable, EventEmitter, Output } from '@angular/core';
|
import { Injectable, EventEmitter } from '@angular/core';
|
||||||
import { BrowserDomAdapter as DOM } from '../utils/browser-adapter';
|
import { BrowserDomAdapter as DOM } from '../utils/browser-adapter';
|
||||||
import { OptionsService } from './options.service';
|
import { OptionsService } from './options.service';
|
||||||
import { throttle } from '../utils/helpers';
|
import { throttle } from '../utils/helpers';
|
||||||
|
@ -14,14 +14,19 @@ export const INVIEW_POSITION = {
|
||||||
export class ScrollService {
|
export class ScrollService {
|
||||||
scrollYOffset: any;
|
scrollYOffset: any;
|
||||||
$scrollParent: any;
|
$scrollParent: any;
|
||||||
@Output() scroll = new EventEmitter();
|
scroll = new EventEmitter();
|
||||||
private prevOffsetY: number;
|
private prevOffsetY: number;
|
||||||
private _cancel:any;
|
private _cancel:any;
|
||||||
constructor(optionsService:OptionsService) {
|
private _savedPosition:number;
|
||||||
|
private _stickElement: HTMLElement;
|
||||||
|
constructor(private optionsService:OptionsService) {
|
||||||
this.scrollYOffset = () => optionsService.options.scrollYOffset();
|
this.scrollYOffset = () => optionsService.options.scrollYOffset();
|
||||||
this.$scrollParent = optionsService.options.$scrollParent;
|
this.$scrollParent = optionsService.options.$scrollParent || window;
|
||||||
this.scroll = new EventEmitter();
|
this.scroll = new EventEmitter();
|
||||||
this.bind();
|
this.bind();
|
||||||
|
if ('scrollRestoration' in history) {
|
||||||
|
history.scrollRestoration = 'manual';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollY() {
|
scrollY() {
|
||||||
|
@ -42,20 +47,45 @@ export class ScrollService {
|
||||||
return INVIEW_POSITION.INVIEW;
|
return INVIEW_POSITION.INVIEW;
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollTo($el, offset:number = 0) {
|
scrollToPos(posY: number) {
|
||||||
// TODO: rewrite this to use offsetTop as more reliable solution
|
|
||||||
let subjRect = $el.getBoundingClientRect();
|
|
||||||
let posY = this.scrollY() + subjRect.top - this.scrollYOffset() + offset + 1;
|
|
||||||
if (this.$scrollParent.scrollTo) {
|
if (this.$scrollParent.scrollTo) {
|
||||||
this.$scrollParent.scrollTo(0, posY);
|
this.$scrollParent.scrollTo(0, Math.floor(posY));
|
||||||
} else {
|
} else {
|
||||||
this.$scrollParent.scrollTop = posY;
|
this.$scrollParent.scrollTop = posY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
scrollTo($el, offset:number = 0) {
|
||||||
|
if (!$el) return;
|
||||||
|
// TODO: rewrite this to use offsetTop as more reliable solution
|
||||||
|
let subjRect = $el.getBoundingClientRect();
|
||||||
|
let posY = this.scrollY() + subjRect.top - this.scrollYOffset() + offset + 1;
|
||||||
|
this.scrollToPos(posY);
|
||||||
|
return posY;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveScroll() {
|
||||||
|
let $el = this._stickElement;
|
||||||
|
if (!$el) return;
|
||||||
|
let offsetParent = $el.offsetParent;
|
||||||
|
this._savedPosition = $el.offsetTop + (<any>offsetParent).offsetTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
setStickElement($el) {
|
||||||
|
this._stickElement = $el;
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreScroll() {
|
||||||
|
let $el = this._stickElement;
|
||||||
|
if (!$el) return;
|
||||||
|
let offsetParent = $el.offsetParent;
|
||||||
|
let currentPosition = $el.offsetTop + (<any>offsetParent).offsetTop;
|
||||||
|
let newY = this.scrollY() + (currentPosition - this._savedPosition);
|
||||||
|
this.scrollToPos(newY);
|
||||||
|
}
|
||||||
|
|
||||||
relativeScrollPos($el) {
|
relativeScrollPos($el) {
|
||||||
let subjRect = $el.getBoundingClientRect();
|
let subjRect = $el.getBoundingClientRect();
|
||||||
return - subjRect.top + this.scrollYOffset() - 1;
|
return -subjRect.top + this.scrollYOffset() - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollHandler(evt) {
|
scrollHandler(evt) {
|
||||||
|
|
|
@ -27,7 +27,7 @@ export class CopyButton implements OnInit {
|
||||||
onClick() {
|
onClick() {
|
||||||
let copied;
|
let copied;
|
||||||
if (this.copyText) {
|
if (this.copyText) {
|
||||||
copied = Clipboard.copyCustom(this.copyText);
|
copied = Clipboard.copyCustom(JSON.stringify(this.copyText));
|
||||||
} else {
|
} else {
|
||||||
copied = Clipboard.copyElement(this.copyElement);
|
copied = Clipboard.copyElement(this.copyElement);
|
||||||
}
|
}
|
||||||
|
|
172
lib/shared/components/LazyFor/lazy-for.ts
Normal file
172
lib/shared/components/LazyFor/lazy-for.ts
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Directive,
|
||||||
|
Input,
|
||||||
|
TemplateRef,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
ViewContainerRef,
|
||||||
|
Injectable,
|
||||||
|
NgZone
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
|
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||||
|
|
||||||
|
import { ScrollService } from '../../../services/scroll.service';
|
||||||
|
import { OptionsService } from '../../../services/options.service';
|
||||||
|
|
||||||
|
import { isSafari } from '../../../utils/helpers';
|
||||||
|
|
||||||
|
export class LazyForRow {
|
||||||
|
constructor(public $implicit: any, public index: number, public show: boolean) {}
|
||||||
|
|
||||||
|
get first(): boolean { return this.index === 0; }
|
||||||
|
|
||||||
|
get even(): boolean { return this.index % 2 === 0; }
|
||||||
|
|
||||||
|
get odd(): boolean { return !this.even; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LazyTasksService {
|
||||||
|
private _tasks = [];
|
||||||
|
private _current: number = 0;
|
||||||
|
private _syncCount: number = 0;
|
||||||
|
private menuService;
|
||||||
|
|
||||||
|
public loadProgress = new BehaviorSubject<number>(0);
|
||||||
|
public allSync = false;
|
||||||
|
constructor(public optionsService: OptionsService, private zone: NgZone) {
|
||||||
|
}
|
||||||
|
|
||||||
|
get empty() {
|
||||||
|
return this._current === this._tasks.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
set syncCount(n: number) {
|
||||||
|
this._syncCount = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
set lazy(sync:boolean) {
|
||||||
|
this.allSync = sync;
|
||||||
|
}
|
||||||
|
|
||||||
|
addTasks(tasks:any[], callback:Function) {
|
||||||
|
tasks.forEach((task) => {
|
||||||
|
let taskCopy = Object.assign({_callback: callback}, task);
|
||||||
|
this._tasks.push(taskCopy);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTaskSync() {
|
||||||
|
let task = this._tasks[this._current];
|
||||||
|
if (!task) return;
|
||||||
|
task._callback(task.idx, true);
|
||||||
|
this._current++;
|
||||||
|
this.menuService.enableItem(task.catIdx, task.idx);
|
||||||
|
this.loadProgress.next(this._current / this._tasks.length * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTask() {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
let task = this._tasks[this._current];
|
||||||
|
if (!task) return;
|
||||||
|
task._callback(task.idx, false).then(() => {
|
||||||
|
this._current++;
|
||||||
|
this.menuService.enableItem(task.catIdx, task.idx);
|
||||||
|
setTimeout(()=> this.nextTask());
|
||||||
|
this.loadProgress.next(this._current / this._tasks.length * 100);
|
||||||
|
}).catch(err => console.error(err));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sortTasks(catIdx, metIdx) {
|
||||||
|
let idxMap = {};
|
||||||
|
this._tasks.forEach((task, idx) => {
|
||||||
|
idxMap[task.catIdx + '_' + task.idx] = idx;
|
||||||
|
});
|
||||||
|
metIdx = metIdx < 0 ? 0 : metIdx;
|
||||||
|
let destIdx = idxMap[catIdx + '_' + metIdx] || 0;
|
||||||
|
this._tasks.sort((a, b) => {
|
||||||
|
let aIdx = idxMap[a.catIdx + '_' + a.idx];
|
||||||
|
let bIdx = idxMap[b.catIdx + '_' + b.idx];
|
||||||
|
return Math.abs(aIdx - destIdx) - Math.abs(bIdx - destIdx);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
start(catIdx, metIdx, menuService) {
|
||||||
|
this.menuService = menuService;
|
||||||
|
let syncCount = 5;
|
||||||
|
// I know this is bad practice to detect browsers but there is an issue on Safari only
|
||||||
|
// http://stackoverflow.com/questions/40692365/maintaining-scroll-position-while-inserting-elements-above-glitching-only-in-sa
|
||||||
|
if (isSafari && this.optionsService.options.$scrollParent === window) {
|
||||||
|
syncCount = (metIdx >= 0) ?
|
||||||
|
this._tasks.findIndex(task => (task.catIdx === catIdx) && (task.idx === metIdx))
|
||||||
|
: this._tasks.findIndex(task => task.catIdx === catIdx);
|
||||||
|
syncCount += 1;
|
||||||
|
} else {
|
||||||
|
this.sortTasks(catIdx, metIdx);
|
||||||
|
}
|
||||||
|
if (this.allSync) syncCount = this._tasks.length;
|
||||||
|
for (var i = this._current; i < syncCount; i++) {
|
||||||
|
this.nextTaskSync();
|
||||||
|
}
|
||||||
|
this.nextTask();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LazyTasksServiceSync extends LazyTasksService {
|
||||||
|
constructor(optionsService: OptionsService, zone: NgZone) {
|
||||||
|
super(optionsService, zone);
|
||||||
|
this.allSync = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[lazyFor][lazyForOf]'
|
||||||
|
})
|
||||||
|
export class LazyFor {
|
||||||
|
@Input() lazyForOf: any;
|
||||||
|
|
||||||
|
prevIdx = null;
|
||||||
|
|
||||||
|
private _viewRef;
|
||||||
|
constructor(
|
||||||
|
public _template: TemplateRef<LazyForRow>,
|
||||||
|
public cdr: ChangeDetectorRef,
|
||||||
|
public _viewContainer: ViewContainerRef,
|
||||||
|
public lazyTasks: LazyTasksService,
|
||||||
|
public scroll: ScrollService
|
||||||
|
){
|
||||||
|
}
|
||||||
|
|
||||||
|
nextIteration(idx: number, sync: boolean):Promise<void> {
|
||||||
|
const view = this._viewContainer.createEmbeddedView(
|
||||||
|
this._template, new LazyForRow(this.lazyForOf[idx], idx, sync), idx < this.prevIdx ? 0 : undefined);
|
||||||
|
this.prevIdx = idx;
|
||||||
|
view.context.index = idx;
|
||||||
|
(<any>view as ChangeDetectorRef).markForCheck();
|
||||||
|
(<any>view as ChangeDetectorRef).detectChanges();
|
||||||
|
if (sync) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.scroll.saveScroll();
|
||||||
|
|
||||||
|
view.context.show = true;
|
||||||
|
(<any>view as ChangeDetectorRef).markForCheck();
|
||||||
|
(<any>view as ChangeDetectorRef).detectChanges();
|
||||||
|
|
||||||
|
this.scroll.restoreScroll();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.lazyTasks.addTasks(this.lazyForOf, this.nextIteration.bind(this))
|
||||||
|
}
|
||||||
|
}
|
|
@ -83,7 +83,7 @@ export class StickySidebar implements OnInit, OnDestroy {
|
||||||
// FIXME use more reliable code
|
// FIXME use more reliable code
|
||||||
this.$redocEl = this.$element.offsetParent.parentNode || DOM.defaultDoc().body;
|
this.$redocEl = this.$element.offsetParent.parentNode || DOM.defaultDoc().body;
|
||||||
this.bind();
|
this.bind();
|
||||||
setTimeout(() => this.updatePosition());
|
requestAnimationFrame(() => this.updatePosition());
|
||||||
//this.updatePosition()
|
//this.updatePosition()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,9 @@ export class Tabs implements OnInit {
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
styles: [`
|
styles: [`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
.tab-wrap {
|
.tab-wrap {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,8 @@ $zippy-redirect-bg-color: rgba($zippy-redirect-color, .08);
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
// performance optimization
|
// performance optimization
|
||||||
transform: translate3d(0, 0, 0);
|
// transform: translate3d(0, 0, 0);
|
||||||
backface-visibility: hidden;
|
// backface-visibility: hidden;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,11 @@ import { Zippy } from './Zippy/zippy';
|
||||||
import { CopyButton } from './CopyButton/copy-button.directive';
|
import { CopyButton } from './CopyButton/copy-button.directive';
|
||||||
import { SelectOnClick } from './SelectOnClick/select-on-click.directive';
|
import { SelectOnClick } from './SelectOnClick/select-on-click.directive';
|
||||||
import { DynamicNg2Viewer, DynamicNg2Wrapper } from './DynamicNg2Viewer/dynamic-ng2-viewer.component';
|
import { DynamicNg2Viewer, DynamicNg2Wrapper } from './DynamicNg2Viewer/dynamic-ng2-viewer.component';
|
||||||
|
import { LazyFor, LazyTasksService, LazyTasksServiceSync } from './LazyFor/lazy-for';
|
||||||
|
|
||||||
export const REDOC_COMMON_DIRECTIVES = [
|
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 }
|
||||||
|
|
|
@ -22,7 +22,7 @@ $base-font: Roboto;
|
||||||
$base-font-family: sans-serif;
|
$base-font-family: sans-serif;
|
||||||
$base-font-weight: $light;
|
$base-font-weight: $light;
|
||||||
$base-font-size: 1em;
|
$base-font-size: 1em;
|
||||||
$base-line-height: 1.55em;
|
$base-line-height: 1.5em;
|
||||||
$text-color: $black;
|
$text-color: $black;
|
||||||
|
|
||||||
// Heading Font
|
// Heading Font
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -13,24 +13,10 @@ export class SpecManager {
|
||||||
public basePath: string;
|
public basePath: string;
|
||||||
|
|
||||||
public spec = new BehaviorSubject<any|null>(null);
|
public spec = new BehaviorSubject<any|null>(null);
|
||||||
private _instance: any;
|
|
||||||
private _url: string;
|
private _url: string;
|
||||||
private parser: any;
|
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) {
|
load(urlOrObject: string|Object) {
|
||||||
this.schema = null;
|
|
||||||
let promise = new Promise((resolve, reject) => {
|
let promise = new Promise((resolve, reject) => {
|
||||||
this.parser = new JsonSchemaRefParser();
|
this.parser = new JsonSchemaRefParser();
|
||||||
this.parser.bundle(urlOrObject, {http: {withCredentials: false}})
|
this.parser.bundle(urlOrObject, {http: {withCredentials: false}})
|
||||||
|
|
7
manual-types/index.d.ts
vendored
7
manual-types/index.d.ts
vendored
|
@ -19,4 +19,11 @@ declare var AOT: any;
|
||||||
interface ErrorStackTraceLimit {
|
interface ErrorStackTraceLimit {
|
||||||
stackTraceLimit: number;
|
stackTraceLimit: number;
|
||||||
}
|
}
|
||||||
|
interface History {
|
||||||
|
scrollRestoration: "auto"|"manual";
|
||||||
|
}
|
||||||
|
interface Window {
|
||||||
|
HTMLElement: any
|
||||||
|
}
|
||||||
|
declare var safari: any;
|
||||||
interface ErrorConstructor extends ErrorStackTraceLimit {}
|
interface ErrorConstructor extends ErrorStackTraceLimit {}
|
||||||
|
|
|
@ -48,6 +48,7 @@ beforeEach(function() {
|
||||||
services.OptionsService,
|
services.OptionsService,
|
||||||
services.ComponentParser,
|
services.ComponentParser,
|
||||||
services.ContentProjector,
|
services.ContentProjector,
|
||||||
|
{ provide: sharedComponents.LazyTasksService, useClass: sharedComponents.LazyTasksServiceSync },
|
||||||
{ provide: ErrorHandler, useClass: services.CustomErrorHandler },
|
{ provide: ErrorHandler, useClass: services.CustomErrorHandler },
|
||||||
{ provide: services.COMPONENT_PARSER_ALLOWED, useValue: { 'security-definitions': components.SecurityDefinitions }}
|
{ 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/);
|
var testContext = require.context('..', true, /\.spec\.ts/);
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,6 @@ describe('Utils', () => {
|
||||||
specMgr = new SpecManager();
|
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', ()=> {
|
it('load should return a promise', ()=> {
|
||||||
specMgr.load('/tests/schemas/extended-petstore.yml').should.be.instanceof(Promise);
|
specMgr.load('/tests/schemas/extended-petstore.yml').should.be.instanceof(Promise);
|
||||||
});
|
});
|
||||||
|
@ -35,15 +30,10 @@ describe('Utils', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Schema manager basic functionality', ()=> {
|
describe('Schema manager basic functionality', ()=> {
|
||||||
beforeAll(function (done) {
|
beforeEach(function (done) {
|
||||||
specMgr.load('/tests/schemas/extended-petstore.yml').then(() => {
|
specMgr.load('/tests/schemas/extended-petstore.yml').then(done, done.fail);
|
||||||
done();
|
|
||||||
}, () => {
|
|
||||||
throw new Error('Error handler should not be called');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should contain non-empty schema', ()=> {
|
it('should contain non-empty schema', ()=> {
|
||||||
specMgr.schema.should.be.an.Object();
|
specMgr.schema.should.be.an.Object();
|
||||||
specMgr.schema.should.be.not.empty();
|
specMgr.schema.should.be.not.empty();
|
||||||
|
@ -68,9 +58,9 @@ describe('Utils', () => {
|
||||||
|
|
||||||
it('should substitute api host when spec host is undefined', () => {
|
it('should substitute api host when spec host is undefined', () => {
|
||||||
specMgr._schema.host = undefined;
|
specMgr._schema.host = undefined;
|
||||||
specMgr._url = 'https://petstore.swagger.io/v2';
|
specMgr._url = 'http://petstore.swagger.io/v2';
|
||||||
specMgr.init();
|
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', () => {
|
describe('byPointer method', () => {
|
||||||
|
@ -88,7 +78,7 @@ describe('Utils', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getTagsMap method', () => {
|
describe('getTagsMap method', () => {
|
||||||
beforeAll(function () {
|
beforeEach(function () {
|
||||||
specMgr._schema = {
|
specMgr._schema = {
|
||||||
tags: [
|
tags: [
|
||||||
{name: 'tag1', description: 'info1'},
|
{name: 'tag1', description: 'info1'},
|
||||||
|
@ -114,12 +104,8 @@ describe('Utils', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getMethodParams method', () => {
|
describe('getMethodParams method', () => {
|
||||||
beforeAll((done:any) => {
|
beforeEach((done:any) => {
|
||||||
specMgr.load('/tests/schemas/schema-mgr-methodparams.json').then(() => {
|
specMgr.load('/tests/schemas/schema-mgr-methodparams.json').then(done, done.fail);
|
||||||
done();
|
|
||||||
}, () => {
|
|
||||||
done(new Error('Error handler should not be called'));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should propagate path parameters', () => {
|
it('should propagate path parameters', () => {
|
||||||
|
@ -163,12 +149,8 @@ describe('Utils', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('findDerivedDefinitions method', () => {
|
describe('findDerivedDefinitions method', () => {
|
||||||
beforeAll((done:any) => {
|
beforeEach((done) => {
|
||||||
specMgr.load('/tests/schemas/extended-petstore.yml').then(() => {
|
specMgr.load('/tests/schemas/extended-petstore.yml').then(done, done.fail);
|
||||||
done();
|
|
||||||
}, () => {
|
|
||||||
done(new Error('Error handler should not be called'));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should find derived definitions for Pet', () => {
|
it('should find derived definitions for Pet', () => {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user