mapillary-js/src/component/direction/DirectionComponent.ts
2016-10-25 11:22:20 +02:00

221 lines
7.9 KiB
TypeScript

/// <reference path="../../../typings/index.d.ts" />
import * as vd from "virtual-dom";
import {Observable} from "rxjs/Observable";
import {Subscription} from "rxjs/Subscription";
import {Subject} from "rxjs/Subject";
import "rxjs/add/observable/combineLatest";
import "rxjs/add/operator/do";
import "rxjs/add/operator/distinctUntilChanged";
import "rxjs/add/operator/filter";
import "rxjs/add/operator/map";
import "rxjs/add/operator/share";
import {
ComponentService,
Component,
DirectionDOMRenderer,
IDirectionConfiguration,
} from "../../Component";
import {IEdgeStatus, Node, Sequence} from "../../Graph";
import {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";
private _renderer: DirectionDOMRenderer;
private _hoveredKeySubject$: Subject<string>;
private _hoveredKey$: Observable<string>;
private _configurationSubscription: Subscription;
private _nodeSubscription: Subscription;
private _renderCameraSubscription: Subscription;
private _hoveredKeySubscription: Subscription;
constructor(name: string, container: Container, navigator: Navigator) {
super(name, container, navigator);
this._renderer = new DirectionDOMRenderer(this.defaultConfiguration, container.element);
this._hoveredKeySubject$ = new Subject<string>();
this._hoveredKey$ = this._hoveredKeySubject$.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 });
}
/** @inheritdoc */
public resize(): void {
this._renderer.resize(this._container.element);
}
protected _activate(): void {
this._configurationSubscription = this._configuration$
.subscribe(
(configuration: IDirectionConfiguration): void => {
this._renderer.setConfiguration(configuration);
});
this._nodeSubscription = this._navigator.stateService.currentNode$
.do(
(node: Node): void => {
this._container.domRenderer.render$.next({name: this._name, vnode: vd.h("div", {}, [])});
this._renderer.setNode(node);
})
.withLatestFrom(this._configuration$)
.switchMap<[IEdgeStatus, Sequence]>(
(nc: [Node, IDirectionConfiguration]): Observable<[IEdgeStatus, Sequence]> => {
let node: Node = nc[0];
let configuration: IDirectionConfiguration = nc[1];
return node.spatialEdges$
.withLatestFrom(
configuration.distinguishSequence ?
this._navigator.newGraphService
.cacheSequence$(node.sequenceKey)
.catch(
(error: Error, caught: Observable<Sequence>): Observable<Sequence> => {
console.error(`Failed to cache sequence (${node.sequenceKey})`, error);
return Observable.empty<Sequence>();
}) :
Observable.of<Sequence>(null));
})
.subscribe(
(es: [IEdgeStatus, Sequence]): void => {
this._renderer.setEdges(es[0], es[1]);
});
this._renderCameraSubscription = this._container.renderService.renderCameraFrame$
.do(
(renderCamera: RenderCamera): void => {
this._renderer.setRenderCamera(renderCamera);
})
.map<DirectionDOMRenderer>(
(renderCamera: RenderCamera): DirectionDOMRenderer => {
return this._renderer;
})
.filter(
(renderer: DirectionDOMRenderer): boolean => {
return renderer.needsRender;
})
.map<IVNodeHash>(
(renderer: DirectionDOMRenderer): IVNodeHash => {
return { name: this._name, vnode: renderer.render(this._navigator) };
})
.subscribe(this._container.domRenderer.render$);
this._hoveredKeySubscription = Observable
.combineLatest<Element>(
[
this._container.domRenderer.element$,
this._container.renderService.renderCamera$,
this._container.mouseService.mouseMove$.startWith(null),
this._container.mouseService.mouseUp$.startWith(null),
],
(e: Element, rc: RenderCamera, mm: MouseEvent, mu: MouseEvent): Element => {
return e;
})
.map<string>(
(element: Element): string => {
let elements: NodeListOf<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$);
}
protected _deactivate(): void {
this._configurationSubscription.unsubscribe();
this._nodeSubscription.unsubscribe();
this._renderCameraSubscription.unsubscribe();
this._hoveredKeySubscription.unsubscribe();
}
protected _getDefaultConfiguration(): IDirectionConfiguration {
return {
distinguishSequence: false,
maxWidth: 460,
minWidth: 260,
};
}
}
ComponentService.register(DirectionComponent);
export default DirectionComponent;