From a80d017274dc10849e79b752be4f7da340f5b911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petro=CC=81=20Tama=CC=81s?= Date: Fri, 6 Apr 2018 19:31:13 +0200 Subject: [PATCH] feat: refactor route planner libs --- src/app/app.module.ts | 5 + .../hike-edit-map/hike-edit-map.component.ts | 25 ++-- .../hike-edit-pois-external.component.ts | 93 +++++++----- .../hike-edit-pois-gtrack-table.component.ts | 5 + .../hike-edit-pois-gtrack.component.ts | 45 ++++-- .../hike-edit-pois-hike-table.component.ts | 5 + .../hike-edit-pois-hike.component.ts | 34 +++-- .../hike-edit-route-planner.component.ts | 44 +++--- .../pages/hike-edit/hike-edit.component.ts | 12 +- src/app/shared/services/admin-map/index.ts | 3 + .../services/admin-map/lib/admin-map.ts | 131 ++++++----------- .../services/admin-map/lib/route-info.ts | 112 -------------- ...te-planner.ts => route-planner.service.ts} | 139 +++++++++--------- ...-control.ts => routing-control.service.ts} | 57 ++++--- ...t-marker.ts => waypoint-marker.service.ts} | 39 ++--- .../services/hike-data/hike-data.service.ts | 14 +- .../shared/services/poi/poi-editor.service.ts | 105 +++++++------ src/app/store/actions/hike-edit-poi.ts | 50 ++++--- .../store/actions/test/hike-edit-poi.spec.ts | 41 ++++-- .../store/reducer/hike-edig-general-info.ts | 15 ++ src/app/store/reducer/hike-edit-map.ts | 28 ++++ src/app/store/reducer/hike-edit-poi.ts | 60 +++++++- .../store/reducer/hike-edit-route-planner.ts | 23 ++- .../store/reducer/test/hike-edit-poi.spec.ts | 47 +++++- .../selectors/hike-edit-route-planner.ts | 5 + src/app/store/state/hike-edit-poi.ts | 1 + .../store/state/hike-edit-route-planner.ts | 2 +- 27 files changed, 621 insertions(+), 519 deletions(-) delete mode 100644 src/app/shared/services/admin-map/lib/route-info.ts rename src/app/shared/services/admin-map/{lib/route-planner.ts => route-planner.service.ts} (54%) rename src/app/shared/services/admin-map/{lib/routing-control.ts => routing-control.service.ts} (73%) rename src/app/shared/services/admin-map/{lib/waypoint-marker.ts => waypoint-marker.service.ts} (75%) diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 7a94599a..6d63a8f2 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -65,6 +65,8 @@ import { ToasterModule, ToasterService } from 'angular2-toaster'; // Global styles import './styles'; +import { RoutePlannerService, RoutingControlService } from './shared/services/admin-map'; +import { WaypointMarkerService } from './shared/services/admin-map/waypoint-marker.service'; const hikeModuleConfig = new HikeModuleConfig(); hikeModuleConfig.storeDomains = { @@ -153,6 +155,9 @@ export class CustomRouterStateSerializer implements RouterStateSerializer{ // London @@ -53,6 +55,7 @@ export class HikeEditMapComponent implements OnInit, OnDestroy, AfterViewInit { constructor( private _store: Store, private _adminMapService: AdminMapService, + private _waypointMarkerService: WaypointMarkerService, private _hikeEditRoutePlannerSelectors: HikeEditRoutePlannerSelectors ) {} @@ -75,7 +78,7 @@ export class HikeEditMapComponent implements OnInit, OnDestroy, AfterViewInit { this._destroy$.next(true); this._destroy$.unsubscribe(); - this.mapComponent.map.destroy(); + this._store.dispatch(new adminMapActions.ResetMap()); } ngAfterViewInit() { @@ -88,7 +91,7 @@ export class HikeEditMapComponent implements OnInit, OnDestroy, AfterViewInit { .on('click', (e: LeafletMouseEvent) => { if (this.mode === 'routing') { // TODO action - this.mapComponent.map.waypointMarker.addWaypoint(e.latlng); + this._waypointMarkerService.addWaypoint(e.latlng); } else { // console.log('todo _createCheckpoint'); // this._createCheckpoint(e.latlng); @@ -133,10 +136,14 @@ export class HikeEditMapComponent implements OnInit, OnDestroy, AfterViewInit { } private _addBuffer() { - const _buffer = this.mapComponent.map.getBuffer(); - if (_buffer) { - this._geoJsonOnMap = this.mapComponent.map.addGeoJSON(_buffer); - } + this.mapComponent.map + .getBuffer() + .map((buffer) => { + console.log('TEST _addBuffer ', buffer); + if (buffer) { + this._geoJsonOnMap = this.mapComponent.map.addGeoJSON(buffer); + } + }); } private _removeBuffer() { diff --git a/src/app/pages/hike-edit/components/hike-edit-pois-external/hike-edit-pois-external.component.ts b/src/app/pages/hike-edit/components/hike-edit-pois-external/hike-edit-pois-external.component.ts index 38f0b2b1..4daca24e 100644 --- a/src/app/pages/hike-edit/components/hike-edit-pois-external/hike-edit-pois-external.component.ts +++ b/src/app/pages/hike-edit/components/hike-edit-pois-external/hike-edit-pois-external.component.ts @@ -3,12 +3,12 @@ import { Component, Input, OnInit, OnDestroy } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; -import { AdminMap, AdminMapService, AdminMapMarker } from 'app/shared/services/admin-map'; +import { AdminMap, AdminMapService, AdminMapMarker, RoutePlannerService } from 'app/shared/services/admin-map'; import { PoiEditorService } from 'app/shared/services'; import { Poi, PoiSelectors } from 'subrepos/gtrack-common-ngx'; import { IPoi } from 'subrepos/provider-client'; import { - IExternalPoiType, IExternalPoi, IWikipediaPoi, IGooglePoi, IOsmPoi + IExternalPoiType, IExternalPoi, IWikipediaPoi, IGooglePoi, IOsmPoi, IGTrackPoi } from 'app/shared/interfaces'; import { State, hikeEditPoiActions, IExternalPoiListContextState, commonPoiActions, IHikeEditRoutePlannerState @@ -34,6 +34,7 @@ export class HikeEditPoisExternalComponent implements OnInit, OnDestroy { constructor( private _store: Store, private _adminMapService: AdminMapService, + private _routePlannerService: RoutePlannerService, private _hikeEditMapSelectors: HikeEditMapSelectors, private _hikeEditPoiSelectors: HikeEditPoiSelectors, private _poiSelectors: PoiSelectors, @@ -61,35 +62,42 @@ export class HikeEditPoisExternalComponent implements OnInit, OnDestroy { .select(this._hikeEditPoiSelectors.getHikeEditContextPropertySelector(this.poiType.subdomain, 'loaded')) .takeUntil(this._destroy$) .filter(loaded => !!loaded) - .subscribe((loaded: boolean) => { - // We have pure poi list on the store, now we have update some property - this._getSubdomainSelector(this.poiType.subdomain) - .take(1) - .switchMap((pois: IExternalPoi[]) => this._poiEditorService.organizePois(pois, this._map.routeInfo.getPath())) - .switchMap((pois: IExternalPoi[]) => this._poiEditorService.assignOnOffRoutePois(pois)) - .switchMap((pois: IExternalPoi[]) => this._poiEditorService.handleElevation(pois)) - .subscribe((pois) => { - // Refresh poi list on the store - this._updateSubdomainPois(pois); - - // Refresh markers - this._poiEditorService.refreshPoiMarkers(this._map); - - // Get gTrack pois for checking inGtrackDb - this._poiEditorService.getGTrackPois(this._map); - }); + .switchMap(() => Observable.combineLatest( + this._getSubdomainSelector(this.poiType.subdomain).take(1), + this._store.select(this._hikeEditRoutePlannerSelectors.getPath) + )) + .filter(([pois, path]: [IExternalPoi[], any]) => typeof pois !== 'undefined') + .switchMap(([pois, path]: [IExternalPoi[], any]) => { + return Observable.of(this._poiEditorService.organizePois(_.cloneDeep(pois), path)); + }) + .switchMap((pois: IExternalPoi[]) => this._poiEditorService.assignOnOffRoutePois(pois)) + .switchMap((pois: IExternalPoi[]) => this._poiEditorService.handleElevation(pois)) + .subscribe((pois) => { + // Refresh poi list on the store + this._updateSubdomainPois(pois); + + // DIRTY FLAG TODO + + // Get gTrack pois for checking inGtrackDb + if (pois.length > 0) { + // gTrack poi will call marker refresher! + this._poiEditorService.getGTrackPois(this._map); + } else { + // Refresh markers + console.log('Call refreshPoiMarkers 1'); + this._poiEditorService.refreshPoiMarkers(this._map); + } }); // Update inGtrackDb properties after common poi list has been refreshed - this._store.select(this._poiSelectors.getAllPois) - .takeUntil(this._destroy$) - .subscribe((gTRackPois) => { - this._getSubdomainSelector(this.poiType.subdomain) - .take(1) - .subscribe((pois) => { - this._patchSubdomainPois(this._poiEditorService.handleGTrackPois(pois, gTRackPois), ['id', 'inGtrackDb']); - }); - }); + Observable.combineLatest( + this._store.select(this._poiSelectors.getAllPois), + this._getSubdomainSelector(this.poiType.subdomain).take(1) + ).subscribe(([gTrackPois, externalPois]: [IGTrackPoi[], IExternalPoi[]]) => { + if (gTrackPois.length > 0 && externalPois.length > 0) { + this._setSubdomainPoisInGtrackDb(this._poiEditorService.handleGTrackPois(externalPois, gTrackPois)); + } + }); // // Contexts @@ -155,21 +163,34 @@ export class HikeEditPoisExternalComponent implements OnInit, OnDestroy { } } - private _patchSubdomainPois(pois, props: string[]) { + private _setSubdomainPoisInGtrackDb(pois) { + console.log('_patchSubdomainPois', this.poiType.subdomain); switch (this.poiType.subdomain) { case 'google': - this._store.dispatch(new hikeEditPoiActions.SetGooglePois({ pois: pois })); break; + this._store.dispatch(new hikeEditPoiActions.SetGooglePoisInGtrackDb({ + properties: pois.map(p => _.pick(p, ['id', 'inGtrackDb'])) + })); + break; case 'wikipedia': - this._store.dispatch(new hikeEditPoiActions.SetWikipediaPois({ pois: pois })); break; + this._store.dispatch(new hikeEditPoiActions.SetWikipediaPoisInGtrackDb({ + properties: pois.map(p => _.pick(p, ['id', 'inGtrackDb'])) + })); + break; case 'osmAmenity': - this._store.dispatch(new hikeEditPoiActions.SetOsmAmenityPois({ pois: pois })); break; + this._store.dispatch(new hikeEditPoiActions.SetOsmAmenityPoisInGtrackDb({ + properties: pois.map(p => _.pick(p, ['id', 'inGtrackDb'])) + })); + break; case 'osmNatural': - this._store.dispatch(new hikeEditPoiActions.PatchOsmNaturalPois({ - properties: pois.map(p => _.pick(p, props)) + this._store.dispatch(new hikeEditPoiActions.SetOsmNaturalPoisInGtrackDb({ + properties: pois.map(p => _.pick(p, ['id', 'inGtrackDb'])) })); break; case 'osmRoute': - this._store.dispatch(new hikeEditPoiActions.SetOsmRoutePois({ pois: pois })); break; + this._store.dispatch(new hikeEditPoiActions.SetOsmRoutePoisInGtrackDb({ + properties: pois.map(p => _.pick(p, ['id', 'inGtrackDb'])) + })); + break; } } @@ -177,7 +198,7 @@ export class HikeEditPoisExternalComponent implements OnInit, OnDestroy { * Get pois for the current subdomain */ public getPois() { - let _bounds = this._map.routeInfo.getSearchBounds(); + let _bounds = this._routePlannerService.getSearchBounds(); if (_bounds) { // Get pois for the current domain diff --git a/src/app/pages/hike-edit/components/hike-edit-pois-gtrack-table/hike-edit-pois-gtrack-table.component.ts b/src/app/pages/hike-edit/components/hike-edit-pois-gtrack-table/hike-edit-pois-gtrack-table.component.ts index e2297a33..82bc2e66 100644 --- a/src/app/pages/hike-edit/components/hike-edit-pois-gtrack-table/hike-edit-pois-gtrack-table.component.ts +++ b/src/app/pages/hike-edit/components/hike-edit-pois-gtrack-table/hike-edit-pois-gtrack-table.component.ts @@ -25,6 +25,11 @@ export class HikeEditPoisGTrackTableComponent { this._store.dispatch(new hikeEditGeneralInfoActions.AddPoi({ poi: poi.id })); + + this._store.dispatch(new hikeEditPoiActions.SetDirty({ + subdomain: 'gTrack', + dirty: true + })); } public openModal($event, poi: IGTrackPoi) { diff --git a/src/app/pages/hike-edit/components/hike-edit-pois-gtrack/hike-edit-pois-gtrack.component.ts b/src/app/pages/hike-edit/components/hike-edit-pois-gtrack/hike-edit-pois-gtrack.component.ts index 2f4a4d2f..1516f0a2 100644 --- a/src/app/pages/hike-edit/components/hike-edit-pois-gtrack/hike-edit-pois-gtrack.component.ts +++ b/src/app/pages/hike-edit/components/hike-edit-pois-gtrack/hike-edit-pois-gtrack.component.ts @@ -4,7 +4,7 @@ import { Store } from '@ngrx/store'; import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; import { - PoiSelectors, CenterRadius, GeometryService, GeoSearchSelectors, Poi, PoiSaved, IGeoSearchContextState + PoiSelectors, CenterRadius, GeometryService, GeoSearchSelectors, Poi, PoiSaved, IGeoSearchContextState, IGeoSearchResponseItem } from 'subrepos/gtrack-common-ngx'; import { IPoiStored, IPoi } from 'subrepos/provider-client'; import { AdminMap, AdminMapService, AdminMapMarker } from 'app/shared/services/admin-map'; @@ -56,24 +56,51 @@ export class HikeEditPoisGTrackComponent implements OnInit, OnDestroy { }); // Get pois by id from geoSearch result - this._store - .select(this._geoSearchSelectors.getGeoSearch('gTrackPois')) + Observable + .combineLatest( + this._store.select(this._geoSearchSelectors.getGeoSearch('gTrackPois')), + this._store.select(this._poiSelectors.getPoiIds) + ) .takeUntil(this._destroy$) - .subscribe((searchData) => { + .debounceTime(100) + .subscribe(([searchData, inStorePoiIds]: [IGeoSearchResponseItem, string[]]) => { if (searchData) { - this._store.dispatch(new commonPoiActions.LoadPois((searchData).results)); + const poiIds = _.difference((searchData).results, _.intersection((searchData).results, inStorePoiIds)) + + // Get only the not-loaded pois + if (poiIds && poiIds.length > 0) { + this._store.dispatch(new commonPoiActions.LoadPois(poiIds)); + } } }); // Poi list based on geoSearch results - this.pois$ = this._store - .select(this._geoSearchSelectors.getGeoSearchResults<(IPoi)>('gTrackPois', this._poiSelectors.getAllPois)) - .switchMap((pois: Poi[]) => this._poiEditorService.organizePois(_.cloneDeep(pois), this._map.routeInfo.getPath())) - .switchMap((pois: IGTrackPoi[]) => this._poiEditorService.handleHikeInclusion(pois)); + this.pois$ = Observable + .combineLatest( + this._store.select(this._geoSearchSelectors.getGeoSearchResults<(IPoi)>('gTrackPois', this._poiSelectors.getAllPois)), + this._store.select(this._hikeEditRoutePlannerSelectors.getPath), + this._store.select(this._hikeEditPoiSelectors.getHikeEditContextPropertySelector('gTrack', 'dirty')) + ) + .takeUntil(this._destroy$) + .debounceTime(100) + .filter(([pois, path, dirty]: [Poi[], any, boolean]) => typeof pois !== 'undefined') + .switchMap(([pois, path, dirty]: [Poi[], any, boolean]) => { + return Observable.of(this._poiEditorService.organizePois(_.cloneDeep(pois), path)); + }) + .switchMap((pois: IGTrackPoi[]) => { + this._store.dispatch(new hikeEditPoiActions.SetDirty({ + subdomain: 'gTrack', + dirty: false + })); + + return Observable.of(this._poiEditorService.handleHikeInclusion(pois)); + }); this.pois$ .takeUntil(this._destroy$) + .debounceTime(100) .subscribe((pois: Poi[]) => { + console.log('POI CAHNGES IN GTRAK'); // Refresh markers this._poiEditorService.refreshPoiMarkers(this._map); }); diff --git a/src/app/pages/hike-edit/components/hike-edit-pois-hike-table/hike-edit-pois-hike-table.component.ts b/src/app/pages/hike-edit/components/hike-edit-pois-hike-table/hike-edit-pois-hike-table.component.ts index ed0bd47d..42de60ec 100644 --- a/src/app/pages/hike-edit/components/hike-edit-pois-hike-table/hike-edit-pois-hike-table.component.ts +++ b/src/app/pages/hike-edit/components/hike-edit-pois-hike-table/hike-edit-pois-hike-table.component.ts @@ -25,6 +25,11 @@ export class HikeEditPoisHikeTableComponent { this._store.dispatch(new hikeEditGeneralInfoActions.RemovePoi({ poi: poi.id })); + + this._store.dispatch(new hikeEditPoiActions.SetDirty({ + subdomain: 'gTrack', + dirty: true + })); } public openModal($event, poi: IGTrackPoi) { diff --git a/src/app/pages/hike-edit/components/hike-edit-pois-hike/hike-edit-pois-hike.component.ts b/src/app/pages/hike-edit/components/hike-edit-pois-hike/hike-edit-pois-hike.component.ts index 58eb0b16..ff49037b 100644 --- a/src/app/pages/hike-edit/components/hike-edit-pois-hike/hike-edit-pois-hike.component.ts +++ b/src/app/pages/hike-edit/components/hike-edit-pois-hike/hike-edit-pois-hike.component.ts @@ -55,23 +55,39 @@ export class HikeEditPoisHikeComponent implements OnInit, OnDestroy { }); // Get pois by id - this._store - .select(this._hikeEditGeneralInfoSelectors.getPois) + Observable + .combineLatest( + this._store.select(this._hikeEditGeneralInfoSelectors.getPois), + this._store.select(this._poiSelectors.getPoiIds) + ) + .takeUntil(this._destroy$) + .debounceTime(100) .takeUntil(this._destroy$) - .delay(2000) // TODO wait for map.routeInfo with a better way - .subscribe((poiIds) => { - if (poiIds) { + .subscribe(([inHikePoiIds, inStorePoiIds]: [string[], string[]]) => { + const poiIds = _.difference(inHikePoiIds, _.intersection(inHikePoiIds, inStorePoiIds)) + + // Get only the not-loaded pois + if (poiIds && poiIds.length > 0) { this._store.dispatch(new commonPoiActions.LoadPois(poiIds)); } }); - // Poi list - this.pois$ = this._store - .select(this._hikeEditGeneralInfoSelectors.getHikePois<(IPoi)>(this._poiSelectors.getAllPois)) - .switchMap((pois: Poi[]) => this._poiEditorService.organizePois(_.cloneDeep(pois), this._map.routeInfo.getPath())) + // Poi list TODO uncomment + this.pois$ = Observable + .combineLatest( + this._store.select(this._hikeEditGeneralInfoSelectors.getHikePois<(IPoi)>(this._poiSelectors.getAllPois)), + this._store.select(this._hikeEditRoutePlannerSelectors.getPath) + ) + .takeUntil(this._destroy$) + .debounceTime(100) + .filter(([pois, path]: [Poi[], any]) => typeof pois !== 'undefined') + .switchMap(([pois, path]: [Poi[], any]) => { + return Observable.of(this._poiEditorService.organizePois(_.cloneDeep(pois), path)); + }); this.pois$ .takeUntil(this._destroy$) + .debounceTime(100) .subscribe((pois: Poi[]) => { // Refresh markers this._poiEditorService.refreshPoiMarkers(this._map); diff --git a/src/app/pages/hike-edit/components/hike-edit-route-planner/hike-edit-route-planner.component.ts b/src/app/pages/hike-edit/components/hike-edit-route-planner/hike-edit-route-planner.component.ts index 8a7f132e..0693ef25 100644 --- a/src/app/pages/hike-edit/components/hike-edit-route-planner/hike-edit-route-planner.component.ts +++ b/src/app/pages/hike-edit/components/hike-edit-route-planner/hike-edit-route-planner.component.ts @@ -1,5 +1,7 @@ import { Component, OnInit, OnDestroy } from '@angular/core'; -import { AdminMap, AdminMapService } from 'app/shared/services/admin-map'; +import { + AdminMap, AdminMapService, WaypointMarkerService, RoutingControlService +} from 'app/shared/services/admin-map'; import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; import { Store } from '@ngrx/store'; @@ -27,6 +29,8 @@ export class HikeEditRoutePlannerComponent implements OnInit, OnDestroy { constructor( private _adminMapService: AdminMapService, + private _routingControlService: RoutingControlService, + private _waypointMarkerService: WaypointMarkerService, private _hikeEditMapSelectors: HikeEditMapSelectors, private _hikeEditGeneralInfoSelectors: HikeEditGeneralInfoSelectors, private _hikeEditRoutePlannerSelectors: HikeEditRoutePlannerSelectors, @@ -81,36 +85,30 @@ export class HikeEditRoutePlannerComponent implements OnInit, OnDestroy { } public removeLast() { - this._map.waypointMarker.deleteLast(); + this._waypointMarkerService.deleteLast(); } public closeCircle() { - this._map.waypointMarker.closeCircle(); + this._waypointMarkerService.closeCircle(); } public deletePlan() { - this._map.routeInfo.deletePlan(); - this._map.waypointMarker.reset(); - this._map.routingControl.clearControls(); + this._waypointMarkerService.reset(); + this._routingControlService.clearControls(); } public saveRoute() { - const _routePlannerState: Observable = this._store - .select(this._hikeEditRoutePlannerSelectors.getRoutePlanner) - .take(1) - - const _generalInfoState: Observable = this._store - .select(this._hikeEditGeneralInfoSelectors.getRouteId) - .take(1) - Observable - .forkJoin(_routePlannerState, _generalInfoState) - .subscribe(data => { - if (data[0] && data[1]) { + .combineLatest( + this._store.select(this._hikeEditRoutePlannerSelectors.getRoutePlanner).take(1), + this._store.select(this._hikeEditGeneralInfoSelectors.getRouteId).take(1) + ) + .subscribe(([routePlannerState, routeId]: [IHikeEditRoutePlannerState, string]) => { + if (routePlannerState && routeId) { let _route: IRoute = { - id: data[1], - bounds: data[0].route.bounds, - route: _.pick(data[0].route, ['type', 'features']) + id: routeId, + bounds: (routePlannerState.route).bounds, + route: _.pick(routePlannerState.route, ['type', 'features']) }; this._store.dispatch(new commonRouteActions.SaveRoute(_route)); @@ -119,8 +117,8 @@ export class HikeEditRoutePlannerComponent implements OnInit, OnDestroy { } private _loadRoute(routeData: Route) { - if (this._map && this._map.waypointMarker) { - this._map.waypointMarker.reset(); + if (this._map && this._waypointMarkerService) { + this._waypointMarkerService.reset(); for (let feature of routeData.route.features) { let latlng = L.latLng( @@ -128,7 +126,7 @@ export class HikeEditRoutePlannerComponent implements OnInit, OnDestroy { feature.geometry.coordinates[0] ); - this._map.waypointMarker.addWaypoint(latlng); + this._waypointMarkerService.addWaypoint(latlng); } } } diff --git a/src/app/pages/hike-edit/hike-edit.component.ts b/src/app/pages/hike-edit/hike-edit.component.ts index b426a575..699060f5 100644 --- a/src/app/pages/hike-edit/hike-edit.component.ts +++ b/src/app/pages/hike-edit/hike-edit.component.ts @@ -9,7 +9,7 @@ import { } from 'app/store'; import { HikeEditGeneralInfoSelectors, HikeEditRoutePlannerSelectors } from 'app/store/selectors'; import { HikeDataService } from 'app/shared/services'; -import { IHikeProgramStored, IHikeProgram } from 'subrepos/provider-client'; +import { IHikeProgramStored, IHikeProgram, IPoi } from 'subrepos/provider-client'; import { RouteActionTypes, HikeSelectors, IHikeContextState } from 'subrepos/gtrack-common-ngx'; import { ToasterService } from 'angular2-toaster'; @@ -63,12 +63,12 @@ export class HikeEditComponent implements OnInit, OnDestroy { // Generate initial hike id and load the empty hikeProgram (for save toaster handling) const _hikeId = uuid(); this._store.dispatch(new hikeEditGeneralInfoActions.SetHikeId({ hikeId: _hikeId })); - this._store.dispatch(new commonHikeActions.LoadHikeProgram(_hikeId)); + this._store.dispatch(new commonHikeActions.HikeProgramUnsaved(_hikeId)); // Generate initial route id and load the empty route (for save toaster handling) const _routeId = uuid(); this._store.dispatch(new hikeEditGeneralInfoActions.SetRouteId({ routeId: _routeId })); - this._store.dispatch(new commonRouteActions.LoadRoute(_routeId)); + this._store.dispatch(new commonRouteActions.RouteUnsaved(_routeId)); // Create initial language block this._store.dispatch(new hikeEditGeneralInfoActions.SetDescriptions({ @@ -90,10 +90,8 @@ export class HikeEditComponent implements OnInit, OnDestroy { this.allowSave$ = Observable.combineLatest( this._store.select(this._hikeEditGeneralInfoSelectors.getPois), this.routeInfoData$ - ).map(data => { - const _pois = data[0]; - const _segments = data[1].segments; - return _pois.length > 0 && (_segments && _segments.length > 0); + ).map(([inHikePois, routeInfoData]: [any[], any]) => { + return inHikePois.length > 0 && (routeInfoData.segments && routeInfoData.segments.length > 0); }) // Handling hike save diff --git a/src/app/shared/services/admin-map/index.ts b/src/app/shared/services/admin-map/index.ts index 1f3bcf5d..842d0c4d 100644 --- a/src/app/shared/services/admin-map/index.ts +++ b/src/app/shared/services/admin-map/index.ts @@ -1,3 +1,6 @@ export * from './admin-map.service'; export { AdminMap } from './lib/admin-map'; export { AdminMapMarker } from './lib/admin-map-marker'; +export { RoutePlannerService } from './route-planner.service'; +export { RoutingControlService } from './routing-control.service'; +export { WaypointMarkerService } from './waypoint-marker.service'; diff --git a/src/app/shared/services/admin-map/lib/admin-map.ts b/src/app/shared/services/admin-map/lib/admin-map.ts index ea21e087..721d8d8e 100644 --- a/src/app/shared/services/admin-map/lib/admin-map.ts +++ b/src/app/shared/services/admin-map/lib/admin-map.ts @@ -1,23 +1,14 @@ import { Store } from '@ngrx/store'; import { State, adminMapActions } from 'app/store'; +import { Observable } from 'rxjs/Observable'; import { HikeEditRoutePlannerSelectors } from 'app/store/selectors'; -import { RoutingControl } from './routing-control'; -import { WaypointMarker } from './waypoint-marker'; -import { RouteInfo } from './route-info'; -import { RoutePlanner } from './route-planner'; -import { - Map, IconService, MapMarkerService, GameRuleService, RouteService, ElevationService -} from 'subrepos/gtrack-common-ngx/app'; +import { Map, IconService, MapMarkerService } from 'subrepos/gtrack-common-ngx/app'; import * as L from 'leaflet'; import * as turf from '@turf/turf'; import * as _ from 'lodash'; export class AdminMap extends Map { - private _routingControl: RoutingControl; - private _waypointMarker: WaypointMarker; - private _routeInfo: RouteInfo; - private _routePlanner: RoutePlanner; public markersGroup: L.LayerGroup; constructor( @@ -26,79 +17,39 @@ export class AdminMap extends Map { protected iconService: IconService, protected mapMarkerService: MapMarkerService, private _store: Store, - private _gameRuleService: GameRuleService, - private _routeService: RouteService, - private _hikeEditRoutePlannerSelectors: HikeEditRoutePlannerSelectors, - private _elevationService: ElevationService + private _hikeEditRoutePlannerSelectors: HikeEditRoutePlannerSelectors ) { super(id, map, iconService, mapMarkerService); - - this._routeInfo = new RouteInfo( - this._gameRuleService, - this._routeService, - this._hikeEditRoutePlannerSelectors, - this._store - ); - - this._routingControl = new RoutingControl( - this.leafletMap, - this._store, - this._elevationService, - this._routeService, - this._routeInfo - ); - - this._waypointMarker = new WaypointMarker( - this._routeInfo, - this._routingControl - ); - } - - public destroy() { - this._routeInfo.deletePlan(); - delete this._routeInfo; - delete this._routingControl; - delete this._waypointMarker; - - this._store.dispatch(new adminMapActions.ResetMap()); - } - - public get routeInfo(): RouteInfo { - return this._routeInfo; - } - - public get routingControl(): RoutingControl { - return this._routingControl; - } - - public get waypointMarker(): WaypointMarker { - return this._waypointMarker; } /** * Get buffer geoJSON */ - public getBuffer(): GeoJSON.Feature | undefined { - const _path = this._routeInfo.getPath(); - - if (typeof _path !== 'undefined') { - let _buffer = >turf.buffer( - _path, 50, {units: 'meters'} - ); - - if (typeof _buffer !== 'undefined') { - _buffer = _.assign(_buffer, { - properties: { - name: 'buffer polygon', - draw_type: 'small_buffer' + public getBuffer(): Observable | undefined> { + // Update totals on each segment update + return this._store + .select(this._hikeEditRoutePlannerSelectors.getPath) + .take(1) + .map((path) => { + if (typeof path !== 'undefined') { + let _buffer = >turf.buffer( + path, 50, {units: 'meters'} + ); + + if (typeof _buffer !== 'undefined') { + _buffer = _.assign(_buffer, { + properties: { + name: 'buffer polygon', + draw_type: 'small_buffer' + } + }); } - }); - } - - return _buffer; - } else { - return; - } + console.log('TEST BUFFER', _buffer); + return _buffer; + } else { + return; + } + }); } /** @@ -106,7 +57,7 @@ export class AdminMap extends Map { */ public addGeoJSON(geoJson): L.GeoJSON { const _geoJSON = L.geoJSON(geoJson, { - style: this._getGeoJsonStyle(geoJson), + style: this._getGeoJsonStyle(geoJson), onEachFeature: this._propagateClick }); _geoJSON.addTo(this.leafletMap); @@ -116,26 +67,20 @@ export class AdminMap extends Map { /** * addGeoJSON submethod + * + * TODO: when called with route_{n} ??? */ private _getGeoJsonStyle(feature) { - let style; - switch (feature.properties.draw_type) { - case 'small_buffer': - style = { color: '#000044', weight: 2, fillColor: '#000077' }; - break; case 'route_0': - style = { color: 'black', opacity: 0.15, weight: 9 }; - break; + return { color: 'black', opacity: 0.15, weight: 9 }; case 'route_1': - style = { color: 'white', opacity: 0.8, weight: 6 }; - break; + return { color: 'white', opacity: 0.8, weight: 6 }; case 'route_2': - style = { color: 'red', opacity: 1, weight: 2 }; - break; + return { color: 'red', opacity: 1, weight: 2 }; + case 'small_buffer': + return { color: '#000044', weight: 2, fillColor: '#000077' }; } - - return style; } /** @@ -148,6 +93,12 @@ export class AdminMap extends Map { layerPoint: this.map.latLngToLayerPoint(event.latlng), containerPoint: this.map.latLngToContainerPoint(event.latlng) }); + + console.log('TEST _propagateClick', { + latlng: event.latlng, + layerPoint: this.map.latLngToLayerPoint(event.latlng), + containerPoint: this.map.latLngToContainerPoint(event.latlng) + }); }); } diff --git a/src/app/shared/services/admin-map/lib/route-info.ts b/src/app/shared/services/admin-map/lib/route-info.ts deleted file mode 100644 index fef9885c..00000000 --- a/src/app/shared/services/admin-map/lib/route-info.ts +++ /dev/null @@ -1,112 +0,0 @@ -/* OLD: RouteService */ -import { Store } from '@ngrx/store'; -import { State, IHikeEditRoutePlannerState } from 'app/store'; -import { HikeEditRoutePlannerSelectors } from 'app/store/selectors'; -import { AdminMap } from './admin-map'; -import { RoutePlanner } from './route-planner'; -import { GameRuleService, RouteService } from 'subrepos/gtrack-common-ngx/app'; -import { Feature } from 'geojson'; -import { Polygon } from 'leaflet'; -import { ExtendedFeature, ExtendedGeometryCollection } from 'd3'; - -import * as turf from '@turf/turf'; -import * as rewind from 'geojson-rewind'; -import * as d3 from 'd3'; - -export class RouteInfo { - private _savedMapTrack: any; - public planner: RoutePlanner; - - constructor( - private _gameRuleService: GameRuleService, - private _routeService: RouteService, - private _hikeEditRoutePlannerSelectors: HikeEditRoutePlannerSelectors, - private _store: Store - ) {} - - public newPlan() { - this.planner = new RoutePlanner( - this._gameRuleService, - this._routeService, - this._hikeEditRoutePlannerSelectors, - this._store - ); - } - - public deletePlan() { - // Cleanup subscriptions - if (this.planner) { - this.planner.destroy(); - delete this.planner; - } - } - - /** - * Get route from current plan - */ - public getRoute() { - // return this._getRoute().route; - - const route = this._getRoute(); - - if (route && route.route) { - return route.route; - } else { - return null; - } - } - - /** - * Get track path from current route - */ - public getPath() { - // Feature[0] contains the route polyLine - const route = this._getRoute(); - - if (route && route.route) { - return route.route.features[0]; - } else { - return null; - } - } - - private _getRoute() { - if (!this.planner) { - return null; - } - - // TODO get from store - return this.planner.routeInfoData; - } - - /** - * Get path bounds for POI search - */ - public getSearchBounds() { - let _path = this.getPath(); - - if (_path) { - // declare as 'any' for avoid d3.geoBounds error - let _buffer: any = turf.buffer(_path, 1000, {units: 'meters'}); - - if (typeof _buffer !== 'undefined') { - let _bounds = d3.geoBounds(rewind(_buffer, true)); - - return { - NorthEast: { - lat: _bounds[1][1], - lon: _bounds[1][0] - }, - SouthWest: { - lat: _bounds[0][1], - lon: _bounds[0][0] - } - }; - } else { - return; - } - } else { - return; - } - } -} diff --git a/src/app/shared/services/admin-map/lib/route-planner.ts b/src/app/shared/services/admin-map/route-planner.service.ts similarity index 54% rename from src/app/shared/services/admin-map/lib/route-planner.ts rename to src/app/shared/services/admin-map/route-planner.service.ts index ec8faf5c..d5a875da 100644 --- a/src/app/shared/services/admin-map/lib/route-planner.ts +++ b/src/app/shared/services/admin-map/route-planner.service.ts @@ -1,71 +1,40 @@ -/* OLD: TrackPlanner */ -import { AdminMap } from './admin-map'; +import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; -import { Subject } from 'rxjs/Subject'; import { Store } from '@ngrx/store'; -import { State, IHikeEditRoutePlannerState, hikeEditRoutePlannerActions } from 'app/store'; +import { State, hikeEditRoutePlannerActions } from 'app/store'; +import { GameRuleService, ISegment, RouteService } from 'subrepos/gtrack-common-ngx'; +import { initialRouteDataState } from 'app/store/reducer'; import { HikeEditRoutePlannerSelectors } from 'app/store/selectors'; -import { ISegment, GameRuleService, RouteService } from 'subrepos/gtrack-common-ngx/app'; import * as _ from 'lodash'; +import * as turf from '@turf/turf'; +import * as rewind from 'geojson-rewind'; +import * as d3 from 'd3'; -export class RoutePlanner { - public routeInfoData: IHikeEditRoutePlannerState; - private _geoJSON: GeoJSON.FeatureCollection; - private _destroy$: Subject = new Subject(); - +@Injectable() +export class RoutePlannerService { constructor( + private _store: Store, + private _hikeEditRoutePlannerSelectors: HikeEditRoutePlannerSelectors, private _gameRuleService: GameRuleService, private _routeService: RouteService, - private _hikeEditRoutePlannerSelectors: HikeEditRoutePlannerSelectors, - private _store: Store ) { - // Initial value - this._geoJSON = { - type: 'FeatureCollection', - features: [{ - type: 'Feature', - geometry: { - type: 'LineString', - coordinates: [] - }, - properties: { - name: 'Tour track' - } - }] - } - - // Parent classes use routeInfoData - this._store.select(this._hikeEditRoutePlannerSelectors.getRoutePlanner) - .takeUntil(this._destroy$) - .subscribe((routeInfoData: IHikeEditRoutePlannerState) => { - this.routeInfoData = routeInfoData; - }); - // Update totals on each segment update - this._store.select(this._hikeEditRoutePlannerSelectors.getSegments) - .takeUntil(this._destroy$) + this._store + .select(this._hikeEditRoutePlannerSelectors.getSegments) .subscribe((segments: ISegment[]) => { - let _total = this._calculateTotal(segments); - // Update total for route info this._store.dispatch(new hikeEditRoutePlannerActions.UpdateTotal({ - total: _total + total: this._calculateTotal(segments) })); - // Refresh track data - this._createGeoJSON(segments); + // Refresh route data + this._store.dispatch(new hikeEditRoutePlannerActions.AddRoute({ + route: this._createGeoJSON(segments) + })); }); } - public destroy() { - // Clear state - this._store.dispatch(new hikeEditRoutePlannerActions.ResetRoutePlanningState()); - - this._destroy$.next(true); - this._destroy$.unsubscribe(); - } - public addRouteSegment(coordinates, summary, updown) { let _segment: ISegment = { distance: summary.totalDistance, // in meters @@ -110,51 +79,37 @@ export class RoutePlanner { * Create track from geoJson */ private _createGeoJSON(segments) { - this._resetGeoJSON(); + let _geoJSON: any = _.cloneDeep(initialRouteDataState); for (let i in segments) { // Add segment coords to LineString const _segment = segments[i]; for (let p of _segment.coordinates) { - this._geoJSON.features[0].geometry.coordinates.push([p[1], p[0], p[2]]); + _geoJSON.features[0].geometry.coordinates.push([p[1], p[0], p[2]]); } + // Add the segment start point - this._addRoutePoint(_segment.coordinates[0], i + 1); + _geoJSON.features.push((this._createRoutePoint(_segment.coordinates[0], i + 1))); } // Add the last route point: the last point of the last segment if (segments.length > 0) { - this._addRoutePoint(this._getLastPointOfLastSegment(segments), segments.length + 1); + _geoJSON.features.push((this._createRoutePoint(this._getLastPointOfLastSegment(segments), segments.length + 1))); } - let _route: any = _.cloneDeep(this._geoJSON); - // todo: getBounds fails when segments array is empty if (segments.length > 0) { - _route.bounds = this._routeService.getBounds(_route); + _geoJSON.bounds = this._routeService.getBounds(_geoJSON); } - this._store.dispatch(new hikeEditRoutePlannerActions.AddRoute({ - route: _route - })); + return _geoJSON; } /** * _createGeoJSON submethod */ - private _resetGeoJSON() { - // Clear LineString coordinates - this._geoJSON.features[0].geometry.coordinates = []; - - // Remove points - this._geoJSON.features.splice(1, this._geoJSON.features.length - 1); - } - - /** - * _createGeoJSON submethod - */ - private _addRoutePoint(dataPoint, index) { - this._geoJSON.features.push({ + private _createRoutePoint(dataPoint, index) { + return { type: 'Feature', geometry: { type: 'Point', @@ -163,12 +118,50 @@ export class RoutePlanner { properties: { name: `Route point ${index}` } - }); + }; } + /** + * _createGeoJSON submethod + */ private _getLastPointOfLastSegment(segments) { const _lastSegment = segments[segments.length - 1]; const _coordinateNumInLastSegment = _lastSegment.coordinates.length; + return _lastSegment.coordinates[_coordinateNumInLastSegment - 1]; } + + /** + * Get path bounds for POI search + */ + public getSearchBounds() { + let _path; + + this._store + .select(this._hikeEditRoutePlannerSelectors.getPath) + .take(1) + .subscribe((path) => { + // declare as 'any' for avoid d3.geoBounds error + let _buffer: any = turf.buffer(path, 1000, {units: 'meters'}); + + if (typeof _buffer !== 'undefined') { + let _bounds = d3.geoBounds(rewind(_buffer, true)); + + _path = { + NorthEast: { + lat: _bounds[1][1], + lon: _bounds[1][0] + }, + SouthWest: { + lat: _bounds[0][1], + lon: _bounds[0][0] + } + }; + } else { + _path = null; + } + }); + + return _path; + } } diff --git a/src/app/shared/services/admin-map/lib/routing-control.ts b/src/app/shared/services/admin-map/routing-control.service.ts similarity index 73% rename from src/app/shared/services/admin-map/lib/routing-control.ts rename to src/app/shared/services/admin-map/routing-control.service.ts index 655b230e..5b6cbbdd 100644 --- a/src/app/shared/services/admin-map/lib/routing-control.ts +++ b/src/app/shared/services/admin-map/routing-control.service.ts @@ -1,12 +1,11 @@ +import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs/Observable'; -import { State, routingActions } from 'app/store'; +import { ElevationService } from 'subrepos/gtrack-common-ngx'; +import { State, hikeEditRoutePlannerActions, routingActions } from 'app/store'; +import { HikeEditRoutePlannerSelectors, HikeEditMapSelectors } from 'app/store/selectors'; +import { AdminMapService, AdminMap, RoutePlannerService } from './index'; import { environment } from 'environments/environment'; -import { RouteInfo } from './route-info'; -import { - ElevationService, - RouteService -} from 'subrepos/gtrack-common-ngx/app'; import * as _ from 'lodash'; import * as L from 'leaflet'; @@ -14,17 +13,25 @@ import 'leaflet-spin'; import 'leaflet-routing-machine'; import 'lrm-graphhopper'; -export class RoutingControl { +@Injectable() +export class RoutingControlService { private _controls: Array; private _coordinates: Array; + private _map: AdminMap; constructor( - private _leafletMap: L.Map, private _store: Store, - private _elevationService: ElevationService, - private _routeService: RouteService, - private _routeInfo: RouteInfo + private _adminMapService: AdminMapService, + private _routePlannerService: RoutePlannerService, + private _hikeEditMapSelectors: HikeEditMapSelectors, + private _elevationService: ElevationService ) { + this._store.select(this._hikeEditMapSelectors.getMapId) + .filter(id => id !== '') + .subscribe((mapId: string) => { + this._map = this._adminMapService.getMapById(mapId); + }); + this._reset(); } @@ -43,12 +50,6 @@ export class RoutingControl { return this._controls[this._controls.length - 1]; } - /* - private _getActualControlNum() { - return this._controls.length; - } - */ - public getControl(index) { return this._controls[index]; } @@ -61,11 +62,9 @@ export class RoutingControl { return; } - this._leafletMap.removeControl(_control); + this._map.leafletMap.removeControl(_control); - if (this._routeInfo.planner) { - this._routeInfo.planner.removeLastSegment(); - } + this._routePlannerService.removeLastSegment(); return _control; } @@ -83,7 +82,7 @@ export class RoutingControl { icon: _icon }); - _marker.addTo(this._leafletMap); + _marker.addTo(this._map.leafletMap); return _marker; } @@ -109,12 +108,12 @@ export class RoutingControl { }) }); - _control.addTo(this._leafletMap); - // _control.hide(); + _control.addTo(this._map.leafletMap); + _control.hide(); _control.on('routingstart', () => { this._store.dispatch(new routingActions.RoutingStart()); - this._leafletMap.spin(true); + this._map.leafletMap.spin(true); }); _control.on('routesfound', (e) => { @@ -151,18 +150,18 @@ export class RoutingControl { downhill: this._elevationService.calculateDownhill(_coordsArr) }; - this._routeInfo.planner.addRouteSegment(_coordsArr, e.routes[0].summary, upDown); + this._routePlannerService.addRouteSegment(_coordsArr, e.routes[0].summary, upDown); this._store.dispatch(new routingActions.RoutingFinished()); - this._leafletMap.spin(false); + this._map.leafletMap.spin(false); }).catch(() => { this._store.dispatch(new routingActions.RoutingError()); - this._leafletMap.spin(false); + this._map.leafletMap.spin(false); }); }); _control.on('routingerror', (e) => { this._store.dispatch(new routingActions.RoutingError()); - this._leafletMap.spin(false); + this._map.leafletMap.spin(false); }); this._controls.push(_control); diff --git a/src/app/shared/services/admin-map/lib/waypoint-marker.ts b/src/app/shared/services/admin-map/waypoint-marker.service.ts similarity index 75% rename from src/app/shared/services/admin-map/lib/waypoint-marker.ts rename to src/app/shared/services/admin-map/waypoint-marker.service.ts index 889c8c32..b8387b59 100644 --- a/src/app/shared/services/admin-map/lib/waypoint-marker.ts +++ b/src/app/shared/services/admin-map/waypoint-marker.service.ts @@ -1,17 +1,21 @@ -import { RouteInfo } from './route-info'; -import { RoutingControl } from './routing-control'; +import { Injectable } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { State } from 'app/store'; import * as L from 'leaflet'; +import { RoutingControlService } from '.'; -export class WaypointMarker { - private _maxAllowedValhallaWayPoints = 2; +@Injectable() +export class WaypointMarkerService { + private _maxAllowedValhallaWayPoints = 2; // TOO: deprecated??? private _routeSegmentIndex: number; private _waypointIndex: number; constructor( - private _routeInfo: RouteInfo, - private _routingControl: RoutingControl + private _store: Store, + private _routingControlService: RoutingControlService ) { + this.reset(); } @@ -31,23 +35,22 @@ export class WaypointMarker { } if (_isLastWaypointInAdditionalSegment) { - this._routingControl.pop(); + this._routingControlService.pop(); this._routeSegmentIndex--; this._waypointIndex = this._maxAllowedValhallaWayPoints; } else if (_isFirstWaypointInFirstSegment) { this.reset(); - this._routingControl.clearControls(); + this._routingControlService.clearControls(); } else if (_isSecondWaypoint) { - const _control = this._routingControl.getActualControl(); + const _control = this._routingControlService.getActualControl(); const _latlng = _control.getWaypoints()[0].latLng; - this._routeInfo.deletePlan(); this.reset(); - this._routingControl.clearControls(); + this._routingControlService.clearControls(); this.addWaypoint(_latlng); } else { - const _control = this._routingControl.getActualControl(); + const _control = this._routingControlService.getActualControl(); _control.spliceWaypoints(this._waypointIndex - 1, 1); this._waypointIndex--; _control.route(); @@ -71,7 +74,7 @@ export class WaypointMarker { } public closeCircle() { - const _firstControl: L.Routing.Control = this._routingControl.getControl(0); + const _firstControl: L.Routing.Control = this._routingControlService.getControl(0); if (_firstControl && _firstControl.getWaypoints().length) { const firstPointLatLng = _firstControl.getWaypoints()[0].latLng; @@ -85,24 +88,22 @@ export class WaypointMarker { const _shouldAddNewSegment = this._waypointIndex === this._maxAllowedValhallaWayPoints; if (_isStartRouting) { - this._routeInfo.newPlan(); - - _control = this._routingControl.addNew(); + _control = this._routingControlService.addNew(); this._routeSegmentIndex = 1; this._waypointIndex = 0; } else if (_shouldAddNewSegment) { - const _previousControl = this._routingControl.getActualControl(); + const _previousControl = this._routingControlService.getActualControl(); const _waypoints = _previousControl.getWaypoints(); const _lastWaypoint = _waypoints[_waypoints.length - 1]; - _control = this._routingControl.addNew(); + _control = this._routingControlService.addNew(); _control.spliceWaypoints(0, 1, _lastWaypoint); this._waypointIndex = 1; this._routeSegmentIndex++; } else { - _control = this._routingControl.getActualControl(); + _control = this._routingControlService.getActualControl(); } const _waypoint = { diff --git a/src/app/shared/services/hike-data/hike-data.service.ts b/src/app/shared/services/hike-data/hike-data.service.ts index f8ea2d07..89d94562 100644 --- a/src/app/shared/services/hike-data/hike-data.service.ts +++ b/src/app/shared/services/hike-data/hike-data.service.ts @@ -2,9 +2,9 @@ import { Injectable } from '@angular/core'; import { DataSource } from '@angular/cdk/collections'; import { Observable } from 'rxjs/Observable'; import { Store } from '@ngrx/store'; - import { State, hikeEditGeneralInfoActions, commonRouteActions } from 'app/store'; import { HikeEditGeneralInfoSelectors, HikeEditRoutePlannerSelectors } from 'app/store/selectors'; +import { IGeneralInfoState } from 'app/store/state'; import { ReverseGeocodingService } from '../hike-data/reverse-geocoding.service'; import { ITextualDescriptionItem } from '../../interfaces'; import { IHikeProgram, ILocalizedItem, ITextualDescription } from 'subrepos/provider-client'; @@ -28,13 +28,13 @@ export class HikeDataService { return Observable.combineLatest( this._store.select(this._hikeEditGeneralInfoSelectors.getGeneralInfo), this._store.select(this._hikeEditRoutePlannerSelectors.getIsRoundTrip) - ).map(data => { + ).map(([generalInfo, isRoundTrip]: [IGeneralInfoState, boolean]) => { return { - id: data[0].hikeId, - routeId: data[0].routeId, - difficulty: data[0].difficulty.toString(), // TODO it will be number!! - isRoundTrip: data[1], - pois: data[0].pois + id: generalInfo.hikeId, + routeId: generalInfo.routeId, + difficulty: generalInfo.difficulty.toString(), // TODO it will be number!! + isRoundTrip: isRoundTrip, + pois: generalInfo.pois }; }); } diff --git a/src/app/shared/services/poi/poi-editor.service.ts b/src/app/shared/services/poi/poi-editor.service.ts index 5a19df77..69ea932d 100644 --- a/src/app/shared/services/poi/poi-editor.service.ts +++ b/src/app/shared/services/poi/poi-editor.service.ts @@ -6,8 +6,8 @@ import { } from 'subrepos/gtrack-common-ngx'; import { IPoi } from 'subrepos/provider-client'; import { State, IExternalPoiListContextItemState, commonGeoSearchActions, IExternalPoiListContextState } from 'app/store'; -import { HikeEditMapSelectors, HikeEditPoiSelectors, HikeEditGeneralInfoSelectors, } from 'app/store/selectors'; -import { AdminMap, AdminMapService, AdminMapMarker } from '../admin-map'; +import { HikeEditMapSelectors, HikeEditPoiSelectors, HikeEditGeneralInfoSelectors, HikeEditRoutePlannerSelectors, } from 'app/store/selectors'; +import { AdminMap, AdminMapService, AdminMapMarker, RoutePlannerService } from '../admin-map'; import { LanguageService } from '../language.service'; import { IExternalPoi, IWikipediaPoi, IGooglePoi, IOsmPoi, IGTrackPoi } from 'app/shared/interfaces/index'; @@ -28,6 +28,7 @@ export class PoiEditorService { constructor( private _store: Store, private _geometryService: GeometryService, + private _routePlannerService: RoutePlannerService, private _elevationService: ElevationService, private _poiService: PoiService, private _iconService: IconService, @@ -35,6 +36,7 @@ export class PoiEditorService { private _hikeEditMapSelectors: HikeEditMapSelectors, private _hikeEditPoiSelectors: HikeEditPoiSelectors, private _hikeEditGeneralInfoSelectors: HikeEditGeneralInfoSelectors, + private _hikeEditRoutePlannerSelectors: HikeEditRoutePlannerSelectors, private _geoSearchSelectors: GeoSearchSelectors, private _poiSelectors: PoiSelectors, ) {} @@ -154,26 +156,30 @@ export class PoiEditorService { } } - return Observable.of(_pois); + return _pois; } /** * HikeEditPoi effect submethod - for gTrack pois */ public handleHikeInclusion(pois: IGTrackPoi[]) { - return this._store.select(this._hikeEditGeneralInfoSelectors.getPois) + let _pois; + + this._store.select(this._hikeEditGeneralInfoSelectors.getPois) .take(1) - .map((hikePois: string[]) => { + .subscribe((hikePois: string[]) => { if (pois) { let gTrackPois = _.cloneDeep(pois); gTrackPois.map((gTrackPoi: IGTrackPoi) => { gTrackPoi.inHike = _.includes(hikePois, gTrackPoi.id); }); - return gTrackPois; + _pois = gTrackPois; } else { - return []; + _pois = []; } }); + + return _pois; } private _getOnroutePois(pois: IExternalPoi[]) { @@ -279,7 +285,7 @@ export class PoiEditorService { } public getGTrackPois(map) { - let _bounds = map.routeInfo.getSearchBounds(); + let _bounds = this._routePlannerService.getSearchBounds(); let _geo: CenterRadius = this._geometryService.getCenterRadius(_bounds); let _centerCoord = _geo!.center!.geometry!.coordinates; @@ -293,6 +299,7 @@ export class PoiEditorService { }, 'gTrackPois')); } } + /** * Update inGtrackDb property on the given poi */ @@ -336,48 +343,48 @@ export class PoiEditorService { // Hike pois // - this._store - .select(this._hikeEditPoiSelectors.getHikeEditContextSelector('hike')) - .take(1) - .subscribe((hikePoiContext: IExternalPoiListContextItemState) => { - // Load hike pois if there are visible pois - if (hikePoiContext.showOnrouteMarkers || hikePoiContext.showOffrouteMarkers) { - this._store - .select(this._hikeEditGeneralInfoSelectors.getHikePois<(IPoi)>(this._poiSelectors.getAllPois)) - .switchMap((pois: IPoi[]) => this.organizePois(_.cloneDeep(pois), map.routeInfo.getPath())) - .take(1) - .subscribe((pois: IGTrackPoi[]) => { - _pois = _pois.concat(pois.filter(p => { - let _onRouteCheck = p.onRoute ? hikePoiContext.showOnrouteMarkers : hikePoiContext.showOffrouteMarkers; - return !p.inHike && _onRouteCheck; - })); - }); - } - }); + Observable.combineLatest( + this._store.select(this._hikeEditPoiSelectors.getHikeEditContextSelector('hike')).take(1), + this._store.select(this._hikeEditGeneralInfoSelectors.getHikePois<(IPoi)>(this._poiSelectors.getAllPois)).take(1), + this._store.select(this._hikeEditRoutePlannerSelectors.getPath).take(1) + ) + .filter(([hikePoiContext, pois, path]: [IExternalPoiListContextItemState, IExternalPoi[], any]) => { + return pois && pois.length > 0 && path && (hikePoiContext).showOnrouteMarkers || (hikePoiContext).showOffrouteMarkers + }) + .switchMap(([hikePoiContext, pois, path]: [IExternalPoiListContextItemState, IExternalPoi[], any]) => { + return Observable.of([(hikePoiContext), this.organizePois(_.cloneDeep(pois), path)]); + }) + .subscribe(([hikePoiContext, pois]: [IExternalPoiListContextItemState, IExternalPoi[]]) => { + _pois = _pois.concat(pois.filter(p => { + let _onRouteCheck = p.onRoute ? hikePoiContext.showOnrouteMarkers : hikePoiContext.showOffrouteMarkers; + return !p.inHike && _onRouteCheck; + })); + }); // // gTrackPois // - this._store - .select(this._hikeEditPoiSelectors.getHikeEditContextSelector('gTrack')) - .take(1) - .subscribe((gTrackPoiContext: IExternalPoiListContextItemState) => { - // Load gTrackPois if there are visible pois - if (gTrackPoiContext.showOnrouteMarkers || gTrackPoiContext.showOffrouteMarkers) { - this._store - .select(this._geoSearchSelectors.getGeoSearchResults<(IPoi)>('gTrackPois', this._poiSelectors.getAllPois)) - .switchMap((pois: IPoi[]) => pois ? this.organizePois(_.cloneDeep(pois), map.routeInfo.getPath()) : []) - .switchMap((pois: IGTrackPoi[]) => this.handleHikeInclusion(pois)) - .take(1) - .subscribe((pois: IGTrackPoi[]) => { - _pois = _pois.concat(pois.filter(p => { - let _onRouteCheck = p.onRoute ? gTrackPoiContext.showOnrouteMarkers : gTrackPoiContext.showOffrouteMarkers; - return !p.inHike && _onRouteCheck; - })); - }); - } - }); + Observable.combineLatest( + this._store.select(this._hikeEditPoiSelectors.getHikeEditContextSelector('gTrack')).take(1), + this._store.select(this._geoSearchSelectors.getGeoSearchResults<(IPoi)>('gTrackPois', this._poiSelectors.getAllPois)).take(1), + this._store.select(this._hikeEditRoutePlannerSelectors.getPath).take(1) + ) + .filter(([gTrackPoiContext, pois, path]: [IExternalPoiListContextItemState, IGTrackPoi[], any]) => { + return pois && pois.length > 0 && path && (gTrackPoiContext).showOnrouteMarkers || (gTrackPoiContext).showOffrouteMarkers + }) + .switchMap(([gTrackPoiContext, pois, path]: [IExternalPoiListContextItemState, IGTrackPoi[], any]) => { + return Observable.of([(gTrackPoiContext), this.organizePois(_.cloneDeep(pois), path)]); + }) + .switchMap(([gTrackPoiContext, pois]: [IExternalPoiListContextItemState, IGTrackPoi[]]) => { + return Observable.of([(gTrackPoiContext), this.handleHikeInclusion(pois)]); + }) + .subscribe(([gTrackPoiContext, pois]: [IExternalPoiListContextItemState, IGTrackPoi[]]) => { + _pois = _pois.concat(pois.filter(p => { + let _onRouteCheck = p.onRoute ? gTrackPoiContext.showOnrouteMarkers : gTrackPoiContext.showOffrouteMarkers; + return !p.inHike && _onRouteCheck; + })); + }); // // Service pois @@ -428,10 +435,10 @@ export class PoiEditorService { return Observable.combineLatest( this._store.select(this._hikeEditPoiSelectors.getHikeEditContextSelector(subdomain)).take(1), this._store.select(poiSelector).take(1) - ).map((results: [IExternalPoiListContextItemState, IExternalPoi[]]) => { - if (results[0].showOnrouteMarkers || results[0].showOffrouteMarkers) { - return results[1].filter(p => { - let _onRouteCheck = p.onRoute ? results[0].showOnrouteMarkers : results[0].showOffrouteMarkers; + ).map(([poiContext, pois]: [IExternalPoiListContextItemState, IExternalPoi[]]) => { + if (poiContext.showOnrouteMarkers || poiContext.showOffrouteMarkers) { + return pois.filter(p => { + let _onRouteCheck = p.onRoute ? poiContext.showOnrouteMarkers : poiContext.showOffrouteMarkers; return !p.inGtrackDb && _onRouteCheck; }); } else { diff --git a/src/app/store/actions/hike-edit-poi.ts b/src/app/store/actions/hike-edit-poi.ts index f05c1612..bb434114 100644 --- a/src/app/store/actions/hike-edit-poi.ts +++ b/src/app/store/actions/hike-edit-poi.ts @@ -5,31 +5,32 @@ export const RESET_POI_STATE = '[HikeEditPoi] Reset'; export const GET_GOOGLE_POIS = '[HikeEditPoi] Get Google pois'; export const SET_GOOGLE_POIS = '[HikeEditPoi] Set Google pois'; -export const PATCH_GOOGLE_POIS = '[HikeEditPoi] Patch Google pois'; +export const SET_GOOGLE_POIS_IN_GTRACK_DB = '[HikeEditPoi] Set Google pois inGtrackDb'; export const SET_GOOGLE_POI_IN_HIKE = '[HikeEditPoi] Set Google poi inHike'; export const GET_OSM_AMENITY_POIS = '[HikeEditPoi] Get OSM amenity pois'; export const SET_OSM_AMENITY_POIS = '[HikeEditPoi] Set OSM amenity pois'; -export const PATCH_OSM_AMENITY_POIS = '[HikeEditPoi] Patch OSM amenity pois'; +export const SET_OSM_AMENITY_POIS_IN_GTRACK_DB = '[HikeEditPoi] Set OSM amenity pois inGtrackDb'; export const SET_OSM_AMENITY_POI_IN_HIKE = '[HikeEditPoi] Set OSM amenity poi inHike'; export const GET_OSM_NATURAL_POIS = '[HikeEditPoi] Get OSM natural pois'; export const SET_OSM_NATURAL_POIS = '[HikeEditPoi] Set OSM natural pois'; -export const PATCH_OSM_NATURAL_POIS = '[HikeEditPoi] Patch OSM natural pois'; +export const SET_OSM_NATURAL_POIS_IN_GTRACK_DB = '[HikeEditPoi] Set OSM natural pois inGtrackDb'; export const SET_OSM_NATURAL_POI_IN_HIKE = '[HikeEditPoi] Set OSM natural poi inHike'; export const GET_OSM_ROUTE_POIS = '[HikeEditPoi] Get OSM route pois'; export const SET_OSM_ROUTE_POIS = '[HikeEditPoi] Set OSM route pois'; -export const PATCH_OSM_ROUTE_POIS = '[HikeEditPoi] Patch OSM route pois'; +export const SET_OSM_ROUTE_POIS_IN_GTRACK_DB = '[HikeEditPoi] Set OSM route pois inGtrackDb'; export const SET_OSM_ROUTE_POI_IN_HIKE = '[HikeEditPoi] Set OSM route poi inHike'; export const GET_WIKIPEDIA_POIS = '[HikeEditPoi] Get Wikipedia pois'; export const SET_WIKIPEDIA_POIS = '[HikeEditPoi] Set Wikipedia pois'; -export const PATCH_WIKIPEDIA_POIS = '[HikeEditPoi] Patch Wikipedia pois'; +export const SET_WIKIPEDIA_POIS_IN_GTRACK_DB = '[HikeEditPoi] Set Wikipedia pois inGtrackDb'; export const SET_WIKIPEDIA_POI_IN_HIKE = '[HikeEditPoi] Set Wikipedia poi inHike'; export const TOGGLE_ONROUTE_MARKERS = '[HikeEditPoi] Toggle onroute markers'; export const TOGGLE_OFFROUTE_MARKERS = '[HikeEditPoi] Toggle offroute markers'; +export const SET_DIRTY = '[HikeEditPoi] Set dirty'; export const MARKERS_CONFIG_CHANGED = '[HikeEditPoi] Markers config changed'; export class ResetPoiState implements Action { @@ -56,8 +57,8 @@ export class SetGooglePois implements Action { }) { /* EMPTY */ } } -export class PatchGooglePois implements Action { - readonly type = PATCH_GOOGLE_POIS; +export class SetGooglePoisInGtrackDb implements Action { + readonly type = SET_GOOGLE_POIS_IN_GTRACK_DB; constructor(public payload: { properties: any }) { /* EMPTY */ } @@ -90,8 +91,8 @@ export class SetOsmAmenityPois implements Action { }) { /* EMPTY */ } } -export class PatchOsmAmenityPois implements Action { - readonly type = PATCH_OSM_AMENITY_POIS; +export class SetOsmAmenityPoisInGtrackDb implements Action { + readonly type = SET_OSM_AMENITY_POIS_IN_GTRACK_DB; constructor(public payload: { properties: any }) { /* EMPTY */ } @@ -124,8 +125,8 @@ export class SetOsmNaturalPois implements Action { }) { /* EMPTY */ } } -export class PatchOsmNaturalPois implements Action { - readonly type = PATCH_OSM_NATURAL_POIS; +export class SetOsmNaturalPoisInGtrackDb implements Action { + readonly type = SET_OSM_NATURAL_POIS_IN_GTRACK_DB; constructor(public payload: { properties: any }) { /* EMPTY */ } @@ -160,8 +161,8 @@ export class SetOsmRoutePois implements Action { }) { /* EMPTY */ } } -export class PatchOsmRoutePois implements Action { - readonly type = PATCH_OSM_ROUTE_POIS; +export class SetOsmRoutePoisInGtrackDb implements Action { + readonly type = SET_OSM_ROUTE_POIS_IN_GTRACK_DB; constructor(public payload: { properties: any }) { /* EMPTY */ } @@ -194,8 +195,8 @@ export class SetWikipediaPois implements Action { }) { /* EMPTY */ } } -export class PatchWikipediaPois implements Action { - readonly type = PATCH_WIKIPEDIA_POIS; +export class SetWikipediaPoisInGtrackDb implements Action { + readonly type = SET_WIKIPEDIA_POIS_IN_GTRACK_DB; constructor(public payload: { properties: any }) { /* EMPTY */ } @@ -227,6 +228,14 @@ export class ToggleOffrouteMarkers implements Action { }) { /* EMPTY */ } } +export class SetDirty implements Action { + readonly type = SET_DIRTY; + constructor(public payload: { + subdomain: string, + dirty: boolean + }) { /* EMPTY */ } +} + export class MarkersConfigChanged implements Action { readonly type = MARKERS_CONFIG_CHANGED; constructor() { /* EMPTY */ } @@ -237,29 +246,30 @@ export type AllHikeEditPoiActions = // Google | GetGooglePois | SetGooglePois - | PatchGooglePois + | SetGooglePoisInGtrackDb | SetGooglePoiInHike // Osm Amenity | GetOsmAmenityPois | SetOsmAmenityPois - | PatchOsmAmenityPois + | SetOsmAmenityPoisInGtrackDb | SetOsmAmenityPoiInHike // Osm Natural | GetOsmNaturalPois | SetOsmNaturalPois - | PatchOsmNaturalPois + | SetOsmNaturalPoisInGtrackDb | SetOsmNaturalPoiInHike // Osm Route | GetOsmRoutePois | SetOsmRoutePois - | PatchOsmRoutePois + | SetOsmRoutePoisInGtrackDb | SetOsmRoutePoiInHike // Wikipedia | GetWikipediaPois | SetWikipediaPois - | PatchWikipediaPois + | SetWikipediaPoisInGtrackDb | SetWikipediaPoiInHike // Markers | ToggleOnrouteMarkers | ToggleOffrouteMarkers + | SetDirty | MarkersConfigChanged; diff --git a/src/app/store/actions/test/hike-edit-poi.spec.ts b/src/app/store/actions/test/hike-edit-poi.spec.ts index 534d1147..8bfcb066 100644 --- a/src/app/store/actions/test/hike-edit-poi.spec.ts +++ b/src/app/store/actions/test/hike-edit-poi.spec.ts @@ -7,27 +7,27 @@ describe('HikeEditPoi actions', () => { expect(HikeEditPoiActions.GET_GOOGLE_POIS).toEqual('[HikeEditPoi] Get Google pois'); expect(HikeEditPoiActions.SET_GOOGLE_POIS).toEqual('[HikeEditPoi] Set Google pois'); - expect(HikeEditPoiActions.PATCH_GOOGLE_POIS).toEqual('[HikeEditPoi] Patch Google pois'); + expect(HikeEditPoiActions.SET_GOOGLE_POIS_IN_GTRACK_DB).toEqual('[HikeEditPoi] Set Google pois inGtrackDb'); expect(HikeEditPoiActions.SET_GOOGLE_POI_IN_HIKE).toEqual('[HikeEditPoi] Set Google poi inHike'); expect(HikeEditPoiActions.GET_OSM_AMENITY_POIS).toEqual('[HikeEditPoi] Get OSM amenity pois'); expect(HikeEditPoiActions.SET_OSM_AMENITY_POIS).toEqual('[HikeEditPoi] Set OSM amenity pois'); - expect(HikeEditPoiActions.PATCH_OSM_AMENITY_POIS).toEqual('[HikeEditPoi] Patch OSM amenity pois'); + expect(HikeEditPoiActions.SET_OSM_AMENITY_POIS_IN_GTRACK_DB).toEqual('[HikeEditPoi] Set OSM amenity pois inGtrackDb'); expect(HikeEditPoiActions.SET_OSM_AMENITY_POI_IN_HIKE).toEqual('[HikeEditPoi] Set OSM amenity poi inHike'); expect(HikeEditPoiActions.GET_OSM_NATURAL_POIS).toEqual('[HikeEditPoi] Get OSM natural pois'); expect(HikeEditPoiActions.SET_OSM_NATURAL_POIS).toEqual('[HikeEditPoi] Set OSM natural pois'); - expect(HikeEditPoiActions.PATCH_OSM_NATURAL_POIS).toEqual('[HikeEditPoi] Patch OSM natural pois'); + expect(HikeEditPoiActions.SET_OSM_NATURAL_POIS_IN_GTRACK_DB).toEqual('[HikeEditPoi] Set OSM natural pois inGtrackDb'); expect(HikeEditPoiActions.SET_OSM_NATURAL_POI_IN_HIKE).toEqual('[HikeEditPoi] Set OSM natural poi inHike'); expect(HikeEditPoiActions.GET_OSM_ROUTE_POIS).toEqual('[HikeEditPoi] Get OSM route pois'); expect(HikeEditPoiActions.SET_OSM_ROUTE_POIS).toEqual('[HikeEditPoi] Set OSM route pois'); - expect(HikeEditPoiActions.PATCH_OSM_ROUTE_POIS).toEqual('[HikeEditPoi] Patch OSM route pois'); + expect(HikeEditPoiActions.SET_OSM_ROUTE_POIS_IN_GTRACK_DB).toEqual('[HikeEditPoi] Set OSM route pois inGtrackDb'); expect(HikeEditPoiActions.SET_OSM_ROUTE_POI_IN_HIKE).toEqual('[HikeEditPoi] Set OSM route poi inHike'); expect(HikeEditPoiActions.GET_WIKIPEDIA_POIS).toEqual('[HikeEditPoi] Get Wikipedia pois'); expect(HikeEditPoiActions.SET_WIKIPEDIA_POIS).toEqual('[HikeEditPoi] Set Wikipedia pois'); - expect(HikeEditPoiActions.PATCH_WIKIPEDIA_POIS).toEqual('[HikeEditPoi] Patch Wikipedia pois'); + expect(HikeEditPoiActions.SET_WIKIPEDIA_POIS_IN_GTRACK_DB).toEqual('[HikeEditPoi] Set Wikipedia pois inGtrackDb'); expect(HikeEditPoiActions.SET_WIKIPEDIA_POI_IN_HIKE).toEqual('[HikeEditPoi] Set Wikipedia poi inHike'); expect(HikeEditPoiActions.TOGGLE_ONROUTE_MARKERS).toEqual('[HikeEditPoi] Toggle onroute markers'); @@ -79,11 +79,11 @@ describe('HikeEditPoi actions', () => { it('should create PatchGooglePois action', () => { const payload = { properties: [] }; - const action = new HikeEditPoiActions.PatchGooglePois(payload); + const action = new HikeEditPoiActions.SetGooglePoisInGtrackDb(payload); expect(action).toBeDefined(); expect({ ...action }).toEqual({ - type: HikeEditPoiActions.PATCH_GOOGLE_POIS, + type: HikeEditPoiActions.SET_GOOGLE_POIS_IN_GTRACK_DB, payload, }); }); @@ -133,11 +133,11 @@ describe('HikeEditPoi actions', () => { it('should create PatchOsmAmenityPois action', () => { const payload = { properties: [] }; - const action = new HikeEditPoiActions.PatchOsmAmenityPois(payload); + const action = new HikeEditPoiActions.SetOsmAmenityPoisInGtrackDb(payload); expect(action).toBeDefined(); expect({ ...action }).toEqual({ - type: HikeEditPoiActions.PATCH_OSM_AMENITY_POIS, + type: HikeEditPoiActions.SET_OSM_AMENITY_POIS_IN_GTRACK_DB, payload, }); }); @@ -187,11 +187,11 @@ describe('HikeEditPoi actions', () => { it('should create PatchOsmNaturalPois action', () => { const payload = { properties: [] }; - const action = new HikeEditPoiActions.PatchOsmNaturalPois(payload); + const action = new HikeEditPoiActions.SetOsmNaturalPoisInGtrackDb(payload); expect(action).toBeDefined(); expect({ ...action }).toEqual({ - type: HikeEditPoiActions.PATCH_OSM_NATURAL_POIS, + type: HikeEditPoiActions.SET_OSM_NATURAL_POIS_IN_GTRACK_DB, payload, }); }); @@ -241,11 +241,11 @@ describe('HikeEditPoi actions', () => { it('should create PatchOsmRoutePois action', () => { const payload = { properties: [] }; - const action = new HikeEditPoiActions.PatchOsmRoutePois(payload); + const action = new HikeEditPoiActions.SetOsmRoutePoisInGtrackDb(payload); expect(action).toBeDefined(); expect({ ...action }).toEqual({ - type: HikeEditPoiActions.PATCH_OSM_ROUTE_POIS, + type: HikeEditPoiActions.SET_OSM_ROUTE_POIS_IN_GTRACK_DB, payload, }); }); @@ -295,11 +295,11 @@ describe('HikeEditPoi actions', () => { it('should create PatchWikipediaPois action', () => { const payload = { properties: [] }; - const action = new HikeEditPoiActions.PatchWikipediaPois(payload); + const action = new HikeEditPoiActions.SetWikipediaPoisInGtrackDb(payload); expect(action).toBeDefined(); expect({ ...action }).toEqual({ - type: HikeEditPoiActions.PATCH_WIKIPEDIA_POIS, + type: HikeEditPoiActions.SET_WIKIPEDIA_POIS_IN_GTRACK_DB, payload, }); }); @@ -344,6 +344,17 @@ describe('HikeEditPoi actions', () => { }); }); + it('should create SetDirty action', () => { + const payload = { subdomain: 'fakeDomain', dirty: false }; + const action = new HikeEditPoiActions.SetDirty(payload); + + expect(action).toBeDefined(); + expect({ ...action }).toEqual({ + type: HikeEditPoiActions.SET_DIRTY, + payload, + }); + }); + it('should create MarkersConfigChanged action', () => { const action = new HikeEditPoiActions.MarkersConfigChanged(); diff --git a/src/app/store/reducer/hike-edig-general-info.ts b/src/app/store/reducer/hike-edig-general-info.ts index 0e70f6ba..04608cd6 100644 --- a/src/app/store/reducer/hike-edig-general-info.ts +++ b/src/app/store/reducer/hike-edig-general-info.ts @@ -24,50 +24,61 @@ export function generalInfoReducer( action: hikeEditGeneralInfoActions.AllHikeEditGeneralInfoActions ): IGeneralInfoState { switch (action.type) { + case hikeEditGeneralInfoActions.RESET_GENERAL_INFO_STATE: return initialGeneralInfoState; + case hikeEditGeneralInfoActions.SET_INITIALIZED: return { ...state, initialized: true }; + case hikeEditGeneralInfoActions.SET_HIKE_ID: return { ...state, hikeId: action.payload.hikeId }; + case hikeEditGeneralInfoActions.SET_ROUTE_ID: return { ...state, routeId: action.payload.routeId }; + case hikeEditGeneralInfoActions.SET_IS_ROUND_TRIP: return { ...state, isRoundTrip: action.payload.isRoundTrip } + case hikeEditGeneralInfoActions.SET_DIFFICULTY: return { ...state, difficulty: action.payload.difficulty } + case hikeEditGeneralInfoActions.SET_POIS: return { ...state, pois: action.payload.pois }; + case hikeEditGeneralInfoActions.ADD_POI: return { ...state, pois: _.union(state.pois, [action.payload.poi]) }; + case hikeEditGeneralInfoActions.REMOVE_POI: return { ...state, pois: state.pois.filter(p => p !== action.payload.poi) }; + default: return state; + } } @@ -83,14 +94,18 @@ const descriptionReducer: ActionReducer = ( action: hikeEditGeneralInfoActions.AllHikeEditGeneralInfoActions ): IDescriptionEntityState => { switch (action.type) { + case hikeEditGeneralInfoActions.SET_DESCRIPTIONS: { return descriptionAdapter.addAll(action.payload.descriptions, state); } + case hikeEditGeneralInfoActions.ADD_DESCRIPTION: { return descriptionAdapter.addOne(action.payload.description, state); } + default: return state; + } } diff --git a/src/app/store/reducer/hike-edit-map.ts b/src/app/store/reducer/hike-edit-map.ts index 6a80397f..052c2dc2 100644 --- a/src/app/store/reducer/hike-edit-map.ts +++ b/src/app/store/reducer/hike-edit-map.ts @@ -19,15 +19,19 @@ export function hikeEditMapMapReducer( action: adminMapActions.AllAdminMapActions ): IHikeEditMapMapState { switch (action.type) { + case adminMapActions.RESET_MAP: return initialMapState; + case adminMapActions.REGISTER_MAP: return { ...state, mapId: action.payload.mapId }; + default: return state; + } } @@ -43,14 +47,18 @@ const wikipediaMarkerReducer: ActionReducer = ( action: hikeEditMapActions.AllHikeEditMapActions ): IWikipediaMarkerEntityState => { switch (action.type) { + case hikeEditMapActions.RESET_MAP_STATE: { return wikipediaMarkerEntityInitialState; } + case hikeEditMapActions.SET_WIKIPEDIA_MARKERS: { return wikipediaMarkerAdapter.addAll(action.payload.markers, state); } + default: return state; + } } @@ -66,14 +74,18 @@ const googleMarkerReducer: ActionReducer = ( action: hikeEditMapActions.AllHikeEditMapActions ): IGoogleMarkerEntityState => { switch (action.type) { + case hikeEditMapActions.RESET_MAP_STATE: { return googleMarkerEntityInitialState; } + case hikeEditMapActions.SET_GOOGLE_MARKERS: { return googleMarkerAdapter.addAll(action.payload.markers, state); } + default: return state; + } } @@ -89,14 +101,18 @@ const osmAmenityMarkerReducer: ActionReducer = ( action: hikeEditMapActions.AllHikeEditMapActions ): IOsmAmenityMarkerEntityState => { switch (action.type) { + case hikeEditMapActions.RESET_MAP_STATE: { return osmAmenityMarkerEntityInitialState; } + case hikeEditMapActions.SET_OSM_AMENITY_MARKERS: { return osmAmenityMarkerAdapter.addAll(action.payload.markers, state); } + default: return state; + } } @@ -112,14 +128,18 @@ const osmNaturalMarkerReducer: ActionReducer = ( action: hikeEditMapActions.AllHikeEditMapActions ): IOsmNaturalMarkerEntityState => { switch (action.type) { + case hikeEditMapActions.RESET_MAP_STATE: { return osmNaturalMarkerEntityInitialState; } + case hikeEditMapActions.SET_OSM_NATURAL_MARKERS: { return osmNaturalMarkerAdapter.addAll(action.payload.markers, state); } + default: return state; + } } @@ -135,14 +155,18 @@ const osmRouteMarkerReducer: ActionReducer = ( action: hikeEditMapActions.AllHikeEditMapActions ): IOsmRouteMarkerEntityState => { switch (action.type) { + case hikeEditMapActions.RESET_MAP_STATE: { return osmRouteMarkerEntityInitialState; } + case hikeEditMapActions.SET_OSM_ROUTE_MARKERS: { return osmRouteMarkerAdapter.addAll(action.payload.markers, state); } + default: return state; + } } @@ -158,13 +182,17 @@ const gTrackMarkerReducer: ActionReducer = ( action: hikeEditMapActions.AllHikeEditMapActions ): IGTrackMarkerEntityState => { switch (action.type) { + case hikeEditMapActions.RESET_MAP_STATE: return gTrackMarkerEntityInitialState; + case hikeEditMapActions.SET_GTRACK_MARKERS: { return gTrackMarkerAdapter.addAll(action.payload.markers, state); } + default: return state; + } } diff --git a/src/app/store/reducer/hike-edit-poi.ts b/src/app/store/reducer/hike-edit-poi.ts index fa439827..70a04361 100644 --- a/src/app/store/reducer/hike-edit-poi.ts +++ b/src/app/store/reducer/hike-edit-poi.ts @@ -22,13 +22,16 @@ const googlePoiReducer: ActionReducer = ( action: hikeEditPoiActions.AllHikeEditPoiActions ): IGooglePoiEntityState => { switch (action.type) { + case hikeEditPoiActions.RESET_POI_STATE: { return googlePoiInitialState; } + case hikeEditPoiActions.SET_GOOGLE_POIS: { return googlePoiAdapter.addAll(action.payload.pois, state); } - case hikeEditPoiActions.PATCH_GOOGLE_POIS: { + + case hikeEditPoiActions.SET_GOOGLE_POIS_IN_GTRACK_DB: { return googlePoiAdapter.updateMany(action.payload.properties.map(poi => { return { id: poi.id, @@ -36,6 +39,7 @@ const googlePoiReducer: ActionReducer = ( }; }), state); } + case hikeEditPoiActions.SET_GOOGLE_POI_IN_HIKE: return googlePoiAdapter.updateOne({ id: action.payload.poiId, @@ -43,8 +47,10 @@ const googlePoiReducer: ActionReducer = ( inHike: action.payload.isInHike } }, state); + default: return state; + } } @@ -60,13 +66,16 @@ const osmAmenityPoiReducer: ActionReducer = ( action: hikeEditPoiActions.AllHikeEditPoiActions ): IOsmAmenityPoiEntityState => { switch (action.type) { + case hikeEditPoiActions.RESET_POI_STATE: { return osmAmenityPoiInitialState; } + case hikeEditPoiActions.SET_OSM_AMENITY_POIS: { return osmAmenityPoiAdapter.addAll(action.payload.pois, state); } - case hikeEditPoiActions.PATCH_OSM_AMENITY_POIS: { + + case hikeEditPoiActions.SET_OSM_AMENITY_POIS_IN_GTRACK_DB: { return osmAmenityPoiAdapter.updateMany(action.payload.properties.map(poi => { return { id: poi.id, @@ -74,6 +83,7 @@ const osmAmenityPoiReducer: ActionReducer = ( }; }), state); } + case hikeEditPoiActions.SET_OSM_AMENITY_POI_IN_HIKE: return osmAmenityPoiAdapter.updateOne({ id: action.payload.poiId, @@ -81,8 +91,10 @@ const osmAmenityPoiReducer: ActionReducer = ( inHike: action.payload.isInHike } }, state); + default: return state; + } } @@ -98,13 +110,16 @@ const osmNaturalPoiReducer: ActionReducer = ( action: hikeEditPoiActions.AllHikeEditPoiActions ): IOsmNaturalPoiEntityState => { switch (action.type) { + case hikeEditPoiActions.RESET_POI_STATE: { return osmNaturalPoiInitialState; } + case hikeEditPoiActions.SET_OSM_NATURAL_POIS: { return osmNaturalPoiAdapter.addAll(action.payload.pois, state); } - case hikeEditPoiActions.PATCH_OSM_NATURAL_POIS: { + + case hikeEditPoiActions.SET_OSM_NATURAL_POIS_IN_GTRACK_DB: { return osmNaturalPoiAdapter.updateMany(action.payload.properties.map(poi => { return { id: poi.id, @@ -112,6 +127,7 @@ const osmNaturalPoiReducer: ActionReducer = ( }; }), state); } + case hikeEditPoiActions.SET_OSM_NATURAL_POI_IN_HIKE: return osmNaturalPoiAdapter.updateOne({ id: action.payload.poiId, @@ -119,8 +135,10 @@ const osmNaturalPoiReducer: ActionReducer = ( inHike: action.payload.isInHike } }, state); + default: return state; + } } @@ -136,13 +154,16 @@ const osmRoutePoiReducer: ActionReducer = ( action: hikeEditPoiActions.AllHikeEditPoiActions ): IOsmRoutePoiEntityState => { switch (action.type) { + case hikeEditPoiActions.RESET_POI_STATE: { return osmRoutePoiInitialState; } + case hikeEditPoiActions.SET_OSM_ROUTE_POIS: { return osmRoutePoiAdapter.addAll(action.payload.pois, state); } - case hikeEditPoiActions.PATCH_OSM_ROUTE_POIS: { + + case hikeEditPoiActions.SET_OSM_ROUTE_POIS_IN_GTRACK_DB: { return osmRoutePoiAdapter.updateMany(action.payload.properties.map(poi => { return { id: poi.id, @@ -150,6 +171,7 @@ const osmRoutePoiReducer: ActionReducer = ( }; }), state); } + case hikeEditPoiActions.SET_OSM_ROUTE_POI_IN_HIKE: return osmRoutePoiAdapter.updateOne({ id: action.payload.poiId, @@ -157,8 +179,10 @@ const osmRoutePoiReducer: ActionReducer = ( inHike: action.payload.isInHike } }, state); + default: return state; + } } @@ -174,13 +198,16 @@ const wikipediaPoiReducer: ActionReducer = ( action: hikeEditPoiActions.AllHikeEditPoiActions ): IWikipediaPoiEntityState => { switch (action.type) { + case hikeEditPoiActions.RESET_POI_STATE: { return wikipediaPoiInitialState; } + case hikeEditPoiActions.SET_WIKIPEDIA_POIS: { return wikipediaPoiAdapter.addAll(action.payload.pois, state); } - case hikeEditPoiActions.PATCH_WIKIPEDIA_POIS: { + + case hikeEditPoiActions.SET_WIKIPEDIA_POIS_IN_GTRACK_DB: { return wikipediaPoiAdapter.updateMany(action.payload.properties.map(poi => { return { id: poi.id, @@ -188,6 +215,7 @@ const wikipediaPoiReducer: ActionReducer = ( }; }), state); } + case hikeEditPoiActions.SET_WIKIPEDIA_POI_IN_HIKE: return wikipediaPoiAdapter.updateOne({ id: action.payload.poiId, @@ -195,8 +223,10 @@ const wikipediaPoiReducer: ActionReducer = ( inHike: action.payload.isInHike } }, state); + default: return state; + } } @@ -225,6 +255,7 @@ export function externalPoiListContextReducer( action: hikeEditPoiActions.AllHikeEditPoiActions ): IExternalPoiListContextState { switch (action.type) { + case hikeEditPoiActions.RESET_POI_STATE: return externalPoiInitialContextState; @@ -241,6 +272,7 @@ export function externalPoiListContextReducer( } }; } + case hikeEditPoiActions.SET_WIKIPEDIA_POIS: { return { ...state, @@ -265,6 +297,7 @@ export function externalPoiListContextReducer( } }; } + case hikeEditPoiActions.SET_GOOGLE_POIS: { return { ...state, @@ -289,6 +322,7 @@ export function externalPoiListContextReducer( } }; } + case hikeEditPoiActions.SET_OSM_AMENITY_POIS: { return { ...state, @@ -313,6 +347,7 @@ export function externalPoiListContextReducer( } }; } + case hikeEditPoiActions.SET_OSM_NATURAL_POIS: { return { ...state, @@ -337,6 +372,7 @@ export function externalPoiListContextReducer( } }; } + case hikeEditPoiActions.SET_OSM_ROUTE_POIS: { return { ...state, @@ -348,6 +384,18 @@ export function externalPoiListContextReducer( }; } + /** + * GTrack + */ + case hikeEditPoiActions.SET_DIRTY: + return { + ...state, + [action.payload.subdomain]: { + ...state[action.payload.subdomain], + dirty: action.payload.dirty + } + }; + /** * Toggle markers */ @@ -359,6 +407,7 @@ export function externalPoiListContextReducer( showOnrouteMarkers: !state[action.payload.subdomain].showOnrouteMarkers } }; + case hikeEditPoiActions.TOGGLE_OFFROUTE_MARKERS: return { ...state, @@ -370,6 +419,7 @@ export function externalPoiListContextReducer( default: return state; + } } diff --git a/src/app/store/reducer/hike-edit-route-planner.ts b/src/app/store/reducer/hike-edit-route-planner.ts index e5ef9149..191e7372 100644 --- a/src/app/store/reducer/hike-edit-route-planner.ts +++ b/src/app/store/reducer/hike-edit-route-planner.ts @@ -2,11 +2,25 @@ import { Action } from '@ngrx/store'; import { IHikeEditRoutePlannerState } from '../state'; import { hikeEditRoutePlannerActions } from '../index'; +export const initialRouteDataState: GeoJSON.FeatureCollection = { + type: 'FeatureCollection', + features: [{ + type: 'Feature', + geometry: { + type: 'LineString', + coordinates: [] + }, + properties: { + name: 'Tour track' + } + }] +}; + export const initialRouteInfoDataState: IHikeEditRoutePlannerState = { segments: [], total: {}, location: '', - route: {}, + route: initialRouteDataState }; export function hikeEditRoutePlannerReducer( @@ -14,13 +28,16 @@ export function hikeEditRoutePlannerReducer( action: hikeEditRoutePlannerActions.AllHikeEditRoutePlannerActions ): IHikeEditRoutePlannerState { switch (action.type) { + case hikeEditRoutePlannerActions.RESET_ROUTE_PLANNING_STATE: return initialRouteInfoDataState; + case hikeEditRoutePlannerActions.ADD_ROUTE: return { ...state, route: action.payload.route }; + case hikeEditRoutePlannerActions.PUSH_SEGMENT: return { ...state, @@ -29,17 +46,21 @@ export function hikeEditRoutePlannerReducer( action.payload.segment ] }; + case hikeEditRoutePlannerActions.POP_SEGMENT: return { ...state, segments: state.segments.length > 1 ? state.segments.slice(0, state.segments.length - 1) : [] }; + case hikeEditRoutePlannerActions.UPDATE_TOTAL: return { ...state, total: action.payload.total }; + default: return state; + } } diff --git a/src/app/store/reducer/test/hike-edit-poi.spec.ts b/src/app/store/reducer/test/hike-edit-poi.spec.ts index 7ac60c4b..236df341 100644 --- a/src/app/store/reducer/test/hike-edit-poi.spec.ts +++ b/src/app/store/reducer/test/hike-edit-poi.spec.ts @@ -112,7 +112,7 @@ describe('HikeEditPoi reducers', () => { describe('PatchGooglePois action', () => { it('should patch google pois', () => { - const action = new hikeEditPoiActions.PatchGooglePois({ + const action = new hikeEditPoiActions.SetGooglePoisInGtrackDb({ properties: pois.map(p => p.elevation = 100).map(p => _.pick(p, ['id', 'elevation'])) }); const state = hikeEditPoiReducer(_.merge({}, initialState, { @@ -188,7 +188,7 @@ describe('HikeEditPoi reducers', () => { describe('PatchOsmAmenityPois action', () => { it('should patch osmAmenity pois', () => { - const action = new hikeEditPoiActions.PatchOsmAmenityPois({ + const action = new hikeEditPoiActions.SetOsmAmenityPoisInGtrackDb({ properties: pois.map(p => p.elevation = 100).map(p => _.pick(p, ['id', 'elevation'])) }); const state = hikeEditPoiReducer(_.merge({}, initialState, { @@ -264,7 +264,7 @@ describe('HikeEditPoi reducers', () => { describe('PatchOsmNaturalPois action', () => { it('should patch osmNatural pois', () => { - const action = new hikeEditPoiActions.PatchOsmNaturalPois({ + const action = new hikeEditPoiActions.SetOsmNaturalPoisInGtrackDb({ properties: pois.map(p => p.elevation = 100).map(p => _.pick(p, ['id', 'elevation'])) }); const state = hikeEditPoiReducer(_.merge({}, initialState, { @@ -340,7 +340,7 @@ describe('HikeEditPoi reducers', () => { describe('PatchOsmRoutePois action', () => { it('should patch osmNRoute pois', () => { - const action = new hikeEditPoiActions.PatchOsmRoutePois({ + const action = new hikeEditPoiActions.SetOsmRoutePoisInGtrackDb({ properties: pois.map(p => p.elevation = 100).map(p => _.pick(p, ['id', 'elevation'])) }); const state = hikeEditPoiReducer(_.merge({}, initialState, { @@ -416,7 +416,7 @@ describe('HikeEditPoi reducers', () => { describe('PatchWikipediaPois action', () => { it('should patch wikipedia pois', () => { - const action = new hikeEditPoiActions.PatchWikipediaPois({ + const action = new hikeEditPoiActions.SetWikipediaPoisInGtrackDb({ properties: pois.map(p => p.elevation = 100).map(p => _.pick(p, ['id', 'elevation'])) }); const state = hikeEditPoiReducer(_.merge({}, initialState, { @@ -540,4 +540,41 @@ describe('HikeEditPoi reducers', () => { expect(state.contexts.wikipedia.showOffrouteMarkers).toEqual(true); }); }); + + describe('SetDirty action', () => { + it('should set dirty google pois', () => { + const action = new hikeEditPoiActions.SetDirty({ subdomain: 'google', dirty: true }); + const state = hikeEditPoiReducer(initialState, action); + + expect(state.contexts.google.dirty).toEqual(true); + }); + + it('should set dirty osmAmenity pois', () => { + const action = new hikeEditPoiActions.SetDirty({ subdomain: 'osmAmenity', dirty: true }); + const state = hikeEditPoiReducer(initialState, action); + + expect(state.contexts.osmAmenity.dirty).toEqual(true); + }); + + it('should set dirty osmNatural pois', () => { + const action = new hikeEditPoiActions.SetDirty({ subdomain: 'osmNatural', dirty: true }); + const state = hikeEditPoiReducer(initialState, action); + + expect(state.contexts.osmNatural.dirty).toEqual(true); + }); + + it('should set dirty osmRoute pois', () => { + const action = new hikeEditPoiActions.SetDirty({ subdomain: 'osmRoute', dirty: true }); + const state = hikeEditPoiReducer(initialState, action); + + expect(state.contexts.osmRoute.dirty).toEqual(true); + }); + + it('should set dirty wikipedia pois', () => { + const action = new hikeEditPoiActions.SetDirty({ subdomain: 'wikipedia', dirty: true }); + const state = hikeEditPoiReducer(initialState, action); + + expect(state.contexts.wikipedia.dirty).toEqual(true); + }); + }); }); diff --git a/src/app/store/selectors/hike-edit-route-planner.ts b/src/app/store/selectors/hike-edit-route-planner.ts index 5732ad85..9e9576e1 100644 --- a/src/app/store/selectors/hike-edit-route-planner.ts +++ b/src/app/store/selectors/hike-edit-route-planner.ts @@ -14,6 +14,7 @@ export class HikeEditRoutePlannerSelectors { private _featureSelector: MemoizedSelector; public getRoutePlanner: MemoizedSelector; public getRoute: MemoizedSelector; + public getPath: MemoizedSelector; public getSegments: MemoizedSelector; public getIsRoundTrip: MemoizedSelector; @@ -28,6 +29,10 @@ export class HikeEditRoutePlannerSelectors { (state: IHikeEditRoutePlannerState) => state.route ); + this.getPath = createSelector(this._featureSelector, + (state: IHikeEditRoutePlannerState) => state.route.features[0] + ); + this.getSegments = createSelector(this._featureSelector, (state: IHikeEditRoutePlannerState) => state.segments ); diff --git a/src/app/store/state/hike-edit-poi.ts b/src/app/store/state/hike-edit-poi.ts index 32e26c7d..6951b9bd 100644 --- a/src/app/store/state/hike-edit-poi.ts +++ b/src/app/store/state/hike-edit-poi.ts @@ -12,6 +12,7 @@ export interface IExternalPoiListContextItemState { loaded: boolean; showOnrouteMarkers?: boolean; showOffrouteMarkers?: boolean; + dirty?: boolean; } export interface IExternalPoiListContextState { diff --git a/src/app/store/state/hike-edit-route-planner.ts b/src/app/store/state/hike-edit-route-planner.ts index a444b9b4..436d61e3 100644 --- a/src/app/store/state/hike-edit-route-planner.ts +++ b/src/app/store/state/hike-edit-route-planner.ts @@ -4,5 +4,5 @@ export interface IHikeEditRoutePlannerState { segments: ISegment[]; total: any; location: any; - route: any; + route: GeoJSON.FeatureCollection; };