Compare commits
	
		
			65 Commits
		
	
	
		
			feature/Mi
			...
			9697e93915
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 9697e93915 | |||
| 
						 | 
					e057e463bd | ||
| 
						 | 
					d494a7c7d6 | ||
| 
						 | 
					e525227865 | ||
| 
						 | 
					10bdd27608 | ||
| 
						 | 
					2790ae74e7 | ||
| 
						 | 
					31d01c75c5 | ||
| 
						 | 
					0da8d3d46d | ||
| 
						 | 
					60761e9f4a | ||
| 
						 | 
					ec9684b826 | ||
| 
						 | 
					1073044f31 | ||
| 
						 | 
					81126974f5 | ||
| 06f443a503 | |||
| eb560aaf33 | |||
| 
						 | 
					464b82bfb9 | ||
| 
						 | 
					1c75fb6f71 | ||
| 
						 | 
					5f5d8a9712 | ||
| 
						 | 
					dc329a0e29 | ||
| 
						 | 
					15a09e4b05 | ||
| 94f973406e | |||
| 6404e45d66 | |||
| c0f689331e | |||
| 0f5a5251db | |||
| 3352bfd805 | |||
| 
						 | 
					60ed2afaa4 | ||
| 
						 | 
					9ac534c171 | ||
| 
						 | 
					cecbdb2ded | ||
| 
						 | 
					1092d5dcaf | ||
| 
						 | 
					4af6b2937c | ||
| 263fb6c3d4 | |||
| 
						 | 
					09fa8b4f1f | ||
| 
						 | 
					214dc3ffa1 | ||
| 
						 | 
					fad04744cf | ||
| 
						 | 
					033b74517f | ||
| 
						 | 
					0b7564e874 | ||
| 
						 | 
					0d247e6416 | ||
| 
						 | 
					b92cc7bc09 | ||
| 
						 | 
					eba18b0235 | ||
| 
						 | 
					69f8184dfe | ||
| 
						 | 
					31c1570ffb | ||
| 
						 | 
					fe2650c4fd | ||
| 
						 | 
					f89209c555 | ||
| 
						 | 
					b3d0a6f0c7 | ||
| 
						 | 
					6bd1726fc9 | ||
| 
						 | 
					b0ff54d157 | ||
| 
						 | 
					277dc7051e | ||
| 
						 | 
					ace4a6b364 | ||
| 
						 | 
					146514d386 | ||
| 
						 | 
					1187ee9fd6 | ||
| 4527276254 | |||
| 
						 | 
					6f52302875 | ||
| 
						 | 
					cbe27c2add | ||
| 
						 | 
					cd156ab1bc | ||
| 
						 | 
					f9d0e2aee0 | ||
| 
						 | 
					253b3d3c90 | ||
| 
						 | 
					28d4adc571 | ||
| 
						 | 
					925af1e645 | ||
| 
						 | 
					4130e0a796 | ||
| 
						 | 
					01933b1602 | ||
| 0482aa7124 | |||
| 75015f6d22 | |||
| aa3707aa56 | |||
| 
						 | 
					ff06b419f2 | ||
| 
						 | 
					c1c0bd2596 | ||
| 
						 | 
					978cbdabfc | 
							
								
								
									
										23
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										23
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -2692,14 +2692,14 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@farmmaps/common": {
 | 
			
		||||
      "version": "0.0.1-prerelease.267",
 | 
			
		||||
      "resolved": "https://repository.akkerweb.nl/repository/npm-group/@farmmaps/common/-/common-0.0.1-prerelease.267.tgz",
 | 
			
		||||
      "integrity": "sha512-U2D6IhxIzpZVtExnRHSP8oNyva9eYCGnZEBy7jWFCY83X4R54w/+PxCB8sK8STf14IJFJcmaSXpHc7Wyd7GrbA=="
 | 
			
		||||
      "version": "0.0.1-prerelease.325",
 | 
			
		||||
      "resolved": "https://repository.akkerweb.nl/repository/npm-group/@farmmaps/common/-/common-0.0.1-prerelease.325.tgz",
 | 
			
		||||
      "integrity": "sha512-taXTcyYXHqkSZfKCKr9Oqp/eXA15NY3ryIBZ3W3nV43VvzPEQHQJSiMt8uSR1/Xwt7Vim0rTjaiVF2kS0iuz9g=="
 | 
			
		||||
    },
 | 
			
		||||
    "@farmmaps/common-map": {
 | 
			
		||||
      "version": "0.0.1-prerelease.267",
 | 
			
		||||
      "resolved": "https://repository.akkerweb.nl/repository/npm-group/@farmmaps/common-map/-/common-map-0.0.1-prerelease.267.tgz",
 | 
			
		||||
      "integrity": "sha512-lZG9abbuVNtFNPKLddYfBRD6qhc6fMslL4wTIKjX8P7yfBM6/W5FAlkcQEJqcTj9k/0fzRlzzKQ2dJouGkctcQ=="
 | 
			
		||||
      "version": "0.0.1-prerelease.325",
 | 
			
		||||
      "resolved": "https://repository.akkerweb.nl/repository/npm-group/@farmmaps/common-map/-/common-map-0.0.1-prerelease.325.tgz",
 | 
			
		||||
      "integrity": "sha512-T33qDcusnxKab8hlTxgcQEfANPDag+Ys1+32UvPwkVlk7Gfa5sm0Xw6on70epBOINHMfvbsVpm3dV/lIdgZHhA=="
 | 
			
		||||
    },
 | 
			
		||||
    "@istanbuljs/schema": {
 | 
			
		||||
      "version": "0.1.2",
 | 
			
		||||
@@ -9669,6 +9669,11 @@
 | 
			
		||||
        "deepmerge": "^3.2.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "ngx-bootstrap": {
 | 
			
		||||
      "version": "5.6.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ngx-bootstrap/-/ngx-bootstrap-5.6.1.tgz",
 | 
			
		||||
      "integrity": "sha512-8fDs3VaaWgKpupakPKS0QaUc+1E/JMBGJDxUUODjyIkLtFr1A8vH4cjXiV3AfrPvhK27GH0oyTPyKWKcCjEtVg=="
 | 
			
		||||
    },
 | 
			
		||||
    "ngx-openlayers": {
 | 
			
		||||
      "version": "1.0.0-next.13",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ngx-openlayers/-/ngx-openlayers-1.0.0-next.13.tgz",
 | 
			
		||||
@@ -9678,9 +9683,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "ngx-uploadx": {
 | 
			
		||||
      "version": "3.3.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ngx-uploadx/-/ngx-uploadx-3.3.4.tgz",
 | 
			
		||||
      "integrity": "sha512-kWe6HLPQQDU/5W6UlFb8btrdQfzxKAmGUALFYPLzLRNAYmUnw0Tqf5ZllI0CCriavExqmUHzfG6zQ7fme+reTg==",
 | 
			
		||||
      "version": "3.5.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ngx-uploadx/-/ngx-uploadx-3.5.1.tgz",
 | 
			
		||||
      "integrity": "sha512-G9JXAQjlwmocaJFHgxmUowFX6DrYM1kP0oMdEWErX5fYuG8gm1LCsUHeCZDZFRpGFQ/xp5mwAoS2/0H22DCr7w==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "tslib": "^1.9.0"
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								package.json
									
									
									
									
									
								
							@@ -19,26 +19,27 @@
 | 
			
		||||
    "@angular/platform-browser": "~9.1.0",
 | 
			
		||||
    "@angular/platform-browser-dynamic": "~9.1.0",
 | 
			
		||||
    "@angular/router": "~9.1.0",
 | 
			
		||||
    "@farmmaps/common": ">=0.0.1-prerelease.325 <0.0.1",
 | 
			
		||||
    "@farmmaps/common-map": ">=0.0.1-prerelease.325 <0.0.1",
 | 
			
		||||
    "@microsoft/signalr": "^3.1.3",
 | 
			
		||||
    "@farmmaps/common": ">=0.0.1-prerelease.267 <0.0.1",
 | 
			
		||||
    "@farmmaps/common-map": ">=0.0.1-prerelease.267 <0.0.1",
 | 
			
		||||
    "@ng-bootstrap/ng-bootstrap": "^6.0",
 | 
			
		||||
    "@ngrx/effects": "^9.0",
 | 
			
		||||
    "@ngrx/router-store": "^9.0",
 | 
			
		||||
    "@ngrx/store": "^9.0",
 | 
			
		||||
    "ngx-uploadx": "^3.3.4",
 | 
			
		||||
    "angular-oauth2-oidc": "^9.1",
 | 
			
		||||
    "bootstrap": "^4.4.1",
 | 
			
		||||
    "core-js": "^2.6.11",
 | 
			
		||||
    "font-awesome": "^4.7.0",
 | 
			
		||||
    "ngrx-store-localstorage": "^9.0",
 | 
			
		||||
    "ngx-bootstrap": "^5.6.1",
 | 
			
		||||
    "ngx-openlayers": "1.0.0-next.13",
 | 
			
		||||
    "ngx-uploadx": "^3.5.1",
 | 
			
		||||
    "ol": "6.1.1",
 | 
			
		||||
    "resumablejs": "^1.1.0",
 | 
			
		||||
    "rxjs": "^6.5.4",
 | 
			
		||||
    "tassign": "^1.0.0",
 | 
			
		||||
    "tslib": "^1.10.0",
 | 
			
		||||
    "zone.js": "~0.10.2",
 | 
			
		||||
    "ngx-openlayers": "1.0.0-next.13",
 | 
			
		||||
    "ol": "6.1.1"
 | 
			
		||||
    "zone.js": "~0.10.2"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@angular-devkit/build-angular": "~0.901.0",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										79
									
								
								projects/common-map/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										79
									
								
								projects/common-map/package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -1,82 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "@farmmaps/common-map",
 | 
			
		||||
  "version": "0.0.1",
 | 
			
		||||
  "lockfileVersion": 1,
 | 
			
		||||
  "requires": true,
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@openlayers/pepjs": {
 | 
			
		||||
      "version": "0.5.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@openlayers/pepjs/-/pepjs-0.5.3.tgz",
 | 
			
		||||
      "integrity": "sha512-Bgvi5c14BS0FJWyYWWFstNEnXsB30nK8Jt8hkAAdqr7E0gDdBBWVDglF3Ub19wTxvgJ/CVHyTY6VuCtnyRzglg=="
 | 
			
		||||
    },
 | 
			
		||||
    "ieee754": {
 | 
			
		||||
      "version": "1.1.13",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
 | 
			
		||||
      "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
 | 
			
		||||
    },
 | 
			
		||||
    "ngx-openlayers": {
 | 
			
		||||
      "version": "1.0.0-next.13",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ngx-openlayers/-/ngx-openlayers-1.0.0-next.13.tgz",
 | 
			
		||||
      "integrity": "sha512-6y724s5JV6n1oxEhryxP3wcbwzpierj9RLiMLXInfKOG3l5IO1AtPNkeK9+mKctVDUVm5URIfzzZH02Ld+bvSQ==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "tslib": "^1.9.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "ol": {
 | 
			
		||||
      "version": "6.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ol/-/ol-6.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-0dL3i3eJqgOpqIjDKEY3grkeQnjAYfV5L/JCxhOu4SxiaizRwFrFgeas6LILRoxKa03jhQFbut2r2bbgcLGQeA==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@openlayers/pepjs": "^0.5.3",
 | 
			
		||||
        "pbf": "3.2.1",
 | 
			
		||||
        "pixelworks": "1.1.0",
 | 
			
		||||
        "rbush": "^3.0.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "pbf": {
 | 
			
		||||
      "version": "3.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz",
 | 
			
		||||
      "integrity": "sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "ieee754": "^1.1.12",
 | 
			
		||||
        "resolve-protobuf-schema": "^2.1.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "pixelworks": {
 | 
			
		||||
      "version": "1.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/pixelworks/-/pixelworks-1.1.0.tgz",
 | 
			
		||||
      "integrity": "sha1-Hwla1I3Ki/ihyCWOAJIDGkTyLKU="
 | 
			
		||||
    },
 | 
			
		||||
    "protocol-buffers-schema": {
 | 
			
		||||
      "version": "3.4.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.4.0.tgz",
 | 
			
		||||
      "integrity": "sha512-G/2kcamPF2S49W5yaMGdIpkG6+5wZF0fzBteLKgEHjbNzqjZQ85aAs1iJGto31EJaSTkNvHs5IXuHSaTLWBAiA=="
 | 
			
		||||
    },
 | 
			
		||||
    "quickselect": {
 | 
			
		||||
      "version": "2.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw=="
 | 
			
		||||
    },
 | 
			
		||||
    "rbush": {
 | 
			
		||||
      "version": "3.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "quickselect": "^2.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "resolve-protobuf-schema": {
 | 
			
		||||
      "version": "2.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "protocol-buffers-schema": "^3.3.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "tslib": {
 | 
			
		||||
      "version": "1.10.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
 | 
			
		||||
      "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  "lockfileVersion": 1
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@
 | 
			
		||||
    "@ngrx/router-store": "^9.0",
 | 
			
		||||
    "@ngrx/store": "^9.0",
 | 
			
		||||
    "tassign": "^1.0.0",
 | 
			
		||||
    "@farmmaps/common": ">=0.0.1-prerelease.265 <0.0.1",
 | 
			
		||||
    "@farmmaps/common": ">=0.0.1-prerelease.308 <0.0.1",
 | 
			
		||||
    "ngx-openlayers": "1.0.0-next.13",
 | 
			
		||||
    "ol": "6.1.1"
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -105,7 +105,7 @@ export class SelectItem implements Action {
 | 
			
		||||
export class SelectItemSuccess implements Action {
 | 
			
		||||
  readonly type = SELECTITEMSUCCESS;
 | 
			
		||||
 | 
			
		||||
  constructor(public item: IItem) { }
 | 
			
		||||
  constructor(public item: IItem, public parentItem: IItem) { }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class SelectTemporalItemsSuccess implements Action {
 | 
			
		||||
 
 | 
			
		||||
@@ -64,6 +64,7 @@ import { WidgetStatusComponent } from './components/widget-status/widget-status.
 | 
			
		||||
import { ForChild} from './components/for-item/for-child.decorator';
 | 
			
		||||
import {ForItemType } from './components/for-item/for-itemtype.decorator';
 | 
			
		||||
import { ForSourceTask} from './components/for-item/for-sourcetask.decorator';
 | 
			
		||||
import { ForPackage } from './components/for-item/for-package.decorator';
 | 
			
		||||
import { PanToLocation} from './components/aol/pan-to-location/pan-to-location.component';
 | 
			
		||||
import {LayerSwitcher} from './components/layer-switcher/layer-switcher.component';
 | 
			
		||||
 | 
			
		||||
@@ -143,7 +144,8 @@ export {
 | 
			
		||||
  IPeriodState,
 | 
			
		||||
  ForChild,
 | 
			
		||||
  ForItemType,
 | 
			
		||||
  ForSourceTask
 | 
			
		||||
  ForSourceTask,
 | 
			
		||||
  ForPackage
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@NgModule({  
 | 
			
		||||
 
 | 
			
		||||
@@ -89,13 +89,13 @@ export class ItemLayersComponent extends LayerGroupComponent implements OnChange
 | 
			
		||||
    var layerIndex = -1;
 | 
			
		||||
    var layer: Layer = null;  
 | 
			
		||||
    layerIndex = itemLayer.layerIndex != -1 ? itemLayer.layerIndex : item.data.layers[0].index;
 | 
			
		||||
    let source = new XYZ({ maxZoom: 19, minZoom: 1, url: `${this._apiEndPoint}/api/v1/items/${item.code}/tiles/${layerIndex}/{z}/{x}/{y}.png?v=${item.updated.getTime()}` });
 | 
			
		||||
    let source = new XYZ({ maxZoom: 19, minZoom: 1, url: `${this._apiEndPoint}/api/v1/items/${item.code}/tiles/${layerIndex}/{z}/{x}/{y}.png?v=${Date.parse(item.updated)}` });
 | 
			
		||||
    layer = new Tile({ source: source });
 | 
			
		||||
    var data = item.data;
 | 
			
		||||
    var l = (data && data.layers && data.layers.length > 0) ? data.layers[0] : null;
 | 
			
		||||
    if (l && l.rendering && l.rendering.renderoutputType == "Tiles") {
 | 
			
		||||
      var rt = l.rendering as IRenderoutputTiles;
 | 
			
		||||
      let source = new XYZ({ maxZoom: rt.maxzoom, minZoom: rt.minzoom, url: `${this._apiEndPoint}/api/v1/items/${item.code}/tiles/${layerIndex}/{z}/{x}/{y}.png?v=${item.updated.getTime()}` });
 | 
			
		||||
      let source = new XYZ({ maxZoom: rt.maxzoom, minZoom: rt.minzoom, url: `${this._apiEndPoint}/api/v1/items/${item.code}/tiles/${layerIndex}/{z}/{x}/{y}.png?v=${Date.parse(item.updated)}` });
 | 
			
		||||
      layer = new Tile({ source: source });
 | 
			
		||||
    }
 | 
			
		||||
    if (l && l.rendering && l.rendering.renderoutputType == "Image") {
 | 
			
		||||
@@ -105,7 +105,7 @@ export class ItemLayersComponent extends LayerGroupComponent implements OnChange
 | 
			
		||||
        units: 'pixels',
 | 
			
		||||
        extent: ri.extent
 | 
			
		||||
      });
 | 
			
		||||
      let source = new ImageStatic({ imageExtent: ri.extent, projection: projection, url: `${this._apiEndPoint}/api/v1/items/${item.code}/mapimage/${layerIndex}?v=${item.updated.getTime()}` });
 | 
			
		||||
      let source = new ImageStatic({ imageExtent: ri.extent, projection: projection, url: `${this._apiEndPoint}/api/v1/items/${item.code}/mapimage/${layerIndex}?v=${Date.parse(item.updated)}` });
 | 
			
		||||
      layer = new Image({ source: source });
 | 
			
		||||
    }
 | 
			
		||||
    return layer;
 | 
			
		||||
@@ -125,7 +125,7 @@ export class ItemLayersComponent extends LayerGroupComponent implements OnChange
 | 
			
		||||
          maxZoom: rt.maxzoom,
 | 
			
		||||
          minZoom: rt.minzoom,
 | 
			
		||||
          format: new MVT(),
 | 
			
		||||
          url: `${this._apiEndPoint}/api/v1/items/${item.code}/vectortiles/{z}/{x}/{y}.pbf?v=${item.updated.getTime()}`
 | 
			
		||||
          url: `${this._apiEndPoint}/api/v1/items/${item.code}/vectortiles/{z}/{x}/{y}.pbf?v=${Date.parse(item.updated)}`
 | 
			
		||||
        }),
 | 
			
		||||
        style: (feature) => {
 | 
			
		||||
          return this.getColorFromGradient(l, feature);
 | 
			
		||||
@@ -137,7 +137,7 @@ export class ItemLayersComponent extends LayerGroupComponent implements OnChange
 | 
			
		||||
        source: new XYZ({
 | 
			
		||||
          maxZoom: rt.maxzoom,
 | 
			
		||||
          minZoom: rt.minzoom,
 | 
			
		||||
          url: `${this._apiEndPoint}/api/v1/items/${item.code}/vectortiles/image_tiles/${layerIndex}/{z}/{x}/{y}.png?v=${item.updated.getTime()}`
 | 
			
		||||
          url: `${this._apiEndPoint}/api/v1/items/${item.code}/vectortiles/image_tiles/${layerIndex}/{z}/{x}/{y}.png?v=${Date.parse(item.updated)}`
 | 
			
		||||
        })
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ import { Component, Input, OnInit, ComponentFactoryResolver, ViewChild, SimpleCh
 | 
			
		||||
import { Feature } from 'ol';
 | 
			
		||||
import { FeatureListComponent,AbstractFeatureListComponent } from '../feature-list/feature-list.component';
 | 
			
		||||
import { WidgetHostDirective } from '../widget-host/widget-host.directive';
 | 
			
		||||
import  {IQueryState } from '@farmmaps/common';
 | 
			
		||||
import  {IQueryState,PackageService } from '@farmmaps/common';
 | 
			
		||||
import * as mapReducers from '../../reducers/map.reducer';
 | 
			
		||||
import * as mapActions from '../../actions/map.actions';
 | 
			
		||||
import { Store } from '@ngrx/store';
 | 
			
		||||
@@ -16,7 +16,7 @@ import { Observable } from 'rxjs';
 | 
			
		||||
})
 | 
			
		||||
export class FeatureListContainerComponent {
 | 
			
		||||
 | 
			
		||||
  constructor(private store: Store<mapReducers.State>,private componentFactoryResolver: ComponentFactoryResolver, @Inject(AbstractFeatureListComponent) public featureLists: AbstractFeatureListComponent[] ) {
 | 
			
		||||
  constructor(private store: Store<mapReducers.State>,private componentFactoryResolver: ComponentFactoryResolver, @Inject(AbstractFeatureListComponent) public featureLists: AbstractFeatureListComponent[],private packageService:PackageService ) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Input() features: Array<Feature>
 | 
			
		||||
@@ -28,30 +28,44 @@ export class FeatureListContainerComponent {
 | 
			
		||||
  componentRef:any;
 | 
			
		||||
 | 
			
		||||
  loadComponent(queryState:IQueryState) {
 | 
			
		||||
    var componentFactory: ComponentFactory<AbstractFeatureListComponent> = this.componentFactoryResolver.resolveComponentFactory(FeatureListComponent); // default
 | 
			
		||||
    var selected = -1;
 | 
			
		||||
    for (var i = 0; i < this.featureLists.length; i++) {
 | 
			
		||||
      if (this.featureLists[i]['forItemType'] == queryState.itemType && this.featureLists[i]['forChild'] && queryState.parentCode && queryState.parentCode != "") {
 | 
			
		||||
        selected = i;
 | 
			
		||||
        break;
 | 
			
		||||
      } else if (this.featureLists[i]['forItemType'] == queryState.itemType) {
 | 
			
		||||
        selected = i;        
 | 
			
		||||
        break;
 | 
			
		||||
    let componentFactory: ComponentFactory<AbstractFeatureListComponent> = this.componentFactoryResolver.resolveComponentFactory(FeatureListComponent); // default
 | 
			
		||||
    let selected = -1;
 | 
			
		||||
    let maxMatches =0;
 | 
			
		||||
    let showItem = true;
 | 
			
		||||
    for (let i = 0; i < this.featureLists.length; i++) {
 | 
			
		||||
      let matches=0;
 | 
			
		||||
      let criteria=0;
 | 
			
		||||
      if (this.featureLists[i]['forItemType']) {
 | 
			
		||||
        criteria++;
 | 
			
		||||
        if( this.featureLists[i]['forItemType'].indexOf(queryState.itemType) >= 0) {
 | 
			
		||||
          matches++;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if(this.featureLists[i]['forChild'] ) {
 | 
			
		||||
        criteria++;
 | 
			
		||||
        if(queryState.parentCode && queryState.parentCode != "") {
 | 
			
		||||
          matches++;
 | 
			
		||||
        }
 | 
			
		||||
      }     
 | 
			
		||||
      if(criteria == matches && matches > maxMatches) {
 | 
			
		||||
        selected=i;
 | 
			
		||||
        maxMatches = matches;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (selected >= 0) {
 | 
			
		||||
      componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.featureLists[i]['constructor'] as any);
 | 
			
		||||
      componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.featureLists[selected]['constructor'] as any);
 | 
			
		||||
      if (this.featureLists[selected]['collapseSearch'] === true) {
 | 
			
		||||
        this.store.dispatch(new mapActions.CollapseSearch());
 | 
			
		||||
      }
 | 
			
		||||
      }     
 | 
			
		||||
    }
 | 
			
		||||
    const viewContainerRef = this.widgetHost.viewContainerRef;
 | 
			
		||||
    viewContainerRef.clear();
 | 
			
		||||
 | 
			
		||||
    this.componentRef = viewContainerRef.createComponent(componentFactory);
 | 
			
		||||
    (<AbstractFeatureListComponent>this.componentRef.instance).features = null;
 | 
			
		||||
    (<AbstractFeatureListComponent>this.componentRef.instance).queryState = queryState;
 | 
			
		||||
    (<AbstractFeatureListComponent>this.componentRef.instance).selectedFeature = null;
 | 
			
		||||
    if(showItem) {
 | 
			
		||||
      this.componentRef = viewContainerRef.createComponent(componentFactory);
 | 
			
		||||
      (<AbstractFeatureListComponent>this.componentRef.instance).features = null;
 | 
			
		||||
      (<AbstractFeatureListComponent>this.componentRef.instance).queryState = queryState;
 | 
			
		||||
      (<AbstractFeatureListComponent>this.componentRef.instance).selectedFeature = null;
 | 
			
		||||
    }    
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
 
 | 
			
		||||
@@ -23,11 +23,27 @@ export class FeatureListFeatureContainerComponent {
 | 
			
		||||
 | 
			
		||||
  loadComponent() {
 | 
			
		||||
    var componentFactory: ComponentFactory<AbstractFeatureListFeatureComponent> = this.componentFactoryResolver.resolveComponentFactory(FeatureListFeatureComponent); // default
 | 
			
		||||
    for (var i = 0; i < this.featureLists.length; i++) {
 | 
			
		||||
      if (this.featureLists[i]['forItemType'] == this.feature.get("itemType")) {
 | 
			
		||||
        componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.featureLists[i]['constructor'] as any);
 | 
			
		||||
   
 | 
			
		||||
    let selected = -1;
 | 
			
		||||
    let maxMatches =0;
 | 
			
		||||
    for (let i = 0; i < this.featureLists.length; i++) {
 | 
			
		||||
      let matches=0;
 | 
			
		||||
      let criteria=0;
 | 
			
		||||
      if (this.featureLists[i]['forItemType']) {
 | 
			
		||||
        criteria++;
 | 
			
		||||
        if(this.featureLists[i]['forItemType'].indexOf(this.feature.get("itemType")) >= 0) {
 | 
			
		||||
          matches++;
 | 
			
		||||
        }
 | 
			
		||||
      }         
 | 
			
		||||
      if(criteria == matches && matches > maxMatches) {
 | 
			
		||||
        selected=i;
 | 
			
		||||
        maxMatches = matches;
 | 
			
		||||
      }
 | 
			
		||||
    }    
 | 
			
		||||
    }
 | 
			
		||||
    if (selected >= 0) {
 | 
			
		||||
      componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.featureLists[selected]['constructor'] as any);        
 | 
			
		||||
    }     
 | 
			
		||||
 | 
			
		||||
    const viewContainerRef = this.widgetHost.viewContainerRef;
 | 
			
		||||
    viewContainerRef.clear();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
export function ForPackage(packageCode: string) {
 | 
			
		||||
  return function (constructor:Function) {
 | 
			
		||||
    constructor.prototype.forPackage = packageCode;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
@@ -23,15 +23,35 @@ export class ItemListItemContainerComponent {
 | 
			
		||||
 | 
			
		||||
  loadComponent() {
 | 
			
		||||
    var componentFactory: ComponentFactory<AbstractItemListItemComponent> = this.componentFactoryResolver.resolveComponentFactory(ItemListItemComponent); // default
 | 
			
		||||
    for (var i = 0; i < this.itemComponentList.length; i++) {
 | 
			
		||||
      if (this.itemComponentList[i]['forItemType'] &&
 | 
			
		||||
        this.itemComponentList[i]['forItemType'].indexOf(this.item.itemType) >= 0 &&
 | 
			
		||||
        this.itemComponentList[i]['forSourceTask'] &&
 | 
			
		||||
        this.itemComponentList[i]['forSourceTask'].indexOf(this.item.sourceTask) >= 0 )
 | 
			
		||||
      {
 | 
			
		||||
        componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.itemComponentList[i]['constructor'] as any);
 | 
			
		||||
 | 
			
		||||
    let selected = -1;
 | 
			
		||||
    let maxMatches =0;
 | 
			
		||||
    let showItem = true;
 | 
			
		||||
    for (let i = 0; i < this.itemComponentList.length; i++) {
 | 
			
		||||
      let matches=0;
 | 
			
		||||
      let criteria=0;
 | 
			
		||||
      if (this.itemComponentList[i]['forItemType']) {
 | 
			
		||||
        criteria++;
 | 
			
		||||
        if(this.itemComponentList[i]['forItemType'].indexOf(this.item.itemType) >= 0) {
 | 
			
		||||
          matches++;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }    
 | 
			
		||||
      if (this.itemComponentList[i]['forSourceTask']) {
 | 
			
		||||
        criteria++;
 | 
			
		||||
        if(this.itemComponentList[i]['forSourceTask'].indexOf(this.item.sourceTask) >= 0) {
 | 
			
		||||
          matches++;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
        
 | 
			
		||||
      if(criteria == matches && matches > maxMatches) {
 | 
			
		||||
        selected=i;
 | 
			
		||||
        maxMatches = matches;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (selected >= 0) {
 | 
			
		||||
      componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.itemComponentList[selected]['constructor'] as any);         
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    const viewContainerRef = this.widgetHost.viewContainerRef;
 | 
			
		||||
    viewContainerRef.clear();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -36,10 +36,12 @@ export class LayerSwitcher  implements OnInit,OnChanges{
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  handleClick(event:Event) {
 | 
			
		||||
    event.stopPropagation();
 | 
			
		||||
    this.store.dispatch(new mapActions.ShowLayerSwitcher(true));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  close(event:Event) {
 | 
			
		||||
    event.stopPropagation();
 | 
			
		||||
    this.store.dispatch(new mapActions.ShowLayerSwitcher(false));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<table class="container" *ngIf="showLegend()">
 | 
			
		||||
  <tr>
 | 
			
		||||
    <td colspan="2">
 | 
			
		||||
    <td colspan="3">
 | 
			
		||||
      <div class="title">
 | 
			
		||||
        <h4 *ngIf="showTitle">{{layer.name}}</h4>
 | 
			
		||||
        <b *ngIf="layer.unit">({{layer.unit}})</b>
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@
 | 
			
		||||
  panelCollapsed:panelCollapsed$|async,
 | 
			
		||||
  searchMinified:searchMinified$|async,
 | 
			
		||||
  selectedItem:selectedItem$|async,
 | 
			
		||||
  parentItem:parentItem$|async,
 | 
			
		||||
  queryState:queryState$|async,
 | 
			
		||||
  searchCollapsed:searchCollapsed$|async,
 | 
			
		||||
  clearEnabled:clearEnabled$|async,
 | 
			
		||||
@@ -20,9 +21,9 @@
 | 
			
		||||
  styles:styles$|async,
 | 
			
		||||
  selectedFeature:selectedFeature$|async
 | 
			
		||||
} 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}" class="map">
 | 
			
		||||
    <div>
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
    <aol-view [zoom]="state.mapState.zoom" [rotation]="state.mapState.rotation">
 | 
			
		||||
       <aol-coordinate [x]="state.mapState.xCenter" [y]="state.mapState.yCenter" [srid]="'EPSG:4326'"></aol-coordinate>
 | 
			
		||||
@@ -51,13 +52,13 @@
 | 
			
		||||
      <div class="panel-top bg-secondary" *ngIf="!(state.searchMinified)">
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="panel-bottom">
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
        <div *ngIf="!(state.selectedItem)">
 | 
			
		||||
          <fm-map-feature-list-container [features]="state.features" [selectedFeature]="state.selectedFeature" [queryState]="state.queryState" [clickedFeature]="clickedFeature"></fm-map-feature-list-container>
 | 
			
		||||
        </div>
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
        <div *ngIf="state.selectedItem;let item">
 | 
			
		||||
          <fm-map-selected-item-container [item]="item" [itemLayer]="state.selectedItemLayer" [overlayLayers]="state.overlayLayers"></fm-map-selected-item-container>
 | 
			
		||||
          <fm-map-selected-item-container [item]="item" [parentItem]="state.parentItem" [itemLayer]="state.selectedItemLayer" [overlayLayers]="state.overlayLayers"></fm-map-selected-item-container>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div *ngIf="state.features.length == 0" class="no-results  m-2">
 | 
			
		||||
          <div *ngIf="state.queryState.query">Cannot find <span>{{state.queryState?.query}}</span></div>
 | 
			
		||||
 
 | 
			
		||||
@@ -64,6 +64,7 @@ export class MapComponent implements OnInit, OnDestroy,AfterViewInit {
 | 
			
		||||
  public selectedFeature$: Observable<Feature> = this.store.select(mapReducers.selectGetSelectedFeature);
 | 
			
		||||
  public clickedFeature: Subject<Feature> = new Subject<Feature>();
 | 
			
		||||
  public selectedItem$: Observable<IItem> = this.store.select(mapReducers.selectGetSelectedItem);
 | 
			
		||||
  public parentItem$: Observable<IItem> =this.store.select(mapReducers.selectGetParentItem);
 | 
			
		||||
  public queryState$: Observable<IQueryState> = this.store.select(mapReducers.selectGetQueryState);
 | 
			
		||||
  public state$:Observable<{mapState:IMapState,queryState:IQueryState,setStateCount:number}> = this.store.select(mapReducers.selectGetState);
 | 
			
		||||
  public period$: Observable<IPeriodState> = this.store.select(mapReducers.selectGetPeriod);
 | 
			
		||||
@@ -82,14 +83,14 @@ export class MapComponent implements OnInit, OnDestroy,AfterViewInit {
 | 
			
		||||
  private lastUrl = "";
 | 
			
		||||
 | 
			
		||||
  @ViewChild('map') map;
 | 
			
		||||
  
 | 
			
		||||
  constructor(private store: Store<mapReducers.State | commonReducers.State>, 
 | 
			
		||||
    private route: ActivatedRoute, 
 | 
			
		||||
 | 
			
		||||
  constructor(private store: Store<mapReducers.State | commonReducers.State>,
 | 
			
		||||
    private route: ActivatedRoute,
 | 
			
		||||
    private router: Router,
 | 
			
		||||
    private uploadService: ResumableFileUploadService, 
 | 
			
		||||
    private serializeService: StateSerializerService, 
 | 
			
		||||
    public itemTypeService: ItemTypeService, 
 | 
			
		||||
    private location: Location, 
 | 
			
		||||
    private uploadService: ResumableFileUploadService,
 | 
			
		||||
    private serializeService: StateSerializerService,
 | 
			
		||||
    public itemTypeService: ItemTypeService,
 | 
			
		||||
    private location: Location,
 | 
			
		||||
    private geolocationService: GeolocationService,
 | 
			
		||||
    private zone: NgZone,
 | 
			
		||||
    private deviceorientationService:DeviceOrientationService) {
 | 
			
		||||
@@ -98,29 +99,29 @@ export class MapComponent implements OnInit, OnDestroy,AfterViewInit {
 | 
			
		||||
          let newQueryState = tassign(mapReducers.initialQueryState);
 | 
			
		||||
          console.debug(`Do Query ${setStateCount}`);
 | 
			
		||||
          let urlparts=[];
 | 
			
		||||
          if (queryState.itemCode && queryState.itemCode != "") {         
 | 
			
		||||
          if (queryState.itemCode && queryState.itemCode != "") {
 | 
			
		||||
            if(queryState.itemType && queryState.itemType!= "") {
 | 
			
		||||
              let itemType = this.itemTypeService.itemTypes[queryState.itemType];
 | 
			
		||||
              if (itemType && itemType.viewer && itemType.viewer == "edit_in_editor" && itemType.editor) {
 | 
			
		||||
                urlparts.push('/editor');
 | 
			
		||||
                urlparts.push(itemType.editor);
 | 
			
		||||
                urlparts.push('item');
 | 
			
		||||
                urlparts.push(queryState.itemCode);               
 | 
			
		||||
              }    
 | 
			
		||||
            }         
 | 
			
		||||
                urlparts.push(queryState.itemCode);
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          } else {
 | 
			
		||||
            newQueryState= queryState;
 | 
			
		||||
          }
 | 
			
		||||
          if(urlparts.length==0 ) {              
 | 
			
		||||
          if(urlparts.length==0 ) {
 | 
			
		||||
            newQueryState.itemCode = queryState.itemCode;
 | 
			
		||||
            this.zone.run(() => {
 | 
			
		||||
              this.store.dispatch(new mapActions.SetQueryState(newQueryState,false));
 | 
			
		||||
            })
 | 
			
		||||
          } else {
 | 
			
		||||
            this.router.navigate(urlparts);
 | 
			
		||||
          }          
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      });        
 | 
			
		||||
      });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @HostListener('document:keyup', ['$event'])
 | 
			
		||||
@@ -159,7 +160,7 @@ export class MapComponent implements OnInit, OnDestroy,AfterViewInit {
 | 
			
		||||
    console.debug("Init");
 | 
			
		||||
    this.store.dispatch(new mapActions.Clear());
 | 
			
		||||
    this.selectedFeatures$.next({x:0,y:0,features:[]});
 | 
			
		||||
    this.selectedFeatures$.next(null);      
 | 
			
		||||
    this.selectedFeatures$.next(null);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  initCustomStyles() {
 | 
			
		||||
@@ -203,8 +204,8 @@ export class MapComponent implements OnInit, OnDestroy,AfterViewInit {
 | 
			
		||||
 | 
			
		||||
  normalizeMapState(mapState:IMapState):IMapState {
 | 
			
		||||
    if(!mapState) return null;
 | 
			
		||||
    return {zoom: this.round(mapState.zoom,0), 
 | 
			
		||||
      rotation: this.round(mapState.rotation,2), 
 | 
			
		||||
    return {zoom: this.round(mapState.zoom,0),
 | 
			
		||||
      rotation: this.round(mapState.rotation,2),
 | 
			
		||||
      xCenter: this.round(mapState.xCenter,5),
 | 
			
		||||
      yCenter: this.round(mapState.yCenter,5),
 | 
			
		||||
      baseLayerCode: mapState.baseLayerCode };
 | 
			
		||||
@@ -219,7 +220,7 @@ export class MapComponent implements OnInit, OnDestroy,AfterViewInit {
 | 
			
		||||
      let queryState = params.get("queryState");
 | 
			
		||||
      var newQueryState = tassign(mapReducers.initialQueryState);
 | 
			
		||||
      if (queryState != "") {
 | 
			
		||||
        newQueryState = this.serializeService.deserialize(queryState);          
 | 
			
		||||
        newQueryState = this.serializeService.deserialize(queryState);
 | 
			
		||||
      }
 | 
			
		||||
      return newQueryState;
 | 
			
		||||
    } else {
 | 
			
		||||
@@ -227,16 +228,16 @@ export class MapComponent implements OnInit, OnDestroy,AfterViewInit {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngAfterViewInit() {    
 | 
			
		||||
  ngAfterViewInit() {
 | 
			
		||||
    console.debug("View init");
 | 
			
		||||
    this.initCustomStyles();   
 | 
			
		||||
    this.initCustomStyles();
 | 
			
		||||
 | 
			
		||||
    // url to state
 | 
			
		||||
 | 
			
		||||
    this.paramSub = this.route.paramMap.pipe(withLatestFrom(this.state$),switchMap(([params,state]) => {
 | 
			
		||||
      var newMapState: IMapState = state.mapState;
 | 
			
		||||
      var newQueryState: IQueryState = state.queryState;
 | 
			
		||||
     
 | 
			
		||||
 | 
			
		||||
      var queryStateChanged = false;
 | 
			
		||||
      var mapStateChanged = false;
 | 
			
		||||
      let urlMapState = this.getMapStateFromUrl(params);
 | 
			
		||||
@@ -250,7 +251,7 @@ export class MapComponent implements OnInit, OnDestroy,AfterViewInit {
 | 
			
		||||
        newQueryState = urlQueryState;
 | 
			
		||||
        queryStateChanged = this.serializeService.serialize(state.queryState) !=  this.serializeService.serialize(urlQueryState);
 | 
			
		||||
      }
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
      if(queryStateChanged && mapStateChanged && state.setStateCount ==0) {
 | 
			
		||||
        return of(new mapActions.SetState(newMapState,newQueryState));
 | 
			
		||||
        window.localStorage.setItem("FarmMapsCommonMap_mapState",this.serializeMapState(newMapState));
 | 
			
		||||
@@ -258,16 +259,16 @@ export class MapComponent implements OnInit, OnDestroy,AfterViewInit {
 | 
			
		||||
        return of(new mapActions.SetQueryState(newQueryState));
 | 
			
		||||
      } return of(new mapActions.SetReplaceUrl(true));
 | 
			
		||||
    })).subscribe((action) => {
 | 
			
		||||
      if(action) {    
 | 
			
		||||
      if(action) {
 | 
			
		||||
        this.zone.run(() => {
 | 
			
		||||
          console.debug("Url to state");
 | 
			
		||||
          this.store.dispatch(action);  
 | 
			
		||||
        });       
 | 
			
		||||
          this.store.dispatch(action);
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // state to url
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    this.stateSub = this.state$.pipe(switchMap((state) => {
 | 
			
		||||
      let newUrl = this.serializeMapState(state.mapState) + "_" + this.serializeService.serialize(state.queryState);
 | 
			
		||||
      if(this.lastUrl!=newUrl && state.setStateCount>0) {
 | 
			
		||||
@@ -276,13 +277,13 @@ export class MapComponent implements OnInit, OnDestroy,AfterViewInit {
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        return of(null);
 | 
			
		||||
      }          
 | 
			
		||||
      }
 | 
			
		||||
      })).subscribe((newUrlState) =>{
 | 
			
		||||
        if(newUrlState) {
 | 
			
		||||
          console.debug(`State to url ${newUrlState.setStateCount}`);
 | 
			
		||||
          this.replaceUrl(newUrlState.mapState,newUrlState.queryState,newUrlState.replaceUrl);
 | 
			
		||||
        }
 | 
			
		||||
      });  
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      this.map.instance.updateSize();
 | 
			
		||||
@@ -315,7 +316,7 @@ export class MapComponent implements OnInit, OnDestroy,AfterViewInit {
 | 
			
		||||
    event.preventDefault();
 | 
			
		||||
    var queryState = tassign(mapReducers.initialQueryState, query);
 | 
			
		||||
    this.store.dispatch(new mapActions.DoQuery(queryState));
 | 
			
		||||
  } 
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  replaceUrl(mapState: IMapState, queryState: IQueryState, replace: boolean = true) {
 | 
			
		||||
      let parts =["."];
 | 
			
		||||
@@ -328,7 +329,7 @@ export class MapComponent implements OnInit, OnDestroy,AfterViewInit {
 | 
			
		||||
        parts.push( this.serializeService.serialize(queryState));
 | 
			
		||||
        console.debug("Replace url",parts);
 | 
			
		||||
        this.router.navigate(parts, { replaceUrl: replace,relativeTo:this.route.parent });
 | 
			
		||||
      }      
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleOnMoveEnd(event) {
 | 
			
		||||
@@ -350,13 +351,13 @@ export class MapComponent implements OnInit, OnDestroy,AfterViewInit {
 | 
			
		||||
          this.store.dispatch(new mapActions.SetViewExtent(state.extent));
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    });   
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleOnMouseDown(event: MouseEvent) {
 | 
			
		||||
    event.stopPropagation();
 | 
			
		||||
    this.zone.run(() =>{
 | 
			
		||||
      this.store.dispatch(new mapActions.CollapseSearch());
 | 
			
		||||
      this.store.dispatch(new commonActions.CloseAll());
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -366,7 +367,7 @@ export class MapComponent implements OnInit, OnDestroy,AfterViewInit {
 | 
			
		||||
 | 
			
		||||
  handleClearSearch(event) {
 | 
			
		||||
    this.store.dispatch(new commonActions.Escape(true, false));
 | 
			
		||||
  }  
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleOnDelete(itemLayer: IItemLayer) {
 | 
			
		||||
    this.store.dispatch(new mapActions.RemoveLayer(itemLayer));
 | 
			
		||||
@@ -385,7 +386,7 @@ export class MapComponent implements OnInit, OnDestroy,AfterViewInit {
 | 
			
		||||
    extend(extent, itemLayer.layer.getExtent());
 | 
			
		||||
    if (extent) {
 | 
			
		||||
      this.store.dispatch(new mapActions.SetExtent(extent));
 | 
			
		||||
    }    
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleSelectBaseLayer(itemLayer: IItemLayer) {
 | 
			
		||||
@@ -400,7 +401,7 @@ export class MapComponent implements OnInit, OnDestroy,AfterViewInit {
 | 
			
		||||
    if (this.paramSub) this.paramSub.unsubscribe();
 | 
			
		||||
    if (this.itemTypeSub) this.itemTypeSub.unsubscribe();
 | 
			
		||||
    if (this.stateSub) this.stateSub.unsubscribe();
 | 
			
		||||
    if (this.queryStateSub) this.queryStateSub.unsubscribe();  
 | 
			
		||||
    if (this.queryStateSub) this.queryStateSub.unsubscribe();
 | 
			
		||||
    if (this.querySub) this.querySub.unsubscribe();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,25 @@
 | 
			
		||||
.custom-day {
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  padding: 0.185rem 0.25rem;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  height: 2rem;
 | 
			
		||||
  width: 2rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.custom-day.focused {
 | 
			
		||||
  background-color: #e6e6e6;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.custom-day.range, .custom-day:hover {
 | 
			
		||||
  background-color: rgb(2, 117, 216);
 | 
			
		||||
  color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.custom-day.faded {
 | 
			
		||||
  background-color: rgba(2, 117, 216, 0.5);
 | 
			
		||||
}
 | 
			
		||||
.custom-day {
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  padding: 0.185rem 0.25rem;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  height: 2rem;
 | 
			
		||||
  width: 2rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.custom-day.focused {
 | 
			
		||||
  background-color: #e6e6e6;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.custom-day.range, .custom-day:hover {
 | 
			
		||||
  background-color: rgb(2, 117, 216);
 | 
			
		||||
  color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.custom-day.faded {
 | 
			
		||||
  background-color: rgba(2, 117, 216, 0.5);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ngb-datepicker {
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  max-width: calc(100vw - 3em);
 | 
			
		||||
}
 | 
			
		||||
@@ -8,7 +8,7 @@ import { IItemLayer } from '../../models/item.layer';
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'fm-map-selected-item-container',
 | 
			
		||||
  templateUrl: './selected-item-container.component.html',
 | 
			
		||||
  styleUrls: ['./selected-item-container.component.scss'] 
 | 
			
		||||
  styleUrls: ['./selected-item-container.component.scss']
 | 
			
		||||
})
 | 
			
		||||
export class SelectedItemContainerComponent {
 | 
			
		||||
 | 
			
		||||
@@ -16,34 +16,49 @@ export class SelectedItemContainerComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Input() item: IItem;
 | 
			
		||||
  @Input() parentItem: IItem;
 | 
			
		||||
  @Input() itemLayer:IItemLayer;
 | 
			
		||||
  @Input() overlayLayers:Array<IItemLayer>;
 | 
			
		||||
 | 
			
		||||
  @ViewChild(WidgetHostDirective, { static: true }) widgetHost: WidgetHostDirective; 
 | 
			
		||||
  @ViewChild(WidgetHostDirective, { static: true }) widgetHost: WidgetHostDirective;
 | 
			
		||||
 | 
			
		||||
  loadComponent() {
 | 
			
		||||
    let componentFactory: ComponentFactory<AbstractSelectedItemComponent> = this.componentFactoryResolver.resolveComponentFactory(SelectedItemComponent); // default
 | 
			
		||||
    let firstComponentWithTypeAndTask = this.selectedItemComponents
 | 
			
		||||
      .find(value => value['forSourceTask'] == this.item.sourceTask &&
 | 
			
		||||
        value['forItemType'] == this.item.itemType
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
    if (firstComponentWithTypeAndTask) {
 | 
			
		||||
      componentFactory = this.componentFactoryResolver.resolveComponentFactory(firstComponentWithTypeAndTask['constructor'] as any);
 | 
			
		||||
    } else {
 | 
			
		||||
      let firstComponentWithType = this.selectedItemComponents
 | 
			
		||||
        .find(value => value['forSourceTask'] == null &&
 | 
			
		||||
          value['forItemType'] == this.item.itemType);
 | 
			
		||||
    let selected = -1;
 | 
			
		||||
    let maxMatches =0;
 | 
			
		||||
    let showItem = true;
 | 
			
		||||
    for (let i = 0; i < this.selectedItemComponents.length; i++) {
 | 
			
		||||
      let matches=0;
 | 
			
		||||
      let criteria=0;
 | 
			
		||||
      if (this.selectedItemComponents[i]['forItemType'] ) {
 | 
			
		||||
        criteria++;
 | 
			
		||||
        if(this.selectedItemComponents[i]['forItemType'].indexOf(this.item.itemType) >= 0) {
 | 
			
		||||
          matches++;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (this.selectedItemComponents[i]['forSourceTask']) {
 | 
			
		||||
        criteria++;
 | 
			
		||||
        if( this.selectedItemComponents[i]['forSourceTask'].indexOf(this.item.sourceTask) >= 0) {
 | 
			
		||||
          matches++;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (firstComponentWithType) {
 | 
			
		||||
        componentFactory = this.componentFactoryResolver.resolveComponentFactory(firstComponentWithType['constructor'] as any);
 | 
			
		||||
      if(criteria==matches && matches > maxMatches) {
 | 
			
		||||
        selected=i;
 | 
			
		||||
        maxMatches = matches;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (selected >= 0) {
 | 
			
		||||
      componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.selectedItemComponents[selected]['constructor'] as any);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const viewContainerRef = this.widgetHost.viewContainerRef;
 | 
			
		||||
    viewContainerRef.clear();
 | 
			
		||||
 | 
			
		||||
    const componentRef = viewContainerRef.createComponent(componentFactory);
 | 
			
		||||
    (<AbstractSelectedItemComponent>componentRef.instance).item = this.item;
 | 
			
		||||
    (<AbstractSelectedItemComponent>componentRef.instance).parentItem = this.parentItem;
 | 
			
		||||
    (<AbstractSelectedItemComponent>componentRef.instance).itemLayer = this.itemLayer;
 | 
			
		||||
    (<AbstractSelectedItemComponent>componentRef.instance).overlayLayers = this.overlayLayers;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -7,9 +7,10 @@ import { commonReducers, ItemTypeService, IItem, Item, ItemService, FolderServic
 | 
			
		||||
import { Router, ActivatedRoute, ParamMap, Event } from '@angular/router';
 | 
			
		||||
import { ForItemType } from '../for-item/for-itemtype.decorator';
 | 
			
		||||
import { AbstractSelectedItemComponent } from '../selected-item/selected-item.component';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { Observable,of } from 'rxjs';
 | 
			
		||||
import {GeoJSON} from 'ol/format';
 | 
			
		||||
import {getArea} from 'ol/sphere';
 | 
			
		||||
import { withLatestFrom,switchMap,combineLatest } from 'rxjs/operators';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ForItemType("vnd.farmmaps.itemtype.cropfield")
 | 
			
		||||
@@ -38,6 +39,25 @@ export class SelectedItemCropfieldComponent extends AbstractSelectedItemComponen
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
    this. items = this.folderService$.getItems(this.item.code, 0, 1000);
 | 
			
		||||
    var childItems = this.folderService$.getItems(this.item.code, 0, 1000);
 | 
			
		||||
    var atLocationItems = this.itemService$.getItemList(null,null,null,this.item.code,true);
 | 
			
		||||
    this.items =  childItems.pipe(
 | 
			
		||||
      combineLatest(atLocationItems),
 | 
			
		||||
      switchMap(([ci,ali]) => {
 | 
			
		||||
          let retVal:IListItem[] = [];
 | 
			
		||||
          let codes = {};
 | 
			
		||||
          ci.forEach((listItem) => {
 | 
			
		||||
             retVal.push(listItem);
 | 
			
		||||
             codes[listItem.code]=listItem;
 | 
			
		||||
          });
 | 
			
		||||
          ali.forEach((atlocationitem) => {
 | 
			
		||||
             let listItem = atlocationitem as IListItem;
 | 
			
		||||
             let allowedItemTypes = "vnd.farmmaps.itemtype.device.senml"; // this is a hack
 | 
			
		||||
             if(!codes[listItem.code] && allowedItemTypes.indexOf(listItem.itemType) >= 0) {
 | 
			
		||||
               retVal.push(listItem);
 | 
			
		||||
             }
 | 
			
		||||
          });
 | 
			
		||||
          return of(retVal);
 | 
			
		||||
      }));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
    <div class="card-body">
 | 
			
		||||
      <div class="mb-2"><a href="#" (click)="handleBackToList($event)" i18n>Back</a></div>
 | 
			
		||||
      <div class="card menu-card">
 | 
			
		||||
        <h2 *ngIf="parentItem">{{parentItem.name}}</h2>
 | 
			
		||||
        <h1>{{item.name}}</h1>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="legend-container" *ngIf="item?.data.layers;let layers">
 | 
			
		||||
@@ -13,14 +14,15 @@
 | 
			
		||||
              <option *ngFor="let l of layers;" [value]="l.index"  [selected]="itemLayer.layerIndex == l.index">{{l.name}}</option>
 | 
			
		||||
            </select>
 | 
			
		||||
          </div>
 | 
			
		||||
          <fm-map-layer-legend [layer]="layer(layers,itemLayer.layerIndex)" [histogramenabled]="true"></fm-map-layer-legend>
 | 
			
		||||
          <fm-map-layer-legend [showTitle]="layers.length == 1"
 | 
			
		||||
            [layer]="layer(layers,itemLayer.layerIndex)" [histogramenabled]="true"></fm-map-layer-legend>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="card menu-card">
 | 
			
		||||
        <ul class="p-0 mt-2">
 | 
			
		||||
          <li *ngIf="item.isEditable"><a href="#" class="mt-1 mr-1" (click)="handleOnEdit(item)"><i class="fa fa-pencil" aria-hidden="true" i18n-title title="Edit"></i> <span i18n>Edit</span></a></li>
 | 
			
		||||
          <ng-container *ngIf="itemTypeService.isLayer(item)">
 | 
			
		||||
            <li *ngIf="!getItemLayer(item,itemLayer.layerIndex)"><a   href="#" (click)="handleAddAsLayer(item,itemLayer.layerIndex)"><i class="fa fa-eye" aria-hidden="true" i18n-title title="Add as layer"></i> <span i18n>Add as overlay</span></a></li> 
 | 
			
		||||
            <li *ngIf="!getItemLayer(item,itemLayer.layerIndex)"><a   href="#" (click)="handleAddAsLayer(item,itemLayer.layerIndex)"><i class="fa fa-eye" aria-hidden="true" i18n-title title="Add as layer"></i> <span i18n>Add as overlay</span></a></li>
 | 
			
		||||
            <li *ngIf="getItemLayer(item,itemLayer.layerIndex)"><a  href="#" (click)="handleRemoveLayer(item,itemLayer.layerIndex)"><i class="fa fa-eye" aria-hidden="true" i18n-title title="Remove overlay"></i> <span i18n>Remove overlay</span></a></li>
 | 
			
		||||
          </ng-container>
 | 
			
		||||
        </ul>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
    <div class="card-body">
 | 
			
		||||
      <div class="mb-2"><a href="#" (click)="handleBackToList($event)" i18n>Back</a></div>
 | 
			
		||||
      <div class="card menu-card">
 | 
			
		||||
        <h2 *ngIf="parentItem">{{parentItem.name}}</h2>
 | 
			
		||||
        <h1>{{item.name}}</h1>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="legend-container" *ngIf="item?.data.layers;let layers">
 | 
			
		||||
@@ -20,7 +21,7 @@
 | 
			
		||||
        <ul class="p-0 mt-2">
 | 
			
		||||
          <li *ngIf="item.isEditable"><a href="#" class="mt-1 mr-1" (click)="handleOnEdit(item)" ><i class="fa fa-pencil" aria-hidden="true" i18n-title title="Edit"></i> <span i18n>Edit</span></a></li>
 | 
			
		||||
          <ng-container *ngIf="itemTypeService.isLayer(item)">
 | 
			
		||||
            <li *ngIf="!getItemLayer(item,itemLayer.layerIndex)"><a   href="#" (click)="handleAddAsLayer(item,itemLayer.layerIndex)" ><i class="fa fa-eye" aria-hidden="true" i18n-title title="Add as layer"></i> <span i18n>Add as overlay</span></a></li> 
 | 
			
		||||
            <li *ngIf="!getItemLayer(item,itemLayer.layerIndex)"><a   href="#" (click)="handleAddAsLayer(item,itemLayer.layerIndex)" ><i class="fa fa-eye" aria-hidden="true" i18n-title title="Add as layer"></i> <span i18n>Add as overlay</span></a></li>
 | 
			
		||||
            <li *ngIf="getItemLayer(item,itemLayer.layerIndex)"><a  href="#" (click)="handleRemoveLayer(item,itemLayer.layerIndex)" ><i class="fa fa-eye" aria-hidden="true" i18n-title title="Remove overlay"></i> <span i18n>Remove overlay</span></a></li>
 | 
			
		||||
          </ng-container>
 | 
			
		||||
        </ul>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
    <div class="card-body">
 | 
			
		||||
      <div class="mb-2"><a href="#" (click)="handleBackToList($event)" i18n>Back</a></div>
 | 
			
		||||
      <div class="card menu-card">
 | 
			
		||||
        <h2 *ngIf="parentItem">{{parentItem.name}}</h2>
 | 
			
		||||
        <h1>{{item.name}}</h1>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="legend-container" *ngIf="item?.data.layers;let layers">
 | 
			
		||||
@@ -28,7 +29,8 @@
 | 
			
		||||
              <option *ngFor="let l of layers;" [value]="l.index"  [selected]="itemLayer.layerIndex == l.index">{{l.name}}</option>
 | 
			
		||||
            </select>
 | 
			
		||||
          </div>
 | 
			
		||||
          <fm-map-layer-legend [layer]="layer(layers,itemLayer.layerIndex)" [histogramenabled]="true"></fm-map-layer-legend>
 | 
			
		||||
          <fm-map-layer-legend [showTitle]="layers.length == 1"
 | 
			
		||||
            [layer]="layer(layers,itemLayer.layerIndex)" [histogramenabled]="true"></fm-map-layer-legend>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="card menu-card">
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@ export class SelectedItemTemporalComponent extends AbstractSelectedItemComponent
 | 
			
		||||
  nextDate():Date {
 | 
			
		||||
    let temporalItemLayer = this.itemLayer as  ITemporalItemLayer;
 | 
			
		||||
    if(temporalItemLayer.nextItemLayer.item) 
 | 
			
		||||
      return temporalItemLayer.nextItemLayer.item.dataDate;
 | 
			
		||||
      return new Date(Date.parse(temporalItemLayer.nextItemLayer.item.dataDate)) ;
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -47,7 +47,7 @@ export class SelectedItemTemporalComponent extends AbstractSelectedItemComponent
 | 
			
		||||
  previousDate():Date {
 | 
			
		||||
    let temporalItemLayer = this.itemLayer as  ITemporalItemLayer;
 | 
			
		||||
    if(temporalItemLayer.previousItemLayer.item) 
 | 
			
		||||
      return temporalItemLayer.previousItemLayer.item.dataDate;
 | 
			
		||||
      return new Date(Date.parse(temporalItemLayer.previousItemLayer.item.dataDate));
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -74,7 +74,7 @@ export class SelectedItemTemporalComponent extends AbstractSelectedItemComponent
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleGoToChart(item: IItem) {  
 | 
			
		||||
    this.router.navigate(['/viewer', 'temporal', 'item', item.parentCode, item.dataDate.getUTCFullYear()]);
 | 
			
		||||
    this.router.navigate(['/viewer', 'temporal', 'item', item.parentCode, new Date(Date.parse(item.dataDate)).getUTCFullYear()]);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<div *ngIf="item;let item">
 | 
			
		||||
  <div class="card border-0">
 | 
			
		||||
    <img *ngIf="item.thumbnail" class="card-img-top" [src]="config.getConfig('apiEndPoint') +'/api/v1/items/'+item.code+'/thumbnail?v='+item.updated.getTime()" />
 | 
			
		||||
    <img *ngIf="item.thumbnail" class="card-img-top" [src]="getThumbnailUrl(item)" />
 | 
			
		||||
    <div *ngIf="!item.thumbnail" class="big-icon" [style.background-color]="itemTypeService.getColor(item.itemType)">
 | 
			
		||||
      <i [ngClass]="itemTypeService.getIcon(item.itemType)"></i>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,8 @@ import { IItemLayer } from '../../models/item.layer';
 | 
			
		||||
@Injectable()
 | 
			
		||||
@Directive()
 | 
			
		||||
export abstract class AbstractSelectedItemComponent {
 | 
			
		||||
  @Input() item: IItem
 | 
			
		||||
  @Input() item: IItem;
 | 
			
		||||
  @Input() parentItem: IItem;
 | 
			
		||||
  @Input() itemLayer: IItemLayer;
 | 
			
		||||
  @Input() overlayLayers: Array<IItemLayer>;
 | 
			
		||||
  constructor(public store: Store<mapReducers.State | commonReducers.State>, public itemTypeService: ItemTypeService, private location: Location, public router: Router) {
 | 
			
		||||
@@ -60,6 +61,7 @@ export abstract class AbstractSelectedItemComponent {
 | 
			
		||||
    event.preventDefault();
 | 
			
		||||
    this.location.back();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
@@ -73,4 +75,8 @@ export class SelectedItemComponent extends AbstractSelectedItemComponent {
 | 
			
		||||
  constructor(store: Store<mapReducers.State | commonReducers.State>, itemTypeService: ItemTypeService, location: Location, router: Router, public config:AppConfig) {
 | 
			
		||||
    super(store, itemTypeService,location,router);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getThumbnailUrl(item:IItem):string {
 | 
			
		||||
    return this.config.getConfig('apiEndPoint') +'/api/v1/items/'+item.code+'/thumbnail?v=' + Date.parse(item.updated);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -60,7 +60,7 @@ export class MapEffects {
 | 
			
		||||
            anchor: [0.5, 1],
 | 
			
		||||
            scale: 0.05,
 | 
			
		||||
          src: this.featureIconService$.getIconImageDataUrl("fa fa-file-o")
 | 
			
		||||
        }),         
 | 
			
		||||
        }),
 | 
			
		||||
        stroke: new style.Stroke({
 | 
			
		||||
          color: 'red',
 | 
			
		||||
          width: 1
 | 
			
		||||
@@ -82,7 +82,7 @@ export class MapEffects {
 | 
			
		||||
        fill: new style.Fill({
 | 
			
		||||
          color: 'rgba(0, 0, 255, 0.1)'
 | 
			
		||||
        })
 | 
			
		||||
      })));      
 | 
			
		||||
      })));
 | 
			
		||||
 | 
			
		||||
      return actions;
 | 
			
		||||
    }
 | 
			
		||||
@@ -116,7 +116,7 @@ export class MapEffects {
 | 
			
		||||
    switchMap(([action,setStateCount]) => {
 | 
			
		||||
      let a = action as mapActions.StartSearch;
 | 
			
		||||
      var startDate = a.queryState.startDate;
 | 
			
		||||
      var endDate = a.queryState.endDate;    
 | 
			
		||||
      var endDate = a.queryState.endDate;
 | 
			
		||||
      var newAction:Observable<Action>;
 | 
			
		||||
      if (a.queryState.itemCode || a.queryState.parentCode || a.queryState.itemType || a.queryState.query || a.queryState.tags) {
 | 
			
		||||
        newAction= this.itemService$.getFeatures(a.queryState.bbox, "EPSG:3857", a.queryState.query, a.queryState.tags, startDate, endDate, a.queryState.itemType, a.queryState.parentCode).pipe(
 | 
			
		||||
@@ -132,7 +132,7 @@ export class MapEffects {
 | 
			
		||||
          catchError(error => of(new commonActions.Fail(error))));
 | 
			
		||||
      } else {
 | 
			
		||||
        return [];
 | 
			
		||||
      }     
 | 
			
		||||
      }
 | 
			
		||||
      return newAction;
 | 
			
		||||
    }));
 | 
			
		||||
 | 
			
		||||
@@ -145,12 +145,12 @@ export class MapEffects {
 | 
			
		||||
      let actions =[];
 | 
			
		||||
      actions.push(new commonActions.SetMenuVisible(false));
 | 
			
		||||
      let extent = createEmpty();
 | 
			
		||||
      if (!action.query.bboxFilter) {        
 | 
			
		||||
      if (!action.query.bboxFilter) {
 | 
			
		||||
         if (extent) {
 | 
			
		||||
          for (let f of action.features) {
 | 
			
		||||
            extend(extent, (f as Feature).getGeometry().getExtent());
 | 
			
		||||
          }
 | 
			
		||||
        }  
 | 
			
		||||
        }
 | 
			
		||||
        actions.push(new mapActions.SetExtent(extent));
 | 
			
		||||
      }
 | 
			
		||||
      return actions;
 | 
			
		||||
@@ -173,7 +173,14 @@ export class MapEffects {
 | 
			
		||||
        let itemCode = selectedItem ? selectedItem.code : "";
 | 
			
		||||
        if (a.itemCode != itemCode || setStateCount == 1) {
 | 
			
		||||
          return this.itemService$.getItem(a.itemCode).pipe(
 | 
			
		||||
            map((item: IItem) => new mapActions.SelectItemSuccess(item)),
 | 
			
		||||
            switchMap(child => {
 | 
			
		||||
              return this.itemService$.getItem(child.parentCode)
 | 
			
		||||
                .pipe(map(parent => {
 | 
			
		||||
                    return {child, parent};
 | 
			
		||||
                  })
 | 
			
		||||
                );
 | 
			
		||||
            }),
 | 
			
		||||
            map(data => new mapActions.SelectItemSuccess(data.child, data.parent)),
 | 
			
		||||
            catchError(error => of(new commonActions.Fail(error))))
 | 
			
		||||
        } else {
 | 
			
		||||
          return [];
 | 
			
		||||
@@ -202,14 +209,14 @@ export class MapEffects {
 | 
			
		||||
        if(action.item.itemType == "vnd.farmmaps.itemtype.temporal") {
 | 
			
		||||
          return this.itemService$.getChildItemList(action.item.code,null).pipe(
 | 
			
		||||
            map(items => new mapActions.SelectTemporalItemsSuccess(
 | 
			
		||||
              items.sort((a, b) => 
 | 
			
		||||
                 -(b.dataDate.getTime() - a.dataDate.getTime())
 | 
			
		||||
              items.sort((a, b) =>
 | 
			
		||||
                 -(Date.parse(b.dataDate) - Date.parse(a.dataDate))
 | 
			
		||||
              )
 | 
			
		||||
            )),
 | 
			
		||||
            catchError(error => of(new commonActions.Fail(error))));
 | 
			
		||||
        } else {
 | 
			
		||||
           return [];
 | 
			
		||||
        }        
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      ));
 | 
			
		||||
 | 
			
		||||
@@ -231,9 +238,9 @@ export class MapEffects {
 | 
			
		||||
          feature = f;
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
      }     
 | 
			
		||||
      }
 | 
			
		||||
      if (feature) {
 | 
			
		||||
        return of(new mapActions.UpdateFeatureSuccess(this.toPointFeature(deviceUpdateEventAction)));        
 | 
			
		||||
        return of(new mapActions.UpdateFeatureSuccess(this.toPointFeature(deviceUpdateEventAction)));
 | 
			
		||||
      } else {
 | 
			
		||||
        return [];
 | 
			
		||||
      }
 | 
			
		||||
@@ -244,10 +251,17 @@ export class MapEffects {
 | 
			
		||||
    ofType(commonActions.ITEMCHANGEDEVENT),
 | 
			
		||||
    withLatestFrom(this.store$.select(mapReducers.selectGetSelectedItem)),
 | 
			
		||||
    mergeMap(([action, selectedItem]) => {
 | 
			
		||||
      let itemChangedAction = action as commonActions.ItemChangedEvent;   
 | 
			
		||||
      let itemChangedAction = action as commonActions.ItemChangedEvent;
 | 
			
		||||
      if (selectedItem && selectedItem.code == itemChangedAction.itemCode) {
 | 
			
		||||
        return this.itemService$.getItem(itemChangedAction.itemCode).pipe(
 | 
			
		||||
          map((item: IItem) => new mapActions.SelectItemSuccess(item)),
 | 
			
		||||
          switchMap(child => {
 | 
			
		||||
            return this.itemService$.getItem(child.parentCode)
 | 
			
		||||
              .pipe(map(parent => {
 | 
			
		||||
                  return {child, parent};
 | 
			
		||||
                })
 | 
			
		||||
              );
 | 
			
		||||
          }),
 | 
			
		||||
          map(data => new mapActions.SelectItemSuccess(data.child, data.parent)),
 | 
			
		||||
          catchError(error => of(new commonActions.Fail(error))));
 | 
			
		||||
      } else {
 | 
			
		||||
        return [];
 | 
			
		||||
@@ -256,17 +270,17 @@ export class MapEffects {
 | 
			
		||||
 | 
			
		||||
  getActionFromQueryState(queryState:IQueryState, inSearch:boolean):Observable<Action>|[] {
 | 
			
		||||
    if(!inSearch && (queryState.itemType || queryState.parentCode || queryState.itemCode || queryState.query || queryState.tags)) {
 | 
			
		||||
      var newAction:Action;     
 | 
			
		||||
      if (queryState.itemCode && queryState.itemCode != "") {         
 | 
			
		||||
        newAction= new mapActions.SelectItem(queryState.itemCode);          
 | 
			
		||||
      var newAction:Action;
 | 
			
		||||
      if (queryState.itemCode && queryState.itemCode != "") {
 | 
			
		||||
        newAction= new mapActions.SelectItem(queryState.itemCode);
 | 
			
		||||
      } else {
 | 
			
		||||
        newAction= new mapActions.StartSearch(queryState);
 | 
			
		||||
      }
 | 
			
		||||
      return of(newAction);  
 | 
			
		||||
      return of(newAction);
 | 
			
		||||
    } else {
 | 
			
		||||
      return of(new commonActions.Escape(true,false));
 | 
			
		||||
    }
 | 
			
		||||
  } 
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Effect()
 | 
			
		||||
  setQueryState$: Observable<Action> = this.actions$.pipe(
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
import { IDataLayer } from '@farmmaps/common';
 | 
			
		||||
 | 
			
		||||
export interface IColor {
 | 
			
		||||
  red: number,
 | 
			
		||||
  green: number,
 | 
			
		||||
@@ -64,11 +66,7 @@ export interface IRenderoutputImage {
 | 
			
		||||
  extent: [number,number,number,number]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ILayer {
 | 
			
		||||
  name: string,
 | 
			
		||||
  unit: string,
 | 
			
		||||
  index: number,
 | 
			
		||||
  scale: number,
 | 
			
		||||
export interface ILayer extends IDataLayer {
 | 
			
		||||
  renderer: IRenderer,
 | 
			
		||||
  rendering: IRenderoutput
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -44,9 +44,10 @@ export interface State {
 | 
			
		||||
  panelCollapsed: boolean,
 | 
			
		||||
  selectedFeature: Feature,
 | 
			
		||||
  selectedItem:IItem,
 | 
			
		||||
  parentItem:IItem,
 | 
			
		||||
  clearEnabled: boolean,
 | 
			
		||||
  searchCollapsed: boolean,
 | 
			
		||||
  searchMinified: boolean, 
 | 
			
		||||
  searchMinified: boolean,
 | 
			
		||||
  extent: number[],
 | 
			
		||||
  baseLayers: Array<IItemLayer>
 | 
			
		||||
  overlayLayers: Array<IItemLayer>,
 | 
			
		||||
@@ -81,7 +82,8 @@ export const initialState: State = {
 | 
			
		||||
  panelVisible: false,
 | 
			
		||||
  panelCollapsed: false,
 | 
			
		||||
  selectedFeature: null,
 | 
			
		||||
  selectedItem: null, 
 | 
			
		||||
  selectedItem: null,
 | 
			
		||||
  parentItem: null,
 | 
			
		||||
  clearEnabled: false,
 | 
			
		||||
  searchCollapsed: true,
 | 
			
		||||
  searchMinified:false,
 | 
			
		||||
@@ -99,7 +101,7 @@ export const initialState: State = {
 | 
			
		||||
  replaceUrl:true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function reducer(state = initialState, action: mapActions.Actions | commonActions.Actions | RouterNavigationAction): State {    
 | 
			
		||||
export function reducer(state = initialState, action: mapActions.Actions | commonActions.Actions | RouterNavigationAction): State {
 | 
			
		||||
  switch (action.type) {
 | 
			
		||||
    case ROUTER_NAVIGATION: {
 | 
			
		||||
      let a = action as RouterNavigationAction;
 | 
			
		||||
@@ -125,13 +127,13 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
 | 
			
		||||
    }
 | 
			
		||||
    case mapActions.SETPARENT: {
 | 
			
		||||
      let a = action as mapActions.SetParent;
 | 
			
		||||
      return tassign(state, {       
 | 
			
		||||
      return tassign(state, {
 | 
			
		||||
        parentCode : a.parentCode
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    case mapActions.STARTSEARCHSUCCESS: {
 | 
			
		||||
      let a = action as mapActions.StartSearchSuccess;
 | 
			
		||||
      return tassign(state, {       
 | 
			
		||||
      return tassign(state, {
 | 
			
		||||
        features: a.features,
 | 
			
		||||
        inSearch:false
 | 
			
		||||
      });
 | 
			
		||||
@@ -141,8 +143,8 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
 | 
			
		||||
      return tassign(state, {
 | 
			
		||||
        selectedFeature: state.selectedItem?state.selectedFeature: a.feature
 | 
			
		||||
      });
 | 
			
		||||
    }    
 | 
			
		||||
    case mapActions.SELECTITEM: {  
 | 
			
		||||
    }
 | 
			
		||||
    case mapActions.SELECTITEM: {
 | 
			
		||||
      let a = action as mapActions.SelectItem;
 | 
			
		||||
      let itemCode = state.selectedItem ? state.selectedItem.code : "";
 | 
			
		||||
      let inSearch = (a.itemCode != itemCode || state.setStateCount == 1)
 | 
			
		||||
@@ -160,11 +162,12 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
 | 
			
		||||
        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);       
 | 
			
		||||
        itemLayer = new TemporalItemLayer(a.item);
 | 
			
		||||
      }
 | 
			
		||||
      return tassign(state, {
 | 
			
		||||
        inSearch:false,
 | 
			
		||||
        selectedItem: a.item,
 | 
			
		||||
        parentItem: a.parentItem,
 | 
			
		||||
        selectedItemLayer: itemLayer,
 | 
			
		||||
        panelVisible: a.item != null,
 | 
			
		||||
        clearEnabled: a.item != null,
 | 
			
		||||
@@ -181,7 +184,7 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
 | 
			
		||||
      if(a.temporalItems.length>0) {
 | 
			
		||||
        let item = a.temporalItems[a.temporalItems.length-1];
 | 
			
		||||
        layerIndex = item.data.layers[0].index;
 | 
			
		||||
        selectedItemLayer.selectedItemLayer = new ItemLayer(item,1,true,layerIndex);        
 | 
			
		||||
        selectedItemLayer.selectedItemLayer = new ItemLayer(item,1,true,layerIndex);
 | 
			
		||||
      } else {
 | 
			
		||||
        selectedItemLayer.selectedItemLayer = null;
 | 
			
		||||
      }
 | 
			
		||||
@@ -189,9 +192,9 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
 | 
			
		||||
      selectedItemLayer.nextItemLayer = null;
 | 
			
		||||
      if(selectedItemLayer.selectedItemLayer) {
 | 
			
		||||
        let layerIndex = selectedItemLayer.selectedItemLayer.item.data.layers[0].index;
 | 
			
		||||
        selectedItemLayer.layerIndex = layerIndex;        
 | 
			
		||||
        selectedItemLayer.layerIndex = layerIndex;
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
 | 
			
		||||
      return tassign(state,{selectedItemLayer:tassign(state.selectedItemLayer,selectedItemLayer as ItemLayer)});
 | 
			
		||||
    }
 | 
			
		||||
    case mapActions.NEXTTEMPORAL: {
 | 
			
		||||
@@ -234,11 +237,11 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
 | 
			
		||||
            temporalLayer.nextItemLayer.opacity=0;
 | 
			
		||||
            temporalLayer.nextItemLayer.layerIndex = temporalLayer.layerIndex;
 | 
			
		||||
          }
 | 
			
		||||
          temporalLayer.selectedItemLayer = temporalLayer.previousItemLayer;          
 | 
			
		||||
          temporalLayer.selectedItemLayer = temporalLayer.previousItemLayer;
 | 
			
		||||
          if( temporalLayer.selectedItemLayer) {
 | 
			
		||||
            temporalLayer.selectedItemLayer.opacity=1;
 | 
			
		||||
            temporalLayer.selectedItemLayer.layerIndex = temporalLayer.layerIndex;
 | 
			
		||||
          } 
 | 
			
		||||
          }
 | 
			
		||||
          temporalLayer.previousItemLayer = index-2 >=0? new ItemLayer(temporalLayer.temporalItems[index-2],0,true,temporalLayer.layerIndex):null;
 | 
			
		||||
          if( temporalLayer.previousItemLayer)  {
 | 
			
		||||
            temporalLayer.previousItemLayer.opacity=0;
 | 
			
		||||
@@ -289,7 +292,7 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    case mapActions.UPDATEFEATURESUCCESS: {
 | 
			
		||||
      let a = action as mapActions.UpdateFeatureSuccess;     
 | 
			
		||||
      let a = action as mapActions.UpdateFeatureSuccess;
 | 
			
		||||
      let features: any[] = [];
 | 
			
		||||
      var index = -1;
 | 
			
		||||
      for (var i = 0; i < state.features.length; i++) {
 | 
			
		||||
@@ -307,10 +310,13 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
 | 
			
		||||
    case mapActions.COLLAPSESEARCH: {
 | 
			
		||||
      return tassign(state, { searchCollapsed: state.panelVisible ? false: true});
 | 
			
		||||
    }
 | 
			
		||||
    case commonActions.CLOSEALL: {
 | 
			
		||||
      return tassign(state, { searchCollapsed: state.panelVisible ? false: true,showLayerSwitcher:false});
 | 
			
		||||
    }
 | 
			
		||||
    case mapActions.SETEXTENT: {
 | 
			
		||||
      let a = action as mapActions.SetExtent;    
 | 
			
		||||
      let a = action as mapActions.SetExtent;
 | 
			
		||||
      return tassign(state, { extent: a.extent });
 | 
			
		||||
    }   
 | 
			
		||||
    }
 | 
			
		||||
    case mapActions.ADDLAYER: {
 | 
			
		||||
      let a = action as mapActions.AddLayer;
 | 
			
		||||
      let itemLayers = state.overlayLayers.slice(0);
 | 
			
		||||
@@ -323,7 +329,7 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
 | 
			
		||||
      } else {
 | 
			
		||||
        return state;
 | 
			
		||||
      }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    case mapActions.REMOVELAYER: {
 | 
			
		||||
      let a = action as mapActions.RemoveLayer;
 | 
			
		||||
@@ -353,7 +359,7 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
 | 
			
		||||
    }
 | 
			
		||||
    case mapActions.SETLAYERINDEX: {
 | 
			
		||||
      let a = action as mapActions.SetLayerIndex;
 | 
			
		||||
      if (a.itemLayer == null) {      
 | 
			
		||||
      if (a.itemLayer == null) {
 | 
			
		||||
        if(state.selectedItemLayer.item.itemType == "vnd.farmmaps.itemtype.temporal") {
 | 
			
		||||
          var newItemlayer = tassign(state.selectedItemLayer,{layerIndex:a.layerIndex});
 | 
			
		||||
          let tl = newItemlayer as ITemporalItemLayer;
 | 
			
		||||
@@ -381,7 +387,7 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
 | 
			
		||||
        } else {
 | 
			
		||||
          var newItemlayer = new ItemLayer(state.selectedItemLayer.item);
 | 
			
		||||
          newItemlayer.layerIndex = a.layerIndex;
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        return tassign(state, { selectedItemLayer: newItemlayer})
 | 
			
		||||
      } else {
 | 
			
		||||
@@ -401,7 +407,7 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
 | 
			
		||||
      }
 | 
			
		||||
      var selectedBaseLayer: IItemLayer = null;
 | 
			
		||||
      var mapState = tassign(state.mapState);
 | 
			
		||||
      let sb = baseLayers.filter(layer => layer.item.code === mapState.baseLayerCode)[0]; 
 | 
			
		||||
      let sb = baseLayers.filter(layer => layer.item.code === mapState.baseLayerCode)[0];
 | 
			
		||||
      if (baseLayers.length > 0 && mapState.baseLayerCode != "" && sb) {
 | 
			
		||||
        selectedBaseLayer = sb;
 | 
			
		||||
        selectedBaseLayer.visible = true;
 | 
			
		||||
@@ -457,7 +463,7 @@ export function reducer(state = initialState, action: mapActions.Actions | commo
 | 
			
		||||
    case mapActions.SHOWLAYERSWITCHER:{
 | 
			
		||||
      let a = action as mapActions.ShowLayerSwitcher;
 | 
			
		||||
      return tassign(state,{showLayerSwitcher:a.show});
 | 
			
		||||
    }  
 | 
			
		||||
    }
 | 
			
		||||
    case mapActions.SETREPLACEURL: {
 | 
			
		||||
      let a= action as mapActions.SetReplaceUrl;
 | 
			
		||||
      return tassign(state,{replaceUrl:a.replaceUrl});
 | 
			
		||||
@@ -478,6 +484,7 @@ export const getPanelVisible = (state: State) => state.panelVisible;
 | 
			
		||||
export const getPanelCollapsed = (state: State) => state.panelCollapsed;
 | 
			
		||||
export const getSelectedFeature = (state: State) => state.selectedFeature;
 | 
			
		||||
export const getSelectedItem = (state: State) => state.selectedItem;
 | 
			
		||||
export const getParentItem = (state: State) => state.parentItem;
 | 
			
		||||
export const getQueryState = (state: State) => state.queryState;
 | 
			
		||||
export const getClearEnabled = (state: State) => state.clearEnabled;
 | 
			
		||||
export const getSearchCollapsed = (state: State) => state.searchCollapsed;
 | 
			
		||||
@@ -505,6 +512,7 @@ export const selectGetPanelVisible = createSelector(selectMapState, getPanelVisi
 | 
			
		||||
export const selectGetPanelCollapsed = createSelector(selectMapState, getPanelCollapsed);
 | 
			
		||||
export const selectGetSelectedFeature = createSelector(selectMapState, getSelectedFeature);
 | 
			
		||||
export const selectGetSelectedItem = createSelector(selectMapState, getSelectedItem);
 | 
			
		||||
export const selectGetParentItem = createSelector(selectMapState, getParentItem);
 | 
			
		||||
export const selectGetQueryState = createSelector(selectMapState, getQueryState);
 | 
			
		||||
export const selectGetClearEnabled = createSelector(selectMapState, getClearEnabled);
 | 
			
		||||
export const selectGetSearchCollapsed = createSelector(selectMapState, getSearchCollapsed);
 | 
			
		||||
@@ -515,7 +523,7 @@ export const selectGetBaseLayers = createSelector(selectMapState, getBaseLayers)
 | 
			
		||||
export const selectGetProjection = createSelector(selectMapState, getProjection);
 | 
			
		||||
export const selectGetSelectedBaseLayer = createSelector(selectMapState, getSelectedBaseLayer);
 | 
			
		||||
export const selectGetSelectedOverlayLayer = createSelector(selectMapState, getSelectedOverlayLayer);
 | 
			
		||||
export const selectGetQuery = createSelector(selectMapState, getQuery); 
 | 
			
		||||
export const selectGetQuery = createSelector(selectMapState, getQuery);
 | 
			
		||||
export const selectGetSelectedItemLayer = createSelector(selectMapState, getSelectedItemLayer);
 | 
			
		||||
export const selectGetPeriod = createSelector(selectMapState, getPeriod);
 | 
			
		||||
export const selectGetStyles = createSelector(selectMapState, getStyles);
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import { IItemTypes } from '../models/item.types';
 | 
			
		||||
import { IListItem } from '../models/list.item';
 | 
			
		||||
import { IUser } from '../models/user';
 | 
			
		||||
import { IItem } from '../models/item';
 | 
			
		||||
import { UserInfo } from 'angular-oauth2-oidc';
 | 
			
		||||
 | 
			
		||||
export const INITUSER = '[AppCommon] InitUser';
 | 
			
		||||
export const INITUSERSUCCESS = '[AppCommon] InitUserSuccess';
 | 
			
		||||
@@ -17,7 +18,9 @@ export const INITROOTSUCCESS = '[Explorer] InitRootSuccess';
 | 
			
		||||
export const OPENMODAL = '[AppCommon] OpenModal';
 | 
			
		||||
export const CLOSEMODAL = '[AppCommon] CloseModal';
 | 
			
		||||
export const LOGIN = '[AppCommon] Login';
 | 
			
		||||
export const LOGOUT = '[AppCommon] Logout';
 | 
			
		||||
export const ESCAPE = '[AppCommon] Escape';
 | 
			
		||||
export const CLOSEALL = '[AppCommon] CloseAll';
 | 
			
		||||
 | 
			
		||||
export const LOADITEMTYPES = '[AppCommon] LoadItemTypes';
 | 
			
		||||
export const LOADITEMTYPESSUCCESS = '[AppCommon] LoadItemTypesSuccess';
 | 
			
		||||
@@ -51,8 +54,14 @@ export const UPLOADEDFILECLICK = '[AppCommon] UploadedFileClick';
 | 
			
		||||
 | 
			
		||||
export const TOGGLEMENU = '[AppCommon] ToggleMenu';
 | 
			
		||||
 | 
			
		||||
export const TOGGLEACCOUNTMENU = '[AppCommon] ToggleAccountMenu';
 | 
			
		||||
 | 
			
		||||
export const SETMENUVISIBLE = '[AppCommon] SetMenuVisible';
 | 
			
		||||
 | 
			
		||||
export const ONLINE = '[AppCommon] Online';
 | 
			
		||||
 | 
			
		||||
export const OFFLINE = '[AppCommon] Offline';
 | 
			
		||||
 | 
			
		||||
export class InitUser implements Action {
 | 
			
		||||
  readonly type = INITUSER;
 | 
			
		||||
 | 
			
		||||
@@ -62,7 +71,7 @@ export class InitUser implements Action {
 | 
			
		||||
export class InitUserSuccess implements Action {
 | 
			
		||||
  readonly type = INITUSERSUCCESS;
 | 
			
		||||
 | 
			
		||||
  constructor(public user:IUser ) { }
 | 
			
		||||
  constructor(public user:IUser,public userinfo:UserInfo ) { }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class InitUserPackagesSuccess implements Action {
 | 
			
		||||
@@ -95,6 +104,12 @@ export class CloseModal implements Action {
 | 
			
		||||
  constructor() { }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CloseAll implements Action {
 | 
			
		||||
  readonly type = CLOSEALL;
 | 
			
		||||
 | 
			
		||||
  constructor() { }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class StartRouteLoading implements Action {
 | 
			
		||||
  readonly type = STARTROUTELOADING;
 | 
			
		||||
 | 
			
		||||
@@ -113,6 +128,12 @@ export class Login implements Action {
 | 
			
		||||
  constructor(public url: string) { }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Logout implements Action {
 | 
			
		||||
  readonly type = LOGOUT;
 | 
			
		||||
 | 
			
		||||
  constructor() { }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Escape implements Action {
 | 
			
		||||
  readonly type = ESCAPE;
 | 
			
		||||
 | 
			
		||||
@@ -226,11 +247,29 @@ export class ToggleMenu implements Action {
 | 
			
		||||
  constructor() { }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ToggleAccountMenu implements Action {
 | 
			
		||||
  readonly type = TOGGLEACCOUNTMENU;
 | 
			
		||||
 | 
			
		||||
  constructor() { }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class SetMenuVisible implements Action {
 | 
			
		||||
  readonly type = SETMENUVISIBLE;
 | 
			
		||||
 | 
			
		||||
  constructor(public visible:boolean) { }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Online implements Action {
 | 
			
		||||
  readonly type = ONLINE;
 | 
			
		||||
 | 
			
		||||
  constructor() { }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Offline implements Action {
 | 
			
		||||
  readonly type = OFFLINE;
 | 
			
		||||
 | 
			
		||||
  constructor() { }
 | 
			
		||||
}
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
export type Actions = OpenModal
 | 
			
		||||
@@ -238,6 +277,7 @@ export type Actions = OpenModal
 | 
			
		||||
  | InitRootSuccess
 | 
			
		||||
  | CloseModal
 | 
			
		||||
  | Login
 | 
			
		||||
  | Logout
 | 
			
		||||
  | ItemChangedEvent
 | 
			
		||||
  | ItemAddedEvent
 | 
			
		||||
  | ItemDeletedEvent
 | 
			
		||||
@@ -261,5 +301,10 @@ export type Actions = OpenModal
 | 
			
		||||
  | DeviceUpdateEvent
 | 
			
		||||
  | ToggleMenu
 | 
			
		||||
  | SetMenuVisible
 | 
			
		||||
  | InitUserPackagesSuccess;
 | 
			
		||||
  | InitUserPackagesSuccess
 | 
			
		||||
  | ToggleAccountMenu
 | 
			
		||||
  | CloseAll
 | 
			
		||||
  | Online
 | 
			
		||||
  | Offline;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ import { OAuthModule, OAuthService, OAuthStorage } from 'angular-oauth2-oidc';
 | 
			
		||||
 | 
			
		||||
//components
 | 
			
		||||
import { ItemTypeService } from './services/itemtype.service';
 | 
			
		||||
import { SchemaService } from './services/schema.service';
 | 
			
		||||
import { FolderService } from './services/folder.service';
 | 
			
		||||
import { TimespanService } from './services/timespan.service';
 | 
			
		||||
import { ItemService } from './services/item.service';
 | 
			
		||||
@@ -28,6 +29,8 @@ import { ResumableFileUploadService } from './components/resumable-file-upload/r
 | 
			
		||||
import { NgbDateNativeAdapter } from './services/date-adapter.service'
 | 
			
		||||
import { AuthConfigFactory } from './shared/authconfigFactory';
 | 
			
		||||
import { StateSerializerService } from './services/state-serializer.service';
 | 
			
		||||
import { PackageService } from './services/package.service';
 | 
			
		||||
import { SenmlService } from './services/senml-service';
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  FolderService,
 | 
			
		||||
@@ -47,7 +50,10 @@ export {
 | 
			
		||||
  AuthCallbackGuard,
 | 
			
		||||
  ResumableFileUploadService,
 | 
			
		||||
  NgbDateNativeAdapter,
 | 
			
		||||
  StateSerializerService
 | 
			
		||||
  StateSerializerService,
 | 
			
		||||
  SchemaService,
 | 
			
		||||
  PackageService,
 | 
			
		||||
  SenmlService
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
@@ -71,7 +77,7 @@ export class AppCommonServiceModule {
 | 
			
		||||
        {
 | 
			
		||||
          provide: APP_INITIALIZER,
 | 
			
		||||
          useFactory: appConfigFactory,
 | 
			
		||||
          deps: [Injector, AppConfig, OAuthService, AuthConfigFactory, OAuthStorage,ItemTypeService],
 | 
			
		||||
          deps: [Injector, AppConfig, OAuthService, AuthConfigFactory, OAuthStorage, ItemTypeService],
 | 
			
		||||
          multi: true
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,8 @@ import { TimespanComponent } from './components/timespan/timespan.component';
 | 
			
		||||
import { TagInputComponent } from './components/tag-input/tag-input.component';
 | 
			
		||||
import { MenuBackgroundComponent } from './components/menu-background/menu-background.component';
 | 
			
		||||
import { HasPackageDirective} from './components/has-package/has-package.directive';
 | 
			
		||||
import { HasClaimDirective} from './components/has-claim/has-claim.directive';
 | 
			
		||||
import { UserMenuComponent} from './components/user-menu/user-menu.component';
 | 
			
		||||
import { Alert } from './enumerations/alert.enum';
 | 
			
		||||
import { IEventMessage } from './models/event.message';
 | 
			
		||||
import { IItem, Item } from './models/item';
 | 
			
		||||
@@ -39,11 +41,14 @@ import { IItemType } from './models/item.type';
 | 
			
		||||
import { IItemTypes } from './models/item.types';
 | 
			
		||||
import { IItemTask, ItemTask } from './models/itemTask';
 | 
			
		||||
import { IListItem } from './models/list.item';
 | 
			
		||||
import { ITypeaheadItem } from './models/typeahead.item';
 | 
			
		||||
import { ITypeaheadItem } from './models/typeahead.item'
 | 
			
		||||
import { IJsonline } from './models/json-line';
 | 
			
		||||
import { ISenMLItem } from './models/senml-item';
 | 
			
		||||
import { IPackage,IPackages } from './models/package';
 | 
			
		||||
import { IUser } from './models/user';
 | 
			
		||||
import { IQueryState } from './models/query.state';
 | 
			
		||||
import { ICodeListItem } from './models/code.list.item';
 | 
			
		||||
import { IDataLayer } from './models/data.layer';
 | 
			
		||||
import * as commonActions from './actions/app-common.actions';
 | 
			
		||||
import * as commonReducers from './reducers/app-common.reducer';
 | 
			
		||||
import * as commonEffects from './effects/app-common.effects';
 | 
			
		||||
@@ -60,7 +65,9 @@ export {
 | 
			
		||||
  SidePanelComponent,
 | 
			
		||||
  TimespanComponent,
 | 
			
		||||
  TagInputComponent,
 | 
			
		||||
  UserMenuComponent,
 | 
			
		||||
  HasPackageDirective,
 | 
			
		||||
  HasClaimDirective,
 | 
			
		||||
  Alert,
 | 
			
		||||
  IEventMessage,
 | 
			
		||||
  IItem,
 | 
			
		||||
@@ -82,7 +89,10 @@ export {
 | 
			
		||||
  AuthConfigFactory,
 | 
			
		||||
  MenuBackgroundComponent,
 | 
			
		||||
  SecureOAuthStorage,
 | 
			
		||||
  WeatherCurrentObservation
 | 
			
		||||
  WeatherCurrentObservation,
 | 
			
		||||
  IJsonline,
 | 
			
		||||
  ISenMLItem,
 | 
			
		||||
  IDataLayer
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
@@ -109,7 +119,9 @@ export {
 | 
			
		||||
    TagInputComponent,
 | 
			
		||||
    SessionClearedComponent,
 | 
			
		||||
    MenuBackgroundComponent,
 | 
			
		||||
    HasPackageDirective
 | 
			
		||||
    HasPackageDirective,
 | 
			
		||||
    HasClaimDirective,
 | 
			
		||||
    UserMenuComponent
 | 
			
		||||
  ],
 | 
			
		||||
  exports: [
 | 
			
		||||
    NgbModule,
 | 
			
		||||
@@ -127,7 +139,9 @@ export {
 | 
			
		||||
    TagInputComponent,
 | 
			
		||||
    SessionClearedComponent,
 | 
			
		||||
    MenuBackgroundComponent,
 | 
			
		||||
    HasPackageDirective
 | 
			
		||||
    HasPackageDirective,
 | 
			
		||||
    HasClaimDirective,
 | 
			
		||||
    UserMenuComponent
 | 
			
		||||
  ]
 | 
			
		||||
})
 | 
			
		||||
export class AppCommonModule {
 | 
			
		||||
 
 | 
			
		||||
@@ -23,4 +23,7 @@
 | 
			
		||||
  <ng-container *ngIf="showUploadProgress">
 | 
			
		||||
    <fm-resumable-file-upload></fm-resumable-file-upload>
 | 
			
		||||
  </ng-container>  
 | 
			
		||||
  <div class="user-menu">
 | 
			
		||||
    <fm-user-menu [user]="user|async" [showMenu]="accountMenuVisible|async"></fm-user-menu>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -76,3 +76,14 @@ body { background: #f1f1f1; line-height: 18px; user-select:none;}
 | 
			
		||||
.logo {
 | 
			
		||||
  margin-left: 1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.user-menu {
 | 
			
		||||
  transition: top 0.5s ease-out;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top:0.25em;
 | 
			
		||||
  right:1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.fullscreen > .user-menu  {
 | 
			
		||||
  top:1em;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,13 +2,16 @@ import { Component, OnInit, OnDestroy, Inject, ViewEncapsulation, RendererFactor
 | 
			
		||||
import { Router, NavigationStart, NavigationEnd, RouteConfigLoadStart, RouteConfigLoadEnd, ActivatedRoute, PRIMARY_OUTLET } from '@angular/router';
 | 
			
		||||
import { Meta, Title, MetaDefinition } from '@angular/platform-browser';import { DOCUMENT } from "@angular/common";
 | 
			
		||||
import { Subscription ,  Observable } from 'rxjs';
 | 
			
		||||
import { distinctUntilChanged} from 'rxjs/operators';
 | 
			
		||||
import { Store, Action } from '@ngrx/store';
 | 
			
		||||
import { IUser } from '../../models/user';
 | 
			
		||||
 | 
			
		||||
//AppCommon
 | 
			
		||||
import { IEventMessage } from '../../models/event.message';
 | 
			
		||||
import { IListItem} from '../../models/list.item';
 | 
			
		||||
import { EventService } from '../../services/event.service';
 | 
			
		||||
import * as  commonActions  from '../../actions/app-common.actions';
 | 
			
		||||
import { HealthCheckService } from '../../services/healthcheck.service';
 | 
			
		||||
 | 
			
		||||
import * as appReducers from '../../reducers/app-common.reducer';
 | 
			
		||||
 | 
			
		||||
@@ -34,6 +37,8 @@ export class AppComponent implements OnInit, OnDestroy {
 | 
			
		||||
  public fullScreen: Observable<boolean>;
 | 
			
		||||
  public routeLoading: Observable<boolean>;
 | 
			
		||||
  public menuVisible: Observable<boolean>;
 | 
			
		||||
  public accountMenuVisible: Observable<boolean>;
 | 
			
		||||
  public user:Observable<IUser>;
 | 
			
		||||
  @Input() showUploadProgress: boolean =true;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
@@ -43,9 +48,18 @@ export class AppComponent implements OnInit, OnDestroy {
 | 
			
		||||
    private meta: Meta,
 | 
			
		||||
    private store: Store<appReducers.State>,
 | 
			
		||||
    private eventService: EventService,
 | 
			
		||||
  ) {    
 | 
			
		||||
    private healthCheckService: HealthCheckService
 | 
			
		||||
  ) {   
 | 
			
		||||
    
 | 
			
		||||
    //check health every 30 seconds
 | 
			
		||||
    this.healthCheckService.check(30000).pipe(distinctUntilChanged()).subscribe((online) => {
 | 
			
		||||
       if(online) {
 | 
			
		||||
         this.store.dispatch(new commonActions.Online());
 | 
			
		||||
       } else {
 | 
			
		||||
         this.store.dispatch(new commonActions.Offline());
 | 
			
		||||
       }
 | 
			
		||||
    });   
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
 | 
			
		||||
  getActionFromEvent(event: IEventMessage): Action {
 | 
			
		||||
@@ -88,17 +102,19 @@ export class AppComponent implements OnInit, OnDestroy {
 | 
			
		||||
    this.fullScreen = this.store.select(appReducers.selectGetFullScreen);
 | 
			
		||||
    this.routeLoading = this.store.select(appReducers.selectGetRouteLoading);
 | 
			
		||||
    this.menuVisible = this.store.select(appReducers.SelectGetMenuVisible);
 | 
			
		||||
    this.accountMenuVisible = this.store.select(appReducers.SelectGetAccountMenuVisible);
 | 
			
		||||
    this.user = this.store.select(appReducers.SelectGetUser);
 | 
			
		||||
    this.InstallRouteEventHandler();
 | 
			
		||||
    this.InstallEventServiceEventHandler();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @HostListener('document:keyup', ['$event'])
 | 
			
		||||
  keyUp(event: KeyboardEvent) {
 | 
			
		||||
  onKeyUp(event: KeyboardEvent) {
 | 
			
		||||
    let x = event.keyCode;
 | 
			
		||||
    if (x === 27) {
 | 
			
		||||
      this.store.dispatch(new commonActions.Escape(true,false));
 | 
			
		||||
    }    
 | 
			
		||||
  }
 | 
			
		||||
  }  
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy() {
 | 
			
		||||
    // Subscription clean-up
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,30 @@
 | 
			
		||||
import { Directive, ViewContainerRef,TemplateRef,OnInit,Input,OnDestroy } from '@angular/core';
 | 
			
		||||
import { Store} from '@ngrx/store';
 | 
			
		||||
import * as appCommonReducer from '../../reducers/app-common.reducer'
 | 
			
		||||
import { IPackages } from '../../models/package';
 | 
			
		||||
import { Observable, Subscription } from 'rxjs';
 | 
			
		||||
import { skip } from 'rxjs/operators';
 | 
			
		||||
import {OAuthService } from 'angular-oauth2-oidc';
 | 
			
		||||
import { IUser } from '../../models/user';
 | 
			
		||||
 | 
			
		||||
@Directive({
 | 
			
		||||
  selector: '[fm-hasclaim]',
 | 
			
		||||
})
 | 
			
		||||
export class HasClaimDirective  implements OnInit{
 | 
			
		||||
  @Input('fm-hasclaim') claim:string;
 | 
			
		||||
 | 
			
		||||
  constructor(private templateRef$: TemplateRef<any>,private viewContainerRef$: ViewContainerRef,private store$: Store<appCommonReducer.State>) { }
 | 
			
		||||
  private user$:Observable<IUser> = this.store$.select(appCommonReducer.SelectGetUser).pipe(skip(1));
 | 
			
		||||
  private hasView = false;
 | 
			
		||||
  ngOnInit() {   
 | 
			
		||||
     this.user$.subscribe((user) => {
 | 
			
		||||
      if (user.claims[this.claim]) {
 | 
			
		||||
        this.viewContainerRef$.createEmbeddedView(this.templateRef$);
 | 
			
		||||
        this.hasView=true;
 | 
			
		||||
      } else if (this.hasView) {
 | 
			
		||||
        this.viewContainerRef$.clear();
 | 
			
		||||
        this.hasView = false;     
 | 
			
		||||
      }  
 | 
			
		||||
    });    
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,149 +1,150 @@
 | 
			
		||||
import { Injectable, OnDestroy } from '@angular/core';
 | 
			
		||||
import { OAuthService } from 'angular-oauth2-oidc';
 | 
			
		||||
import { Subject , Subscription } from 'rxjs';
 | 
			
		||||
import { HttpClient } from "@angular/common/http";
 | 
			
		||||
import { UploadxService, UploadState,UploadxOptions} from 'ngx-uploadx';
 | 
			
		||||
import { AppConfig } from '../../shared/app.config';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class ResumableFileUploadService implements OnDestroy{
 | 
			
		||||
  public files: Array<File> = new Array<File>();
 | 
			
		||||
  public isUploading = false;
 | 
			
		||||
  public totalProgress = 0;
 | 
			
		||||
  public isClosed = true;
 | 
			
		||||
  public isMinimized = false;
 | 
			
		||||
  public parentCode: string;
 | 
			
		||||
  public refresh: Subject<any> = new Subject<any>();
 | 
			
		||||
  private _eventSub:Subscription;
 | 
			
		||||
  private initialized = false;
 | 
			
		||||
 | 
			
		||||
  constructor(private httpClient: HttpClient,private oauthService: OAuthService,private uploadService: UploadxService,public appConfig: AppConfig) {
 | 
			
		||||
   
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  endPoint() {
 | 
			
		||||
    return `${this.appConfig.getConfig("apiEndPoint")}/api/v1/file`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init() {
 | 
			
		||||
    if(!this.initialized) {
 | 
			
		||||
      this._eventSub=this.uploadService.init({
 | 
			
		||||
        endpoint:this.endPoint(),
 | 
			
		||||
        token:() => this.oauthService.getAccessToken(),
 | 
			
		||||
        chunkSize: 2097152}).subscribe((uploadState:UploadState) => {
 | 
			
		||||
        this.handleState(uploadState);  
 | 
			
		||||
     } );
 | 
			
		||||
     this.initialized=true;  
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  updatetotalprogress() {
 | 
			
		||||
    var totalProgress =0;
 | 
			
		||||
    var n=0;
 | 
			
		||||
    for(var i =0;i<this.files.length;i++) {
 | 
			
		||||
      if(!this.files[i].error) {
 | 
			
		||||
        totalProgress+=this.files[i].progress;
 | 
			
		||||
        n++;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    this.totalProgress=totalProgress/this.files.length;
 | 
			
		||||
    if(this.totalProgress==100) this.isUploading=false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleState(state:UploadState) {
 | 
			
		||||
      switch(state.status) {
 | 
			
		||||
        case "queue": {
 | 
			
		||||
            this.files.push(new File(state));
 | 
			
		||||
            this.isClosed=false;
 | 
			
		||||
        };break;
 | 
			
		||||
        case "uploading": {
 | 
			
		||||
          this.isUploading = true;
 | 
			
		||||
          var file =this.files.find((f) => f.identifier == state.uploadId )
 | 
			
		||||
          if(file) {
 | 
			
		||||
            file.progress = (state.progress?state.progress:0);
 | 
			
		||||
          }
 | 
			
		||||
        };break;
 | 
			
		||||
        case "complete": {
 | 
			
		||||
            var file =this.files.find((f) => f.identifier == state.uploadId )
 | 
			
		||||
            if(file) {
 | 
			
		||||
              var parts = state.url.split("/");
 | 
			
		||||
              file.itemCode = parts[parts.length-1];
 | 
			
		||||
              file.progress = (state.progress?state.progress:0);
 | 
			
		||||
              file.success=true;
 | 
			
		||||
            }
 | 
			
		||||
        };break;
 | 
			
		||||
        case "error": {
 | 
			
		||||
          var file =this.files.find((f) => f.identifier == state.uploadId )
 | 
			
		||||
          if(file) {
 | 
			
		||||
            file.error=true;
 | 
			
		||||
            file.errorMessage = state.response;
 | 
			
		||||
          }
 | 
			
		||||
        };break;
 | 
			
		||||
      }
 | 
			
		||||
      this.updatetotalprogress();
 | 
			
		||||
      this.refresh.next({});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addFiles = (files: any[], event: any, metadata:any) => {
 | 
			
		||||
    for (let f of files) {
 | 
			
		||||
      var options:UploadxOptions = {metadata:metadata};
 | 
			
		||||
      this.uploadService.handleFile(f,options);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  toggleMinimize = function () {
 | 
			
		||||
    this.isMinimized = !this.isMinimized;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  cancelFile = function (file) {
 | 
			
		||||
    this.uploadService.control({action:'cancel',uploadId:file.identifier});    
 | 
			
		||||
    var index = this.files.indexOf(file, 0);
 | 
			
		||||
    if (index > -1) {
 | 
			
		||||
      this.files.splice(index, 1);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  doClose = function () {
 | 
			
		||||
    this.uploadService.control({action:'cancelAll'});
 | 
			
		||||
    this.files = new Array<File>();
 | 
			
		||||
    this.isClosed = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  close = function () {
 | 
			
		||||
    let close = true;
 | 
			
		||||
    if (this.isUploading) {
 | 
			
		||||
      close = false;
 | 
			
		||||
    }
 | 
			
		||||
    if (close) {
 | 
			
		||||
      this.doClose();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy() {
 | 
			
		||||
    if(this._eventSub) this._eventSub.unsubscribe();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class File {
 | 
			
		||||
  private file: any;
 | 
			
		||||
  public fileName: string;
 | 
			
		||||
  public progress: number;
 | 
			
		||||
  public identifier: string;
 | 
			
		||||
  public itemCode: string;
 | 
			
		||||
  public success: boolean;
 | 
			
		||||
  public error: boolean;
 | 
			
		||||
  public errorMessage: string;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  constructor(state: UploadState) {
 | 
			
		||||
    this.file = state;
 | 
			
		||||
    this.fileName = state.file.name;
 | 
			
		||||
    this.progress = state.progress?state.progress:0;
 | 
			
		||||
    this.identifier = state.uploadId;
 | 
			
		||||
    this.success = false;
 | 
			
		||||
    this.error = false;
 | 
			
		||||
    this.errorMessage = "";
 | 
			
		||||
    this.itemCode = null;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
import { Injectable, OnDestroy } from '@angular/core';
 | 
			
		||||
import { OAuthService } from 'angular-oauth2-oidc';
 | 
			
		||||
import { Subject , Subscription } from 'rxjs';
 | 
			
		||||
import { HttpClient } from "@angular/common/http";
 | 
			
		||||
import { UploadxService, UploadState,UploadxOptions} from 'ngx-uploadx';
 | 
			
		||||
import { AppConfig } from '../../shared/app.config';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class ResumableFileUploadService implements OnDestroy{
 | 
			
		||||
  public files: Array<File> = new Array<File>();
 | 
			
		||||
  public isUploading = false;
 | 
			
		||||
  public totalProgress = 0;
 | 
			
		||||
  public isClosed = true;
 | 
			
		||||
  public isMinimized = false;
 | 
			
		||||
  public parentCode: string;
 | 
			
		||||
  public refresh: Subject<any> = new Subject<any>();
 | 
			
		||||
  private _eventSub:Subscription;
 | 
			
		||||
  private initialized = false;
 | 
			
		||||
 | 
			
		||||
  constructor(private httpClient: HttpClient,private oauthService: OAuthService,private uploadService: UploadxService,public appConfig: AppConfig) {
 | 
			
		||||
   
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  endPoint() {
 | 
			
		||||
    return `${this.appConfig.getConfig("apiEndPoint")}/api/v1/file`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init() {
 | 
			
		||||
    if(!this.initialized) {
 | 
			
		||||
      this._eventSub=this.uploadService.init({
 | 
			
		||||
        endpoint:this.endPoint(),
 | 
			
		||||
        token:() => this.oauthService.getAccessToken(),
 | 
			
		||||
        chunkSize: 2097152}).subscribe((uploadState:UploadState) => {
 | 
			
		||||
        this.handleState(uploadState);  
 | 
			
		||||
     } );
 | 
			
		||||
     this.initialized=true;  
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  updatetotalprogress() {
 | 
			
		||||
    var totalProgress =0;
 | 
			
		||||
    var n=0;
 | 
			
		||||
    for(var i =0;i<this.files.length;i++) {
 | 
			
		||||
      if(!this.files[i].error) {
 | 
			
		||||
        totalProgress+=this.files[i].progress;
 | 
			
		||||
        n++;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    this.totalProgress=totalProgress/this.files.length;
 | 
			
		||||
    if(this.totalProgress==100) this.isUploading=false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleState(state:UploadState) {
 | 
			
		||||
      var file =this.files.find((f) => f.identifier == state.uploadId )
 | 
			
		||||
      if(state.status != "cancelled" && !file) {
 | 
			
		||||
        this.files.push(new File(state));
 | 
			
		||||
        this.isClosed=false;  
 | 
			
		||||
      }
 | 
			
		||||
      switch(state.status) {
 | 
			
		||||
        case "uploading": {         
 | 
			
		||||
          if(file) {
 | 
			
		||||
            this.isUploading = true;
 | 
			
		||||
            file.progress = (state.progress?state.progress:0);
 | 
			
		||||
          }
 | 
			
		||||
        };break;
 | 
			
		||||
        case "complete": {
 | 
			
		||||
            if(file) {
 | 
			
		||||
              var parts = state.url.split("/");
 | 
			
		||||
              file.itemCode = parts[parts.length-1];
 | 
			
		||||
              file.progress = (state.progress?state.progress:0);
 | 
			
		||||
              file.success=true;
 | 
			
		||||
            }
 | 
			
		||||
        };break;
 | 
			
		||||
        case "error": {
 | 
			
		||||
          if(file) {
 | 
			
		||||
            file.error=true;
 | 
			
		||||
            file.errorMessage = state.response as string;
 | 
			
		||||
          }
 | 
			
		||||
        };break;
 | 
			
		||||
      }
 | 
			
		||||
      this.updatetotalprogress();
 | 
			
		||||
      this.refresh.next({});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addFiles = (files: any[], event: any, metadata:any) => {
 | 
			
		||||
    for (let f of files) {
 | 
			
		||||
      var options:UploadxOptions = {metadata:metadata};
 | 
			
		||||
      this.uploadService.handleFile(f,options);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  toggleMinimize = function () {
 | 
			
		||||
    this.isMinimized = !this.isMinimized;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  cancelFile = function (file) {
 | 
			
		||||
    this.uploadService.control({action:'cancel',uploadId:file.identifier});    
 | 
			
		||||
    var index = this.files.indexOf(file, 0);
 | 
			
		||||
    if (index > -1) {
 | 
			
		||||
      this.files.splice(index, 1);
 | 
			
		||||
    }
 | 
			
		||||
    if(this.files.length==0) {
 | 
			
		||||
      this.isUploading = false;
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  doClose = function () {
 | 
			
		||||
    this.uploadService.control({action:'cancelAll'});
 | 
			
		||||
    this.files = new Array<File>();
 | 
			
		||||
    this.isClosed = true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  close = function () {
 | 
			
		||||
    let close = true;
 | 
			
		||||
    if (this.isUploading) {
 | 
			
		||||
      close = false;
 | 
			
		||||
    }
 | 
			
		||||
    if (close) {
 | 
			
		||||
      this.doClose();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnDestroy() {
 | 
			
		||||
    if(this._eventSub) this._eventSub.unsubscribe();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class File {
 | 
			
		||||
  private file: any;
 | 
			
		||||
  public fileName: string;
 | 
			
		||||
  public progress: number;
 | 
			
		||||
  public identifier: string;
 | 
			
		||||
  public itemCode: string;
 | 
			
		||||
  public success: boolean;
 | 
			
		||||
  public error: boolean;
 | 
			
		||||
  public errorMessage: string;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  constructor(state: UploadState) {
 | 
			
		||||
    this.file = state;
 | 
			
		||||
    this.fileName = state.file.name;
 | 
			
		||||
    this.progress = state.progress?state.progress:0;
 | 
			
		||||
    this.identifier = state.uploadId;
 | 
			
		||||
    this.success = false;
 | 
			
		||||
    this.error = false;
 | 
			
		||||
    this.errorMessage = "";
 | 
			
		||||
    this.itemCode = null;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
<div *ngIf="user">
 | 
			
		||||
    <div (click)="toggle($event)" class="rounded-circle menu-button" [title]="user.name">
 | 
			
		||||
        <span>{{getLetter()}}</span>
 | 
			
		||||
        <div class="menu hidden" [ngClass]="{'hidden':!showMenu}">
 | 
			
		||||
            <div  class="card">           
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                    <div class="username">{{user.name}}</div>
 | 
			
		||||
                    <div><a href="#" (click)="logout($event)" i18n>logout</a></div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>    
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -0,0 +1,46 @@
 | 
			
		||||
.menu-button {
 | 
			
		||||
    background-color: purple;  
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    width: 2.5em;
 | 
			
		||||
    height: 2.5em;
 | 
			
		||||
    line-height: 2.5em;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    font-size: 1rem;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.card {      
 | 
			
		||||
    padding:0.5rem;
 | 
			
		||||
    min-width: 10rem;      
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.username {
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
    font-size: 1.2rem;
 | 
			
		||||
    line-height: 1.2rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.card-body {
 | 
			
		||||
    text-align: left;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.hidden {
 | 
			
		||||
    max-height: 0;   
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,25 @@
 | 
			
		||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
 | 
			
		||||
import { UserMenuComponent } from './user-menu.component';
 | 
			
		||||
 | 
			
		||||
describe('UserMenuComponent', () => {
 | 
			
		||||
  let component: UserMenuComponent;
 | 
			
		||||
  let fixture: ComponentFixture<UserMenuComponent>;
 | 
			
		||||
 | 
			
		||||
  beforeEach(async(() => {
 | 
			
		||||
    TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ UserMenuComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  }));
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(UserMenuComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@@ -0,0 +1,37 @@
 | 
			
		||||
import { Component, OnInit,Input } from '@angular/core';
 | 
			
		||||
import { OAuthService} from 'angular-oauth2-oidc'
 | 
			
		||||
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-user-menu',
 | 
			
		||||
  templateUrl: './user-menu.component.html',
 | 
			
		||||
  styleUrls: ['./user-menu.component.scss']
 | 
			
		||||
})
 | 
			
		||||
export class UserMenuComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  @Input() user:IUser;
 | 
			
		||||
  @Input() showMenu:boolean;
 | 
			
		||||
 | 
			
		||||
  constructor(private oauthService:OAuthService, private store: Store<appReducers.State>) { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getLetter():string {
 | 
			
		||||
      return this.user.name ? this.user.name.substr(0,1).toUpperCase():"";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  logout(event:MouseEvent) {
 | 
			
		||||
    event.preventDefault();
 | 
			
		||||
    this.store.dispatch(new appActions.Logout());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  toggle(event:MouseEvent) {
 | 
			
		||||
    event.stopPropagation();
 | 
			
		||||
    this.store.dispatch(new appActions.ToggleAccountMenu());
 | 
			
		||||
  } 
 | 
			
		||||
}
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { Router } from '@angular/router';
 | 
			
		||||
import { OAuthService } from 'angular-oauth2-oidc';
 | 
			
		||||
import { OAuthService,UserInfo } from 'angular-oauth2-oidc';
 | 
			
		||||
import { Store, Action } from '@ngrx/store';
 | 
			
		||||
import { Effect, Actions,ofType } from '@ngrx/effects';
 | 
			
		||||
import { Observable ,  defer ,  of } from 'rxjs';
 | 
			
		||||
import { Observable ,  defer ,  of,from } from 'rxjs';
 | 
			
		||||
import { withLatestFrom,mergeMap,switchMap,map,catchError} from 'rxjs/operators';
 | 
			
		||||
import * as appCommonActions from '../actions/app-common.actions';
 | 
			
		||||
import * as appCommonReducers from '../reducers/app-common.reducer';
 | 
			
		||||
@@ -25,10 +25,18 @@ export class AppCommonEffects {
 | 
			
		||||
    withLatestFrom(this.store$.select(appCommonReducers.selectGetInitialized)),
 | 
			
		||||
    mergeMap(([action, initialized]) => {
 | 
			
		||||
      var a = (action as appCommonActions.Login);
 | 
			
		||||
      this.oauthService$.initCodeFlow(a.url);
 | 
			
		||||
      this.oauthService$.initCodeFlow(a.url,{"prompt":"login"});
 | 
			
		||||
      return [];
 | 
			
		||||
    }));
 | 
			
		||||
 | 
			
		||||
    @Effect({ dispatch: false })
 | 
			
		||||
    logout$: Observable<Action> = this.actions$.pipe(
 | 
			
		||||
      ofType(appCommonActions.LOGOUT),
 | 
			
		||||
      mergeMap((action) => {
 | 
			
		||||
        this.oauthService$.logOut(true);
 | 
			
		||||
        return [];
 | 
			
		||||
      }));
 | 
			
		||||
 | 
			
		||||
  @Effect()
 | 
			
		||||
  loadItemTypes$: Observable<Action> = this.actions$.pipe(
 | 
			
		||||
    ofType(appCommonActions.LOADITEMTYPES),
 | 
			
		||||
@@ -44,9 +52,10 @@ export class AppCommonEffects {
 | 
			
		||||
    ofType(appCommonActions.INITUSER),
 | 
			
		||||
    withLatestFrom(this.store$.select(appCommonReducers.selectGetInitialized)),
 | 
			
		||||
    switchMap(([action, initialized]) => {
 | 
			
		||||
      if(!initialized) {
 | 
			
		||||
      if(!initialized) {       
 | 
			
		||||
      return this.userService$.getCurrentUser().pipe(
 | 
			
		||||
        map((user: IUser) => new appCommonActions.InitUserSuccess(user)),
 | 
			
		||||
        withLatestFrom(from(this.oauthService$.loadUserProfile())),
 | 
			
		||||
        switchMap(([user,userInfo]) => {return of(new appCommonActions.InitUserSuccess(user,userInfo as UserInfo))} ),
 | 
			
		||||
        catchError(error => of(new appCommonActions.Fail(error))))
 | 
			
		||||
      } else {
 | 
			
		||||
        return [];
 | 
			
		||||
@@ -100,8 +109,11 @@ export class AppCommonEffects {
 | 
			
		||||
      withLatestFrom(this.store$.select(appCommonReducers.selectGetItemTypes)),
 | 
			
		||||
      switchMap(([action, itemtypes]) => {
 | 
			
		||||
        var a = action as appCommonActions.EditItem;
 | 
			
		||||
        var itemType = itemtypes[a.item.itemType];
 | 
			
		||||
        var editor = itemType.editor ? itemType.editor : "property";
 | 
			
		||||
        var editor =  "property";
 | 
			
		||||
        if(a.item.itemType) {
 | 
			
		||||
          var itemType = itemtypes[a.item.itemType];
 | 
			
		||||
          var editor = itemType && itemType.editor ? itemType.editor : editor;  
 | 
			
		||||
        }
 | 
			
		||||
      this.router$.navigate(['/editor',editor,'item', a.item.code])
 | 
			
		||||
      return [];
 | 
			
		||||
    }
 | 
			
		||||
@@ -150,6 +162,25 @@ export class AppCommonEffects {
 | 
			
		||||
      return null;
 | 
			
		||||
    }));
 | 
			
		||||
 | 
			
		||||
    @Effect({ dispatch: false })
 | 
			
		||||
    online$: Observable<Action> = this.actions$.pipe(
 | 
			
		||||
      ofType(appCommonActions.ONLINE),
 | 
			
		||||
      switchMap((action) => {
 | 
			
		||||
          console.debug("Online: Check token");
 | 
			
		||||
          if(!this.oauthService$.hasValidAccessToken()) {
 | 
			
		||||
            console.debug("No valid token, try to refresh");
 | 
			
		||||
            if(this.oauthService$.getRefreshToken() != null ) {
 | 
			
		||||
              console.debug("We have a refresh token");
 | 
			
		||||
              this.oauthService$.refreshToken().then(() => {              
 | 
			
		||||
                this.store$.dispatch(new appCommonActions.InitUser());
 | 
			
		||||
              });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return of(undefined);
 | 
			
		||||
      }));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  constructor(private actions$: Actions, private store$: Store<appCommonReducers.State>, private oauthService$: OAuthService, private itemService$: ItemService, private folderService$:FolderService, private userService$: UserService, private router$: Router, private stateSerializerService$:StateSerializerService) {
 | 
			
		||||
    store$.dispatch(new appCommonActions.LoadItemTypes());
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								projects/common/src/fm/models/WeatherData.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								projects/common/src/fm/models/WeatherData.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
export interface WeatherData {
 | 
			
		||||
  date: Date;
 | 
			
		||||
  iconCode: number;
 | 
			
		||||
  minTemperature: number;
 | 
			
		||||
  maxTemperature: number;
 | 
			
		||||
  relHumidity: number;
 | 
			
		||||
  precipitation: number;
 | 
			
		||||
  wSpeed: number;
 | 
			
		||||
  wCardinal: string;
 | 
			
		||||
  wDir: number;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								projects/common/src/fm/models/data.layer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								projects/common/src/fm/models/data.layer.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
export interface IDataLayer {
 | 
			
		||||
    name: string,
 | 
			
		||||
    unit: string,
 | 
			
		||||
    index: number,
 | 
			
		||||
    indexKey?: string,
 | 
			
		||||
    scale: number,
 | 
			
		||||
  }
 | 
			
		||||
@@ -15,9 +15,10 @@ export class Item implements IItem {
 | 
			
		||||
  public geometry?:any;
 | 
			
		||||
  public url?: string;
 | 
			
		||||
  public name?: string;
 | 
			
		||||
  public created?: Date;
 | 
			
		||||
  public updated?: Date;
 | 
			
		||||
  public dataDate?: Date;
 | 
			
		||||
  public created?: string;
 | 
			
		||||
  public updated?: string;
 | 
			
		||||
  public dataDate?: string;
 | 
			
		||||
  public dataEndDate?: string;
 | 
			
		||||
  public itemType?: string;
 | 
			
		||||
  public sourceTask?: string;
 | 
			
		||||
  public size?: number;
 | 
			
		||||
 
 | 
			
		||||
@@ -4,5 +4,6 @@ export interface IItemType {
 | 
			
		||||
  editor?: string;
 | 
			
		||||
  isFolder?: boolean;
 | 
			
		||||
  iconColor?: string;
 | 
			
		||||
  schema?: string;
 | 
			
		||||
  extraAttributes?: string;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										5
									
								
								projects/common/src/fm/models/json-line.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								projects/common/src/fm/models/json-line.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
export interface IJsonline {
 | 
			
		||||
  time: string;
 | 
			
		||||
  type: string;
 | 
			
		||||
  data: any;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,13 +1,14 @@
 | 
			
		||||
export interface IListItem {
 | 
			
		||||
    url?: string;
 | 
			
		||||
    code?: string;
 | 
			
		||||
    name?: string;
 | 
			
		||||
    created?: Date;
 | 
			
		||||
    updated?: Date;
 | 
			
		||||
    dataDate?: Date;
 | 
			
		||||
    itemType?: string;
 | 
			
		||||
    sourceTask?: string;
 | 
			
		||||
    size?: number;
 | 
			
		||||
    state?: number;
 | 
			
		||||
    thumbnail?: boolean;
 | 
			
		||||
}
 | 
			
		||||
export interface IListItem {
 | 
			
		||||
    url?: string;
 | 
			
		||||
    code?: string;
 | 
			
		||||
    name?: string;
 | 
			
		||||
    created?: string;
 | 
			
		||||
    updated?: string;
 | 
			
		||||
    dataDate?: string;
 | 
			
		||||
    dataEndDate?: string;
 | 
			
		||||
    itemType?: string;
 | 
			
		||||
    sourceTask?: string;
 | 
			
		||||
    size?: number;
 | 
			
		||||
    state?: number;
 | 
			
		||||
    thumbnail?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										5
									
								
								projects/common/src/fm/models/senml-item.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								projects/common/src/fm/models/senml-item.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
export interface ISenMLItem {    
 | 
			
		||||
    u?: string;
 | 
			
		||||
    v?: string;
 | 
			
		||||
    n?: string;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
export interface IUser {
 | 
			
		||||
  code?: string;
 | 
			
		||||
  name?: string;
 | 
			
		||||
  email?: string;
 | 
			
		||||
}
 | 
			
		||||
export interface IUser {
 | 
			
		||||
  code?: string;
 | 
			
		||||
  name?: string;
 | 
			
		||||
  email?: string;
 | 
			
		||||
  claims: any;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,9 @@ export interface State {
 | 
			
		||||
  fullScreen: boolean,
 | 
			
		||||
  routeLoading:boolean,
 | 
			
		||||
  menuVisible: boolean,
 | 
			
		||||
  userPackages: IPackages
 | 
			
		||||
  userPackages: IPackages,
 | 
			
		||||
  accountMenuVisible: boolean,
 | 
			
		||||
  isOnline: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const initialState: State = {
 | 
			
		||||
@@ -29,14 +31,26 @@ export const initialState: State = {
 | 
			
		||||
  fullScreen: true,
 | 
			
		||||
  routeLoading: false,
 | 
			
		||||
  menuVisible: false,
 | 
			
		||||
  userPackages: {}
 | 
			
		||||
  userPackages: {},
 | 
			
		||||
  accountMenuVisible: false,
 | 
			
		||||
  isOnline: window.navigator.onLine
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function reducer(state = initialState, action: appCommonActions.Actions ): State {
 | 
			
		||||
  switch (action.type) {
 | 
			
		||||
    case appCommonActions.INITUSERSUCCESS: {
 | 
			
		||||
      let a = action as appCommonActions.InitUserSuccess;
 | 
			
		||||
      return tassign(state, { user: a.user,initialized: true });
 | 
			
		||||
      let claims = {}
 | 
			
		||||
      Object.getOwnPropertyNames(a.userinfo).forEach((k) => {
 | 
			
		||||
         claims[k] = a.userinfo[k];
 | 
			
		||||
      });
 | 
			
		||||
      var user:IUser = {
 | 
			
		||||
        code:a.user.code,
 | 
			
		||||
        email:a.userinfo["email"],
 | 
			
		||||
        name:a.userinfo["name"],
 | 
			
		||||
        claims:claims
 | 
			
		||||
      };     
 | 
			
		||||
      return tassign(state, { user: user,initialized: true });
 | 
			
		||||
    }
 | 
			
		||||
    case appCommonActions.INITROOTSUCCESS: {
 | 
			
		||||
      let a = action as appCommonActions.InitRootSuccess;
 | 
			
		||||
@@ -73,14 +87,17 @@ export function reducer(state = initialState, action: appCommonActions.Actions )
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    case appCommonActions.TOGGLEMENU: {
 | 
			
		||||
      return tassign(state, { menuVisible: !state.menuVisible });
 | 
			
		||||
      return tassign(state, { menuVisible: !state.menuVisible,accountMenuVisible:!state.menuVisible?false:state.accountMenuVisible });
 | 
			
		||||
    }
 | 
			
		||||
    case appCommonActions.TOGGLEACCOUNTMENU: {
 | 
			
		||||
      return tassign(state, { accountMenuVisible: !state.accountMenuVisible });
 | 
			
		||||
    }
 | 
			
		||||
    case appCommonActions.ESCAPE: {
 | 
			
		||||
      return tassign(state, { menuVisible: false });
 | 
			
		||||
      return tassign(state, { menuVisible: false,accountMenuVisible:false });
 | 
			
		||||
    }
 | 
			
		||||
    case appCommonActions.SETMENUVISIBLE: {
 | 
			
		||||
      let a = action as appCommonActions.SetMenuVisible;
 | 
			
		||||
      return tassign(state, { menuVisible: a.visible });
 | 
			
		||||
      return tassign(state, { menuVisible: a.visible,accountMenuVisible:a.visible?false:state.accountMenuVisible });
 | 
			
		||||
    }
 | 
			
		||||
    case appCommonActions.INITUSERPACKAGESSUCCESS:{
 | 
			
		||||
      let a = action as appCommonActions.InitUserPackagesSuccess;
 | 
			
		||||
@@ -91,6 +108,18 @@ export function reducer(state = initialState, action: appCommonActions.Actions )
 | 
			
		||||
 | 
			
		||||
      return tassign(state,{userPackages:packages});
 | 
			
		||||
    }
 | 
			
		||||
    case appCommonActions.LOGOUT:{
 | 
			
		||||
      return tassign(state,{user:null,initialized:false});
 | 
			
		||||
    }
 | 
			
		||||
    case appCommonActions.CLOSEALL: {
 | 
			
		||||
      return tassign(state,{accountMenuVisible:false,menuVisible:false });
 | 
			
		||||
    }
 | 
			
		||||
    case appCommonActions.ONLINE:{
 | 
			
		||||
      return tassign(state,{isOnline:true});
 | 
			
		||||
    }
 | 
			
		||||
    case appCommonActions.OFFLINE:{
 | 
			
		||||
      return tassign(state,{isOnline:false});
 | 
			
		||||
    }
 | 
			
		||||
    default: {
 | 
			
		||||
      return state;
 | 
			
		||||
    }
 | 
			
		||||
@@ -106,6 +135,8 @@ export const getRouteLoading = (state: State) => state.routeLoading;
 | 
			
		||||
export const getMenuVisible = (state: State) => state.menuVisible;
 | 
			
		||||
export const getUser = (state: State) => state.user;
 | 
			
		||||
export const getUserPackages = (state: State) => state.userPackages;
 | 
			
		||||
export const getAccountMenuVisible = (state: State) => state.accountMenuVisible;
 | 
			
		||||
export const getIsOnline = (state: State) => state.isOnline;
 | 
			
		||||
 | 
			
		||||
export const selectAppCommonState = createFeatureSelector<State>(MODULE_NAME);
 | 
			
		||||
 | 
			
		||||
@@ -118,4 +149,6 @@ export const selectGetRouteLoading = createSelector(selectAppCommonState, getRou
 | 
			
		||||
export const SelectGetMenuVisible = createSelector(selectAppCommonState,getMenuVisible);
 | 
			
		||||
export const SelectGetUser = createSelector(selectAppCommonState,getUser);
 | 
			
		||||
export const SelectGetUserPackages = createSelector(selectAppCommonState,getUserPackages);
 | 
			
		||||
export const SelectGetAccountMenuVisible = createSelector(selectAppCommonState,getAccountMenuVisible);
 | 
			
		||||
export const SelectGetIsOnline = createSelector(selectAppCommonState,getIsOnline);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,56 +1,49 @@
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { Observable ,  Observer } from 'rxjs';
 | 
			
		||||
import {map} from 'rxjs/operators';
 | 
			
		||||
import { IListItem } from '../models/list.item';
 | 
			
		||||
import { IItem } from '../models/item';
 | 
			
		||||
import { HttpClient } from "@angular/common/http";
 | 
			
		||||
import { AppConfig } from "../shared/app.config";
 | 
			
		||||
 | 
			
		||||
@Injectable({
 | 
			
		||||
  providedIn: 'root',
 | 
			
		||||
})
 | 
			
		||||
export class FolderService {
 | 
			
		||||
 | 
			
		||||
  constructor(public httpClient: HttpClient, public appConfig: AppConfig) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ApiEndpoint() {
 | 
			
		||||
    return this.appConfig.getConfig("apiEndPoint");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  parseDates(item: any): IListItem {
 | 
			
		||||
    item.created = new Date(Date.parse(item.created));
 | 
			
		||||
    item.updated = new Date(Date.parse(item.updated));
 | 
			
		||||
    item.dataDate = new Date(Date.parse(item.dataDate));
 | 
			
		||||
    return item;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    getFolder(code: string): Observable<IListItem> {
 | 
			
		||||
      return this.httpClient.get<IListItem>(`${this.ApiEndpoint()}/api/v1/folders/${code}`).pipe(map(i => this.parseDates(i)));            
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getMyRoots(): Observable<IListItem[]> {
 | 
			
		||||
      return this.httpClient.get<IListItem[]>(`${this.ApiEndpoint()}/api/v1/folders/my_roots`).pipe(map(ia => ia.map(i => this.parseDates(i))));
 | 
			
		||||
    }    
 | 
			
		||||
 | 
			
		||||
    getFolderParents(code: string): Observable<IListItem[]> {
 | 
			
		||||
      return this.httpClient.get<IListItem[]>(`${this.ApiEndpoint()}/api/v1/folders/${code}/parents`).pipe(map(ia => ia.map(i => this.parseDates(i))));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getChildFolders(code: string): Observable<IListItem[]> {
 | 
			
		||||
      return this.httpClient.get<IListItem[]>(`${this.ApiEndpoint()}/api/v1/folders/${code}/listfolders`).pipe(map(ia => ia.map(i => this.parseDates(i))));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getItems(code: string,skip:number, take:number): Observable<IListItem[]> {
 | 
			
		||||
      return this.httpClient.get<IListItem[]>(`${this.ApiEndpoint()}/api/v1/folders/${code}/list?skip=${skip}&take=${take}`).pipe(map(ia => ia.map(i => this.parseDates(i))));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    moveItem(itemCode: string, newParentCode: string): Observable<IListItem> {
 | 
			
		||||
        const body = { itemCode: itemCode,newParentCode: newParentCode };
 | 
			
		||||
      return this.httpClient.post<IListItem>(`${this.ApiEndpoint()}/api/v1/items/move`, body).pipe(map(i => this.parseDates(i)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    createFolder(folder: IItem): Observable<IListItem> {
 | 
			
		||||
      return this.httpClient.post<IListItem>(`${this.ApiEndpoint()}/api/v1/folders/`, folder).pipe(map(i => this.parseDates(i)));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { Observable ,  Observer } from 'rxjs';
 | 
			
		||||
import {map} from 'rxjs/operators';
 | 
			
		||||
import { IListItem } from '../models/list.item';
 | 
			
		||||
import { IItem } from '../models/item';
 | 
			
		||||
import { HttpClient } from "@angular/common/http";
 | 
			
		||||
import { AppConfig } from "../shared/app.config";
 | 
			
		||||
 | 
			
		||||
@Injectable({
 | 
			
		||||
  providedIn: 'root',
 | 
			
		||||
})
 | 
			
		||||
export class FolderService {
 | 
			
		||||
 | 
			
		||||
  constructor(public httpClient: HttpClient, public appConfig: AppConfig) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ApiEndpoint() {
 | 
			
		||||
    return this.appConfig.getConfig("apiEndPoint");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
     getFolder(code: string): Observable<IListItem> {
 | 
			
		||||
      return this.httpClient.get<IListItem>(`${this.ApiEndpoint()}/api/v1/folders/${code}`);            
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getMyRoots(): Observable<IListItem[]> {
 | 
			
		||||
      return this.httpClient.get<IListItem[]>(`${this.ApiEndpoint()}/api/v1/folders/my_roots`);
 | 
			
		||||
    }    
 | 
			
		||||
 | 
			
		||||
    getFolderParents(code: string): Observable<IListItem[]> {
 | 
			
		||||
      return this.httpClient.get<IListItem[]>(`${this.ApiEndpoint()}/api/v1/folders/${code}/parents`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getChildFolders(code: string): Observable<IListItem[]> {
 | 
			
		||||
      return this.httpClient.get<IListItem[]>(`${this.ApiEndpoint()}/api/v1/folders/${code}/listfolders`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getItems(code: string,skip:number, take:number): Observable<IListItem[]> {
 | 
			
		||||
      return this.httpClient.get<IListItem[]>(`${this.ApiEndpoint()}/api/v1/folders/${code}/list?skip=${skip}&take=${take}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    moveItem(itemCode: string, newParentCode: string): Observable<IListItem> {
 | 
			
		||||
        const body = { itemCode: itemCode,newParentCode: newParentCode };
 | 
			
		||||
      return this.httpClient.post<IListItem>(`${this.ApiEndpoint()}/api/v1/items/move`, body);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    createFolder(folder: IItem): Observable<IListItem> {
 | 
			
		||||
      return this.httpClient.post<IListItem>(`${this.ApiEndpoint()}/api/v1/folders/`, folder);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										30
									
								
								projects/common/src/fm/services/healthcheck.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								projects/common/src/fm/services/healthcheck.service.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { Observable,BehaviorSubject,of } from 'rxjs';
 | 
			
		||||
import { map, catchError } from 'rxjs/operators';
 | 
			
		||||
import { HttpClient, HttpXhrBackend } from "@angular/common/http";
 | 
			
		||||
import { AppConfig } from "../shared/app.config";
 | 
			
		||||
 | 
			
		||||
@Injectable({
 | 
			
		||||
    providedIn: 'root',
 | 
			
		||||
  })
 | 
			
		||||
  export class HealthCheckService {
 | 
			
		||||
    private httpClient: HttpClient;
 | 
			
		||||
 | 
			
		||||
    constructor(xhrBackend: HttpXhrBackend, public appConfig: AppConfig) {
 | 
			
		||||
        this.httpClient = new HttpClient(xhrBackend);
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    ApiEndpoint() {
 | 
			
		||||
      return this.appConfig.getConfig("apiEndPoint");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    check(interval:number): Observable<boolean> {
 | 
			
		||||
      let retval = new BehaviorSubject<boolean>(false);
 | 
			
		||||
      setInterval(() => {
 | 
			
		||||
        this.httpClient.get(`${this.ApiEndpoint()}/api/v1/healthcheck`).pipe(map(() => true),catchError((error) => of(false))).toPromise().then((status) => {
 | 
			
		||||
           retval.next(status);
 | 
			
		||||
        });
 | 
			
		||||
      },interval);
 | 
			
		||||
      return retval;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -3,6 +3,7 @@ import { Observable } from 'rxjs';
 | 
			
		||||
import { map } from 'rxjs/operators';
 | 
			
		||||
import { IItemType } from '../models/item.type';
 | 
			
		||||
import { IItem } from '../models/item';
 | 
			
		||||
import { IJsonline } from '../models/json-line';
 | 
			
		||||
import { IItemTask } from '../models/itemTask';
 | 
			
		||||
import { HttpClient, HttpParams } from "@angular/common/http";
 | 
			
		||||
import { AppConfig } from "../shared/app.config";
 | 
			
		||||
@@ -19,13 +20,6 @@ export class ItemService {
 | 
			
		||||
    return this.appConfig.getConfig("apiEndPoint");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  parseDates(item: any): IItem {
 | 
			
		||||
    item.created = new Date(Date.parse(item.created));
 | 
			
		||||
    item.updated = new Date(Date.parse(item.updated));
 | 
			
		||||
    item.dataDate = new Date(Date.parse(item.dataDate));
 | 
			
		||||
    return item;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getItemTypes(): Observable<{ [id: string]: IItemType }> {
 | 
			
		||||
    return this.httpClient.get<{ [id: string]: IItemType }>(`${this.ApiEndpoint()}/api/v1/itemtypes/`);
 | 
			
		||||
  }
 | 
			
		||||
@@ -58,21 +52,21 @@ export class ItemService {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getItem(code: string): Observable<IItem> {
 | 
			
		||||
    return this.httpClient.get<IItem>(`${this.ApiEndpoint()}/api/v1/items/${code}`).pipe(map(i => this.parseDates(i)));
 | 
			
		||||
    return this.httpClient.get<IItem>(`${this.ApiEndpoint()}/api/v1/items/${code}`);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getItemByCodeAndType(code: string, itemType: string): Observable<IItem> {
 | 
			
		||||
    return this.httpClient.get<IItem>(`${this.ApiEndpoint()}/api/v1/items/${code}/${itemType}`);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getItemList(itemType: string, dataFilter?: any, level: number = 1): Observable<IItem[]> {
 | 
			
		||||
  getItemList(itemType?: string, dataFilter?: any, level?:number ,atItemLocationItemCode?:string,indexed?:boolean): Observable<IItem[]> {
 | 
			
		||||
    var params = new HttpParams();
 | 
			
		||||
    params = params.append("it", itemType);
 | 
			
		||||
    if(dataFilter != null){
 | 
			
		||||
      params = params.append("df", JSON.stringify(dataFilter));
 | 
			
		||||
    }
 | 
			
		||||
    params = params.append("lvl", itemType);
 | 
			
		||||
    return this.httpClient.get<IItem[]>(`${this.ApiEndpoint()}/api/v1/items/`, { params: params }).pipe(map(ia => ia.map(i => this.parseDates(i))));
 | 
			
		||||
    if(itemType)  params = params.append("it", itemType);   
 | 
			
		||||
    if(dataFilter) params = params.append("df", JSON.stringify(dataFilter));    
 | 
			
		||||
    if(atItemLocationItemCode) params = params.append("ail",atItemLocationItemCode);    
 | 
			
		||||
    if(indexed) params = params.append("ind",indexed?"true":"false");    
 | 
			
		||||
    if(level) params = params.append("lvl", level.toFixed());
 | 
			
		||||
    return this.httpClient.get<IItem[]>(`${this.ApiEndpoint()}/api/v1/items/`, { params: params });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getChildItemList(parentcode: string, itemType: string, dataFilter?: any, level: number = 1, deep: boolean = true): Observable<IItem[]> {
 | 
			
		||||
@@ -85,7 +79,7 @@ export class ItemService {
 | 
			
		||||
    }
 | 
			
		||||
    params = params.append("lvl", level.toString());
 | 
			
		||||
    params = params.append("deep", deep.toString());
 | 
			
		||||
    return this.httpClient.get<IItem[]>(`${this.ApiEndpoint()}/api/v1/items/${parentcode}/children`, { params: params }).pipe(map(ia => ia.map(i => this.parseDates(i))));
 | 
			
		||||
    return this.httpClient.get<IItem[]>(`${this.ApiEndpoint()}/api/v1/items/${parentcode}/children`, { params: params });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getChildItemListCount(parentcode: string, itemType: string): Observable<Number> {
 | 
			
		||||
@@ -103,7 +97,7 @@ export class ItemService {
 | 
			
		||||
      params = params.append("df", JSON.stringify(dataFilter));
 | 
			
		||||
    }
 | 
			
		||||
    params = params.append("lvl", level.toString());
 | 
			
		||||
    return this.httpClient.get<IItem[]>(`${this.ApiEndpoint()}/api/v1/items/${parentcode}/children`, { params: params }).pipe(map(ia => ia.map(i => this.parseDates(i))));
 | 
			
		||||
    return this.httpClient.get<IItem[]>(`${this.ApiEndpoint()}/api/v1/items/${parentcode}/children`, { params: params });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getItemFeatures(code: string, extent: number[], crs: string, layerIndex?:number): Observable<any> {
 | 
			
		||||
@@ -134,7 +128,7 @@ export class ItemService {
 | 
			
		||||
    return this.httpClient.post<any>(`${this.ApiEndpoint()}/api/v1/items/delete`, itemCodes);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getTemporalLast(code: string): Observable<any> {
 | 
			
		||||
  getTemporalLast(code: string): Observable<IJsonline> {
 | 
			
		||||
    return this.httpClient.get<any>(`${this.ApiEndpoint()}/api/v1/items/${code}/temporal/last`);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -152,6 +146,6 @@ export class ItemService {
 | 
			
		||||
  getItemTaskList(itemcode: string, unfinishedOnly?: boolean): Observable<IItemTask[]> {
 | 
			
		||||
    var params = new HttpParams();
 | 
			
		||||
    if (unfinishedOnly) params = params.append("unfinishedOnly", unfinishedOnly.toString());
 | 
			
		||||
    return this.httpClient.get<IItemTask[]>(`${this.ApiEndpoint()}/api/v1/items/${itemcode}/tasks`, { params: params }).pipe(map(ia => ia.map(i => this.parseDates(i))));
 | 
			
		||||
    return this.httpClient.get<IItemTask[]>(`${this.ApiEndpoint()}/api/v1/items/${itemcode}/tasks`, { params: params });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -9,9 +9,9 @@ export class ItemTypeService {
 | 
			
		||||
    public itemTypes: IItemTypes;
 | 
			
		||||
    private httpClient: HttpClient;
 | 
			
		||||
 | 
			
		||||
    constructor(xhrBackend: HttpXhrBackend) {          
 | 
			
		||||
    constructor(xhrBackend: HttpXhrBackend) {
 | 
			
		||||
      this.httpClient = new HttpClient(xhrBackend);
 | 
			
		||||
    } 
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
     getIcon(itemType: string) {
 | 
			
		||||
       var icon = "fa fa-file-o";
 | 
			
		||||
@@ -31,6 +31,12 @@ export class ItemTypeService {
 | 
			
		||||
      return extraAttributes;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getSchema(itemType: string): string {
 | 
			
		||||
      let schema = null;
 | 
			
		||||
      if (this.itemTypes[itemType]) schema = this.itemTypes[itemType].schema;
 | 
			
		||||
      return schema;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
     hasViewer(item: IItem) {
 | 
			
		||||
        let itemType: string = item.itemType;
 | 
			
		||||
        if (this.itemTypes[itemType]) return this.itemTypes[itemType].viewer !== undefined;
 | 
			
		||||
@@ -49,7 +55,7 @@ export class ItemTypeService {
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      public load(config:AppConfig): Promise<any> {
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        var url = `${ config.getConfig("apiEndPoint")}/api/v1/itemtypes/`
 | 
			
		||||
        return this.httpClient.get(url)
 | 
			
		||||
          .toPromise()
 | 
			
		||||
@@ -59,4 +65,4 @@ export class ItemTypeService {
 | 
			
		||||
          })
 | 
			
		||||
          .catch(error => this.itemTypes = null);
 | 
			
		||||
      };
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										43
									
								
								projects/common/src/fm/services/schema.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								projects/common/src/fm/services/schema.service.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
import {Inject, Injectable, LOCALE_ID} from '@angular/core';
 | 
			
		||||
import {HttpClient} from '@angular/common/http';
 | 
			
		||||
import {AppConfig} from '../shared/app.config';
 | 
			
		||||
import {Observable, of} from 'rxjs';
 | 
			
		||||
import {catchError, switchMap} from 'rxjs/operators';
 | 
			
		||||
 | 
			
		||||
@Injectable({
 | 
			
		||||
  providedIn: 'root',
 | 
			
		||||
})
 | 
			
		||||
export class SchemaService {
 | 
			
		||||
  constructor(private httpClient: HttpClient,  private appConfig: AppConfig,
 | 
			
		||||
              @Inject(LOCALE_ID) private locale) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ApiEndpoint() {
 | 
			
		||||
    return this.appConfig.getConfig('apiEndPoint');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** Takes decoded schema url id */
 | 
			
		||||
  public getSchema(id): Observable<any> {
 | 
			
		||||
    const encodedId = encodeURIComponent(id);
 | 
			
		||||
    return this.httpClient.get<any>(`${this.ApiEndpoint()}/api/v1/schema/${encodedId}`);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getSchemaLayout(schemaUrl: string, locale: string): Observable<any> {
 | 
			
		||||
    const encodedId = encodeURIComponent(schemaUrl);
 | 
			
		||||
    return this.httpClient.get<any>(`${this.ApiEndpoint()}/api/v1/schema/${encodedId}/layout/${locale}`);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getSchemaAndLayout(schemaUrl: string, locale: string = this.locale):
 | 
			
		||||
    Observable<{schemaJson: any, schemaLayout: any}> {
 | 
			
		||||
    return this.getSchema(schemaUrl).pipe(
 | 
			
		||||
      switchMap(
 | 
			
		||||
        schemaJson => this.getSchemaLayout(schemaUrl, locale)
 | 
			
		||||
          .pipe(catchError(err => {
 | 
			
		||||
            return of(undefined);
 | 
			
		||||
          })),
 | 
			
		||||
        (schemaJson, schemaLayout) => {
 | 
			
		||||
          return ({schemaJson, schemaLayout});
 | 
			
		||||
        }
 | 
			
		||||
      ));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								projects/common/src/fm/services/senml-service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								projects/common/src/fm/services/senml-service.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { IJsonline } from '../models/json-line';
 | 
			
		||||
import { ISenMLItem } from '../models/senml-item';
 | 
			
		||||
import { IDataLayer } from '../models/data.layer';
 | 
			
		||||
import { IItem } from '../models/item';
 | 
			
		||||
 | 
			
		||||
@Injectable({
 | 
			
		||||
  providedIn: 'root',
 | 
			
		||||
})
 | 
			
		||||
export class SenmlService {
 | 
			
		||||
    constructor() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getSenMLItem(layer:IDataLayer,jsonLine:IJsonline): ISenMLItem {
 | 
			
		||||
        if (jsonLine) {
 | 
			
		||||
          var senmlPack = jsonLine.data as ISenMLItem[];
 | 
			
		||||
          var temp = senmlPack.filter((i) => i.u == layer.indexKey);
 | 
			
		||||
          if (temp.length == 1) return temp[0];
 | 
			
		||||
          return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getFirstLayer(item:IItem):IDataLayer {
 | 
			
		||||
        if(item && item.data && item.data["layers"] && item.data["layers"].length > 0 ) {
 | 
			
		||||
            return item.data["layers"][0] as IDataLayer;
 | 
			
		||||
        } else {
 | 
			
		||||
            let retVal:IDataLayer = { name:"Soil moisture",index:0,scale:1,unit:"%",indexKey:"%vol" };
 | 
			
		||||
            return retVal;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -3,21 +3,123 @@ import {HttpClient} from '@angular/common/http';
 | 
			
		||||
import {AppConfig} from '../shared/app.config';
 | 
			
		||||
import {Observable} from 'rxjs';
 | 
			
		||||
import {WeatherCurrentObservation} from '../models/weatherCurrentObservation';
 | 
			
		||||
import {DatePipe} from '@angular/common';
 | 
			
		||||
import {WeatherData} from '../models/WeatherData';
 | 
			
		||||
import {GeoJSON} from 'ol/format';
 | 
			
		||||
import {map, switchMap} from 'rxjs/operators';
 | 
			
		||||
import {getCenter} from 'ol/extent';
 | 
			
		||||
import {IItem} from '../models/item';
 | 
			
		||||
 | 
			
		||||
@Injectable({
 | 
			
		||||
  providedIn: 'root',
 | 
			
		||||
})
 | 
			
		||||
export class WeatherService {
 | 
			
		||||
  private apiUrl = '/api/v1/weather/currentobservation';
 | 
			
		||||
  private apiCurrentObservationUrl = 'currentobservation';
 | 
			
		||||
  private apiObservation = 'observation';
 | 
			
		||||
  private apiForecast = 'forecast';
 | 
			
		||||
 | 
			
		||||
  constructor(public httpClient: HttpClient, public appConfig: AppConfig) {
 | 
			
		||||
  private format: GeoJSON;
 | 
			
		||||
 | 
			
		||||
  constructor(public httpClient: HttpClient, public appConfig: AppConfig, private datePipe: DatePipe) {
 | 
			
		||||
    this.format = new GeoJSON();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public GetCurrentObservation(centroid: number[]): Observable<WeatherCurrentObservation> {
 | 
			
		||||
    const endpoint = this.appConfig.getConfig('weatherApiEndPoint');
 | 
			
		||||
    const apiKey = this.appConfig.getConfig('weatherApiKey');
 | 
			
		||||
    const observationUrl = `${endpoint}${this.apiUrl}/?c=${centroid[0]},${centroid[1]}&key=${apiKey}`;
 | 
			
		||||
    const observationUrl = `${endpoint}${this.apiCurrentObservationUrl}/?c=${centroid[0]},${centroid[1]}&key=${apiKey}`;
 | 
			
		||||
 | 
			
		||||
    return this.httpClient.get<WeatherCurrentObservation>(observationUrl);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getWeatherRangeForItem(item: IItem, daysBefore = 10, daysAfter = 10): Observable<WeatherData[]> {
 | 
			
		||||
    const geometry = this.format.readGeometry(item.geometry);
 | 
			
		||||
    const centroid = getCenter(geometry.getExtent());
 | 
			
		||||
 | 
			
		||||
    const currentDate = new Date(Date.now());
 | 
			
		||||
    const sd = new Date(Date.now());
 | 
			
		||||
    const ed = new Date(Date.now());
 | 
			
		||||
    sd.setDate((currentDate.getDate() - daysBefore));
 | 
			
		||||
    ed.setDate((currentDate.getDate() + daysAfter));
 | 
			
		||||
 | 
			
		||||
    return this.getWeatherRange(centroid, this.datePipe.transform(sd, 'yyyy-MM-ddThh:mm:ss'),
 | 
			
		||||
      this.datePipe.transform(ed, 'yyyy-MM-ddThh:mm:ss'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getWeatherRange(centroid: number[], startDate: string, endDate: string): Observable<WeatherData[]> {
 | 
			
		||||
    const endpoint = this.appConfig.getConfig('weatherApiEndPoint');
 | 
			
		||||
    const apiKey = this.appConfig.getConfig('weatherApiKey');
 | 
			
		||||
 | 
			
		||||
    // weather does not support UTC format, also remove Z
 | 
			
		||||
    const sd = encodeURIComponent(this.removeUTCZ(startDate));
 | 
			
		||||
    const ed = encodeURIComponent(this.removeUTCZ(endDate));
 | 
			
		||||
 | 
			
		||||
    const historical = `${endpoint}${this.apiObservation}/?c=${centroid[0]},${centroid[1]}&sd=${sd}&ed=${ed}&t=observation&interval=hourly&key=${apiKey}`;
 | 
			
		||||
    const forecast = `${endpoint}${this.apiForecast}/?c=${centroid[0]},${centroid[1]}&interval=hourly&key=${apiKey}`;
 | 
			
		||||
 | 
			
		||||
    return this.httpClient.get<any[]>(historical).pipe(
 | 
			
		||||
      map(h => h.map(d => ({...d, rain: d.rainPastHour}))),
 | 
			
		||||
      switchMap(h => {
 | 
			
		||||
        return this.httpClient.get<any[]>(forecast)
 | 
			
		||||
          .pipe(
 | 
			
		||||
            map(f => this.hourlyToDaily([...h, ...f.filter(fd => fd.time <= endDate)])
 | 
			
		||||
            ));
 | 
			
		||||
      })
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private hourlyToDaily(hourlyWeatherData: any[]): WeatherData[] {
 | 
			
		||||
    const days = this.groupBy(hourlyWeatherData, hi => hi.time.split('T')[0]);
 | 
			
		||||
    return Object.entries(days)
 | 
			
		||||
      .reduce ( (result, entry) => {
 | 
			
		||||
        const wData = entry[1];
 | 
			
		||||
 | 
			
		||||
        const sortData = wData.sort(d => new Date(d.time).getHours() - 12);
 | 
			
		||||
        const closestToTwelveOClockData =  sortData[0];
 | 
			
		||||
        if (closestToTwelveOClockData.length === 0) {
 | 
			
		||||
          return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const bestCardinal = closestToTwelveOClockData.windDirectionCardinal;
 | 
			
		||||
        const bestWindDirection = closestToTwelveOClockData.windDirection;
 | 
			
		||||
        const bestIconCode = closestToTwelveOClockData.iconCode;
 | 
			
		||||
        return [
 | 
			
		||||
          ...result,
 | 
			
		||||
          {
 | 
			
		||||
            date: entry[0],
 | 
			
		||||
            iconCode: bestIconCode,
 | 
			
		||||
            minTemperature: Math.min(...wData.map(d => d.temperature)),
 | 
			
		||||
            maxTemperature: Math.max(...wData.map(d => d.temperature)),
 | 
			
		||||
            relHumidity: wData.reduce( (r, e) => r + e.relativeHumidity, 0) / wData.length,
 | 
			
		||||
            precipitation: wData.reduce( (r, e) => r + e.rain, 0) * 10,
 | 
			
		||||
            wSpeed: wData.reduce( (r, e) => r + e.windSpeed, 0) / wData.length,
 | 
			
		||||
            wDir: bestWindDirection,
 | 
			
		||||
            wCardinal: bestCardinal
 | 
			
		||||
          }
 | 
			
		||||
        ];
 | 
			
		||||
      }, []);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private groupBy(items: any[], selector: (item) => any): any[] {
 | 
			
		||||
    return items.reduce(
 | 
			
		||||
      (result, item) => {
 | 
			
		||||
        const group = selector(item);
 | 
			
		||||
        return ({
 | 
			
		||||
          ...result,
 | 
			
		||||
          [group]: [
 | 
			
		||||
            ...(result[group] || []),
 | 
			
		||||
            item,
 | 
			
		||||
          ],
 | 
			
		||||
        });},
 | 
			
		||||
      {},
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private removeUTCZ(dateFormat: string): string {
 | 
			
		||||
    if (dateFormat[dateFormat.length - 1] === 'Z') {
 | 
			
		||||
      return dateFormat.substring(0, dateFormat.length - 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return dateFormat;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,47 +1,45 @@
 | 
			
		||||
import { Injectable, Injector, Inject } from '@angular/core';
 | 
			
		||||
import { DOCUMENT } from '@angular/common'
 | 
			
		||||
import { AppConfig } from "./app.config";
 | 
			
		||||
import {
 | 
			
		||||
  HttpRequest,
 | 
			
		||||
  HttpHandler,
 | 
			
		||||
  HttpEvent,
 | 
			
		||||
  HttpInterceptor
 | 
			
		||||
} from '@angular/common/http';
 | 
			
		||||
import { OAuthService } from 'angular-oauth2-oidc';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class AccessTokenInterceptor implements HttpInterceptor {
 | 
			
		||||
  private oauthService: OAuthService = null;
 | 
			
		||||
  private audience: string[] = [];
 | 
			
		||||
  private base: string;
 | 
			
		||||
 | 
			
		||||
  constructor(private injector: Injector, private appConfig: AppConfig, @Inject(DOCUMENT) private document: any) {   
 | 
			
		||||
    this.base = document.location.href;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  hasAudience(url: string): boolean {
 | 
			
		||||
    let u = new URL(url,this.base);
 | 
			
		||||
    for (let audience of this.audience) {
 | 
			
		||||
      if (u.href.startsWith(audience)) return true;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
 | 
			
		||||
    if (this.oauthService && this.hasAudience(request.url)) {
 | 
			
		||||
      request = request.clone({
 | 
			
		||||
        setHeaders: {
 | 
			
		||||
          Authorization: `Bearer ${this.oauthService.getAccessToken()}`
 | 
			
		||||
        }
 | 
			
		||||
        // Please uncomment the next line if you need to connect to the backend running in Docker.
 | 
			
		||||
        //, url: `http://localhost:8082${request.url}`
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      this.oauthService = this.injector.get(OAuthService, null);
 | 
			
		||||
      if(this.oauthService && this.oauthService.issuer) this.audience = (this.appConfig.getConfig("audience") as string).split(",");
 | 
			
		||||
    }
 | 
			
		||||
    return next.handle(request);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
import { Injectable, Injector, Inject } from '@angular/core';
 | 
			
		||||
import { DOCUMENT } from '@angular/common'
 | 
			
		||||
import { AppConfig } from "./app.config";
 | 
			
		||||
import {
 | 
			
		||||
  HttpRequest,
 | 
			
		||||
  HttpHandler,
 | 
			
		||||
  HttpEvent,
 | 
			
		||||
  HttpInterceptor
 | 
			
		||||
} from '@angular/common/http';
 | 
			
		||||
import { OAuthService } from 'angular-oauth2-oidc';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class AccessTokenInterceptor implements HttpInterceptor {
 | 
			
		||||
  private oauthService: OAuthService = null;
 | 
			
		||||
  private audience: string[] = [];
 | 
			
		||||
  private base: string;
 | 
			
		||||
 | 
			
		||||
  constructor(private injector: Injector, private appConfig: AppConfig, @Inject(DOCUMENT) private document: any) {   
 | 
			
		||||
    this.base = document.location.href;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  hasAudience(url: string): boolean {
 | 
			
		||||
    let u = new URL(url,this.base);
 | 
			
		||||
    for (let audience of this.audience) {
 | 
			
		||||
      if (u.href.startsWith(audience)) return true;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
 | 
			
		||||
    if (this.oauthService && this.hasAudience(request.url)) {
 | 
			
		||||
      request = request.clone({
 | 
			
		||||
        setHeaders: {
 | 
			
		||||
          Authorization: `Bearer ${this.oauthService.getAccessToken()}`
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      this.oauthService = this.injector.get(OAuthService, null);
 | 
			
		||||
      if(this.oauthService && this.oauthService.issuer) this.audience = (this.appConfig.getConfig("audience") as string).split(",");
 | 
			
		||||
    }
 | 
			
		||||
    return next.handle(request);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,10 +14,10 @@ export function appConfigFactory(injector:Injector, appConfig: AppConfig, oauthS
 | 
			
		||||
      appConfig.load().then(() => {     
 | 
			
		||||
        oauthService.events.subscribe((event) => {
 | 
			
		||||
          console.debug(event.type);
 | 
			
		||||
          if (event.type == 'token_error' || event.type == 'silent_refresh_timeout') {
 | 
			
		||||
          if (event.type == 'token_error' || event.type == 'silent_refresh_timeout' || event.type == 'logout') {
 | 
			
		||||
            let e = event as OAuthErrorEvent;
 | 
			
		||||
            let p = e.params as any;
 | 
			
		||||
            if (event.type == 'silent_refresh_timeout' || (p.error && p.error == 'login_required')) {
 | 
			
		||||
            if (event.type == 'silent_refresh_timeout' || event.type == 'logout' || (p.error && p.error == 'login_required')) {
 | 
			
		||||
              let router = injector.get(Router);
 | 
			
		||||
              console.debug("Session expired");
 | 
			
		||||
              router.navigate(['loggedout'], { queryParams: { redirectTo: router.url } });
 | 
			
		||||
 
 | 
			
		||||
@@ -6,4 +6,9 @@ import { Component } from '@angular/core';
 | 
			
		||||
})
 | 
			
		||||
export class AppRootComponent {
 | 
			
		||||
  title = 'FarmMaps';
 | 
			
		||||
  constructor() {
 | 
			
		||||
    
 | 
			
		||||
    window.addEventListener('online', () => console.log('came online'));
 | 
			
		||||
    window.addEventListener('offline', () => console.log('came offline'));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ export class Id4AuthconfigFactory implements IAuthconfigFactory {
 | 
			
		||||
    authConfig.redirectUri = window.location.origin + "/cb";
 | 
			
		||||
    authConfig.clientId = appConfig.getConfig("clientId");
 | 
			
		||||
    authConfig.customQueryParams = { audience: appConfig.getConfig("audience") };
 | 
			
		||||
    authConfig.scope = "profile api offline_access";
 | 
			
		||||
    authConfig.scope = "openid profile api offline_access";
 | 
			
		||||
    authConfig.disableAtHashCheck = true;
 | 
			
		||||
    authConfig.responseType = "code";
 | 
			
		||||
    authConfig.requireHttps = appConfig.getConfig("requireHttps");
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user