refactor(aio): switch to pipeable RxJS operators (#22872)
PR Close #22872
This commit is contained in:
parent
da76db9601
commit
01d2dd2a3a
|
@ -3,7 +3,7 @@
|
||||||
"master": {
|
"master": {
|
||||||
"uncompressed": {
|
"uncompressed": {
|
||||||
"inline": 1971,
|
"inline": 1971,
|
||||||
"main": 595662,
|
"main": 584136,
|
||||||
"polyfills": 40272,
|
"polyfills": 40272,
|
||||||
"prettify": 14888
|
"prettify": 14888
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { MatProgressBar, MatSidenav } from '@angular/material';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
|
|
||||||
import { Observable, timer } from 'rxjs';
|
import { Observable, timer } from 'rxjs';
|
||||||
import 'rxjs/add/operator/mapTo';
|
import { mapTo } from 'rxjs/operators';
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
@ -1371,6 +1371,6 @@ class TestHttpClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preserve async nature of `HttpClient`.
|
// Preserve async nature of `HttpClient`.
|
||||||
return timer(1).mapTo(data);
|
return timer(1).pipe(mapTo(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { SearchService } from 'app/search/search.service';
|
||||||
import { TocService } from 'app/shared/toc.service';
|
import { TocService } from 'app/shared/toc.service';
|
||||||
|
|
||||||
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
||||||
import 'rxjs/add/operator/first';
|
import { first, map } from 'rxjs/operators';
|
||||||
|
|
||||||
const sideNavView = 'SideNav';
|
const sideNavView = 'SideNav';
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ export class AppComponent implements OnInit {
|
||||||
// Compute the version picker list from the current version and the versions in the navigation map
|
// Compute the version picker list from the current version and the versions in the navigation map
|
||||||
combineLatest(
|
combineLatest(
|
||||||
this.navigationService.versionInfo,
|
this.navigationService.versionInfo,
|
||||||
this.navigationService.navigationViews.map(views => views['docVersions']))
|
this.navigationService.navigationViews.pipe(map(views => views['docVersions'])))
|
||||||
.subscribe(([versionInfo, versions]) => {
|
.subscribe(([versionInfo, versions]) => {
|
||||||
// TODO(pbd): consider whether we can lookup the stable and next versions from the internet
|
// TODO(pbd): consider whether we can lookup the stable and next versions from the internet
|
||||||
const computedVersions: NavigationNode[] = [
|
const computedVersions: NavigationNode[] = [
|
||||||
|
@ -171,7 +171,7 @@ export class AppComponent implements OnInit {
|
||||||
|
|
||||||
this.navigationService.versionInfo.subscribe(vi => this.versionInfo = vi);
|
this.navigationService.versionInfo.subscribe(vi => this.versionInfo = vi);
|
||||||
|
|
||||||
const hasNonEmptyToc = this.tocService.tocList.map(tocList => tocList.length > 0);
|
const hasNonEmptyToc = this.tocService.tocList.pipe(map(tocList => tocList.length > 0));
|
||||||
combineLatest(hasNonEmptyToc, this.showFloatingToc)
|
combineLatest(hasNonEmptyToc, this.showFloatingToc)
|
||||||
.subscribe(([hasToc, showFloatingToc]) => this.hasFloatingToc = hasToc && showFloatingToc);
|
.subscribe(([hasToc, showFloatingToc]) => this.hasFloatingToc = hasToc && showFloatingToc);
|
||||||
|
|
||||||
|
@ -183,7 +183,7 @@ export class AppComponent implements OnInit {
|
||||||
combineLatest(
|
combineLatest(
|
||||||
this.documentService.currentDocument, // ...needed to determine host classes
|
this.documentService.currentDocument, // ...needed to determine host classes
|
||||||
this.navigationService.currentNodes) // ...needed to determine `sidenav` state
|
this.navigationService.currentNodes) // ...needed to determine `sidenav` state
|
||||||
.first()
|
.pipe(first())
|
||||||
.subscribe(() => this.updateShell());
|
.subscribe(() => this.updateShell());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { catchError, map } from 'rxjs/operators';
|
||||||
import { Logger } from 'app/shared/logger.service';
|
import { Logger } from 'app/shared/logger.service';
|
||||||
import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
|
import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
|
||||||
const announcementsPath = CONTENT_URL_PREFIX + 'announcements.json';
|
const announcementsPath = CONTENT_URL_PREFIX + 'announcements.json';
|
||||||
|
@ -58,15 +59,17 @@ export class AnnouncementBarComponent implements OnInit {
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.http.get<Announcement[]>(announcementsPath)
|
this.http.get<Announcement[]>(announcementsPath)
|
||||||
.catch(error => {
|
.pipe(
|
||||||
this.logger.error(new Error(`${announcementsPath} request failed: ${error.message}`));
|
catchError(error => {
|
||||||
return [];
|
this.logger.error(new Error(`${announcementsPath} request failed: ${error.message}`));
|
||||||
})
|
return [];
|
||||||
.map(announcements => this.findCurrentAnnouncement(announcements))
|
}),
|
||||||
.catch(error => {
|
map(announcements => this.findCurrentAnnouncement(announcements)),
|
||||||
this.logger.error(new Error(`${announcementsPath} contains invalid data: ${error.message}`));
|
catchError(error => {
|
||||||
return [];
|
this.logger.error(new Error(`${announcementsPath} contains invalid data: ${error.message}`));
|
||||||
})
|
return [];
|
||||||
|
}),
|
||||||
|
)
|
||||||
.subscribe(announcement => this.announcement = announcement);
|
.subscribe(announcement => this.announcement = announcement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,7 @@ import { Injectable, OnDestroy } from '@angular/core';
|
||||||
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
||||||
|
|
||||||
import { ReplaySubject, Subject } from 'rxjs';
|
import { ReplaySubject, Subject } from 'rxjs';
|
||||||
import 'rxjs/add/operator/do';
|
import { takeUntil, tap } from 'rxjs/operators';
|
||||||
import 'rxjs/add/operator/map';
|
|
||||||
import 'rxjs/add/operator/takeUntil';
|
|
||||||
|
|
||||||
import { Logger } from 'app/shared/logger.service';
|
import { Logger } from 'app/shared/logger.service';
|
||||||
import { DOC_CONTENT_URL_PREFIX } from 'app/documents/document.service';
|
import { DOC_CONTENT_URL_PREFIX } from 'app/documents/document.service';
|
||||||
|
@ -34,7 +32,7 @@ export class ApiService implements OnDestroy {
|
||||||
private firstTime = true;
|
private firstTime = true;
|
||||||
private onDestroy = new Subject();
|
private onDestroy = new Subject();
|
||||||
private sectionsSubject = new ReplaySubject<ApiSection[]>(1);
|
private sectionsSubject = new ReplaySubject<ApiSection[]>(1);
|
||||||
private _sections = this.sectionsSubject.takeUntil(this.onDestroy);
|
private _sections = this.sectionsSubject.pipe(takeUntil(this.onDestroy));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a cached observable of API sections from a JSON file.
|
* Return a cached observable of API sections from a JSON file.
|
||||||
|
@ -70,8 +68,10 @@ export class ApiService implements OnDestroy {
|
||||||
// TODO: get URL by configuration?
|
// TODO: get URL by configuration?
|
||||||
const url = this.apiBase + (src || this.apiListJsonDefault);
|
const url = this.apiBase + (src || this.apiListJsonDefault);
|
||||||
this.http.get<ApiSection[]>(url)
|
this.http.get<ApiSection[]>(url)
|
||||||
.takeUntil(this.onDestroy)
|
.pipe(
|
||||||
.do(() => this.logger.log(`Got API sections from ${url}`))
|
takeUntil(this.onDestroy),
|
||||||
|
tap(() => this.logger.log(`Got API sections from ${url}`)),
|
||||||
|
)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
sections => this.sectionsSubject.next(sections),
|
sections => this.sectionsSubject.next(sections),
|
||||||
(err: HttpErrorResponse) => {
|
(err: HttpErrorResponse) => {
|
||||||
|
|
|
@ -3,8 +3,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { MatSnackBar } from '@angular/material';
|
import { MatSnackBar } from '@angular/material';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import 'rxjs/add/operator/first';
|
import { first } from 'rxjs/operators';
|
||||||
import 'rxjs/add/operator/toPromise';
|
|
||||||
|
|
||||||
import { CodeComponent } from './code.component';
|
import { CodeComponent } from './code.component';
|
||||||
import { CodeModule } from './code.module';
|
import { CodeModule } from './code.module';
|
||||||
|
@ -65,7 +64,7 @@ describe('CodeComponent', () => {
|
||||||
describe('pretty printing', () => {
|
describe('pretty printing', () => {
|
||||||
const untilCodeFormatted = () => {
|
const untilCodeFormatted = () => {
|
||||||
const emitter = hostComponent.codeComponent.codeFormatted;
|
const emitter = hostComponent.codeComponent.codeFormatted;
|
||||||
return emitter.first().toPromise();
|
return emitter.pipe(first()).toPromise();
|
||||||
};
|
};
|
||||||
const hasLineNumbers = async () => {
|
const hasLineNumbers = async () => {
|
||||||
// presence of `<li>`s are a tell-tale for line numbers
|
// presence of `<li>`s are a tell-tale for line numbers
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Logger } from 'app/shared/logger.service';
|
||||||
import { PrettyPrinter } from './pretty-printer.service';
|
import { PrettyPrinter } from './pretty-printer.service';
|
||||||
import { CopierService } from 'app/shared/copier.service';
|
import { CopierService } from 'app/shared/copier.service';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import 'rxjs/add/operator/do';
|
import { tap } from 'rxjs/operators';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If linenums is not set, this is the default maximum number of lines that
|
* If linenums is not set, this is the default maximum number of lines that
|
||||||
|
@ -120,7 +120,7 @@ export class CodeComponent implements OnChanges {
|
||||||
|
|
||||||
this.pretty
|
this.pretty
|
||||||
.formatCode(leftAlignedCode, this.language, this.getLinenums(leftAlignedCode))
|
.formatCode(leftAlignedCode, this.language, this.getLinenums(leftAlignedCode))
|
||||||
.do(() => this.codeFormatted.emit())
|
.pipe(tap(() => this.codeFormatted.emit()))
|
||||||
.subscribe(c => this.setCodeHtml(c), err => { /* ignore failure to format */ }
|
.subscribe(c => this.setCodeHtml(c), err => { /* ignore failure to format */ }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { from as fromPromise, Observable } from 'rxjs';
|
import { from as fromPromise, Observable } from 'rxjs';
|
||||||
import 'rxjs/add/operator/map';
|
import { first, map, share } from 'rxjs/operators';
|
||||||
import 'rxjs/add/operator/first';
|
|
||||||
|
|
||||||
import { Logger } from 'app/shared/logger.service';
|
import { Logger } from 'app/shared/logger.service';
|
||||||
|
|
||||||
|
@ -21,7 +20,7 @@ export class PrettyPrinter {
|
||||||
private prettyPrintOne: Observable<PrettyPrintOne>;
|
private prettyPrintOne: Observable<PrettyPrintOne>;
|
||||||
|
|
||||||
constructor(private logger: Logger) {
|
constructor(private logger: Logger) {
|
||||||
this.prettyPrintOne = fromPromise(this.getPrettyPrintOne()).share();
|
this.prettyPrintOne = fromPromise(this.getPrettyPrintOne()).pipe(share());
|
||||||
}
|
}
|
||||||
|
|
||||||
private getPrettyPrintOne(): Promise<PrettyPrintOne> {
|
private getPrettyPrintOne(): Promise<PrettyPrintOne> {
|
||||||
|
@ -50,15 +49,17 @@ export class PrettyPrinter {
|
||||||
* @returns Observable<string> - Observable of formatted code
|
* @returns Observable<string> - Observable of formatted code
|
||||||
*/
|
*/
|
||||||
formatCode(code: string, language?: string, linenums?: number | boolean) {
|
formatCode(code: string, language?: string, linenums?: number | boolean) {
|
||||||
return this.prettyPrintOne.map(ppo => {
|
return this.prettyPrintOne.pipe(
|
||||||
try {
|
map(ppo => {
|
||||||
return ppo(code, language, linenums);
|
try {
|
||||||
} catch (err) {
|
return ppo(code, language, linenums);
|
||||||
const msg = `Could not format code that begins '${code.substr(0, 50)}...'.`;
|
} catch (err) {
|
||||||
console.error(msg, err);
|
const msg = `Could not format code that begins '${code.substr(0, 50)}...'.`;
|
||||||
throw new Error(msg);
|
console.error(msg, err);
|
||||||
}
|
throw new Error(msg);
|
||||||
})
|
}
|
||||||
.first(); // complete immediately
|
}),
|
||||||
|
first(), // complete immediately
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
import { Observable } from 'rxjs';
|
import { ConnectableObservable, Observable } from 'rxjs';
|
||||||
import 'rxjs/add/operator/map';
|
import { map, publishLast } from 'rxjs/operators';
|
||||||
import 'rxjs/add/operator/publishLast';
|
|
||||||
|
|
||||||
import { Contributor, ContributorGroup } from './contributors.model';
|
import { Contributor, ContributorGroup } from './contributors.model';
|
||||||
|
|
||||||
|
@ -22,9 +21,9 @@ export class ContributorService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private getContributors() {
|
private getContributors() {
|
||||||
const contributors = this.http.get<{[key: string]: Contributor}>(contributorsPath)
|
const contributors = this.http.get<{[key: string]: Contributor}>(contributorsPath).pipe(
|
||||||
// Create group map
|
// Create group map
|
||||||
.map(contribs => {
|
map(contribs => {
|
||||||
const contribMap: { [name: string]: Contributor[]} = {};
|
const contribMap: { [name: string]: Contributor[]} = {};
|
||||||
Object.keys(contribs).forEach(key => {
|
Object.keys(contribs).forEach(key => {
|
||||||
const contributor = contribs[key];
|
const contributor = contribs[key];
|
||||||
|
@ -38,10 +37,10 @@ export class ContributorService {
|
||||||
});
|
});
|
||||||
|
|
||||||
return contribMap;
|
return contribMap;
|
||||||
})
|
}),
|
||||||
|
|
||||||
// Flatten group map into sorted group array of sorted contributors
|
// Flatten group map into sorted group array of sorted contributors
|
||||||
.map(cmap => {
|
map(cmap => {
|
||||||
return Object.keys(cmap).map(key => {
|
return Object.keys(cmap).map(key => {
|
||||||
const order = knownGroups.indexOf(key);
|
const order = knownGroups.indexOf(key);
|
||||||
return {
|
return {
|
||||||
|
@ -51,10 +50,12 @@ export class ContributorService {
|
||||||
} as ContributorGroup;
|
} as ContributorGroup;
|
||||||
})
|
})
|
||||||
.sort(compareGroups);
|
.sort(compareGroups);
|
||||||
})
|
}),
|
||||||
.publishLast();
|
|
||||||
|
|
||||||
contributors.connect();
|
publishLast(),
|
||||||
|
);
|
||||||
|
|
||||||
|
(contributors as ConnectableObservable<ContributorGroup[]>).connect();
|
||||||
return contributors;
|
return contributors;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
import { Observable } from 'rxjs';
|
import { ConnectableObservable, Observable } from 'rxjs';
|
||||||
import 'rxjs/add/operator/map';
|
import { map, publishLast } from 'rxjs/operators';
|
||||||
import 'rxjs/add/operator/publishLast';
|
|
||||||
|
|
||||||
import { Category, Resource, SubCategory } from './resource.model';
|
import { Category, Resource, SubCategory } from './resource.model';
|
||||||
import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
|
import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
|
||||||
|
@ -20,11 +19,12 @@ export class ResourceService {
|
||||||
|
|
||||||
private getCategories(): Observable<Category[]> {
|
private getCategories(): Observable<Category[]> {
|
||||||
|
|
||||||
const categories = this.http.get<any>(resourcesPath)
|
const categories = this.http.get<any>(resourcesPath).pipe(
|
||||||
.map(data => mkCategories(data))
|
map(data => mkCategories(data)),
|
||||||
.publishLast();
|
publishLast(),
|
||||||
|
);
|
||||||
|
|
||||||
categories.connect();
|
(categories as ConnectableObservable<Category[]>).connect();
|
||||||
return categories;
|
return categories;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
import { switchMap } from 'rxjs/operators';
|
||||||
import { LocationService } from 'app/shared/location.service';
|
import { LocationService } from 'app/shared/location.service';
|
||||||
import { SearchResults } from 'app/search/interfaces';
|
import { SearchResults } from 'app/search/interfaces';
|
||||||
import { SearchService } from 'app/search/search.service';
|
import { SearchService } from 'app/search/search.service';
|
||||||
|
@ -15,9 +16,9 @@ export class FileNotFoundSearchComponent implements OnInit {
|
||||||
constructor(private location: LocationService, private search: SearchService) {}
|
constructor(private location: LocationService, private search: SearchService) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.searchResults = this.location.currentPath.switchMap(path => {
|
this.searchResults = this.location.currentPath.pipe(switchMap(path => {
|
||||||
const query = path.split(/\W+/).join(' ');
|
const query = path.split(/\W+/).join(' ');
|
||||||
return this.search.search(query);
|
return this.search.search(query);
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,7 @@ import { Injectable } from '@angular/core';
|
||||||
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
||||||
|
|
||||||
import { AsyncSubject, Observable, of } from 'rxjs';
|
import { AsyncSubject, Observable, of } from 'rxjs';
|
||||||
import 'rxjs/add/operator/catch';
|
import { catchError, switchMap, tap } from 'rxjs/operators';
|
||||||
import 'rxjs/add/operator/switchMap';
|
|
||||||
|
|
||||||
import { DocumentContents } from './document-contents';
|
import { DocumentContents } from './document-contents';
|
||||||
export { DocumentContents } from './document-contents';
|
export { DocumentContents } from './document-contents';
|
||||||
|
@ -41,7 +40,7 @@ export class DocumentService {
|
||||||
private http: HttpClient,
|
private http: HttpClient,
|
||||||
location: LocationService) {
|
location: LocationService) {
|
||||||
// Whenever the URL changes we try to get the appropriate doc
|
// Whenever the URL changes we try to get the appropriate doc
|
||||||
this.currentDocument = location.currentPath.switchMap(path => this.getDocument(path));
|
this.currentDocument = location.currentPath.pipe(switchMap(path => this.getDocument(path)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private getDocument(url: string) {
|
private getDocument(url: string) {
|
||||||
|
@ -60,15 +59,17 @@ export class DocumentService {
|
||||||
this.logger.log('fetching document from', requestPath);
|
this.logger.log('fetching document from', requestPath);
|
||||||
this.http
|
this.http
|
||||||
.get<DocumentContents>(requestPath, {responseType: 'json'})
|
.get<DocumentContents>(requestPath, {responseType: 'json'})
|
||||||
.do(data => {
|
.pipe(
|
||||||
if (!data || typeof data !== 'object') {
|
tap(data => {
|
||||||
this.logger.log('received invalid data:', data);
|
if (!data || typeof data !== 'object') {
|
||||||
throw Error('Invalid data');
|
this.logger.log('received invalid data:', data);
|
||||||
}
|
throw Error('Invalid data');
|
||||||
})
|
}
|
||||||
.catch((error: HttpErrorResponse) => {
|
}),
|
||||||
return error.status === 404 ? this.getFileNotFoundDoc(id) : this.getErrorDoc(id, error);
|
catchError((error: HttpErrorResponse) => {
|
||||||
})
|
return error.status === 404 ? this.getFileNotFoundDoc(id) : this.getErrorDoc(id, error);
|
||||||
|
}),
|
||||||
|
)
|
||||||
.subscribe(subject);
|
.subscribe(subject);
|
||||||
|
|
||||||
return subject.asObservable();
|
return subject.asObservable();
|
||||||
|
@ -90,7 +91,7 @@ export class DocumentService {
|
||||||
private getErrorDoc(id: string, error: HttpErrorResponse): Observable<DocumentContents> {
|
private getErrorDoc(id: string, error: HttpErrorResponse): Observable<DocumentContents> {
|
||||||
this.logger.error(new Error(`Error fetching document '${id}': (${error.message})`));
|
this.logger.error(new Error(`Error fetching document '${id}': (${error.message})`));
|
||||||
this.cache.delete(id);
|
this.cache.delete(id);
|
||||||
return Observable.of({
|
return of({
|
||||||
id: FETCHING_ERROR_ID,
|
id: FETCHING_ERROR_ID,
|
||||||
contents: FETCHING_ERROR_CONTENTS
|
contents: FETCHING_ERROR_CONTENTS
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,10 +2,7 @@ import { Component, ElementRef, EventEmitter, Input, OnDestroy, Output } from '@
|
||||||
import { Title, Meta } from '@angular/platform-browser';
|
import { Title, Meta } from '@angular/platform-browser';
|
||||||
|
|
||||||
import { Observable, of, timer } from 'rxjs';
|
import { Observable, of, timer } from 'rxjs';
|
||||||
import 'rxjs/add/operator/catch';
|
import { catchError, switchMap, takeUntil, tap } from 'rxjs/operators';
|
||||||
import 'rxjs/add/operator/do';
|
|
||||||
import 'rxjs/add/operator/switchMap';
|
|
||||||
import 'rxjs/add/operator/takeUntil';
|
|
||||||
|
|
||||||
import { DocumentContents, FILE_NOT_FOUND_ID, FETCHING_ERROR_ID } from 'app/documents/document.service';
|
import { DocumentContents, FILE_NOT_FOUND_ID, FETCHING_ERROR_ID } from 'app/documents/document.service';
|
||||||
import { Logger } from 'app/shared/logger.service';
|
import { Logger } from 'app/shared/logger.service';
|
||||||
|
@ -80,8 +77,10 @@ export class DocViewerComponent implements OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.docContents$
|
this.docContents$
|
||||||
.switchMap(newDoc => this.render(newDoc))
|
.pipe(
|
||||||
.takeUntil(this.onDestroy$)
|
switchMap(newDoc => this.render(newDoc)),
|
||||||
|
takeUntil(this.onDestroy$),
|
||||||
|
)
|
||||||
.subscribe();
|
.subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,22 +131,23 @@ export class DocViewerComponent implements OnDestroy {
|
||||||
|
|
||||||
this.setNoIndex(doc.id === FILE_NOT_FOUND_ID || doc.id === FETCHING_ERROR_ID);
|
this.setNoIndex(doc.id === FILE_NOT_FOUND_ID || doc.id === FETCHING_ERROR_ID);
|
||||||
|
|
||||||
return this.void$
|
return this.void$.pipe(
|
||||||
// Security: `doc.contents` is always authored by the documentation team
|
// Security: `doc.contents` is always authored by the documentation team
|
||||||
// and is considered to be safe.
|
// and is considered to be safe.
|
||||||
.do(() => this.nextViewContainer.innerHTML = doc.contents || '')
|
tap(() => this.nextViewContainer.innerHTML = doc.contents || ''),
|
||||||
.do(() => addTitleAndToc = this.prepareTitleAndToc(this.nextViewContainer, doc.id))
|
tap(() => addTitleAndToc = this.prepareTitleAndToc(this.nextViewContainer, doc.id)),
|
||||||
.switchMap(() => this.elementsLoader.loadContainingCustomElements(this.nextViewContainer))
|
switchMap(() => this.elementsLoader.loadContainingCustomElements(this.nextViewContainer)),
|
||||||
.do(() => this.docReady.emit())
|
tap(() => this.docReady.emit()),
|
||||||
.switchMap(() => this.swapViews(addTitleAndToc))
|
switchMap(() => this.swapViews(addTitleAndToc)),
|
||||||
.do(() => this.docRendered.emit())
|
tap(() => this.docRendered.emit()),
|
||||||
.catch(err => {
|
catchError(err => {
|
||||||
const errorMessage = (err instanceof Error) ? err.stack : err;
|
const errorMessage = (err instanceof Error) ? err.stack : err;
|
||||||
this.logger.error(new Error(`[DocViewer] Error preparing document '${doc.id}': ${errorMessage}`));
|
this.logger.error(new Error(`[DocViewer] Error preparing document '${doc.id}': ${errorMessage}`));
|
||||||
this.nextViewContainer.innerHTML = '';
|
this.nextViewContainer.innerHTML = '';
|
||||||
this.setNoIndex(true);
|
this.setNoIndex(true);
|
||||||
return this.void$;
|
return this.void$;
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -199,16 +199,17 @@ export class DocViewerComponent implements OnDestroy {
|
||||||
}
|
}
|
||||||
elem.style.transition = '';
|
elem.style.transition = '';
|
||||||
return animationsDisabled
|
return animationsDisabled
|
||||||
? this.void$.do(() => elem.style[prop] = to)
|
? this.void$.pipe(tap(() => elem.style[prop] = to))
|
||||||
: this.void$
|
: this.void$.pipe(
|
||||||
// In order to ensure that the `from` value will be applied immediately (i.e.
|
// In order to ensure that the `from` value will be applied immediately (i.e.
|
||||||
// without transition) and that the `to` value will be affected by the
|
// without transition) and that the `to` value will be affected by the
|
||||||
// `transition` style, we need to ensure an animation frame has passed between
|
// `transition` style, we need to ensure an animation frame has passed between
|
||||||
// setting each style.
|
// setting each style.
|
||||||
.switchMap(() => raf$).do(() => elem.style[prop] = from)
|
switchMap(() => raf$), tap(() => elem.style[prop] = from),
|
||||||
.switchMap(() => raf$).do(() => elem.style.transition = `all ${duration}ms ease-in-out`)
|
switchMap(() => raf$), tap(() => elem.style.transition = `all ${duration}ms ease-in-out`),
|
||||||
.switchMap(() => raf$).do(() => (elem.style as any)[prop] = to)
|
switchMap(() => raf$), tap(() => (elem.style as any)[prop] = to),
|
||||||
.switchMap(() => timer(getActualDuration(elem))).switchMap(() => this.void$);
|
switchMap(() => timer(getActualDuration(elem))), switchMap(() => this.void$),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const animateLeave = (elem: HTMLElement) => animateProp(elem, 'opacity', '1', '0.1');
|
const animateLeave = (elem: HTMLElement) => animateProp(elem, 'opacity', '1', '0.1');
|
||||||
|
@ -217,25 +218,27 @@ export class DocViewerComponent implements OnDestroy {
|
||||||
let done$ = this.void$;
|
let done$ = this.void$;
|
||||||
|
|
||||||
if (this.currViewContainer.parentElement) {
|
if (this.currViewContainer.parentElement) {
|
||||||
done$ = done$
|
done$ = done$.pipe(
|
||||||
// Remove the current view from the viewer.
|
// Remove the current view from the viewer.
|
||||||
.switchMap(() => animateLeave(this.currViewContainer))
|
switchMap(() => animateLeave(this.currViewContainer)),
|
||||||
.do(() => this.currViewContainer.parentElement!.removeChild(this.currViewContainer))
|
tap(() => this.currViewContainer.parentElement!.removeChild(this.currViewContainer)),
|
||||||
.do(() => this.docRemoved.emit());
|
tap(() => this.docRemoved.emit()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return done$
|
return done$.pipe(
|
||||||
// Insert the next view into the viewer.
|
// Insert the next view into the viewer.
|
||||||
.do(() => this.hostElement.appendChild(this.nextViewContainer))
|
tap(() => this.hostElement.appendChild(this.nextViewContainer)),
|
||||||
.do(() => onInsertedCb())
|
tap(() => onInsertedCb()),
|
||||||
.do(() => this.docInserted.emit())
|
tap(() => this.docInserted.emit()),
|
||||||
.switchMap(() => animateEnter(this.nextViewContainer))
|
switchMap(() => animateEnter(this.nextViewContainer)),
|
||||||
// Update the view references and clean up unused nodes.
|
// Update the view references and clean up unused nodes.
|
||||||
.do(() => {
|
tap(() => {
|
||||||
const prevViewContainer = this.currViewContainer;
|
const prevViewContainer = this.currViewContainer;
|
||||||
this.currViewContainer = this.nextViewContainer;
|
this.currViewContainer = this.nextViewContainer;
|
||||||
this.nextViewContainer = prevViewContainer;
|
this.nextViewContainer = prevViewContainer;
|
||||||
this.nextViewContainer.innerHTML = ''; // Empty to release memory.
|
this.nextViewContainer.innerHTML = ''; // Empty to release memory.
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { AfterViewInit, Component, ElementRef, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core';
|
import { AfterViewInit, Component, ElementRef, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core';
|
||||||
import { asapScheduler as asap, Observable, Subject } from 'rxjs';
|
import { asapScheduler as asap, combineLatest, Subject } from 'rxjs';
|
||||||
import 'rxjs/add/observable/combineLatest';
|
import { startWith, subscribeOn, takeUntil } from 'rxjs/operators';
|
||||||
import 'rxjs/add/operator/subscribeOn';
|
|
||||||
import 'rxjs/add/operator/takeUntil';
|
|
||||||
|
|
||||||
import { ScrollService } from 'app/shared/scroll.service';
|
import { ScrollService } from 'app/shared/scroll.service';
|
||||||
import { TocItem, TocService } from 'app/shared/toc.service';
|
import { TocItem, TocService } from 'app/shared/toc.service';
|
||||||
|
@ -34,7 +32,7 @@ export class TocComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.tocService.tocList
|
this.tocService.tocList
|
||||||
.takeUntil(this.onDestroy)
|
.pipe(takeUntil(this.onDestroy))
|
||||||
.subscribe(tocList => {
|
.subscribe(tocList => {
|
||||||
this.tocList = tocList;
|
this.tocList = tocList;
|
||||||
const itemCount = count(this.tocList, item => item.level !== 'h1');
|
const itemCount = count(this.tocList, item => item.level !== 'h1');
|
||||||
|
@ -54,8 +52,8 @@ export class TocComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
// We use the `asap` scheduler because updates to `activeItemIndex` are triggered by DOM changes,
|
// We use the `asap` scheduler because updates to `activeItemIndex` are triggered by DOM changes,
|
||||||
// which, in turn, are caused by the rendering that happened due to a ChangeDetection.
|
// which, in turn, are caused by the rendering that happened due to a ChangeDetection.
|
||||||
// Without asap, we would be updating the model while still in a ChangeDetection handler, which is disallowed by Angular.
|
// Without asap, we would be updating the model while still in a ChangeDetection handler, which is disallowed by Angular.
|
||||||
Observable.combineLatest(this.tocService.activeItemIndex.subscribeOn(asap), this.items.changes.startWith(this.items))
|
combineLatest(this.tocService.activeItemIndex.pipe(subscribeOn(asap)), this.items.changes.pipe(startWith(this.items)))
|
||||||
.takeUntil(this.onDestroy)
|
.pipe(takeUntil(this.onDestroy))
|
||||||
.subscribe(([index, items]) => {
|
.subscribe(([index, items]) => {
|
||||||
this.activeIndex = index;
|
this.activeIndex = index;
|
||||||
if (index === null || index >= items.length) {
|
if (index === null || index >= items.length) {
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
import { combineLatest, Observable } from 'rxjs';
|
import { combineLatest, ConnectableObservable, Observable } from 'rxjs';
|
||||||
import 'rxjs/add/operator/map';
|
import { map, publishLast, publishReplay } from 'rxjs/operators';
|
||||||
import 'rxjs/add/operator/publishLast';
|
|
||||||
import 'rxjs/add/operator/publishReplay';
|
|
||||||
|
|
||||||
import { LocationService } from 'app/shared/location.service';
|
import { LocationService } from 'app/shared/location.service';
|
||||||
import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
|
import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
|
||||||
|
@ -56,30 +54,32 @@ export class NavigationService {
|
||||||
*/
|
*/
|
||||||
private fetchNavigationInfo(): Observable<NavigationResponse> {
|
private fetchNavigationInfo(): Observable<NavigationResponse> {
|
||||||
const navigationInfo = this.http.get<NavigationResponse>(navigationPath)
|
const navigationInfo = this.http.get<NavigationResponse>(navigationPath)
|
||||||
.publishLast();
|
.pipe(publishLast());
|
||||||
navigationInfo.connect();
|
(navigationInfo as ConnectableObservable<NavigationResponse>).connect();
|
||||||
return navigationInfo;
|
return navigationInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getVersionInfo(navigationInfo: Observable<NavigationResponse>) {
|
private getVersionInfo(navigationInfo: Observable<NavigationResponse>) {
|
||||||
const versionInfo = navigationInfo
|
const versionInfo = navigationInfo.pipe(
|
||||||
.map(response => response.__versionInfo)
|
map(response => response.__versionInfo),
|
||||||
.publishLast();
|
publishLast(),
|
||||||
versionInfo.connect();
|
);
|
||||||
|
(versionInfo as ConnectableObservable<VersionInfo>).connect();
|
||||||
return versionInfo;
|
return versionInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getNavigationViews(navigationInfo: Observable<NavigationResponse>): Observable<NavigationViews> {
|
private getNavigationViews(navigationInfo: Observable<NavigationResponse>): Observable<NavigationViews> {
|
||||||
const navigationViews = navigationInfo
|
const navigationViews = navigationInfo.pipe(
|
||||||
.map(response => {
|
map(response => {
|
||||||
const views = Object.assign({}, response);
|
const views = Object.assign({}, response);
|
||||||
Object.keys(views).forEach(key => {
|
Object.keys(views).forEach(key => {
|
||||||
if (key[0] === '_') { delete views[key]; }
|
if (key[0] === '_') { delete views[key]; }
|
||||||
});
|
});
|
||||||
return views as NavigationViews;
|
return views as NavigationViews;
|
||||||
})
|
}),
|
||||||
.publishLast();
|
publishLast(),
|
||||||
navigationViews.connect();
|
);
|
||||||
|
(navigationViews as ConnectableObservable<NavigationViews>).connect();
|
||||||
return navigationViews;
|
return navigationViews;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,15 +91,15 @@ export class NavigationService {
|
||||||
*/
|
*/
|
||||||
private getCurrentNodes(navigationViews: Observable<NavigationViews>): Observable<CurrentNodes> {
|
private getCurrentNodes(navigationViews: Observable<NavigationViews>): Observable<CurrentNodes> {
|
||||||
const currentNodes = combineLatest(
|
const currentNodes = combineLatest(
|
||||||
navigationViews.map(views => this.computeUrlToNavNodesMap(views)),
|
navigationViews.pipe(map(views => this.computeUrlToNavNodesMap(views))),
|
||||||
this.location.currentPath,
|
this.location.currentPath,
|
||||||
|
|
||||||
(navMap, url) => {
|
(navMap, url) => {
|
||||||
const urlKey = url.startsWith('api/') ? 'api' : url;
|
const urlKey = url.startsWith('api/') ? 'api' : url;
|
||||||
return navMap.get(urlKey) || { '' : { view: '', url: urlKey, nodes: [] }};
|
return navMap.get(urlKey) || { '' : { view: '', url: urlKey, nodes: [] }};
|
||||||
})
|
})
|
||||||
.publishReplay(1);
|
.pipe(publishReplay(1));
|
||||||
currentNodes.connect();
|
(currentNodes as ConnectableObservable<CurrentNodes>).connect();
|
||||||
return currentNodes;
|
return currentNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Component, OnInit, ViewChild, ElementRef, EventEmitter, Output } from '@angular/core';
|
import { Component, OnInit, ViewChild, ElementRef, EventEmitter, Output } from '@angular/core';
|
||||||
import { LocationService } from 'app/shared/location.service';
|
import { LocationService } from 'app/shared/location.service';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import 'rxjs/add/operator/distinctUntilChanged';
|
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component provides a text box to type a search query that will be sent to the SearchService.
|
* This component provides a text box to type a search query that will be sent to the SearchService.
|
||||||
|
@ -30,7 +30,7 @@ export class SearchBoxComponent implements OnInit {
|
||||||
private searchSubject = new Subject<string>();
|
private searchSubject = new Subject<string>();
|
||||||
|
|
||||||
@ViewChild('searchBox') searchBox: ElementRef;
|
@ViewChild('searchBox') searchBox: ElementRef;
|
||||||
@Output() onSearch = this.searchSubject.distinctUntilChanged().debounceTime(this.searchDebounce);
|
@Output() onSearch = this.searchSubject.pipe(distinctUntilChanged(), debounceTime(this.searchDebounce));
|
||||||
@Output() onFocus = new EventEmitter<string>();
|
@Output() onFocus = new EventEmitter<string>();
|
||||||
|
|
||||||
constructor(private locationService: LocationService) { }
|
constructor(private locationService: LocationService) { }
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { ReflectiveInjector, NgZone } from '@angular/core';
|
import { ReflectiveInjector, NgZone } from '@angular/core';
|
||||||
import { fakeAsync, tick } from '@angular/core/testing';
|
import { fakeAsync, tick } from '@angular/core/testing';
|
||||||
import { Observable } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
import 'rxjs/add/observable/of';
|
|
||||||
import { SearchService } from './search.service';
|
import { SearchService } from './search.service';
|
||||||
import { WebWorkerClient } from 'app/shared/web-worker';
|
import { WebWorkerClient } from 'app/shared/web-worker';
|
||||||
|
|
||||||
|
@ -13,7 +12,7 @@ describe('SearchService', () => {
|
||||||
let mockWorker: WebWorkerClient;
|
let mockWorker: WebWorkerClient;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sendMessageSpy = jasmine.createSpy('sendMessage').and.returnValue(Observable.of({}));
|
sendMessageSpy = jasmine.createSpy('sendMessage').and.returnValue(of({}));
|
||||||
mockWorker = { sendMessage: sendMessageSpy } as any;
|
mockWorker = { sendMessage: sendMessageSpy } as any;
|
||||||
spyOn(WebWorkerClient, 'create').and.returnValue(mockWorker);
|
spyOn(WebWorkerClient, 'create').and.returnValue(mockWorker);
|
||||||
|
|
||||||
|
@ -40,7 +39,7 @@ describe('SearchService', () => {
|
||||||
// We must initialize the service before calling connectSearches
|
// We must initialize the service before calling connectSearches
|
||||||
service.initWorker('some/url', 1000);
|
service.initWorker('some/url', 1000);
|
||||||
// Simulate the index being ready so that searches get sent to the worker
|
// Simulate the index being ready so that searches get sent to the worker
|
||||||
(service as any).ready = Observable.of(true);
|
(service as any).ready = of(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should trigger a `loadIndex` synchronously (not waiting for the delay)', () => {
|
it('should trigger a `loadIndex` synchronously (not waiting for the delay)', () => {
|
||||||
|
@ -57,7 +56,7 @@ describe('SearchService', () => {
|
||||||
it('should push the response to the returned observable', () => {
|
it('should push the response to the returned observable', () => {
|
||||||
const mockSearchResults = { results: ['a', 'b'] };
|
const mockSearchResults = { results: ['a', 'b'] };
|
||||||
let actualSearchResults: any;
|
let actualSearchResults: any;
|
||||||
(mockWorker.sendMessage as jasmine.Spy).and.returnValue(Observable.of(mockSearchResults));
|
(mockWorker.sendMessage as jasmine.Spy).and.returnValue(of(mockSearchResults));
|
||||||
service.search('some query').subscribe(results => actualSearchResults = results);
|
service.search('some query').subscribe(results => actualSearchResults = results);
|
||||||
expect(actualSearchResults).toEqual(mockSearchResults);
|
expect(actualSearchResults).toEqual(mockSearchResults);
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,10 +5,8 @@ can be found in the LICENSE file at http://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { NgZone, Injectable } from '@angular/core';
|
import { NgZone, Injectable } from '@angular/core';
|
||||||
import { Observable, race, ReplaySubject, timer } from 'rxjs';
|
import { ConnectableObservable, Observable, race, ReplaySubject, timer } from 'rxjs';
|
||||||
import 'rxjs/add/operator/concatMap';
|
import { concatMap, first, publishReplay } from 'rxjs/operators';
|
||||||
import 'rxjs/add/operator/first';
|
|
||||||
import 'rxjs/add/operator/publishReplay';
|
|
||||||
import { WebWorkerClient } from 'app/shared/web-worker';
|
import { WebWorkerClient } from 'app/shared/web-worker';
|
||||||
import { SearchResults } from 'app/search/interfaces';
|
import { SearchResults } from 'app/search/interfaces';
|
||||||
|
|
||||||
|
@ -31,16 +29,19 @@ export class SearchService {
|
||||||
// Wait for the initDelay or the first search
|
// Wait for the initDelay or the first search
|
||||||
const ready = this.ready = race<any>(
|
const ready = this.ready = race<any>(
|
||||||
timer(initDelay),
|
timer(initDelay),
|
||||||
(this.searchesSubject.asObservable()).first()
|
this.searchesSubject.asObservable().pipe(first()),
|
||||||
)
|
)
|
||||||
.concatMap(() => {
|
.pipe(
|
||||||
// Create the worker and load the index
|
concatMap(() => {
|
||||||
this.worker = WebWorkerClient.create(workerUrl, this.zone);
|
// Create the worker and load the index
|
||||||
return this.worker.sendMessage<boolean>('load-index');
|
this.worker = WebWorkerClient.create(workerUrl, this.zone);
|
||||||
}).publishReplay(1);
|
return this.worker.sendMessage<boolean>('load-index');
|
||||||
|
}),
|
||||||
|
publishReplay(1),
|
||||||
|
);
|
||||||
|
|
||||||
// Connect to the observable to kick off the timer
|
// Connect to the observable to kick off the timer
|
||||||
ready.connect();
|
(ready as ConnectableObservable<boolean>).connect();
|
||||||
return ready;
|
return ready;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +54,6 @@ export class SearchService {
|
||||||
// Trigger the searches subject to override the init delay timer
|
// Trigger the searches subject to override the init delay timer
|
||||||
this.searchesSubject.next(query);
|
this.searchesSubject.next(query);
|
||||||
// Once the index has loaded, switch to listening to the searches coming in.
|
// Once the index has loaded, switch to listening to the searches coming in.
|
||||||
return this.ready.concatMap(() => this.worker.sendMessage<SearchResults>('query-index', query));
|
return this.ready.pipe(concatMap(() => this.worker.sendMessage<SearchResults>('query-index', query)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
||||||
import { Location, PlatformLocation } from '@angular/common';
|
import { Location, PlatformLocation } from '@angular/common';
|
||||||
|
|
||||||
import { ReplaySubject } from 'rxjs';
|
import { ReplaySubject } from 'rxjs';
|
||||||
import 'rxjs/add/operator/do';
|
import { map, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
import { GaService } from 'app/shared/ga.service';
|
import { GaService } from 'app/shared/ga.service';
|
||||||
import { SwUpdatesService } from 'app/sw-updates/sw-updates.service';
|
import { SwUpdatesService } from 'app/sw-updates/sw-updates.service';
|
||||||
|
@ -15,11 +15,12 @@ export class LocationService {
|
||||||
private swUpdateActivated = false;
|
private swUpdateActivated = false;
|
||||||
|
|
||||||
currentUrl = this.urlSubject
|
currentUrl = this.urlSubject
|
||||||
.map(url => this.stripSlashes(url));
|
.pipe(map(url => this.stripSlashes(url)));
|
||||||
|
|
||||||
currentPath = this.currentUrl
|
currentPath = this.currentUrl.pipe(
|
||||||
.map(url => (url.match(/[^?#]*/) || [])[0]) // strip query and hash
|
map(url => (url.match(/[^?#]*/) || [])[0]), // strip query and hash
|
||||||
.do(path => this.gaService.locationChanged(path));
|
tap(path => this.gaService.locationChanged(path)),
|
||||||
|
);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private gaService: GaService,
|
private gaService: GaService,
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
import { Inject, Injectable } from '@angular/core';
|
import { Inject, Injectable } from '@angular/core';
|
||||||
import { DOCUMENT } from '@angular/platform-browser';
|
import { DOCUMENT } from '@angular/platform-browser';
|
||||||
import { Observable, ReplaySubject, Subject } from 'rxjs';
|
import { fromEvent, Observable, ReplaySubject, Subject } from 'rxjs';
|
||||||
import 'rxjs/add/observable/fromEvent';
|
import { auditTime, distinctUntilChanged, takeUntil } from 'rxjs/operators';
|
||||||
import 'rxjs/add/operator/auditTime';
|
|
||||||
import 'rxjs/add/operator/distinctUntilChanged';
|
|
||||||
import 'rxjs/add/operator/takeUntil';
|
|
||||||
|
|
||||||
import { ScrollService } from 'app/shared/scroll.service';
|
import { ScrollService } from 'app/shared/scroll.service';
|
||||||
|
|
||||||
|
@ -122,8 +119,8 @@ export class ScrollSpiedElementGroup {
|
||||||
export class ScrollSpyService {
|
export class ScrollSpyService {
|
||||||
private spiedElementGroups: ScrollSpiedElementGroup[] = [];
|
private spiedElementGroups: ScrollSpiedElementGroup[] = [];
|
||||||
private onStopListening = new Subject();
|
private onStopListening = new Subject();
|
||||||
private resizeEvents = Observable.fromEvent(window, 'resize').auditTime(300).takeUntil(this.onStopListening);
|
private resizeEvents = fromEvent(window, 'resize').pipe(auditTime(300), takeUntil(this.onStopListening));
|
||||||
private scrollEvents = Observable.fromEvent(window, 'scroll').auditTime(10).takeUntil(this.onStopListening);
|
private scrollEvents = fromEvent(window, 'scroll').pipe(auditTime(10), takeUntil(this.onStopListening));
|
||||||
private lastContentHeight: number;
|
private lastContentHeight: number;
|
||||||
private lastMaxScrollTop: number;
|
private lastMaxScrollTop: number;
|
||||||
|
|
||||||
|
@ -159,7 +156,7 @@ export class ScrollSpyService {
|
||||||
this.spiedElementGroups.push(spiedGroup);
|
this.spiedElementGroups.push(spiedGroup);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
active: spiedGroup.activeScrollItem.asObservable().distinctUntilChanged(),
|
active: spiedGroup.activeScrollItem.asObservable().pipe(distinctUntilChanged()),
|
||||||
unspy: () => this.unspy(spiedGroup)
|
unspy: () => this.unspy(spiedGroup)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { ReflectiveInjector } from '@angular/core';
|
||||||
import { fakeAsync, tick } from '@angular/core/testing';
|
import { fakeAsync, tick } from '@angular/core/testing';
|
||||||
import { NgServiceWorker } from '@angular/service-worker';
|
import { NgServiceWorker } from '@angular/service-worker';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import 'rxjs/add/operator/take';
|
import { take } from 'rxjs/operators';
|
||||||
|
|
||||||
import { Logger } from 'app/shared/logger.service';
|
import { Logger } from 'app/shared/logger.service';
|
||||||
import { SwUpdatesService } from './sw-updates.service';
|
import { SwUpdatesService } from './sw-updates.service';
|
||||||
|
@ -153,8 +153,8 @@ class MockNgServiceWorker {
|
||||||
updates = this.$$updatesSubj.asObservable();
|
updates = this.$$updatesSubj.asObservable();
|
||||||
|
|
||||||
activateUpdate = jasmine.createSpy('MockNgServiceWorker.activateUpdate')
|
activateUpdate = jasmine.createSpy('MockNgServiceWorker.activateUpdate')
|
||||||
.and.callFake(() => this.$$activateUpdateSubj.take(1));
|
.and.callFake(() => this.$$activateUpdateSubj.pipe(take(1)));
|
||||||
|
|
||||||
checkForUpdate = jasmine.createSpy('MockNgServiceWorker.checkForUpdate')
|
checkForUpdate = jasmine.createSpy('MockNgServiceWorker.checkForUpdate')
|
||||||
.and.callFake(() => this.$$checkForUpdateSubj.take(1));
|
.and.callFake(() => this.$$checkForUpdateSubj.pipe(take(1)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,7 @@
|
||||||
import { Injectable, OnDestroy } from '@angular/core';
|
import { Injectable, OnDestroy } from '@angular/core';
|
||||||
import { NgServiceWorker } from '@angular/service-worker';
|
import { NgServiceWorker } from '@angular/service-worker';
|
||||||
import { Observable, Subject } from 'rxjs';
|
import { concat, of, Subject } from 'rxjs';
|
||||||
import 'rxjs/add/observable/of';
|
import { debounceTime, filter, map, startWith, take, takeUntil, tap } from 'rxjs/operators';
|
||||||
import 'rxjs/add/operator/concat';
|
|
||||||
import 'rxjs/add/operator/debounceTime';
|
|
||||||
import 'rxjs/add/operator/filter';
|
|
||||||
import 'rxjs/add/operator/map';
|
|
||||||
import 'rxjs/add/operator/startWith';
|
|
||||||
import 'rxjs/add/operator/take';
|
|
||||||
import 'rxjs/add/operator/takeUntil';
|
|
||||||
|
|
||||||
import { Logger } from 'app/shared/logger.service';
|
import { Logger } from 'app/shared/logger.service';
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,19 +20,22 @@ import { Logger } from 'app/shared/logger.service';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SwUpdatesService implements OnDestroy {
|
export class SwUpdatesService implements OnDestroy {
|
||||||
private checkInterval = 1000 * 60 * 60 * 6; // 6 hours
|
private checkInterval = 1000 * 60 * 60 * 6; // 6 hours
|
||||||
private onDestroy = new Subject();
|
private onDestroy = new Subject<void>();
|
||||||
private checkForUpdateSubj = new Subject();
|
private checkForUpdateSubj = new Subject<void>();
|
||||||
updateActivated = this.sw.updates
|
updateActivated = this.sw.updates.pipe(
|
||||||
.takeUntil(this.onDestroy)
|
takeUntil(this.onDestroy),
|
||||||
.do(evt => this.log(`Update event: ${JSON.stringify(evt)}`))
|
tap(evt => this.log(`Update event: ${JSON.stringify(evt)}`)),
|
||||||
.filter(({type}) => type === 'activation')
|
filter(({type}) => type === 'activation'),
|
||||||
.map(({version}) => version);
|
map(({version}) => version),
|
||||||
|
);
|
||||||
|
|
||||||
constructor(private logger: Logger, private sw: NgServiceWorker) {
|
constructor(private logger: Logger, private sw: NgServiceWorker) {
|
||||||
this.checkForUpdateSubj
|
this.checkForUpdateSubj
|
||||||
.debounceTime(this.checkInterval)
|
.pipe(
|
||||||
.startWith(null)
|
debounceTime(this.checkInterval),
|
||||||
.takeUntil(this.onDestroy)
|
startWith<void>(undefined),
|
||||||
|
takeUntil(this.onDestroy),
|
||||||
|
)
|
||||||
.subscribe(() => this.checkForUpdate());
|
.subscribe(() => this.checkForUpdate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,11 +51,13 @@ export class SwUpdatesService implements OnDestroy {
|
||||||
|
|
||||||
private checkForUpdate() {
|
private checkForUpdate() {
|
||||||
this.log('Checking for update...');
|
this.log('Checking for update...');
|
||||||
this.sw.checkForUpdate()
|
// Temp workaround for https://github.com/angular/mobile-toolkit/pull/137.
|
||||||
// Temp workaround for https://github.com/angular/mobile-toolkit/pull/137.
|
// TODO (gkalpak): Remove once #137 is fixed.
|
||||||
// TODO (gkalpak): Remove once #137 is fixed.
|
concat(this.sw.checkForUpdate(), of(false))
|
||||||
.concat(Observable.of(false)).take(1)
|
.pipe(
|
||||||
.do(v => this.log(`Update available: ${v}`))
|
take(1),
|
||||||
|
tap(v => this.log(`Update available: ${v}`)),
|
||||||
|
)
|
||||||
.subscribe(v => v ? this.activateUpdate() : this.scheduleCheckForUpdate());
|
.subscribe(v => v ? this.activateUpdate() : this.scheduleCheckForUpdate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { enableProdMode, ApplicationRef } from '@angular/core';
|
import { enableProdMode, ApplicationRef } from '@angular/core';
|
||||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
|
|
||||||
import { AppModule } from './app/app.module';
|
import { AppModule } from './app/app.module';
|
||||||
import { environment } from './environments/environment';
|
import { environment } from './environments/environment';
|
||||||
|
@ -11,7 +12,7 @@ if (environment.production) {
|
||||||
platformBrowserDynamic().bootstrapModule(AppModule).then(ref => {
|
platformBrowserDynamic().bootstrapModule(AppModule).then(ref => {
|
||||||
if (environment.production && 'serviceWorker' in (navigator as any)) {
|
if (environment.production && 'serviceWorker' in (navigator as any)) {
|
||||||
const appRef: ApplicationRef = ref.injector.get(ApplicationRef);
|
const appRef: ApplicationRef = ref.injector.get(ApplicationRef);
|
||||||
appRef.isStable.first().subscribe(() => {
|
appRef.isStable.pipe(first()).subscribe(() => {
|
||||||
(navigator as any).serviceWorker.register('/worker-basic.min.js');
|
(navigator as any).serviceWorker.register('/worker-basic.min.js');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
export class MockLocationService {
|
export class MockLocationService {
|
||||||
urlSubject = new BehaviorSubject<string>(this.initialUrl);
|
urlSubject = new BehaviorSubject<string>(this.initialUrl);
|
||||||
currentUrl = this.urlSubject.asObservable().map(url => this.stripSlashes(url));
|
currentUrl = this.urlSubject.asObservable().pipe(map(url => this.stripSlashes(url)));
|
||||||
// strip off query and hash
|
// strip off query and hash
|
||||||
currentPath = this.currentUrl.map(url => url.match(/[^?#]*/)![0]);
|
currentPath = this.currentUrl.pipe(map(url => url.match(/[^?#]*/)![0]));
|
||||||
search = jasmine.createSpy('search').and.returnValue({});
|
search = jasmine.createSpy('search').and.returnValue({});
|
||||||
setSearch = jasmine.createSpy('setSearch');
|
setSearch = jasmine.createSpy('setSearch');
|
||||||
go = jasmine.createSpy('Location.go').and
|
go = jasmine.createSpy('Location.go').and
|
||||||
|
|
Loading…
Reference in New Issue