diff --git a/packages/context/src/lib/context-manager/shared/layer-context.directive.ts b/packages/context/src/lib/context-manager/shared/layer-context.directive.ts index fb4a37a66..df0dc3757 100644 --- a/packages/context/src/lib/context-manager/shared/layer-context.directive.ts +++ b/packages/context/src/lib/context-manager/shared/layer-context.directive.ts @@ -83,6 +83,7 @@ export class LayerContextDirective implements OnInit, OnDestroy { } this.contextLayers = []; + this.layerService.unavailableLayers = []; const layersAndIndex$ = merge( ...context.layers.map((layerOptions: LayerOptions) => { return this.layerService.createAsyncLayer(layerOptions, context.uri); diff --git a/packages/geo/src/lib/datasource/shared/datasources/any-datasource.interface.ts b/packages/geo/src/lib/datasource/shared/datasources/any-datasource.interface.ts index fcdbd13c9..5b4eb9095 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/any-datasource.interface.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/any-datasource.interface.ts @@ -28,3 +28,12 @@ export type AnyDataSourceOptions = | TileArcGISRestDataSourceOptions | MVTDataSourceOptions | ClusterDataSourceOptions; + +export type AnyDataSourceOptionsWithParams = + | FeatureDataSourceOptions + | WFSDataSourceOptions + | WMSDataSourceOptions + | CartoDataSourceOptions + | ArcGISRestDataSourceOptions + | ArcGISRestImageDataSourceOptions + | TileArcGISRestDataSourceOptions; diff --git a/packages/geo/src/lib/layer/layer-item/layer-item.component.html b/packages/geo/src/lib/layer/layer-item/layer-item.component.html index c7f3bd53c..14f82ec88 100644 --- a/packages/geo/src/lib/layer/layer-item/layer-item.component.html +++ b/packages/geo/src/lib/layer/layer-item/layer-item.component.html @@ -1,92 +1,134 @@ - - - - - {{ layer.title }} - - -
- - - + {{ layer.title }} + - + + + + +
+
+ +
+ - more_horiz - +
- + -
- + - -
+ + {{ unavailableLayerTitle }} + +
+
+ +
+ +
+ + diff --git a/packages/geo/src/lib/layer/layer-item/layer-item.component.scss b/packages/geo/src/lib/layer/layer-item/layer-item.component.scss index 71cb79ca9..24e89a12a 100644 --- a/packages/geo/src/lib/layer/layer-item/layer-item.component.scss +++ b/packages/geo/src/lib/layer/layer-item/layer-item.component.scss @@ -26,4 +26,8 @@ width: 16px; padding-right: 0px; } + + .align-disabled-button-with-tooltip { + display: inline-block; + } } diff --git a/packages/geo/src/lib/layer/layer-item/layer-item.component.ts b/packages/geo/src/lib/layer/layer-item/layer-item.component.ts index 4e13d9f4c..3c86ea2a5 100644 --- a/packages/geo/src/lib/layer/layer-item/layer-item.component.ts +++ b/packages/geo/src/lib/layer/layer-item/layer-item.component.ts @@ -26,6 +26,7 @@ import { BehaviorSubject, Subscription } from 'rxjs'; import { MetadataLayerOptions } from '../../metadata/shared/metadata.interface'; import { layerIsQueryable } from '../../query/shared/query.utils'; import { LayerLegendComponent } from '../layer-legend/layer-legend.component'; +import { AnyLayerOptions } from '../shared'; import { Layer } from '../shared/layers/layer'; import { TooltipType } from '../shared/layers/layer.interface'; @@ -131,6 +132,22 @@ export class LayerItemComponent implements OnInit, OnDestroy { @Input() changeDetection; + @Input() unavailableLayer: AnyLayerOptions; + + get unavailableLayerTitle(): string | undefined { + if ( + this.unavailableLayer.sourceOptions && + 'params' in this.unavailableLayer.sourceOptions + ) { + return this.unavailableLayer.sourceOptions.params.LAYERS; + } else if ( + this.unavailableLayer.sourceOptions && + 'layer' in this.unavailableLayer?.sourceOptions + ) { + return this.unavailableLayer.sourceOptions.layer; + } + } + get opacity() { return this.layer.opacity * 100; } @@ -148,7 +165,9 @@ export class LayerItemComponent implements OnInit, OnDestroy { } } - @Output() action: EventEmitter = new EventEmitter(undefined); + @Output() action: EventEmitter = new EventEmitter< + Layer | AnyLayerOptions + >(undefined); @Output() checkbox = new EventEmitter<{ layer: Layer; check: boolean; @@ -162,46 +181,48 @@ export class LayerItemComponent implements OnInit, OnDestroy { ) {} ngOnInit() { - if ( - this.layer.visible && - this.expandLegendIfVisible && - this.layer.firstLoadComponent === true - ) { - this.layer.firstLoadComponent = false; - this.layer.legendCollapsed = false; - } - this.toggleLegend(this.layer.legendCollapsed); - this.updateQueryBadge(); - - const resolution$ = this.layer.map.viewController.resolution$; - this.resolution$$ = resolution$.subscribe(() => { - this.onResolutionChange(); - }); - this.tooltipText = this.computeTooltip(); + if (!this.unavailableLayer) { + if ( + this.layer.visible && + this.expandLegendIfVisible && + this.layer.firstLoadComponent === true + ) { + this.layer.firstLoadComponent = false; + this.layer.legendCollapsed = false; + } + this.toggleLegend(this.layer.legendCollapsed); + this.updateQueryBadge(); - this.network$$ = this.networkService - .currentState() - .subscribe((state: ConnectionState) => { - this.state = state; + const resolution$ = this.layer.map.viewController.resolution$; + this.resolution$$ = resolution$.subscribe(() => { this.onResolutionChange(); }); + this.tooltipText = this.computeTooltip(); + + this.network$$ = this.networkService + .currentState() + .subscribe((state: ConnectionState) => { + this.state = state; + this.onResolutionChange(); + }); + + this.layers$$ = this.layers$.subscribe(() => { + if (this.layer && this.layer.options.active) { + this.layerTool$.next(true); + this.renderer.addClass(this.elRef.nativeElement, this.focusedCls); + } + }); - this.layers$$ = this.layers$.subscribe(() => { - if (this.layer && this.layer.options.active) { - this.layerTool$.next(true); - this.renderer.addClass(this.elRef.nativeElement, this.focusedCls); + if (this.changeDetection) { + this.changeDetection.subscribe(() => this.cdRef.detectChanges()); } - }); - - if (this.changeDetection) { - this.changeDetection.subscribe(() => this.cdRef.detectChanges()); } } ngOnDestroy() { - this.resolution$$.unsubscribe(); - this.network$$.unsubscribe(); - this.layers$$.unsubscribe(); + this.resolution$$?.unsubscribe(); + this.network$$?.unsubscribe(); + this.layers$$?.unsubscribe(); } toggleLegend(collapsed: boolean) { @@ -282,4 +303,8 @@ export class LayerItemComponent implements OnInit, OnDestroy { this.layerCheck = !this.layerCheck; this.checkbox.emit({ layer: this.layer, check: this.layerCheck }); } + + deleteUnavailableLayer(anyLayerOptions: AnyLayerOptions) { + this.action.emit(anyLayerOptions); + } } diff --git a/packages/geo/src/lib/layer/layer-item/layer-item.theme.scss b/packages/geo/src/lib/layer/layer-item/layer-item.theme.scss index 4720fd220..a91bd6e05 100644 --- a/packages/geo/src/lib/layer/layer-item/layer-item.theme.scss +++ b/packages/geo/src/lib/layer/layer-item/layer-item.theme.scss @@ -9,6 +9,7 @@ $foreground: map.get($theme, foreground); $primary: map.get($theme, primary); $accent: map.get($theme, accent); + $background: map.get($theme, background); igo-layer-item { &.igo-layer-item-focused > mat-list-item { @@ -17,7 +18,7 @@ } button { - &:not(.mat-primary) { + &.mat-default { mat-icon { color: mat.m2-get-color-from-palette($foreground, base); } @@ -29,5 +30,12 @@ } } } + + .unavailable-layer { + background-color: mat.get-color-from-palette( + $background, + disabled-list-option + ); + } } } diff --git a/packages/geo/src/lib/layer/layer-list/layer-list.component.html b/packages/geo/src/lib/layer/layer-list/layer-list.component.html index 24c7dec43..057210734 100644 --- a/packages/geo/src/lib/layer/layer-list/layer-list.component.html +++ b/packages/geo/src/lib/layer/layer-list/layer-list.component.html @@ -28,6 +28,15 @@ + + + + + (undefined); private selectAllCheck$$: Subscription; - constructor(private elRef: ElementRef) {} + constructor( + private elRef: ElementRef, + private layerService: LayerService + ) {} /** * Subscribe to the search term stream and trigger researches @@ -998,4 +1007,8 @@ export class LayerListComponent implements OnInit, OnDestroy { } return layersList; } + + deleteUnavailableLayer(anyLayerOptions: AnyLayerOptions) { + this.layerService.deleteUnavailableLayers(anyLayerOptions); + } } diff --git a/packages/geo/src/lib/layer/shared/layer.service.ts b/packages/geo/src/lib/layer/shared/layer.service.ts index 59d7fbce8..a0d8c01d2 100644 --- a/packages/geo/src/lib/layer/shared/layer.service.ts +++ b/packages/geo/src/lib/layer/shared/layer.service.ts @@ -16,7 +16,10 @@ import { catchError, concatMap, map } from 'rxjs/operators'; import { DataSourceService } from '../../datasource/shared/datasource.service'; import { + AnyDataSourceOptions, + AnyDataSourceOptionsWithParams, ArcGISRestDataSource, + ArcGISRestDataSourceOptions, CartoDataSource, ClusterDataSource, FeatureDataSource, @@ -28,6 +31,7 @@ import { WFSDataSource, WMSDataSource, WMTSDataSource, + WMTSDataSourceOptions, WebSocketDataSource, XYZDataSource } from '../../datasource/shared/datasources'; @@ -53,6 +57,8 @@ import { providedIn: 'root' }) export class LayerService { + public unavailableLayers: AnyLayerOptions[] = []; + constructor( private http: HttpClient, private styleService: StyleService, @@ -127,6 +133,13 @@ export class LayerService { .pipe( map((source) => { if (source === undefined) { + const found = this.unavailableLayers.some( + (el) => el === layerOptions + ); + if (!found) { + this.unavailableLayers.push(layerOptions); + } + return undefined; } return this.createLayer(Object.assign(layerOptions, { source })); @@ -407,4 +420,38 @@ export class LayerService { }) ); } + + deleteUnavailableLayers(anyLayerOptions: AnyLayerOptions) { + const anyLayerSourceOptions = anyLayerOptions.sourceOptions; + const index = this.unavailableLayers.findIndex((item) => { + const baseSourceOptions = item.sourceOptions; + if ( + this.sourceOptionsWithParams(baseSourceOptions) && + this.sourceOptionsWithParams(anyLayerSourceOptions) + ) { + return ( + baseSourceOptions.params.LAYERS === + anyLayerSourceOptions.params.LAYERS + ); + } else if ( + this.sourceOptionsWithLayer(baseSourceOptions) && + this.sourceOptionsWithLayer(anyLayerSourceOptions) + ) { + return baseSourceOptions.layer === anyLayerSourceOptions.layer; + } + }); + this.unavailableLayers.splice(index, index >= 0 ? 1 : 0); + } + + sourceOptionsWithParams( + sourceOptions: AnyDataSourceOptions + ): sourceOptions is AnyDataSourceOptionsWithParams { + return 'params' in sourceOptions; + } + + sourceOptionsWithLayer( + sourceOptions: AnyDataSourceOptions + ): sourceOptions is ArcGISRestDataSourceOptions | WMTSDataSourceOptions { + return 'layer' in sourceOptions; + } } diff --git a/packages/geo/src/locale/en.geo.json b/packages/geo/src/locale/en.geo.json index 49e8b3338..b1b7cec02 100644 --- a/packages/geo/src/locale/en.geo.json +++ b/packages/geo/src/locale/en.geo.json @@ -223,6 +223,7 @@ "lowerLayer": "Bring layer backward", "hideSelectedLayers": "Hide selected layers", "notInResolution": "Layer isn't visible at the active scale. Zoom in or out to display.", + "unavailableLayer": "Unavailable layer", "filterLowerLayer": "The backward movement is always done according to the order of the map!", "filterPlaceholder": "Filter the layers list", "activateSelectionMode": "Activate multiple selection mode", @@ -239,6 +240,7 @@ "removeLayer": "Remove this layer from the map", "removeSelectedLayers": "Remove selected layers from the map", "removeSelectedLayersRestriction": "Some selected layers cannot be removed", + "deleteLayer": "Delete layer", "showFeaturesList": "Show features list", "showLayer": "Show Layer", "showSelectedLayers": "Show selected layers", diff --git a/packages/geo/src/locale/fr.geo.json b/packages/geo/src/locale/fr.geo.json index 0a9922da1..23c92f1a7 100644 --- a/packages/geo/src/locale/fr.geo.json +++ b/packages/geo/src/locale/fr.geo.json @@ -222,6 +222,7 @@ "hideLayer": "Masquer la couche", "hideSelectedLayers": "Masquer les couches sélectionnées", "notInResolution": "La couche n'est pas visible à l'échelle active. Veuillez zoomer ou dézoomer pour la visualiser.", + "unavailableLayer": "Couche non disponible", "lowerLayer": "Mettre en arrière", "filterLowerLayer": "Le déplacement arrière se fait toujours selon l'ordre de la carte!", "filterPlaceholder": "Filtrer la liste de couches", @@ -239,6 +240,7 @@ "removeLayer": "Retirer la couche de la carte", "removeSelectedLayers": "Retirer les couches sélectionnées de la carte", "removeSelectedLayersRestriction": "Certaines couches sélectionnées ne peuvent être supprimées", + "deleteLayer": "Supprimer la couche", "showFeaturesList": "Afficher la liste des entités", "showLayer": "Afficher la couche", "showSelectedLayers": "Afficher les couches sélectionnées",