import * as vd from "virtual-dom"; import { combineLatest as observableCombineLatest, of as observableOf, Observable, Subject, } from "rxjs"; import { catchError, distinctUntilChanged, filter, map, share, startWith, switchMap, tap, withLatestFrom, } from "rxjs/operators"; import { Image } from "../../graph/Image"; import { Container } from "../../viewer/Container"; import { Navigator } from "../../viewer/Navigator"; import { NavigationEdgeStatus } from "../../graph/interfaces/NavigationEdgeStatus"; import { Sequence } from "../../graph/Sequence"; import { ViewportSize } from "../../render/interfaces/ViewportSize"; import { VirtualNodeHash } from "../../render/interfaces/VirtualNodeHash"; import { RenderCamera } from "../../render/RenderCamera"; import { Component } from "../Component"; import { DirectionConfiguration } from "../interfaces/DirectionConfiguration"; import { DirectionDOMRenderer } from "./DirectionDOMRenderer"; import { ComponentEventType } from "../events/ComponentEventType"; import { ComponentHoverEvent } from "../events/ComponentHoverEvent"; import { ComponentStateEvent } from "../events/ComponentStateEvent"; import { ComponentName } from "../ComponentName"; /** * @class DirectionComponent * @classdesc Component showing navigation arrows for steps and turns. */ export class DirectionComponent extends Component { /** @inheritdoc */ public static componentName: ComponentName = "direction"; private _renderer: DirectionDOMRenderer; private _hoveredIdSubject$: Subject; private _hoveredId$: Observable; /** @ignore */ constructor( name: string, container: Container, navigator: Navigator, directionDOMRenderer?: DirectionDOMRenderer) { super(name, container, navigator); this._renderer = !!directionDOMRenderer ? directionDOMRenderer : new DirectionDOMRenderer( this.defaultConfiguration, { height: container.container.offsetHeight, width: container.container.offsetWidth }); this._hoveredIdSubject$ = new Subject(); this._hoveredId$ = this._hoveredIdSubject$.pipe(share()); } public fire( type: "hover", event: ComponentHoverEvent) : void; /** @ignore */ public fire( type: ComponentEventType, event: ComponentStateEvent) : void; public fire( type: ComponentEventType, event: T): void { super.fire(type, event); } public off( type: "hover", handler: (event: ComponentHoverEvent) => void) : void; /** @ignore */ public off( type: ComponentEventType, handler: (event: ComponentStateEvent) => void) : void; public off( type: ComponentEventType, handler: (event: T) => void): void { super.off(type, handler); } /** * Fired when the hovered element of a component changes. * * @event hover * @example * ```js * // Initialize the viewer * var viewer = new mapillary.Viewer({ // viewer options }); * var component = viewer.getComponent(''); * // Set an event listener * component.on('hover', function() { * console.log("A hover event has occurred."); * }); * ``` */ public on( type: "hover", handler: (event: ComponentHoverEvent) => void) : void; /** @ignore */ public on( type: ComponentEventType, handler: (event: ComponentStateEvent) => void) : void; public on( type: ComponentEventType, handler: (event: T) => void): void { super.on(type, handler); } protected _activate(): void { const subs = this._subscriptions; subs.push(this._configuration$ .subscribe( (configuration: DirectionConfiguration): void => { this._renderer.setConfiguration(configuration); })); subs.push(this._container.renderService.size$ .subscribe( (size: ViewportSize): void => { this._renderer.resize(size); })); subs.push(this._navigator.stateService.currentImage$.pipe( tap( (image: Image): void => { this._container.domRenderer.render$.next({ name: this._name, vNode: vd.h("div", {}, []) }); this._renderer.setImage(image); }), withLatestFrom(this._configuration$), switchMap( ([image, configuration]: [Image, DirectionConfiguration]): Observable<[NavigationEdgeStatus, Sequence]> => { return observableCombineLatest( image.spatialEdges$, configuration.distinguishSequence ? this._navigator.graphService .cacheSequence$(image.sequenceId).pipe( catchError( (error: Error): Observable => { console.error(`Failed to cache sequence (${image.sequenceId})`, error); return observableOf(null); })) : observableOf(null)); })) .subscribe( ([edgeStatus, sequence]: [NavigationEdgeStatus, Sequence]): void => { this._renderer.setEdges(edgeStatus, sequence); })); subs.push(this._container.renderService.renderCameraFrame$.pipe( tap( (renderCamera: RenderCamera): void => { this._renderer.setRenderCamera(renderCamera); }), map( (): DirectionDOMRenderer => { return this._renderer; }), filter( (renderer: DirectionDOMRenderer): boolean => { return renderer.needsRender; }), map( (renderer: DirectionDOMRenderer): VirtualNodeHash => { return { name: this._name, vNode: renderer.render(this._navigator) }; })) .subscribe(this._container.domRenderer.render$)); subs.push(observableCombineLatest( this._container.domRenderer.element$, this._container.renderService.renderCamera$, this._container.mouseService.mouseMove$.pipe(startWith(null)), this._container.mouseService.mouseUp$.pipe(startWith(null))).pipe( map( ([element]: [Element, RenderCamera, MouseEvent, MouseEvent]): string => { let elements: HTMLCollectionOf = >element.getElementsByClassName("mapillary-direction-perspective"); for (let i: number = 0; i < elements.length; i++) { let hovered: Element = elements.item(i).querySelector(":hover"); if (hovered != null && hovered.hasAttribute("data-id")) { return hovered.getAttribute("data-id"); } } return null; }), distinctUntilChanged()) .subscribe(this._hoveredIdSubject$)); subs.push(this._hoveredId$ .subscribe( (id: string): void => { const type: ComponentEventType = "hover"; const event: ComponentHoverEvent = { id, target: this, type, }; this.fire(type, event); })); } protected _deactivate(): void { this._subscriptions.unsubscribe(); } protected _getDefaultConfiguration(): DirectionConfiguration { return { distinguishSequence: false, maxWidth: 460, minWidth: 260, }; } }