mapillary-js
Version:
WebGL JavaScript library for displaying street level imagery from mapillary.com
232 lines (199 loc) • 6.66 kB
text/typescript
import {
merge as observableMerge,
from as observableFrom,
combineLatest as observableCombineLatest,
Observable,
Subject,
Subscription,
} from "rxjs";
import {
withLatestFrom,
startWith,
switchMap,
map,
mergeMap,
} from "rxjs/operators";
import {
ComponentService,
Component,
IComponentConfiguration,
Popup,
} from "../../Component";
import {Transform} from "../../Geo";
import {
ISize,
RenderCamera,
} from "../../Render";
import {DOM} from "../../Utils";
import {
Container,
Navigator,
} from "../../Viewer";
/**
* @class PopupComponent
*
* @classdesc Component for showing HTML popup objects.
*
* The `add` method is used for adding new popups. Popups are removed by reference.
*
* It is not possible to update popups in the set by updating any properties
* directly on the popup object. Popups need to be replaced by
* removing them and creating new ones with relevant changed properties and
* adding those instead.
*
* Popups are only relevant to a single image because they are based on
* 2D basic image coordinates. Popups related to a certain image should
* be removed when the viewer is moved to another node.
*
* To retrive and use the popup component
*
* @example
* ```
* var viewer = new Mapillary.Viewer(
* "<element-id>",
* "<client-id>",
* "<my key>",
* { component: { popup: true } });
*
* var popupComponent = viewer.getComponent("popup");
* ```
*/
export class PopupComponent extends Component<IComponentConfiguration> {
public static componentName: string = "popup";
private _dom: DOM;
private _popupContainer: HTMLDivElement;
private _popups: Popup[];
private _added$: Subject<Popup[]>;
private _popups$: Subject<Popup[]>;
private _updateAllSubscription: Subscription;
private _updateAddedChangedSubscription: Subscription;
/** @ignore */
constructor(name: string, container: Container, navigator: Navigator, dom?: DOM) {
super(name, container, navigator);
this._dom = !!dom ? dom : new DOM();
this._popups = [];
this._added$ = new Subject<Popup[]>();
this._popups$ = new Subject<Popup[]>();
}
/**
* Add popups to the popups set.
*
* @description Adding a new popup never replaces an old one
* because they are stored by reference. Adding an already
* existing popup has no effect.
*
* @param {Array<Popup>} popups - Popups to add.
*
* @example ```popupComponent.add([popup1, popup2]);```
*/
public add(popups: Popup[]): void {
for (const popup of popups) {
if (this._popups.indexOf(popup) !== -1) {
continue;
}
this._popups.push(popup);
if (this._activated) {
popup.setParentContainer(this._popupContainer);
}
}
this._added$.next(popups);
this._popups$.next(this._popups);
}
/**
* Returns an array of all popups.
*
* @example ```var popups = popupComponent.getAll();```
*/
public getAll(): Popup[] {
return this._popups.slice();
}
/**
* Remove popups based on reference from the popup set.
*
* @param {Array<Popup>} popups - Popups to remove.
*
* @example ```popupComponent.remove([popup1, popup2]);```
*/
public remove(popups: Popup[]): void {
for (const popup of popups) {
this._remove(popup);
}
this._popups$.next(this._popups);
}
/**
* Remove all popups from the popup set.
*
* @example ```popupComponent.removeAll();```
*/
public removeAll(): void {
for (const popup of this._popups.slice()) {
this._remove(popup);
}
this._popups$.next(this._popups);
}
protected _activate(): void {
this._popupContainer = this._dom.createElement("div", "mapillary-js-popup-container", this._container.element) ;
for (const popup of this._popups) {
popup.setParentContainer(this._popupContainer);
}
this._updateAllSubscription = observableCombineLatest(
this._container.renderService.renderCamera$,
this._container.renderService.size$,
this._navigator.stateService.currentTransform$)
.subscribe(
([renderCamera, size, transform]: [RenderCamera, ISize, Transform]): void => {
for (const popup of this._popups) {
popup.update(renderCamera, size, transform);
}
});
const changed$: Observable<Popup[]> = this._popups$.pipe(
startWith(this._popups),
switchMap(
(popups: Popup[]): Observable<Popup> => {
return observableFrom(popups).pipe(
mergeMap(
(popup: Popup): Observable<Popup> => {
return popup.changed$;
}));
}),
map(
(popup: Popup): Popup[] => {
return [popup];
}));
this._updateAddedChangedSubscription = observableMerge(this._added$, changed$).pipe(
withLatestFrom(
this._container.renderService.renderCamera$,
this._container.renderService.size$,
this._navigator.stateService.currentTransform$))
.subscribe(
([popups, renderCamera, size, transform]: [Popup[], RenderCamera, ISize, Transform]): void => {
for (const popup of popups) {
popup.update(renderCamera, size, transform);
}
});
}
protected _deactivate(): void {
this._updateAllSubscription.unsubscribe();
this._updateAddedChangedSubscription.unsubscribe();
for (const popup of this._popups) {
popup.remove();
}
this._container.element.removeChild(this._popupContainer);
delete this._popupContainer;
}
protected _getDefaultConfiguration(): IComponentConfiguration {
return {};
}
private _remove(popup: Popup): void {
const index: number = this._popups.indexOf(popup);
if (index === -1) {
return;
}
const removed: Popup = this._popups.splice(index, 1)[0];
if (this._activated) {
removed.remove();
}
}
}
ComponentService.register(PopupComponent);
export default PopupComponent;