Merge branch 'develop' into feature/vectortileselect

This commit is contained in:
Willem Dantuma 2020-12-18 19:22:33 +01:00
commit 1a1cd7a81c
27 changed files with 489 additions and 72 deletions

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -23,6 +23,30 @@
content: "b"; content: "b";
} }
.fm-trijntje:before {
content: "c";
}
.fm-satellite:before {
content: "d";
}
.fm-sensoterra:before {
content: "e";
}
.fm-blight:before {
content: "f";
}
.fm-agrodatacube:before {
content: "g";
}
.fm-app-menu:before {
content: "h";
}
@font-face { @font-face {
font-family: "FarmMaps"; font-family: "FarmMaps";
src: url("./FMIconFont.woff") format("woff"), /* Modern Browsers */ src: url("./FMIconFont.woff") format("woff"), /* Modern Browsers */

View File

@ -16,6 +16,7 @@ export const STARTSEARCHSUCCESS = '[Map] StartSearchSuccess';
export const SELECTFEATURE = '[Map] SelectFeature'; export const SELECTFEATURE = '[Map] SelectFeature';
export const SELECTITEM = '[Map] SelectItem'; export const SELECTITEM = '[Map] SelectItem';
export const SELECTITEMSUCCESS = '[Map] SelectItemSuccess'; export const SELECTITEMSUCCESS = '[Map] SelectItemSuccess';
export const SETSELECTEDITEMLAYER = '[Map] SetSelectedItemLayer';
export const SELECTTEMPORALITEMSSUCCESS = '[Map] SelectTemporalItemsSuccess'; export const SELECTTEMPORALITEMSSUCCESS = '[Map] SelectTemporalItemsSuccess';
export const NEXTTEMPORAL = '[Map] NextTemporal'; export const NEXTTEMPORAL = '[Map] NextTemporal';
export const PREVIOUSTEMPORAL = '[Map] PreviousTemporal'; export const PREVIOUSTEMPORAL = '[Map] PreviousTemporal';
@ -32,6 +33,7 @@ export const SETVISIBILITY = '[Map] SetVisibility';
export const SETOPACITY = '[Map] SetOpacity'; export const SETOPACITY = '[Map] SetOpacity';
export const SETLAYERINDEX = '[Map] SetLayerIndex'; export const SETLAYERINDEX = '[Map] SetLayerIndex';
export const REMOVELAYER = '[Map] RemoveLayer'; export const REMOVELAYER = '[Map] RemoveLayer';
export const CLEARLAYERS = '[Map] ClearLayers';
export const LOADBASELAYERS = '[Map] LoadLayers'; export const LOADBASELAYERS = '[Map] LoadLayers';
export const LOADBASELAYERSSUCCESS = '[Map] LoadLayersSuccess'; export const LOADBASELAYERSSUCCESS = '[Map] LoadLayersSuccess';
export const SELECTBASELAYER = '[Map] SelectBaseLayers'; export const SELECTBASELAYER = '[Map] SelectBaseLayers';
@ -42,6 +44,7 @@ export const SETSTYLE = '[Map] SetStyle';
export const SHOWLAYERSWITCHER = '[Map] ShowLayerSwitcher'; export const SHOWLAYERSWITCHER = '[Map] ShowLayerSwitcher';
export const CLEAR = '[Map] Clear'; export const CLEAR = '[Map] Clear';
export const SETREPLACEURL = '[Map] SetReplaceUrl'; export const SETREPLACEURL = '[Map] SetReplaceUrl';
export const SETFEATURES = '[Map] SetFeatures'
export class Clear implements Action { export class Clear implements Action {
readonly type = CLEAR; readonly type = CLEAR;
@ -180,6 +183,12 @@ export class AddLayer implements Action {
constructor(public item:IItem,public layerIndex=-1) { } constructor(public item:IItem,public layerIndex=-1) { }
} }
export class SetSelectedItemLayer implements Action {
readonly type = SETSELECTEDITEMLAYER;
constructor(public item:IItem,public layerIndex=-1) { }
}
export class SetVisibility implements Action { export class SetVisibility implements Action {
readonly type = SETVISIBILITY; readonly type = SETVISIBILITY;
@ -204,6 +213,12 @@ export class RemoveLayer implements Action {
constructor(public itemLayer: IItemLayer) { } constructor(public itemLayer: IItemLayer) { }
} }
export class ClearLayers implements Action {
readonly type = CLEARLAYERS;
constructor() { }
}
export class LoadBaseLayers implements Action { export class LoadBaseLayers implements Action {
readonly type = LOADBASELAYERS; readonly type = LOADBASELAYERS;
@ -256,6 +271,12 @@ export class SetReplaceUrl implements Action {
constructor(public replaceUrl:boolean) {} constructor(public replaceUrl:boolean) {}
} }
export class SetFeatures implements Action {
readonly type = SETFEATURES;
constructor(public features: Array<Feature>) { }
}
export type Actions = SetMapState export type Actions = SetMapState
| Init | Init
| Clear | Clear
@ -278,6 +299,7 @@ export type Actions = SetMapState
| SetTimeSpan | SetTimeSpan
| AddLayer | AddLayer
| RemoveLayer | RemoveLayer
| ClearLayers
| SetVisibility | SetVisibility
| SetOpacity | SetOpacity
| SetLayerIndex | SetLayerIndex
@ -291,5 +313,7 @@ export type Actions = SetMapState
| DoQuery | DoQuery
| SetStyle | SetStyle
| ShowLayerSwitcher | ShowLayerSwitcher
| SetReplaceUrl; | SetReplaceUrl
| SetFeatures
| SetSelectedItemLayer;

View File

@ -8,6 +8,10 @@ const routes = [
path: '', path: '',
component: MapComponent component: MapComponent
}, },
{
path: ':xCenter/:yCenter/:zoom/:rotation/:baseLayer',
component: MapComponent
},
{ {
path: ':xCenter/:yCenter/:zoom/:rotation/:baseLayer/:queryState', path: ':xCenter/:yCenter/:zoom/:rotation/:baseLayer/:queryState',
component: MapComponent component: MapComponent

View File

@ -19,9 +19,10 @@
period:period$|async, period:period$|async,
compassHeading:compassHeading$|async, compassHeading:compassHeading$|async,
styles:styles$|async, styles:styles$|async,
selectedFeature:selectedFeature$|async selectedFeature:selectedFeature$|async,
fullscreen:fullscreen$|async
} as state"> } as state">
<aol-map #map (moveEnd)="handleOnMoveEnd($event)" (click)="handleOnMouseDown($event)" [ngClass]="{'panel-visible':state.panelVisible}" class="map"> <aol-map #map (moveEnd)="handleOnMoveEnd($event)" (click)="handleOnMouseDown($event)" [ngClass]="{'panel-visible':state.panelVisible,'fullscreen':state.fullscreen}" class="map">
<div> <div>
</div> </div>
@ -38,7 +39,7 @@
<fm-map-item-source-vector [styles]="state.styles" [features]="state.features" (onFeatureSelected)="handleFeatureClick($event)" (onFeatureHover)="handleFeatureHover($event)" [selectedFeature]="state.selectedFeature" [selectedItem]="state.selectedItem"></fm-map-item-source-vector> <fm-map-item-source-vector [styles]="state.styles" [features]="state.features" (onFeatureSelected)="handleFeatureClick($event)" (onFeatureHover)="handleFeatureHover($event)" [selectedFeature]="state.selectedFeature" [selectedItem]="state.selectedItem"></fm-map-item-source-vector>
</aol-layer-vector> </aol-layer-vector>
<!-- <fm-map-gps-location [position]="state.position" [headingTolerance]="20" [showHeading]="true" [heading]="state.compassHeading"></fm-map-gps-location> --> <!-- <fm-map-gps-location [position]="state.position" [headingTolerance]="20" [showHeading]="true" [heading]="state.compassHeading"></fm-map-gps-location> -->
<div class="control-container"> <div class="control-container" >
<router-outlet name="map-controls"></router-outlet> <router-outlet name="map-controls"></router-outlet>
<fm-map-layer-switcher></fm-map-layer-switcher> <fm-map-layer-switcher></fm-map-layer-switcher>
<fm-map-pan-to-location [position]="state.position" [mapState]="state.mapState" [animate]="true"></fm-map-pan-to-location> <fm-map-pan-to-location [position]="state.position" [mapState]="state.mapState" [animate]="true"></fm-map-pan-to-location>
@ -46,9 +47,11 @@
</div> </div>
<fm-map-file-drop-target [parentCode]="state.parentCode" (onFileDropped)="handleFileDropped($event)"></fm-map-file-drop-target> <fm-map-file-drop-target [parentCode]="state.parentCode" (onFileDropped)="handleFileDropped($event)"></fm-map-file-drop-target>
</aol-map> </aol-map>
<fm-map-map-search #mapSearch [openedModalName]="state.openedModalName" (onOpenModal)="handleOpenModal($event)" (onCloseModal)="handleCloseModal()" [ngClass]="{'menuVisible':state.menuVisible}" (onToggleMenu)="handleToggleMenu($event)" (onSearchCollapse)="handleSearchCollapse($event)" (onSearchExpand)="handleSearchExpand($event)" [collapsed]="state.searchCollapsed" [searchMinified]="state.searchMinified" (onSearch)="handleSearch($event)" (onClear)="handleClearSearch($event)" [filterOptions]="state.queryState" [clearEnabled]="state.clearEnabled" [period]="state.period"></fm-map-map-search> <div *ngIf="noContent">
<fm-side-panel [resizeable]="true" [visible]="state.panelVisible" [collapsed]="state.panelCollapsed" [collapsable]="false"> <fm-map-map-search #mapSearch [openedModalName]="state.openedModalName" (onOpenModal)="handleOpenModal($event)" (onCloseModal)="handleCloseModal()" [ngClass]="{'menuVisible':state.menuVisible}" (onToggleMenu)="handleToggleMenu($event)" (onSearchCollapse)="handleSearchCollapse($event)" (onSearchExpand)="handleSearchExpand($event)" [collapsed]="state.searchCollapsed" [searchMinified]="state.searchMinified" (onSearch)="handleSearch($event)" (onClear)="handleClearSearch($event)" [filterOptions]="state.queryState" [clearEnabled]="state.clearEnabled" [period]="state.period"></fm-map-map-search>
<div class="panel-wrapper"> </div>
<fm-side-panel [resizeable]="true" [visible]="state.panelVisible && noContent" [collapsed]="state.panelCollapsed" [collapsable]="false">
<div class="panel-wrapper" *ngIf="noContent">
<div class="panel-top bg-secondary" *ngIf="!(state.searchMinified)"> <div class="panel-top bg-secondary" *ngIf="!(state.searchMinified)">
</div> </div>
<div class="panel-bottom"> <div class="panel-bottom">
@ -65,7 +68,10 @@
<div *ngIf="state.queryState?.tags">Cannot find tag <span>{{state.queryState?.tags}}</span></div> <div *ngIf="state.queryState?.tags">Cannot find tag <span>{{state.queryState?.tags}}</span></div>
</div> </div>
</div> </div>
</div> </div>
</fm-side-panel>
<fm-side-panel [resizeable]="true" [visible]="!noContent">
<router-outlet></router-outlet>
</fm-side-panel> </fm-side-panel>
</ng-container> </ng-container>

View File

@ -49,6 +49,10 @@ aol-map { position:absolute;width:100%;height:calc(100vh + 4rem);}
.control-container { .control-container {
position: absolute; position: absolute;
right: 1em; right: 1em;
bottom: 8.1em;
}
.fullscreen .control-container {
bottom: 5em; bottom: 5em;
} }

View File

@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, HostListener, ViewChild, AfterViewInit,NgZone } from '@angular/core'; import { Component, OnInit, OnDestroy, HostListener, ViewChild, AfterViewInit,NgZone,ElementRef } from '@angular/core';
import { Location } from '@angular/common'; import { Location } from '@angular/common';
import { Observable, Subject, Subscription, from,of ,EMPTY } from 'rxjs'; import { Observable, Subject, Subscription, from,of ,EMPTY } from 'rxjs';
import { withLatestFrom, switchMap,skip } from 'rxjs/operators'; import { withLatestFrom, switchMap,skip } from 'rxjs/operators';
@ -79,10 +79,13 @@ export class MapComponent implements OnInit, OnDestroy,AfterViewInit {
public overlayLayersCollapsed: boolean = true; public overlayLayersCollapsed: boolean = true;
public extent$: Observable<Extent> = this.store.select(mapReducers.selectGetExtent); public extent$: Observable<Extent> = this.store.select(mapReducers.selectGetExtent);
public styles$:Observable<IStyles> = this.store.select(mapReducers.selectGetStyles); public styles$:Observable<IStyles> = this.store.select(mapReducers.selectGetStyles);
public fullscreen$: Observable<boolean> = this.store.select(commonReducers.selectGetFullScreen);
private lastUrl = ""; private lastUrl = "";
private initialized: boolean = false; private initialized: boolean = false;
public noContent: boolean = false;
@ViewChild('map') map; @ViewChild('map') map;
@ViewChild('contentDiv') contentDiv: ElementRef;
constructor(private store: Store<mapReducers.State | commonReducers.State>, constructor(private store: Store<mapReducers.State | commonReducers.State>,
private route: ActivatedRoute, private route: ActivatedRoute,
@ -162,7 +165,7 @@ export class MapComponent implements OnInit, OnDestroy,AfterViewInit {
this.store.dispatch(new mapActions.DoQuery(queryState)); this.store.dispatch(new mapActions.DoQuery(queryState));
} }
ngOnInit() { ngOnInit() {
this.initialized = false; this.initialized = false;
console.debug("Init"); console.debug("Init");
this.store.dispatch(new mapActions.Clear()); this.store.dispatch(new mapActions.Clear());
@ -237,23 +240,27 @@ export class MapComponent implements OnInit, OnDestroy,AfterViewInit {
ngAfterViewInit() { ngAfterViewInit() {
console.debug("View init"); console.debug("View init");
if(this.route.children.length == 0) {
this.noContent=true;
}
this.initCustomStyles(); this.initCustomStyles();
// url to state // url to state
let urlMapState = this.getMapStateFromUrl(this.route.snapshot.paramMap); let urlMapState = this.getMapStateFromUrl(this.route.snapshot.paramMap);
let urlQueryState = this.getQueryStateFromUrl(this.route.snapshot.paramMap); let urlQueryState = this.getQueryStateFromUrl(this.route.snapshot.paramMap);
if(urlQueryState && urlMapState) { if(urlQueryState && urlMapState && this.noContent) {
this.store.dispatch(new mapActions.SetState(urlMapState,urlQueryState)); this.store.dispatch(new mapActions.SetState(urlMapState,urlQueryState));
window.localStorage.setItem("FarmMapsCommonMap_mapState",this.serializeMapState(urlMapState)); window.localStorage.setItem("FarmMapsCommonMap_mapState",this.serializeMapState(urlMapState));
} else if(urlQueryState) { } else if(urlQueryState && this.noContent) {
this.store.dispatch(new mapActions.SetQueryState(urlQueryState)); this.store.dispatch(new mapActions.SetQueryState(urlQueryState));
} else { } else {
this.store.dispatch(new mapActions.SetReplaceUrl(true)); this.store.dispatch(new mapActions.SetReplaceUrl(true));
} }
this.paramSub = this.route.paramMap.pipe(withLatestFrom(this.state$),switchMap(([params,state]) => { this.paramSub = this.route.paramMap.pipe(withLatestFrom(this.state$),switchMap(([params,state]) => {
if(this.initialized) { if(this.initialized && this.noContent) {
let urlQueryState = this.getQueryStateFromUrl(params); let urlQueryState = this.getQueryStateFromUrl(params);
if( this.serializeService.serialize(state.queryState) != this.serializeService.serialize(urlQueryState)) { if( this.serializeService.serialize(state.queryState) != this.serializeService.serialize(urlQueryState)) {
return of(new mapActions.SetState(state.mapState,urlQueryState)); return of(new mapActions.SetState(state.mapState,urlQueryState));
@ -318,24 +325,25 @@ export class MapComponent implements OnInit, OnDestroy,AfterViewInit {
} }
replaceUrl(mapState: IMapState, queryState: IQueryState, replace: boolean = true) { replaceUrl(mapState: IMapState, queryState: IQueryState, replace: boolean = true) {
if(this.noContent) {
let newMapState = this.serializeMapState(mapState); let newMapState = this.serializeMapState(mapState);
let newQueryState = this.serializeService.serialize(queryState); let newQueryState = this.serializeService.serialize(queryState);
let currentMapState = this.serializeMapState(this.getMapStateFromUrl(this.route.snapshot.paramMap)); let currentMapState = this.serializeMapState(this.getMapStateFromUrl(this.route.snapshot.paramMap));
let urlQueryState = this.getQueryStateFromUrl(this.route.snapshot.paramMap); let urlQueryState = this.getQueryStateFromUrl(this.route.snapshot.paramMap);
let currentQueryState = urlQueryState==null?"":this.serializeService.serialize(urlQueryState); let currentQueryState = urlQueryState==null?"":this.serializeService.serialize(urlQueryState);
if(mapState.baseLayerCode!="" && ((newMapState!= currentMapState) || (newQueryState!=currentQueryState))) { if(mapState.baseLayerCode!="" && ((newMapState!= currentMapState) || (newQueryState!=currentQueryState))) {
let parts =["."]; let parts =["."];
parts.push(mapState.xCenter.toFixed(5)); parts.push(mapState.xCenter.toFixed(5));
parts.push(mapState.yCenter.toFixed(5)); parts.push(mapState.yCenter.toFixed(5));
parts.push( mapState.zoom.toFixed(0)); parts.push( mapState.zoom.toFixed(0));
parts.push( mapState.rotation.toFixed(2)); parts.push( mapState.rotation.toFixed(2));
parts.push(mapState.baseLayerCode); parts.push(mapState.baseLayerCode);
parts.push( this.serializeService.serialize(queryState)); parts.push( this.serializeService.serialize(queryState));
console.debug("Replace url",parts); console.debug("Replace url",parts);
this.router.navigate(parts, { replaceUrl: replace,relativeTo:this.route.parent }); this.router.navigate(parts, { replaceUrl: replace,relativeTo:this.route.parent });
} }
}
} }
handleOnMoveEnd(event) { handleOnMoveEnd(event) {

View File

@ -150,6 +150,19 @@ export class MapEffects {
return actions; return actions;
})); }));
@Effect()
zoomToExtent2$: Observable<Action> = this.actions$.pipe(
ofType(mapActions.SETFEATURES),
map((action: mapActions.SetFeatures) => {
let extent = createEmpty();
if (extent) {
for (let f of action.features) {
extend(extent, (f as Feature).getGeometry().getExtent());
}
}
return new mapActions.SetExtent(extent);
}));
@Effect() @Effect()
hideMenu$: Observable<Action> = this.actions$.pipe( hideMenu$: Observable<Action> = this.actions$.pipe(
ofType(mapActions.STARTSEARCHSUCCESS), ofType(mapActions.STARTSEARCHSUCCESS),
@ -181,6 +194,14 @@ export class MapEffects {
} }
)); ));
@Effect()
selectItemSuccessSetLayer$: Observable<Action> = this.actions$.pipe(
ofType(mapActions.SELECTITEMSUCCESS),
map((action:mapActions.SelectItemSuccess) =>
new mapActions.SetSelectedItemLayer(action.item)
)
);
@Effect() @Effect()
selectItemSuccess$: Observable<Action> = this.actions$.pipe( selectItemSuccess$: Observable<Action> = this.actions$.pipe(
ofType(mapActions.SELECTITEMSUCCESS), ofType(mapActions.SELECTITEMSUCCESS),

View File

@ -138,6 +138,12 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
inSearch:false inSearch:false
}); });
} }
case mapActions.SETFEATURES: {
let a = action as mapActions.SetFeatures;
return tassign(state, {
features: a.features
});
}
case mapActions.SELECTFEATURE: { case mapActions.SELECTFEATURE: {
let a = action as mapActions.SelectFeature; let a = action as mapActions.SelectFeature;
return tassign(state, { return tassign(state, {
@ -157,18 +163,10 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
} }
case mapActions.SELECTITEMSUCCESS: { case mapActions.SELECTITEMSUCCESS: {
let a = action as mapActions.SelectItemSuccess; let a = action as mapActions.SelectItemSuccess;
var itemLayer = null;
if (a.item && "vnd.farmmaps.itemtype.layer,vnd.farmmaps.itemtype.shape.processed,vnd.farmmaps.itemtype.geotiff.processed".indexOf(a.item.itemType) >=0 ) {
itemLayer = new ItemLayer(a.item);
itemLayer.layerIndex = a.item.data.layers?a.item.data.layers[0].index:-1;
} else if (a.item && a.item.itemType == "vnd.farmmaps.itemtype.temporal") {
itemLayer = new TemporalItemLayer(a.item);
}
return tassign(state, { return tassign(state, {
inSearch:false, inSearch:false,
selectedItem: a.item, selectedItem: a.item,
parentItem: a.parentItem, parentItem: a.parentItem,
selectedItemLayer: itemLayer,
panelVisible: a.item != null, panelVisible: a.item != null,
clearEnabled: a.item != null, clearEnabled: a.item != null,
searchCollapsed: false, searchCollapsed: false,
@ -176,6 +174,20 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
queryState: tassign(state.queryState, {itemCode:a.item ? a.item.code:null}) queryState: tassign(state.queryState, {itemCode:a.item ? a.item.code:null})
}); });
} }
case mapActions.SETSELECTEDITEMLAYER: {
let a = action as mapActions.SetSelectedItemLayer;
var itemLayer = null;
if (a.item && "vnd.farmmaps.itemtype.layer,vnd.farmmaps.itemtype.shape.processed,vnd.farmmaps.itemtype.geotiff.processed".indexOf(a.item.itemType) >=0 ) {
itemLayer = new ItemLayer(a.item);
itemLayer.layerIndex = a.layerIndex>=0?a.layerIndex:a.item.data.layers?a.item.data.layers[0].index:-1;
} else if (a.item && a.item.itemType == "vnd.farmmaps.itemtype.temporal") {
itemLayer = new TemporalItemLayer(a.item);
}
return tassign(state, {
selectedItemLayer: itemLayer,
});
}
case mapActions.SELECTTEMPORALITEMSSUCCESS:{ case mapActions.SELECTTEMPORALITEMSSUCCESS:{
let a = action as mapActions.SelectTemporalItemsSuccess; let a = action as mapActions.SelectTemporalItemsSuccess;
let selectedItemLayer=tassign(state.selectedItemLayer) as TemporalItemLayer; let selectedItemLayer=tassign(state.selectedItemLayer) as TemporalItemLayer;
@ -316,7 +328,7 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
case mapActions.SETEXTENT: { case mapActions.SETEXTENT: {
let a = action as mapActions.SetExtent; let a = action as mapActions.SetExtent;
return tassign(state, { extent: a.extent }); return tassign(state, { extent: a.extent });
} }
case mapActions.ADDLAYER: { case mapActions.ADDLAYER: {
let a = action as mapActions.AddLayer; let a = action as mapActions.AddLayer;
let itemLayers = state.overlayLayers.slice(0); let itemLayers = state.overlayLayers.slice(0);
@ -343,6 +355,9 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
newLayers.splice(i, 1); newLayers.splice(i, 1);
return tassign(state, { overlayLayers: newLayers, selectedOverlayLayer: selectedOverlayLayer }); return tassign(state, { overlayLayers: newLayers, selectedOverlayLayer: selectedOverlayLayer });
} }
case mapActions.CLEARLAYERS: {
return tassign(state, {overlayLayers: [], selectedOverlayLayer: null});
}
case mapActions.SETVISIBILITY: { case mapActions.SETVISIBILITY: {
let a = action as mapActions.SetVisibility; let a = action as mapActions.SetVisibility;
let newLayers = state.overlayLayers.slice(0); let newLayers = state.overlayLayers.slice(0);
@ -436,7 +451,7 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
let a = action as mapActions.SelectOverlayLayer; let a = action as mapActions.SelectOverlayLayer;
return tassign(state, { selectedOverlayLayer: a.itemLayer }); return tassign(state, { selectedOverlayLayer: a.itemLayer });
} }
case mapActions.CLEAR: { case mapActions.CLEAR: {
let newQueryState = tassign(state.queryState, { query: null, tags: null, itemCode: null, parentCode: null, itemType: null }); let newQueryState = tassign(state.queryState, { query: null, tags: null, itemCode: null, parentCode: null, itemType: null });
return tassign(state, { return tassign(state, {
@ -468,7 +483,7 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
case mapActions.SETREPLACEURL: { case mapActions.SETREPLACEURL: {
let a= action as mapActions.SetReplaceUrl; let a= action as mapActions.SetReplaceUrl;
return tassign(state,{replaceUrl:a.replaceUrl}); return tassign(state,{replaceUrl:a.replaceUrl});
} }
default: { default: {
return state; return state;
} }

View File

@ -60,6 +60,8 @@ export const TOGGLEMENU = '[AppCommon] ToggleMenu';
export const TOGGLEACCOUNTMENU = '[AppCommon] ToggleAccountMenu'; export const TOGGLEACCOUNTMENU = '[AppCommon] ToggleAccountMenu';
export const TOGGLEAPPMENU = '[AppCommon] ToggleAppMenu';
export const SETMENUVISIBLE = '[AppCommon] SetMenuVisible'; export const SETMENUVISIBLE = '[AppCommon] SetMenuVisible';
export const ONLINE = '[AppCommon] Online'; export const ONLINE = '[AppCommon] Online';
@ -283,6 +285,12 @@ export class ToggleAccountMenu implements Action {
constructor() { } constructor() { }
} }
export class ToggleAppMenu implements Action {
readonly type = TOGGLEAPPMENU;
constructor() { }
}
export class SetMenuVisible implements Action { export class SetMenuVisible implements Action {
readonly type = SETMENUVISIBLE; readonly type = SETMENUVISIBLE;
@ -346,6 +354,7 @@ export type Actions = OpenModal
| CloseAll | CloseAll
| Online | Online
| Offline | Offline
| SetPageMode; | SetPageMode
| ToggleAppMenu;

View File

@ -55,6 +55,7 @@ import * as commonEffects from './effects/app-common.effects';
import { SecureOAuthStorage} from './shared/secureOAuthStorage'; import { SecureOAuthStorage} from './shared/secureOAuthStorage';
import { GradientComponent } from './components/gradient/gradient.component'; import { GradientComponent } from './components/gradient/gradient.component';
import { GradientSelectComponent } from './components/gradient-select/gradient-select.component'; import { GradientSelectComponent } from './components/gradient-select/gradient-select.component';
import { AppMenuComponent } from './components/app-menu/app-menu.component';
export { export {
SafePipe, SafePipe,
@ -126,7 +127,8 @@ export {
HasClaimDirective, HasClaimDirective,
UserMenuComponent, UserMenuComponent,
GradientComponent, GradientComponent,
GradientSelectComponent GradientSelectComponent,
AppMenuComponent
], ],
exports: [ exports: [
NgbModule, NgbModule,

View File

@ -0,0 +1,9 @@
<div>
<div (click)="toggle($event)" class="rounded-circle menu-button hidden" [ngClass]="{'hidden':!user}">
<span i18n-title title="Apps"><i class="fm fm-app-menu" aria-hidden="true"></i></span>
<div class="menu hidden" [ngClass]="{'hidden':!showMenu}">
<router-outlet name="app-menu"></router-outlet>
</div>
</div>
</div>

View File

@ -0,0 +1,48 @@
.menu-button {
background-color: gray;
display: inline-block;
width: 2.5em;
height: 2.5em;
line-height: 2.5em;
text-align: center;
font-size: 1rem;
position: relative;
}
div.menu-button > span {
color:white;
}
.menu {
max-height: 100vh;
//transition: max-height 0.2s;
overflow: hidden;
box-shadow: 0 0 20px rgba(0,0,0,.3);
position: absolute;
top: 3rem;
right:0;
background-color: #fff;
border-radius: 0.25rem;
padding: 0.5rem;
}
.card {
padding:0.5rem;
min-width: 10rem;
}
.card-body {
text-align: left;
}
.hidden {
max-height: 0;
}
.menu.hidden {
padding: 0;
}
.menu-button.hidden {
overflow: hidden;
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AppMenuComponent } from './app-menu.component';
describe('AppMenuComponent', () => {
let component: AppMenuComponent;
let fixture: ComponentFixture<AppMenuComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ AppMenuComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AppMenuComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,29 @@
import { Component, OnInit, Input } from '@angular/core';
import { IUser } from '../../models/user';
import {Store} from '@ngrx/store';
import * as appReducers from '../../reducers/app-common.reducer';
import * as appActions from '../../actions/app-common.actions';
@Component({
selector: 'fm-app-menu',
templateUrl: './app-menu.component.html',
styleUrls: ['./app-menu.component.scss']
})
export class AppMenuComponent implements OnInit {
@Input() user:IUser;
@Input() showMenu:boolean;
constructor(private store: Store<appReducers.State>) { }
ngOnInit(): void {
}
toggle(event:MouseEvent) {
event.stopPropagation();
this.store.dispatch(new appActions.ToggleAppMenu());
}
}

View File

@ -31,7 +31,8 @@
<fm-resumable-file-upload></fm-resumable-file-upload> <fm-resumable-file-upload></fm-resumable-file-upload>
</ng-container> </ng-container>
<div class="user-menu apponly"> <div class="user-menu apponly">
<fm-user-menu [user]="user|async" [showMenu]="accountMenuVisible|async"></fm-user-menu> <fm-app-menu [user]="user|async" [showMenu]="appMenuVisible|async"></fm-app-menu>
<fm-user-menu [user]="user|async" [showMenu]="accountMenuVisible|async"></fm-user-menu>
</div> </div>
<div class="healthstatus-container online apponly" [ngClass]="{'online' :(isOnline|async)}"> <div class="healthstatus-container online apponly" [ngClass]="{'online' :(isOnline|async)}">
<div class="healthstatus alert alert-danger m-0" > <div class="healthstatus alert alert-danger m-0" >

View File

@ -34,8 +34,8 @@ body { background: #f1f1f1; line-height: 18px; user-select:none;font-family: Lat
} }
.app { .app {
width:100vw; width:100%;
height:100vh; height:100%;
} }
.app > .navbar { .app > .navbar {
@ -124,3 +124,8 @@ body { background: #f1f1f1; line-height: 18px; user-select:none;font-family: Lat
.online { .online {
max-height:0em; max-height:0em;
} }
fm-app-menu,fm-user-menu {
display: inline-block;
margin-left: 1rem;
}

View File

@ -42,6 +42,7 @@ export class AppComponent implements OnInit, OnDestroy {
public routeLoading: Observable<boolean> = this.store$.select(appReducers.selectGetRouteLoading); public routeLoading: Observable<boolean> = this.store$.select(appReducers.selectGetRouteLoading);
public menuVisible: Observable<boolean> = this.store$.select(appReducers.SelectGetMenuVisible); public menuVisible: Observable<boolean> = this.store$.select(appReducers.SelectGetMenuVisible);
public accountMenuVisible: Observable<boolean> = this.store$.select(appReducers.SelectGetAccountMenuVisible); public accountMenuVisible: Observable<boolean> = this.store$.select(appReducers.SelectGetAccountMenuVisible);
public appMenuVisible: Observable<boolean> = this.store$.select(appReducers.SelectGetAppMenuVisible);
public user: Observable<IUser> = this.store$.select(appReducers.SelectGetUser); public user: Observable<IUser> = this.store$.select(appReducers.SelectGetUser);
public isPageMode: Observable<boolean> = this.store$.select(appReducers.SelectGetIsPageMode); public isPageMode: Observable<boolean> = this.store$.select(appReducers.SelectGetIsPageMode);
@Input() showUploadProgress: boolean = true; @Input() showUploadProgress: boolean = true;
@ -101,6 +102,10 @@ export class AppComponent implements OnInit, OnDestroy {
return action; return action;
} }
async loadItemTypes() {
await this.itemTypeService$.load(this.appConfig$)
}
ngOnInit() { ngOnInit() {
this.InstallRouteEventHandler(); this.InstallRouteEventHandler();
this.InstallEventServiceEventHandler(); this.InstallEventServiceEventHandler();
@ -108,7 +113,7 @@ export class AppComponent implements OnInit, OnDestroy {
this.InstallHealthCheck(); this.InstallHealthCheck();
//load item types //load item types
this.itemTypeService$.load(this.appConfig$) this.loadItemTypes();
} }
@HostListener('document:keyup', ['$event']) @HostListener('document:keyup', ['$event'])

View File

@ -13,6 +13,8 @@ export class AuthCallbackComponent {
oauthService$.loadDiscoveryDocument().then(() => { oauthService$.loadDiscoveryDocument().then(() => {
oauthService$.tryLoginCodeFlow().then(() => { oauthService$.tryLoginCodeFlow().then(() => {
router$.navigateByUrl((oauthService$.state && oauthService$.state!="")?decodeURIComponent(oauthService$.state):""); router$.navigateByUrl((oauthService$.state && oauthService$.state!="")?decodeURIComponent(oauthService$.state):"");
}).catch(() => {
router$.navigateByUrl("/");
}); });
}) })
} }

View File

@ -7,7 +7,6 @@
text-align: center; text-align: center;
font-size: 1rem; font-size: 1rem;
position: relative; position: relative;
display: inline-block;
} }
div.menu-button > span { div.menu-button > span {

View File

@ -3,4 +3,14 @@ export interface IUser {
name?: string; name?: string;
email?: string; email?: string;
claims: any; claims: any;
} firstName?: string;
lastName?: string;
address?: string;
postalCode?: string;
city?: string;
country?: string;
phone?: string;
mobile?: string;
organisation?: string;
cocNumber?: string;
}

View File

@ -21,6 +21,7 @@ export interface State {
userPackages: IPackages, userPackages: IPackages,
userSettingsRoot: IItem, userSettingsRoot: IItem,
accountMenuVisible: boolean, accountMenuVisible: boolean,
appMenuVisible: boolean,
isOnline: boolean, isOnline: boolean,
isPageMode:boolean isPageMode:boolean
} }
@ -37,6 +38,7 @@ export const initialState: State = {
userPackages: {}, userPackages: {},
userSettingsRoot: null, userSettingsRoot: null,
accountMenuVisible: false, accountMenuVisible: false,
appMenuVisible: false,
isOnline: true, isOnline: true,
isPageMode: true isPageMode: true
} }
@ -95,17 +97,20 @@ export function reducer(state = initialState, action: appCommonActions.Actions )
}); });
} }
case appCommonActions.TOGGLEMENU: { case appCommonActions.TOGGLEMENU: {
return tassign(state, { menuVisible: !state.menuVisible,accountMenuVisible:!state.menuVisible?false:state.accountMenuVisible }); return tassign(state, { menuVisible: !state.menuVisible,accountMenuVisible:!state.menuVisible?false:state.accountMenuVisible,appMenuVisible:!state.menuVisible?false:state.appMenuVisible });
} }
case appCommonActions.TOGGLEACCOUNTMENU: { case appCommonActions.TOGGLEACCOUNTMENU: {
return tassign(state, { accountMenuVisible: !state.accountMenuVisible }); return tassign(state, { accountMenuVisible: !state.accountMenuVisible,appMenuVisible:false });
}
case appCommonActions.TOGGLEAPPMENU: {
return tassign(state, { appMenuVisible: !state.appMenuVisible,accountMenuVisible:false });
} }
case appCommonActions.ESCAPE: { case appCommonActions.ESCAPE: {
return tassign(state, { menuVisible: false,accountMenuVisible:false }); return tassign(state, { menuVisible: false,accountMenuVisible:false,appMenuVisible: false });
} }
case appCommonActions.SETMENUVISIBLE: { case appCommonActions.SETMENUVISIBLE: {
let a = action as appCommonActions.SetMenuVisible; let a = action as appCommonActions.SetMenuVisible;
return tassign(state, { menuVisible: a.visible,accountMenuVisible:a.visible?false:state.accountMenuVisible }); return tassign(state, { menuVisible: a.visible,accountMenuVisible:a.visible?false:state.accountMenuVisible,appMenuVisible:a.visible?false:state.appMenuVisible });
} }
case appCommonActions.INITUSERPACKAGESSUCCESS:{ case appCommonActions.INITUSERPACKAGESSUCCESS:{
let a = action as appCommonActions.InitUserPackagesSuccess; let a = action as appCommonActions.InitUserPackagesSuccess;
@ -124,7 +129,7 @@ export function reducer(state = initialState, action: appCommonActions.Actions )
return tassign(state,{user:null,initialized:false}); return tassign(state,{user:null,initialized:false});
} }
case appCommonActions.CLOSEALL: { case appCommonActions.CLOSEALL: {
return tassign(state,{accountMenuVisible:false,menuVisible:false }); return tassign(state,{accountMenuVisible:false,appMenuVisible:false, menuVisible:false });
} }
case appCommonActions.ONLINE:{ case appCommonActions.ONLINE:{
return tassign(state,{isOnline:true}); return tassign(state,{isOnline:true});
@ -153,6 +158,7 @@ export const getUser = (state: State) => state.user;
export const getUserPackages = (state: State) => state.userPackages; export const getUserPackages = (state: State) => state.userPackages;
export const getUserSettingsRoot = (state: State) => state.userSettingsRoot; export const getUserSettingsRoot = (state: State) => state.userSettingsRoot;
export const getAccountMenuVisible = (state: State) => state.accountMenuVisible; export const getAccountMenuVisible = (state: State) => state.accountMenuVisible;
export const getAppMenuVisible = (state: State) => state.appMenuVisible;
export const getIsOnline = (state: State) => state.isOnline; export const getIsOnline = (state: State) => state.isOnline;
export const getIsPageMode = (state: State) => state.isPageMode; export const getIsPageMode = (state: State) => state.isPageMode;
@ -169,6 +175,7 @@ export const SelectGetUser = createSelector(selectAppCommonState,getUser);
export const SelectGetUserPackages = createSelector(selectAppCommonState,getUserPackages); export const SelectGetUserPackages = createSelector(selectAppCommonState,getUserPackages);
export const SelectGetUserSettingsRoot = createSelector(selectAppCommonState,getUserSettingsRoot); export const SelectGetUserSettingsRoot = createSelector(selectAppCommonState,getUserSettingsRoot);
export const SelectGetAccountMenuVisible = createSelector(selectAppCommonState,getAccountMenuVisible); export const SelectGetAccountMenuVisible = createSelector(selectAppCommonState,getAccountMenuVisible);
export const SelectGetAppMenuVisible = createSelector(selectAppCommonState,getAppMenuVisible);
export const SelectGetIsOnline = createSelector(selectAppCommonState,getIsOnline); export const SelectGetIsOnline = createSelector(selectAppCommonState,getIsOnline);
export const SelectGetIsPageMode = createSelector(selectAppCommonState,getIsPageMode); export const SelectGetIsPageMode = createSelector(selectAppCommonState,getIsPageMode);

View File

@ -18,4 +18,8 @@ export class UserService {
getCurrentUser(): Observable<IUser> { getCurrentUser(): Observable<IUser> {
return this.httpClient.get<IUser>(`${this.ApiEndpoint()}/api/v1/currentuser`); return this.httpClient.get<IUser>(`${this.ApiEndpoint()}/api/v1/currentuser`);
} }
updateCurrentUser(user: IUser): Observable<IUser> {
return this.httpClient.put<IUser>(`${this.ApiEndpoint()}/api/v1/currentuser`, user);
}
} }

View File

@ -1,9 +1,9 @@
{ {
"issuer": "https://accounts.farmmaps.awtest.nl", "issuer": "https://accounts.test.farmmaps.eu",
"clientId": "farmmapsdev", "clientId": "farmmapsdev",
"audience": "https://farmmaps.awtest.nl/", "audience": "https://test.farmmaps.eu/",
"requireHttps": true, "requireHttps": true,
"apiEndPoint": "https://farmmaps.awtest.nl", "apiEndPoint": "https://test.farmmaps.eu",
"grantType":"code" "grantType":"code"
} }