From ef628fcf10cba8f8f0292e1e90f45ebcca4e2401 Mon Sep 17 00:00:00 2001 From: Mathias Boeck Date: Mon, 19 Sep 2022 08:24:34 +0200 Subject: [PATCH] Layer control enhancements (#142) * fix: show button for settings if layer has action; closes #135 * fix: Show layer expand icon only when the layer has some things to expand * refactor: move style switcher into settings tab * feat: Allow dynamic components for layer.description * feat: Allow dynamic components for layer.description - set input * feat: adjust types to allow Dynamic components for layer description * fix: Do not change the layer object binding in layerentry * refactor: add example for layer.description as component * feat: use inputs.description if possible to generate abstract from dynamic component * fix: Show layer expand icon if filtertype not Baselayers * fix: remove right border on tabsbody for layers inside a group * fix: Do not change the group object binding in * fix: toggle group tabs and show all settings * feat: Allow Dynamic components for Group description * feat: Allow legend on LayerGroup #138 * refactor: add example legend and description components for group * fix: style for group tabsbody and actions button position * refactor: set input on layer IDynamicComponent - no object change needed after c6c437e59c81b2478f0855eadd48f2d450a42cf1 --- CHANGELOG.md | 15 +++ .../files/src/styles/_ukis-layer-nav.scss | 8 +- .../demo-auth/src/styles/_ukis-layer-nav.scss | 8 +- projects/demo-maps/src/app/app.module.ts | 6 +- .../example-group-legend.component.html | 11 +++ .../example-group-legend.component.scss | 8 ++ .../example-group-legend.component.spec.ts | 23 +++++ .../example-group-legend.component.ts | 18 ++++ .../example-layer-action.component.ts | 18 +++- .../example-layer-description.component.html | 12 +++ .../example-layer-description.component.scss | 9 ++ ...xample-layer-description.component.spec.ts | 23 +++++ .../example-layer-description.component.ts | 16 +++ .../route-map4.component.ts | 4 +- .../route-map.component.ts | 20 +++- .../demo-maps/src/styles/_ukis-layer-nav.scss | 8 +- .../layerentry-group.component.html | 45 ++++++--- .../layerentry-group.component.scss | 11 +++ .../layerentry-group.component.ts | 99 +++++++++++++++---- .../lib/layerentry/layerentry.component.html | 40 ++++---- .../lib/layerentry/layerentry.component.ts | 70 +++++++++---- .../src/lib/types/LayerGroup.ts | 8 +- .../services-layers/src/lib/types/Layers.ts | 4 +- .../src/lib/owc/owc-json.service.spec.ts | 2 +- .../src/lib/owc/owc-json.service.ts | 21 +++- 25 files changed, 412 insertions(+), 95 deletions(-) create mode 100644 projects/demo-maps/src/app/components/example-group-legend/example-group-legend.component.html create mode 100644 projects/demo-maps/src/app/components/example-group-legend/example-group-legend.component.scss create mode 100644 projects/demo-maps/src/app/components/example-group-legend/example-group-legend.component.spec.ts create mode 100644 projects/demo-maps/src/app/components/example-group-legend/example-group-legend.component.ts create mode 100644 projects/demo-maps/src/app/components/example-layer-description/example-layer-description.component.html create mode 100644 projects/demo-maps/src/app/components/example-layer-description/example-layer-description.component.scss create mode 100644 projects/demo-maps/src/app/components/example-layer-description/example-layer-description.component.spec.ts create mode 100644 projects/demo-maps/src/app/components/example-layer-description/example-layer-description.component.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index fbe6bc903..b0a604a8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,19 @@ +### Features +* **@dlr-eoc/layer-control:** + - Allow legend on LayerGroup like on the layer - if one legend fits for all layers. + - Allow Dynamic components for Group description to display specific formatted descriptions. + - Allow Dynamic components for Layer description to display specific formatted descriptions (keep in mind that this breaks @dlr-eoc/services-ogc `generateResourceFromLayer` for this layer!!! - If you pass inputs.description to `IDynamicComponent` it will try to take this). + + ### Bug Fixes +* **@dlr-eoc/core-ui:** + - Fix style: remove right border on tabsbody for layers inside a group. + +* **@dlr-eoc/layer-control:** + - Do not change the group object binding in `layerentry-group` when creating a dynamic component from the group. + - Do not change the layer object binding in `layerentry` when creating a dynamic component from the layer. + - For Baselayers also show button to switch to the settings tab if layer has action [Issue #135](https://github.com/dlr-eoc/ukis-frontend-libraries/issues/135). + - Show layer expand icon for 'Baselayers' only when the layer has some things to expand (`legendImg`, `description`, `action`, `actions` or `styles` as array to show a style switch). * **@dlr-eoc/services-map-state:** - Add missing time value in function `setMapState` on state initialization [issue 133](https://github.com/dlr-eoc/ukis-frontend-libraries/issues/133). diff --git a/projects/core-ui/schematics/ng-add/files/src/styles/_ukis-layer-nav.scss b/projects/core-ui/schematics/ng-add/files/src/styles/_ukis-layer-nav.scss index 2b2d45152..2c0ec7b2a 100644 --- a/projects/core-ui/schematics/ng-add/files/src/styles/_ukis-layer-nav.scss +++ b/projects/core-ui/schematics/ng-add/files/src/styles/_ukis-layer-nav.scss @@ -109,9 +109,11 @@ } .layergroup { - .body { - .tabsbody { - border-right: none; + .layer{ + .body { + .tabsbody { + border-right: none; + } } } } diff --git a/projects/demo-auth/src/styles/_ukis-layer-nav.scss b/projects/demo-auth/src/styles/_ukis-layer-nav.scss index 2b2d45152..2c0ec7b2a 100644 --- a/projects/demo-auth/src/styles/_ukis-layer-nav.scss +++ b/projects/demo-auth/src/styles/_ukis-layer-nav.scss @@ -109,9 +109,11 @@ } .layergroup { - .body { - .tabsbody { - border-right: none; + .layer{ + .body { + .tabsbody { + border-right: none; + } } } } diff --git a/projects/demo-maps/src/app/app.module.ts b/projects/demo-maps/src/app/app.module.ts index 711510d66..b056cac9e 100644 --- a/projects/demo-maps/src/app/app.module.ts +++ b/projects/demo-maps/src/app/app.module.ts @@ -41,6 +41,8 @@ import { RasterFeatureInfoComponent } from './components/raster-feature-info/ras import { Popup2Component } from './components/popup2/popup2.component'; import { RouteExampleOwcLayersComponent } from './route-components/route-example-owc-layers/route-example-owc-layers.component'; import { BookmarksComponent } from './route-components/bookmarks/bookmarks.component'; +import { ExampleLayerDescriptionComponent } from './components/example-layer-description/example-layer-description.component'; +import { ExampleGroupLegendComponent } from './components/example-group-legend/example-group-legend.component'; @NgModule({ declarations: [ @@ -69,7 +71,9 @@ import { BookmarksComponent } from './route-components/bookmarks/bookmarks.compo RasterFeatureInfoComponent, Popup2Component, RouteExampleOwcLayersComponent, - BookmarksComponent + BookmarksComponent, + ExampleLayerDescriptionComponent, + ExampleGroupLegendComponent ], imports: [ BrowserModule, diff --git a/projects/demo-maps/src/app/components/example-group-legend/example-group-legend.component.html b/projects/demo-maps/src/app/components/example-group-legend/example-group-legend.component.html new file mode 100644 index 000000000..020005774 --- /dev/null +++ b/projects/demo-maps/src/app/components/example-group-legend/example-group-legend.component.html @@ -0,0 +1,11 @@ +
+ + + + + + + top + below + +
diff --git a/projects/demo-maps/src/app/components/example-group-legend/example-group-legend.component.scss b/projects/demo-maps/src/app/components/example-group-legend/example-group-legend.component.scss new file mode 100644 index 000000000..a47721319 --- /dev/null +++ b/projects/demo-maps/src/app/components/example-group-legend/example-group-legend.component.scss @@ -0,0 +1,8 @@ +.wrapper { + position: relative; + + .stacked { + position: absolute; + pointer-events: none; + } +} diff --git a/projects/demo-maps/src/app/components/example-group-legend/example-group-legend.component.spec.ts b/projects/demo-maps/src/app/components/example-group-legend/example-group-legend.component.spec.ts new file mode 100644 index 000000000..b3cdf957d --- /dev/null +++ b/projects/demo-maps/src/app/components/example-group-legend/example-group-legend.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ExampleGroupLegendComponent } from './example-group-legend.component'; + +describe('ExampleGroupLegendComponent', () => { + let component: ExampleGroupLegendComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ExampleGroupLegendComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ExampleGroupLegendComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/demo-maps/src/app/components/example-group-legend/example-group-legend.component.ts b/projects/demo-maps/src/app/components/example-group-legend/example-group-legend.component.ts new file mode 100644 index 000000000..e614211ac --- /dev/null +++ b/projects/demo-maps/src/app/components/example-group-legend/example-group-legend.component.ts @@ -0,0 +1,18 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { LayerGroup } from '@dlr-eoc/services-layers'; + +@Component({ + selector: 'app-example-group-legend', + templateUrl: './example-group-legend.component.html', + styleUrls: ['./example-group-legend.component.scss'] +}) +export class ExampleGroupLegendComponent implements OnInit { + @Input() group: LayerGroup; + legendImages = []; + constructor() { } + + ngOnInit(): void { + this.legendImages = this.group.layers.filter(l => l.legendImg && typeof l.legendImg === 'string').map(i => { return { url: i.legendImg } }).reverse(); + } + +} diff --git a/projects/demo-maps/src/app/components/example-layer-action/example-layer-action.component.ts b/projects/demo-maps/src/app/components/example-layer-action/example-layer-action.component.ts index bebe8e62f..16496c433 100644 --- a/projects/demo-maps/src/app/components/example-layer-action/example-layer-action.component.ts +++ b/projects/demo-maps/src/app/components/example-layer-action/example-layer-action.component.ts @@ -1,27 +1,35 @@ -import { Component, OnInit, Input, Output, EventEmitter, Inject } from '@angular/core'; +import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; @Component({ selector: 'app-example-layer-action', templateUrl: './example-layer-action.component.html', styleUrls: ['./example-layer-action.component.scss'] }) -export class ExampleLayerActionComponent { +export class ExampleLayerActionComponent implements OnInit { @Input() layer; private privValue = 1; @Input() set value(value: number) { - this.privValue = value; - if (this.layer.custom_layer) { - this.layer.custom_layer.setRadius(value); + if (this.privValue !== value) { + this.privValue = value; + + if (this.layer?.custom_layer) { + this.layer.custom_layer.setRadius(value); + } } } get value() { return this.privValue; } + @Output() valueChange = new EventEmitter(); constructor() { } + ngOnInit(): void { + this.layer.custom_layer.setRadius(this.value); + } + changeValue(event) { const value = parseInt(event.target.value, 10); this.value = value; diff --git a/projects/demo-maps/src/app/components/example-layer-description/example-layer-description.component.html b/projects/demo-maps/src/app/components/example-layer-description/example-layer-description.component.html new file mode 100644 index 000000000..2c2cf6968 --- /dev/null +++ b/projects/demo-maps/src/app/components/example-layer-description/example-layer-description.component.html @@ -0,0 +1,12 @@ +
+ {{this.description}}. +

+ This description was styled with a dynamic component as the layer.description. +

+ +

+ Keep in mind that this breaks @dlr-eoc/services-ogc generateResourceFromLayer() for this layer when exporting a + IOwsContext. +

+ +
diff --git a/projects/demo-maps/src/app/components/example-layer-description/example-layer-description.component.scss b/projects/demo-maps/src/app/components/example-layer-description/example-layer-description.component.scss new file mode 100644 index 000000000..c258ab4d1 --- /dev/null +++ b/projects/demo-maps/src/app/components/example-layer-description/example-layer-description.component.scss @@ -0,0 +1,9 @@ +.custom-description { + word-break: normal; + color: #0072a3; + + p { + word-break: normal; + color: rgb(255, 165, 46); + } +} diff --git a/projects/demo-maps/src/app/components/example-layer-description/example-layer-description.component.spec.ts b/projects/demo-maps/src/app/components/example-layer-description/example-layer-description.component.spec.ts new file mode 100644 index 000000000..2b6eda7d7 --- /dev/null +++ b/projects/demo-maps/src/app/components/example-layer-description/example-layer-description.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ExampleLayerDescriptionComponent } from './example-layer-description.component'; + +describe('ExampleLayerDescriptionComponent', () => { + let component: ExampleLayerDescriptionComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ExampleLayerDescriptionComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ExampleLayerDescriptionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/demo-maps/src/app/components/example-layer-description/example-layer-description.component.ts b/projects/demo-maps/src/app/components/example-layer-description/example-layer-description.component.ts new file mode 100644 index 000000000..3a4e09d84 --- /dev/null +++ b/projects/demo-maps/src/app/components/example-layer-description/example-layer-description.component.ts @@ -0,0 +1,16 @@ +import { Component, Input, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-example-layer-description', + templateUrl: './example-layer-description.component.html', + styleUrls: ['./example-layer-description.component.scss'] +}) +export class ExampleLayerDescriptionComponent implements OnInit { + @Input() layer; + @Input() description; + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/projects/demo-maps/src/app/route-components/route-example-custom-layers/route-map4.component.ts b/projects/demo-maps/src/app/route-components/route-example-custom-layers/route-map4.component.ts index f5a6c0dcc..85292120e 100644 --- a/projects/demo-maps/src/app/route-components/route-example-custom-layers/route-map4.component.ts +++ b/projects/demo-maps/src/app/route-components/route-example-custom-layers/route-map4.component.ts @@ -66,9 +66,7 @@ export class RouteMap4Component implements OnInit, AfterViewInit { setInput() { const layer = this.layersSvc.getLayerById('heatmap_layer') as CustomLayer; this.inputValue.value = 40; - layer.action.inputs = this.inputValue; - /** change object ref to trigger input change */ - layer.action = Object.assign({}, layer.action); + layer.action.inputs.value = this.inputValue.value; } addLayers() { diff --git a/projects/demo-maps/src/app/route-components/route-example-layers/route-map.component.ts b/projects/demo-maps/src/app/route-components/route-example-layers/route-map.component.ts index b145e5c2d..1bcccb574 100644 --- a/projects/demo-maps/src/app/route-components/route-example-layers/route-map.component.ts +++ b/projects/demo-maps/src/app/route-components/route-example-layers/route-map.component.ts @@ -12,6 +12,8 @@ import olFill from 'ol/style/Fill'; import olCircleStyle from 'ol/style/Circle'; import olStroke from 'ol/style/Stroke'; import { WmsService } from '@dlr-eoc/services-ogc'; +import { ExampleLayerDescriptionComponent } from '../../components/example-layer-description/example-layer-description.component'; +import { ExampleGroupLegendComponent } from '../../components/example-group-legend/example-group-legend.component'; @Component({ selector: 'app-route-map', @@ -93,7 +95,10 @@ export class RouteMapComponent implements OnInit { } }, visible: false, - description: 'eoc:world_relief_bw as web map tile service', + description: { + component: ExampleLayerDescriptionComponent, + inputs: { description: 'eoc:world_relief_bw as web map tile service' } + }, attribution: 'Relief: DLR/EOC', legendImg: '' }); @@ -148,8 +153,7 @@ export class RouteMapComponent implements OnInit { // maxZoom: 8, description: 'TDM90_DEM maxZoom: 8', attribution: ' | TDM90 Data ©: DLR licensed for scientific use', - legendImg: '', - expanded: true, + legendImg: 'https://tiles.geoservice.dlr.de/service/wmts?layer=TDM90_DEM&style=default&tilematrixset=EPSG%3A3857&Service=WMTS&Request=GetTile&Version=1.0.0&Format=image%2Fpng&TileMatrix=EPSG%3A3857%3A4&TileCol=8&TileRow=5', cssClass: 'custom-layer' }); @@ -430,6 +434,7 @@ export class RouteMapComponent implements OnInit { const eocBaseoverlay = new EocBaseoverlayTile(); const eocLiteoverlay = new EocLiteoverlayTile(); + eocLiteoverlay.legendImg = 'https://tiles.geoservice.dlr.de/service/wmts?layer=eoc%3Aliteoverlay&style=_empty&tilematrixset=EPSG%3A3857&Service=WMTS&Request=GetTile&Version=1.0.0&Format=image%2Fpng&TileMatrix=EPSG%3A3857%3A4&TileCol=8&TileRow=5'; const OsmLayer = new OsmTileLayer(); /** add a Group of layers */ @@ -440,13 +445,20 @@ export class RouteMapComponent implements OnInit { name: 'Test Group', layers: [OsmLayer, eocBasemap, eocBaseoverlay], description: 'this is a group with OsmLayer, eocBasemap, eocBaseoverlay', + expanded: true, actions: [{ title: 'download', icon: 'download-cloud', action: (group) => { console.log(group); } }] }); const groupLayer2 = new LayerGroup({ id: 'group_2', name: 'Test Group 2', - expanded: true, + description: { + component: ExampleLayerDescriptionComponent, + inputs: { description: `A LayerGroup with a hidden vectorLayer2.` } + }, + legendImg: { + component: ExampleGroupLegendComponent, + }, cssClass: 'custom-layer-group', layers: [TDM90DEMLayer, vectorLayer2, eocLiteoverlay] }); diff --git a/projects/demo-maps/src/styles/_ukis-layer-nav.scss b/projects/demo-maps/src/styles/_ukis-layer-nav.scss index 2b2d45152..2c0ec7b2a 100644 --- a/projects/demo-maps/src/styles/_ukis-layer-nav.scss +++ b/projects/demo-maps/src/styles/_ukis-layer-nav.scss @@ -109,9 +109,11 @@ } .layergroup { - .body { - .tabsbody { - border-right: none; + .layer{ + .body { + .tabsbody { + border-right: none; + } } } } diff --git a/projects/layer-control/src/lib/layerentry-group/layerentry-group.component.html b/projects/layer-control/src/lib/layerentry-group/layerentry-group.component.html index db1c8bb1a..79c2a71ab 100644 --- a/projects/layer-control/src/lib/layerentry-group/layerentry-group.component.html +++ b/projects/layer-control/src/lib/layerentry-group/layerentry-group.component.html @@ -35,19 +35,22 @@
- - - - + + + - + + + @@ -55,16 +58,36 @@ title="Remove group">
-
- + +
+ + + + + + + +
-
+ + +
+ +
+ + + + + + + +
& { legendImg: IDynamicComponent, action: IDynamicComponent, description: IDynamicComponent } { + /** + * TODO: This function is executed quite often!!! even if a user moves on tha map. Try to minimize work here or prevent calling it so often. + * + * creating new objects is needed to pass and change Inputs from the groups DynamicComponent to the dynamically created component bound on the layer. + * There is a new object created to hold the component, inputs and outputs so the group can be passed to the inputs without adding it recursively to itself. + **/ + + // https://stackoverflow.com/a/65347533/10850021 + const obj: IDynamicComponent = group[compProp]; let isComp = false; if (obj && typeof obj === 'object') { if ('component' in obj) { + const component = obj.component; + if (!obj.inputs) { - // https://2ality.com/2014/01/object-assign.html#2.3 - const groupClone = Object.assign({ __proto__: this.group['__proto__'] }, group); - if (groupClone && groupClone[compProp]) { - delete groupClone[compProp]; + this.dynamicComponents[compProp] = { + component: component, + inputs: { group: group } } - obj.inputs = { group: groupClone }; + } else if (obj.inputs && !obj.inputs.group) { + this.dynamicComponents[compProp] = { + component: obj.component, + // create a shallow copy of inputs so they are not changed on the original group + // keep in mind changing some deeper properties will reflect to the original group! + // https://2ality.com/2014/01/object-assign.html#2.3 + inputs: Object.assign({}, obj.inputs, { group: group }) + }; + + } else if (obj.inputs && obj.inputs.group) { + this.dynamicComponents[compProp] = { + component: obj.component, + // create a shallow copy of inputs so they are not changed on the original group + // keep in mind changing some deeper properties will reflect to the original group! + // https://2ality.com/2014/01/object-assign.html#2.3 + inputs: Object.assign({}, obj.inputs) + }; + } + + if (obj.outputs) { + // create a shallow copy of outputs so they are not changed on the original group + // keep in mind changing some deeper properties will reflect to the original group! // https://2ality.com/2014/01/object-assign.html#2.3 - const groupClone = Object.assign({ __proto__: this.group['__proto__'] }, group); - if (groupClone && groupClone[compProp]) { - delete groupClone[compProp]; - } - obj.inputs = Object.assign({ group: groupClone }, obj.inputs); + this.dynamicComponents[compProp].outputs = Object.assign({}, obj.outputs); } isComp = true; } @@ -136,10 +174,35 @@ export class LayerentryGroupComponent implements OnInit { this.group.expanded = !this.group.expanded; } + switchTab(tabName: TactiveTabs, toggle = true) { + for (const key of Object.keys(this.activeTabs)) { + const isTabName = tabName === key; + if (this.activeTabs[key] && toggle) { + this.activeTabs[key] = false; + } else { + this.activeTabs[key] = isTabName; + } + } + } + showHideAllDetails() { - this.openAllLayersProperties = !this.openAllLayersProperties; - this.showAction = this.openAllLayersProperties; - this.showInfo = this.openAllLayersProperties; + if (this.openAllLayersProperties) { + this.openAllLayersProperties = false; + for (const key of Object.keys(this.activeTabs)) { + this.activeTabs[key] = false; + } + + } else { + this.openAllLayersProperties = true; + + if (this.group.legendImg) { + this.switchTab('legend', false); + } else if (this.group.description) { + this.switchTab('description', false); + } else if (this.group.action) { + this.switchTab('settings', false); + } + } } isFirst(group) { diff --git a/projects/layer-control/src/lib/layerentry/layerentry.component.html b/projects/layer-control/src/lib/layerentry/layerentry.component.html index dd34878f7..6f3861d55 100644 --- a/projects/layer-control/src/lib/layerentry/layerentry.component.html +++ b/projects/layer-control/src/lib/layerentry/layerentry.component.html @@ -1,7 +1,7 @@
- @@ -40,14 +40,12 @@
- + - @@ -60,25 +58,33 @@
-
+
-
+
{{" "+layer.opacity}}
+ + + + + - +
- + @@ -87,14 +93,14 @@
- -
- -
- + + + + + + + +
diff --git a/projects/layer-control/src/lib/layerentry/layerentry.component.ts b/projects/layer-control/src/lib/layerentry/layerentry.component.ts index e37ce00ff..ece9539b4 100644 --- a/projects/layer-control/src/lib/layerentry/layerentry.component.ts +++ b/projects/layer-control/src/lib/layerentry/layerentry.component.ts @@ -44,10 +44,16 @@ export class LayerentryComponent implements OnInit { public activeTabs = { settings: false, legend: true, - description: false, - changeStyle: false + description: false }; + public hasTabsbody = true; + + public dynamicComponents: { + legendImg: IDynamicComponent + action: IDynamicComponent + description: IDynamicComponent; + } = { legendImg: null, action: null, description: null }; constructor() { @@ -55,29 +61,54 @@ export class LayerentryComponent implements OnInit { /** * obj: {any| IDynamicComponent} + * + * Check if the compProp on the layer is a dynamic Component, if yes pass it to `` so the component from the layer can be inserted here. */ - checkIsComponentItem(layer: Layer, compProp: string): layer is Omit & { legendImg: IDynamicComponent, action: IDynamicComponent } { + checkIsComponentItem(layer: Layer, compProp: string): layer is Omit & { legendImg: IDynamicComponent, action: IDynamicComponent, description: IDynamicComponent } { + /** + * TODO: This function is executed quite often!!! even if a user moves on tha map. Try to minimize work here or prevent calling it so often. + * + * creating new objects is needed to pass and change Inputs from the layers DynamicComponent to the dynamically created component bound on the layer. + * There is a new object created to hold the component, inputs and outputs so the layer can be passed to the inputs without adding it recursively to itself. + **/ + // https://stackoverflow.com/a/65347533/10850021 - const obj = layer[compProp]; + const obj: IDynamicComponent = layer[compProp]; let isComp = false; if (obj && typeof obj === 'object') { if ('component' in obj) { + const component = obj.component; + if (!obj.inputs) { - // https://2ality.com/2014/01/object-assign.html#2.3 - const layerClone = Object.assign({ __proto__: this.layer['__proto__'] }, layer); - console.log(layerClone) - if (layerClone && layerClone[compProp]) { - delete layerClone[compProp]; + this.dynamicComponents[compProp] = { + component: component, + inputs: { layer: layer } } - obj.inputs = { layer: layerClone }; + } else if (obj.inputs && !obj.inputs.layer) { + this.dynamicComponents[compProp] = { + component: obj.component, + // create a shallow copy of inputs so they are not changed on the original layer + // keep in mind changing some deeper properties will reflect to the original layer! + // https://2ality.com/2014/01/object-assign.html#2.3 + inputs: Object.assign({}, obj.inputs, { layer: layer }) + }; + + } else if (obj.inputs && obj.inputs.layer) { + this.dynamicComponents[compProp] = { + component: obj.component, + // create a shallow copy of inputs so they are not changed on the original layer + // keep in mind changing some deeper properties will reflect to the original layer! + // https://2ality.com/2014/01/object-assign.html#2.3 + inputs: Object.assign({}, obj.inputs) + }; + } + + if (obj.outputs) { + // create a shallow copy of outputs so they are not changed on the original layer + // keep in mind changing some deeper properties will reflect to the original layer! // https://2ality.com/2014/01/object-assign.html#2.3 - const layerClone = Object.assign({ __proto__: this.layer['__proto__'] }, layer); - console.log(layerClone) - if (layerClone && layerClone[compProp]) { - delete layerClone[compProp]; - } - obj.inputs = Object.assign({ layer: layerClone }, obj.inputs); + this.dynamicComponents[compProp].outputs = Object.assign({}, obj.outputs); } isComp = true; } @@ -102,7 +133,6 @@ export class LayerentryComponent implements OnInit { this.activeTabs.description = true; this.activeTabs.legend = false; this.activeTabs.settings = false; - this.activeTabs.changeStyle = false; } if (!this.layer.legendImg && !this.layer.description) { @@ -114,6 +144,10 @@ export class LayerentryComponent implements OnInit { if (this.layer.bbox && this.layer.bbox.length >= 4) { this.canZoomToLayer = true; } + + if (this.layer.filtertype === 'Baselayers' && !this.layer.legendImg && !this.layer.description && !this.layer.action && !this.layer.actions && !this.layer.styles && !(this.layer.styles?.length > 1)) { + this.hasTabsbody = false; + } } /** @@ -223,7 +257,7 @@ export class LayerentryComponent implements OnInit { if (this.group) { return !this.layer.legendImg && this.group.filtertype === 'Baselayers'; } else { - return false; // !this.layer.legendImg; //this.layer.description + return !this.hasTabsbody; } } diff --git a/projects/services-layers/src/lib/types/LayerGroup.ts b/projects/services-layers/src/lib/types/LayerGroup.ts index b9d460a12..e95d609c2 100644 --- a/projects/services-layers/src/lib/types/LayerGroup.ts +++ b/projects/services-layers/src/lib/types/LayerGroup.ts @@ -16,7 +16,10 @@ export interface ILayerGroupOptions { removable?: boolean; layerRemovable?: boolean; bbox?: TGeoExtent; - description?: string; + /** description for the group as string/html or a angular component */ + description?: string | IDynamicComponent; + /** legend for the group as image or a angular component */ + legendImg?: string | IDynamicComponent; actions?: [{ title: string, icon: string, action: (LayerGroup) => void }]; /** optional angular component that can be used e.g. to change the layer style, filter the data or request new data */ action?: IDynamicComponent; @@ -44,7 +47,8 @@ export class LayerGroup implements ILayerGroupOptions { removable = true; layerRemovable = true; bbox?: [number, number, number, number]; - description?: string; + description?: string | IDynamicComponent; + legendImg?: string | IDynamicComponent; actions?: [{ title: string, icon: string, action: (LayerGroup) => void }]; action?: IDynamicComponent; expanded = false; diff --git a/projects/services-layers/src/lib/types/Layers.ts b/projects/services-layers/src/lib/types/Layers.ts index 41b6610c1..3168363a1 100644 --- a/projects/services-layers/src/lib/types/Layers.ts +++ b/projects/services-layers/src/lib/types/Layers.ts @@ -140,7 +140,7 @@ export interface ILayerOptions { continuousWorld?: boolean; attribution?: string; displayName?: string; - description?: string; + description?: string | IDynamicComponent; time?: string; minResolution?: number; maxResolution?: number; @@ -278,7 +278,7 @@ export class Layer implements ILayerOptions { continuousWorld = false; attribution?: string; displayName?: string; - description?: string; + description?: string | IDynamicComponent; protected protTime?: string; minResolution?: number; maxResolution?: number; diff --git a/projects/services-ogc/src/lib/owc/owc-json.service.spec.ts b/projects/services-ogc/src/lib/owc/owc-json.service.spec.ts index a8bfc5f93..3eb3baa0f 100644 --- a/projects/services-ogc/src/lib/owc/owc-json.service.spec.ts +++ b/projects/services-ogc/src/lib/owc/owc-json.service.spec.ts @@ -846,7 +846,7 @@ describe('OwcJsonService: writing data into owc', () => { expect(resource.bbox).toEqual(ukisWmsLayer.bbox); expect(resource.properties.active).toBe(ukisWmsLayer.visible); expect(resource.properties.rights).toBe(ukisWmsLayer.attribution); - expect(resource.properties.abstract).toBe(ukisWmsLayer.description); + expect(resource.properties.abstract).toBe(ukisWmsLayer.description as string); expect(resource.properties.maxZoom).toBe(ukisWmsLayer.maxZoom); expect(resource.properties.minZoom).toBe(ukisWmsLayer.minZoom); expect(resource.properties.opacity).toBe(ukisWmsLayer.opacity); diff --git a/projects/services-ogc/src/lib/owc/owc-json.service.ts b/projects/services-ogc/src/lib/owc/owc-json.service.ts index 20992a524..c484bb4b2 100644 --- a/projects/services-ogc/src/lib/owc/owc-json.service.ts +++ b/projects/services-ogc/src/lib/owc/owc-json.service.ts @@ -561,8 +561,9 @@ export class OwcJsonService { if (layers.length) { /** if filterType is Baselayers -> create a merged Layer */ if (filterType === Filtertypes.Baselayers) { - const mergedDescription = layers.map(i => i.description).filter(d => d); // filter empty elements - const legendImages = layers.map(i => i.legendImg).filter(d => d); + const descriptionLayers = layers.filter(l => l.description); // filter empty elements + const mergedDescription = descriptionLayers.map(i => i.description); + const legendImages = layers.map(i => i.legendImg).filter(d => d); // filter empty elements const layerOptions: IStackedLayerOptions = { id: `${groupName}_${layers.map(i => i.id).join(' ')}`.replace(/\s/g, '_'), name: groupName, @@ -570,7 +571,7 @@ export class OwcJsonService { filtertype: Filtertypes.Baselayers }; if (mergedDescription.length) { - layerOptions.description = mergedDescription.join(';\r\n'); + layerOptions.description = mergedDescription.map((d, index) => this.generateAbstractFromLayerDescription(d, descriptionLayers[index].id)).join(';\r\n'); } if (legendImages) { layerOptions.legendImg = legendImages[0]; @@ -1365,6 +1366,18 @@ export class OwcJsonService { return owc; } + private generateAbstractFromLayerDescription(description: Layer['description'], layerID?: Layer['id']) { + if (typeof description === 'string') { + return description + } else { + if (description.inputs?.description) { + return JSON.stringify(description); + } else { + return `Could not generate description from layer: ${layerID} - dynamic component` + } + } + } + generateResourceFromLayer(layer: Layer, folderName?: string): IEocOwsResource { const resource: IEocOwsResource = { id: layer.id, @@ -1372,7 +1385,7 @@ export class OwcJsonService { title: layer.displayName || layer.name, opacity: layer.opacity, active: layer.visible, - abstract: layer.description, + abstract: this.generateAbstractFromLayerDescription(layer.description, layer.id), rights: layer.attribution, minZoom: layer.minZoom, maxZoom: layer.maxZoom,