UNPKG

mapillary-js

Version:

WebGL JavaScript library for displaying street level imagery from mapillary.com

523 lines (430 loc) 18.4 kB
import { concat as observableConcat, merge as observableMerge, combineLatest as observableCombineLatest, fromEvent as observableFromEvent, of as observableOf, BehaviorSubject, Observable, Subject, } from "rxjs"; import { switchMap, distinctUntilChanged, publishReplay, refCount, scan, map, startWith, filter, share, takeUntil, take, withLatestFrom, first, bufferCount, } from "rxjs/operators"; import { IMouseClaim, IMouseDeferPixels, } from "../Viewer"; export class MouseService { private _activeSubject$: BehaviorSubject<boolean>; private _active$: Observable<boolean>; private _domMouseDown$: Observable<MouseEvent>; private _domMouseMove$: Observable<MouseEvent>; private _domMouseDragStart$: Observable<MouseEvent>; private _domMouseDrag$: Observable<MouseEvent>; private _domMouseDragEnd$: Observable<MouseEvent | FocusEvent>; private _documentMouseMove$: Observable<MouseEvent>; private _documentMouseUp$: Observable<MouseEvent>; private _mouseDown$: Observable<MouseEvent>; private _mouseMove$: Observable<MouseEvent>; private _mouseLeave$: Observable<MouseEvent>; private _mouseUp$: Observable<MouseEvent>; private _mouseOut$: Observable<MouseEvent>; private _mouseOver$: Observable<MouseEvent>; private _contextMenu$: Observable<MouseEvent>; private _consistentContextMenu$: Observable<MouseEvent>; private _click$: Observable<MouseEvent>; private _dblClick$: Observable<MouseEvent>; private _mouseWheel$: Observable<WheelEvent>; private _mouseDragStart$: Observable<MouseEvent>; private _mouseDrag$: Observable<MouseEvent>; private _mouseDragEnd$: Observable<MouseEvent | FocusEvent>; private _deferPixelClaims$: Subject<IMouseDeferPixels>; private _deferPixels$: Observable<number>; private _proximateClick$: Observable<MouseEvent>; private _staticClick$: Observable<MouseEvent>; private _claimMouse$: Subject<IMouseClaim>; private _claimWheel$: Subject<IMouseClaim>; private _mouseOwner$: Observable<string>; private _wheelOwner$: Observable<string>; constructor( container: EventTarget, canvasContainer: EventTarget, domContainer: EventTarget, doc: EventTarget) { this._activeSubject$ = new BehaviorSubject<boolean>(false); this._active$ = this._activeSubject$.pipe( distinctUntilChanged(), publishReplay(1), refCount()); this._claimMouse$ = new Subject<IMouseClaim>(); this._claimWheel$ = new Subject<IMouseClaim>(); this._deferPixelClaims$ = new Subject<IMouseDeferPixels>(); this._deferPixels$ = this._deferPixelClaims$.pipe( scan( (claims: { [key: string]: number }, claim: IMouseDeferPixels): { [key: string]: number } => { if (claim.deferPixels == null) { delete claims[claim.name]; } else { claims[claim.name] = claim.deferPixels; } return claims; }, {}), map( (claims: { [key: string]: number }): number => { let deferPixelMax: number = -1; for (const key in claims) { if (!claims.hasOwnProperty(key)) { continue; } const deferPixels: number = claims[key]; if (deferPixels > deferPixelMax) { deferPixelMax = deferPixels; } } return deferPixelMax; }), startWith(-1), publishReplay(1), refCount()); this._deferPixels$.subscribe((): void => { /* noop */ }); this._documentMouseMove$ = observableFromEvent<MouseEvent>(doc, "mousemove"); this._documentMouseUp$ = observableFromEvent<MouseEvent>(doc, "mouseup"); this._mouseDown$ = observableFromEvent<MouseEvent>(canvasContainer, "mousedown"); this._mouseLeave$ = observableFromEvent<MouseEvent>(canvasContainer, "mouseleave"); this._mouseMove$ = observableFromEvent<MouseEvent>(canvasContainer, "mousemove"); this._mouseUp$ = observableFromEvent<MouseEvent>(canvasContainer, "mouseup"); this._mouseOut$ = observableFromEvent<MouseEvent>(canvasContainer, "mouseout"); this._mouseOver$ = observableFromEvent<MouseEvent>(canvasContainer, "mouseover"); this._domMouseDown$ = observableFromEvent<MouseEvent>(domContainer, "mousedown"); this._domMouseMove$ = observableFromEvent<MouseEvent>(domContainer, "mousemove"); this._click$ = observableFromEvent<MouseEvent>(canvasContainer, "click"); this._contextMenu$ = observableFromEvent<MouseEvent>(canvasContainer, "contextmenu"); this._dblClick$ = observableMerge( observableFromEvent<MouseEvent>(container, "click"), observableFromEvent<MouseEvent>(canvasContainer, "dblclick")).pipe( bufferCount(3, 1), filter( (events: MouseEvent[]): boolean => { const event1: MouseEvent = events[0]; const event2: MouseEvent = events[1]; const event3: MouseEvent = events[2]; return event1.type === "click" && event2.type === "click" && event3.type === "dblclick" && (<HTMLElement>event1.target).parentNode === canvasContainer && (<HTMLElement>event2.target).parentNode === canvasContainer; }), map( (events: MouseEvent[]): MouseEvent => { return events[2]; }), share()); observableMerge( this._domMouseDown$, this._domMouseMove$, this._dblClick$, this._contextMenu$) .subscribe( (event: MouseEvent): void => { event.preventDefault(); }); this._mouseWheel$ = observableMerge( observableFromEvent<WheelEvent>(canvasContainer, "wheel"), observableFromEvent<WheelEvent>(domContainer, "wheel")).pipe( share()); this._consistentContextMenu$ = observableMerge( this._mouseDown$, this._mouseMove$, this._mouseOut$, this._mouseUp$, this._contextMenu$).pipe( bufferCount(3, 1), filter( (events: MouseEvent[]): boolean => { // fire context menu on mouse up both on mac and windows return events[0].type === "mousedown" && events[1].type === "contextmenu" && events[2].type === "mouseup"; }), map( (events: MouseEvent[]): MouseEvent => { return events[1]; }), share()); const dragStop$: Observable<MouseEvent | FocusEvent> = observableMerge( observableFromEvent<FocusEvent>(window, "blur"), this._documentMouseUp$.pipe( filter( (e: MouseEvent): boolean => { return e.button === 0; }))).pipe( share()); const mouseDragInitiate$: Observable<[MouseEvent, MouseEvent]> = this._createMouseDragInitiate$(this._mouseDown$, dragStop$, true).pipe(share()); this._mouseDragStart$ = this._createMouseDragStart$(mouseDragInitiate$).pipe(share()); this._mouseDrag$ = this._createMouseDrag$(mouseDragInitiate$, dragStop$).pipe(share()); this._mouseDragEnd$ = this._createMouseDragEnd$(this._mouseDragStart$, dragStop$).pipe(share()); const domMouseDragInitiate$: Observable<[MouseEvent, MouseEvent]> = this._createMouseDragInitiate$(this._domMouseDown$, dragStop$, false).pipe(share()); this._domMouseDragStart$ = this._createMouseDragStart$(domMouseDragInitiate$).pipe(share()); this._domMouseDrag$ = this._createMouseDrag$(domMouseDragInitiate$, dragStop$).pipe(share()); this._domMouseDragEnd$ = this._createMouseDragEnd$(this._domMouseDragStart$, dragStop$).pipe(share()); this._proximateClick$ = this._mouseDown$.pipe( switchMap( (mouseDown: MouseEvent): Observable<MouseEvent> => { return this._click$.pipe( takeUntil(this._createDeferredMouseMove$(mouseDown, this._documentMouseMove$)), take(1)); }), share()); this._staticClick$ = this._mouseDown$.pipe( switchMap( (e: MouseEvent): Observable<MouseEvent> => { return this._click$.pipe( takeUntil(this._documentMouseMove$), take(1)); }), share()); this._mouseDragStart$.subscribe(); this._mouseDrag$.subscribe(); this._mouseDragEnd$.subscribe(); this._domMouseDragStart$.subscribe(); this._domMouseDrag$.subscribe(); this._domMouseDragEnd$.subscribe(); this._staticClick$.subscribe(); this._mouseOwner$ = this._createOwner$(this._claimMouse$).pipe( publishReplay(1), refCount()); this._wheelOwner$ = this._createOwner$(this._claimWheel$).pipe( publishReplay(1), refCount()); this._mouseOwner$.subscribe(() => { /* noop */ }); this._wheelOwner$.subscribe(() => { /* noop */ }); } public get active$(): Observable<boolean> { return this._active$; } public get activate$(): Subject<boolean> { return this._activeSubject$; } public get documentMouseMove$(): Observable<MouseEvent> { return this._documentMouseMove$; } public get documentMouseUp$(): Observable<MouseEvent> { return this._documentMouseUp$; } public get domMouseDragStart$(): Observable<MouseEvent> { return this._domMouseDragStart$; } public get domMouseDrag$(): Observable<MouseEvent> { return this._domMouseDrag$; } public get domMouseDragEnd$(): Observable<MouseEvent | FocusEvent> { return this._domMouseDragEnd$; } public get domMouseDown$(): Observable<MouseEvent> { return this._domMouseDown$; } public get domMouseMove$(): Observable<MouseEvent> { return this._domMouseMove$; } public get mouseOwner$(): Observable<string> { return this._mouseOwner$; } public get mouseDown$(): Observable<MouseEvent> { return this._mouseDown$; } public get mouseMove$(): Observable<MouseEvent> { return this._mouseMove$; } public get mouseLeave$(): Observable<MouseEvent> { return this._mouseLeave$; } public get mouseOut$(): Observable<MouseEvent> { return this._mouseOut$; } public get mouseOver$(): Observable<MouseEvent> { return this._mouseOver$; } public get mouseUp$(): Observable<MouseEvent> { return this._mouseUp$; } public get click$(): Observable<MouseEvent> { return this._click$; } public get dblClick$(): Observable<MouseEvent> { return this._dblClick$; } public get contextMenu$(): Observable<MouseEvent> { return this._consistentContextMenu$; } public get mouseWheel$(): Observable<WheelEvent> { return this._mouseWheel$; } public get mouseDragStart$(): Observable<MouseEvent> { return this._mouseDragStart$; } public get mouseDrag$(): Observable<MouseEvent> { return this._mouseDrag$; } public get mouseDragEnd$(): Observable<MouseEvent | FocusEvent> { return this._mouseDragEnd$; } public get proximateClick$(): Observable<MouseEvent> { return this._proximateClick$; } public get staticClick$(): Observable<MouseEvent> { return this._staticClick$; } public claimMouse(name: string, zindex: number): void { this._claimMouse$.next({ name: name, zindex: zindex }); } public unclaimMouse(name: string): void { this._claimMouse$.next({ name: name, zindex: null }); } public deferPixels(name: string, deferPixels: number): void { this._deferPixelClaims$.next({ name: name, deferPixels: deferPixels }); } public undeferPixels(name: string): void { this._deferPixelClaims$.next({ name: name, deferPixels: null }); } public claimWheel(name: string, zindex: number): void { this._claimWheel$.next({name: name, zindex: zindex}); } public unclaimWheel(name: string): void { this._claimWheel$.next({name: name, zindex: null}); } public filtered$<T>(name: string, observable$: Observable<T>): Observable<T> { return this._filtered(name, observable$, this._mouseOwner$); } public filteredWheel$<T>(name: string, observable$: Observable<T>): Observable<T> { return this._filtered(name, observable$, this._wheelOwner$); } private _createDeferredMouseMove$( origin: MouseEvent, mouseMove$: Observable<MouseEvent>): Observable<MouseEvent> { return mouseMove$.pipe( map( (mouseMove: MouseEvent): [MouseEvent, number] => { const deltaX: number = mouseMove.clientX - origin.clientX; const deltaY: number = mouseMove.clientY - origin.clientY; return [mouseMove, Math.sqrt(deltaX * deltaX + deltaY * deltaY)]; }), withLatestFrom(this._deferPixels$), filter( ([[mouseMove, delta], deferPixels]: [[MouseEvent, number], number]): boolean => { return delta > deferPixels; }), map( ([[mouseMove, delta], deferPixels]: [[MouseEvent, number], number]): MouseEvent => { return mouseMove; })); } private _createMouseDrag$( mouseDragStartInitiate$: Observable<[MouseEvent, MouseEvent]>, stop$: Observable<Event>): Observable<MouseEvent> { return mouseDragStartInitiate$.pipe( map( ([mouseDown, mouseMove]: [MouseEvent, MouseEvent]): MouseEvent => { return mouseMove; }), switchMap( (mouseMove: MouseEvent): Observable<MouseEvent> => { return observableConcat( observableOf(mouseMove), this._documentMouseMove$).pipe( takeUntil(stop$)); })); } private _createMouseDragEnd$<T>(mouseDragStart$: Observable<MouseEvent>, stop$: Observable<T>): Observable<T> { return mouseDragStart$.pipe( switchMap( (event: MouseEvent): Observable<T> => { return stop$.pipe(first()); })); } private _createMouseDragStart$(mouseDragStartInitiate$: Observable<[MouseEvent, MouseEvent]>): Observable<MouseEvent> { return mouseDragStartInitiate$.pipe( map( ([mouseDown, mouseMove]: [MouseEvent, MouseEvent]): MouseEvent => { return mouseDown; })); } private _createMouseDragInitiate$( mouseDown$: Observable<MouseEvent>, stop$: Observable<Event>, defer: boolean): Observable<[MouseEvent, MouseEvent]> { return mouseDown$.pipe( filter( (mouseDown: MouseEvent): boolean => { return mouseDown.button === 0; }), switchMap( (mouseDown: MouseEvent): Observable<[MouseEvent, MouseEvent]> => { return observableCombineLatest( observableOf(mouseDown), defer ? this._createDeferredMouseMove$(mouseDown, this._documentMouseMove$) : this._documentMouseMove$).pipe( takeUntil(stop$), take(1)); })); } private _createOwner$(claim$: Observable<IMouseClaim>): Observable<string> { return claim$.pipe( scan( (claims: { [key: string]: number }, claim: IMouseClaim): { [key: string]: number } => { if (claim.zindex == null) { delete claims[claim.name]; } else { claims[claim.name] = claim.zindex; } return claims; }, {}), map( (claims: { [key: string]: number }): string => { let owner: string = null; let zIndexMax: number = -1; for (const name in claims) { if (!claims.hasOwnProperty(name)) { continue; } if (claims[name] > zIndexMax) { zIndexMax = claims[name]; owner = name; } } return owner; }), startWith(null)); } private _filtered<T>(name: string, observable$: Observable<T>, owner$: Observable<string>): Observable<T> { return observable$.pipe( withLatestFrom(owner$), filter( ([item, owner]: [T, string]): boolean => { return owner === name; }), map( ([item, owner]: [T, string]): T => { return item; })); } } export default MouseService;