mapillary-js
Version:
WebGL JavaScript library for displaying street level imagery from mapillary.com
249 lines (219 loc) • 8.89 kB
text/typescript
import * as vd from "virtual-dom";
import {
of as observableOf,
combineLatest as observableCombineLatest,
Observable,
Subscription,
Subject,
} from "rxjs";
import {
switchMap,
share,
startWith,
withLatestFrom,
map,
filter,
distinctUntilChanged,
tap,
catchError,
} from "rxjs/operators";
import {
ComponentService,
Component,
DirectionDOMRenderer,
IDirectionConfiguration,
} from "../../Component";
import {IEdgeStatus, Node, Sequence} from "../../Graph";
import {
ISize,
IVNodeHash,
RenderCamera,
} from "../../Render";
import {Container, Navigator} from "../../Viewer";
/**
* @class DirectionComponent
* @classdesc Component showing navigation arrows for steps and turns.
*/
export class DirectionComponent extends Component<IDirectionConfiguration> {
/** @inheritdoc */
public static componentName: string = "direction";
/**
* Event fired when the hovered key changes.
*
* @description Emits the key of the node for the direction
* arrow that is being hovered. When the mouse leaves a
* direction arrow null is emitted.
*
* @event DirectionComponent#hoveredkeychanged
* @type {string} The hovered key, null if no key is hovered.
*/
public static hoveredkeychanged: string = "hoveredkeychanged";
private _renderer: DirectionDOMRenderer;
private _hoveredKeySubject$: Subject<string>;
private _hoveredKey$: Observable<string>;
private _configurationSubscription: Subscription;
private _emitHoveredKeySubscription: Subscription;
private _hoveredKeySubscription: Subscription;
private _nodeSubscription: Subscription;
private _renderCameraSubscription: Subscription;
private _resizeSubscription: Subscription;
constructor(name: string, container: Container, navigator: Navigator, directionDOMRenderer?: DirectionDOMRenderer) {
super(name, container, navigator);
this._renderer = !!directionDOMRenderer ?
directionDOMRenderer :
new DirectionDOMRenderer(
this.defaultConfiguration,
{ height: container.element.offsetHeight, width: container.element.offsetWidth });
this._hoveredKeySubject$ = new Subject<string>();
this._hoveredKey$ = this._hoveredKeySubject$.pipe(share());
}
/**
* Get hovered key observable.
*
* @description An observable emitting the key of the node for the direction
* arrow that is being hovered. When the mouse leaves a direction arrow null
* is emitted.
*
* @returns {Observable<string>}
*/
public get hoveredKey$(): Observable<string> {
return this._hoveredKey$;
}
/**
* Set highlight key.
*
* @description The arrow pointing towards the node corresponding to the
* highlight key will be highlighted.
*
* @param {string} highlightKey Key of node to be highlighted if existing
* among arrows.
*/
public setHighlightKey(highlightKey: string): void {
this.configure({ highlightKey: highlightKey });
}
/**
* Set min width of container element.
*
* @description Set min width of the non transformed container element holding
* the navigation arrows. If the min width is larger than the max width the
* min width value will be used.
*
* The container element is automatically resized when the resize
* method on the Viewer class is called.
*
* @param {number} minWidth
*/
public setMinWidth(minWidth: number): void {
this.configure({ minWidth: minWidth });
}
/**
* Set max width of container element.
*
* @description Set max width of the non transformed container element holding
* the navigation arrows. If the min width is larger than the max width the
* min width value will be used.
*
* The container element is automatically resized when the resize
* method on the Viewer class is called.
*
* @param {number} minWidth
*/
public setMaxWidth(maxWidth: number): void {
this.configure({ maxWidth: maxWidth });
}
protected _activate(): void {
this._configurationSubscription = this._configuration$
.subscribe(
(configuration: IDirectionConfiguration): void => {
this._renderer.setConfiguration(configuration);
});
this._resizeSubscription = this._container.renderService.size$
.subscribe(
(size: ISize): void => {
this._renderer.resize(size);
});
this._nodeSubscription = this._navigator.stateService.currentNode$.pipe(
tap(
(node: Node): void => {
this._container.domRenderer.render$.next({name: this._name, vnode: vd.h("div", {}, [])});
this._renderer.setNode(node);
}),
withLatestFrom(this._configuration$),
switchMap(
([node, configuration]: [Node, IDirectionConfiguration]): Observable<[IEdgeStatus, Sequence]> => {
return observableCombineLatest(
node.spatialEdges$,
configuration.distinguishSequence ?
this._navigator.graphService
.cacheSequence$(node.sequenceKey).pipe(
catchError(
(error: Error, caught: Observable<Sequence>): Observable<Sequence> => {
console.error(`Failed to cache sequence (${node.sequenceKey})`, error);
return observableOf<Sequence>(null);
})) :
observableOf<Sequence>(null));
}))
.subscribe(
([edgeStatus, sequence]: [IEdgeStatus, Sequence]): void => {
this._renderer.setEdges(edgeStatus, sequence);
});
this._renderCameraSubscription = 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): IVNodeHash => {
return { name: this._name, vnode: renderer.render(this._navigator) };
}))
.subscribe(this._container.domRenderer.render$);
this._hoveredKeySubscription = 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> =
<HTMLCollectionOf<Element>>element.getElementsByClassName("DirectionsPerspective");
for (let i: number = 0; i < elements.length; i++) {
let hovered: Element = elements.item(i).querySelector(":hover");
if (hovered != null && hovered.hasAttribute("data-key")) {
return hovered.getAttribute("data-key");
}
}
return null;
}),
distinctUntilChanged())
.subscribe(this._hoveredKeySubject$);
this._emitHoveredKeySubscription = this._hoveredKey$
.subscribe(
(key: string): void => {
this.fire(DirectionComponent.hoveredkeychanged, key);
});
}
protected _deactivate(): void {
this._configurationSubscription.unsubscribe();
this._emitHoveredKeySubscription.unsubscribe();
this._hoveredKeySubscription.unsubscribe();
this._nodeSubscription.unsubscribe();
this._renderCameraSubscription.unsubscribe();
}
protected _getDefaultConfiguration(): IDirectionConfiguration {
return {
distinguishSequence: false,
maxWidth: 460,
minWidth: 260,
};
}
}
ComponentService.register(DirectionComponent);
export default DirectionComponent;