diff --git a/demo/src/app/app.component.html b/demo/src/app/app.component.html index 855e3b99a2..66ea2c628d 100644 --- a/demo/src/app/app.component.html +++ b/demo/src/app/app.component.html @@ -38,6 +38,7 @@
{{version.lib}}
Simple map Layer + Legend Overlay Geometry Feature diff --git a/demo/src/app/app.module.ts b/demo/src/app/app.module.ts index 82d6f0ad18..c61f504357 100644 --- a/demo/src/app/app.module.ts +++ b/demo/src/app/app.module.ts @@ -31,6 +31,7 @@ import { AppAuthFormModule } from './auth/auth-form/auth-form.module'; import { AppSimpleMapModule } from './geo/simple-map/simple-map.module'; import { AppLayerModule } from './geo/layer/layer.module'; +import { AppLegendModule } from './geo/legend/legend.module'; import { AppOverlayModule } from './geo/overlay/overlay.module'; import { AppGeometryModule } from './geo/geometry/geometry.module'; import { AppFeatureModule } from './geo/feature/feature.module'; @@ -83,6 +84,7 @@ import { AppComponent } from './app.component'; AppSimpleMapModule, AppLayerModule, + AppLegendModule, AppOverlayModule, AppGeometryModule, AppFeatureModule, diff --git a/demo/src/app/context/context/context.component.html b/demo/src/app/context/context/context.component.html index 21543d2fbe..7c74a9bb75 100644 --- a/demo/src/app/context/context/context.component.html +++ b/demo/src/app/context/context/context.component.html @@ -24,7 +24,7 @@ - + diff --git a/demo/src/app/geo/layer/layer.component.html b/demo/src/app/geo/layer/layer.component.html index 87ff96254f..61634d9b0e 100644 --- a/demo/src/app/geo/layer/layer.component.html +++ b/demo/src/app/geo/layer/layer.component.html @@ -15,6 +15,7 @@ + Geo + Simple Map with a legend list + +
  • Dependencies: LanguageService
  • + +
    + See the code of this + example +
    +
    + + + + + + + + + + + + diff --git a/demo/src/app/geo/legend/legend.component.scss b/demo/src/app/geo/legend/legend.component.scss new file mode 100644 index 0000000000..0d51a81d5b --- /dev/null +++ b/demo/src/app/geo/legend/legend.component.scss @@ -0,0 +1,9 @@ +igo-map-browser { + width: 100%; + height: 300px; +} + +igo-panel { + width: 100%; + padding-top: 10px; +} diff --git a/demo/src/app/geo/legend/legend.component.ts b/demo/src/app/geo/legend/legend.component.ts new file mode 100644 index 0000000000..051d5759f4 --- /dev/null +++ b/demo/src/app/geo/legend/legend.component.ts @@ -0,0 +1,221 @@ +import { Component } from '@angular/core'; + +import { LanguageService } from '@igo2/core'; +import { + IgoMap, + DataSourceService, + LayerService, + WMSDataSourceOptions, + LayerOptions, + WFSDataSourceOptions, + OgcFilterableDataSourceOptions, + MetadataLayerOptions +} from '@igo2/geo'; + +@Component({ + selector: 'app-legend', + templateUrl: './legend.component.html', + styleUrls: ['./legend.component.scss'] +}) +export class AppLegendComponent { + public map = new IgoMap({ + controls: { + attribution: { + collapsed: true + } + } + }); + + public view = { + center: [-73, 47.2], + zoom: 7 + }; + + constructor( + private languageService: LanguageService, + private dataSourceService: DataSourceService, + private layerService: LayerService + ) { + this.dataSourceService + .createAsyncDataSource({ + type: 'osm' + }) + .subscribe(dataSource => { + this.map.addLayer( + this.layerService.createLayer({ + title: 'OSM', + visible: true, + baseLayer: true, + source: dataSource + }) + ); + }); + + interface WFSoptions + extends WFSDataSourceOptions, + OgcFilterableDataSourceOptions {} + + const wfsDatasource: WFSoptions = { + type: 'wfs', + url: 'https://geoegl.msp.gouv.qc.ca/apis/ws/igo_gouvouvert.fcgi', + params: { + featureTypes: 'vg_observation_v_autre_wmst', + fieldNameGeometry: 'geometry', + maxFeatures: 10000, + version: '2.0.0', + outputFormat: undefined, + outputFormatDownload: 'shp' + }, + ogcFilters: { + enabled: true, + editable: true, + filters: { + operator: 'PropertyIsEqualTo', + propertyName: 'code_municipalite', + expression: '10043' + } + } + }; + + this.dataSourceService + .createAsyncDataSource(wfsDatasource) + .subscribe(dataSource => { + const layer: LayerOptions = { + title: 'WFS ', + visible: true, + source: dataSource + }; + this.map.addLayer(this.layerService.createLayer(layer)); + }); + + this.layerService + .createAsyncLayer({ + sourceOptions: { + type: 'wms', + url: 'https://geoegl.msp.gouv.qc.ca/apis/ws/igo_gouvouvert.fcgi', + optionsFromCapabilities: true, + params: { + LAYERS: 'MELS_CS_ANGLO_S', + VERSION: '1.3.0' + } + } + }) + .subscribe(l => this.map.addLayer(l)); + + this.layerService + .createAsyncLayer({ + title: 'Réseau routier', + visible: false, + sourceOptions: { + type: 'wms', + url: 'https://geoegl.msp.gouv.qc.ca/apis/ws/swtq', + params: { + LAYERS: 'bgr_v_sous_route_res_sup_act', + VERSION: '1.3.0' + } + } + }) + .subscribe(l => this.map.addLayer(l)); + + this.layerService + .createAsyncLayer({ + title: 'lieu habité', + visible: false, + sourceOptions: { + type: 'wms', + url: 'https://geoegl.msp.gouv.qc.ca/apis/ws/swtq', + optionsFromCapabilities: true, + params: { + LAYERS: 'lieuhabite', + VERSION: '1.3.0' + } + } + }) + .subscribe(l => this.map.addLayer(l)); + + this.layerService + .createAsyncLayer({ + title: 'sh_dis_eco', + visible: false, + sourceOptions: { + type: 'wms', + url: 'https://geoegl.msp.gouv.qc.ca/apis/ws/mffpecofor.fcgi', + optionsFromCapabilities: true, + params: { + LAYERS: 'sh_dis_eco', + VERSION: '1.3.0' + } + } + }) + .subscribe(l => this.map.addLayer(l)); + + this.layerService + .createAsyncLayer({ + title: 'nurc:Arc_Sample_Parent', + visible: false, + legendOptions: { + // collapsed: false, + display: true, + // url: 'https://v.seloger.com/s/width/1144/visuels/0/m/l/4/0ml42xbt1n3itaboek3qec5dtskdgw6nlscu7j69k.jpg', + stylesAvailable: [ + { name: 'rain', title: 'Pluie' }, + { name: 'raster', title: 'Défaut' } + ] // + }, + sourceOptions: { + type: 'wms', + url: 'https://demo.geo-solutions.it/geoserver/ows', + optionsFromCapabilities: true, + params: { + LAYERS: 'nurc:Arc_Sample', // , test:Linea_costa + VERSION: '1.3.0' + } + } + }) + .subscribe(l => this.map.addLayer(l)); + + this.layerService + .createAsyncLayer({ + title: 'Avertissements routier', + visible: false, + sourceOptions: { + type: 'wms', + url: 'https://geoegl.msp.gouv.qc.ca/apis/ws/swtq', + params: { + LAYERS: 'evenements', + VERSION: '1.3.0' + } + } + }) + .subscribe(l => this.map.addLayer(l)); + + const datasource: WMSDataSourceOptions = { + type: 'wms', + url: 'https://geoegl.msp.gouv.qc.ca/apis/ws/igo_gouvouvert.fcgi', + refreshIntervalSec: 15, + params: { + LAYERS: 'vg_observation_v_inondation_embacle_wmst', + VERSION: '1.3.0' + } + }; + + interface LayerOptionsWithMetadata + extends LayerOptions, + MetadataLayerOptions {} + + this.dataSourceService + .createAsyncDataSource(datasource) + .subscribe(dataSource => { + const layer: LayerOptionsWithMetadata = { + title: 'Embâcle', + source: dataSource, + metadata: { + url: + 'https://www.donneesquebec.ca/recherche/fr/dataset/historique-publique-d-embacles-repertories-au-msp', + extern: true + } + }; + this.map.addLayer(this.layerService.createLayer(layer)); + }); + } +} diff --git a/demo/src/app/geo/legend/legend.module.ts b/demo/src/app/geo/legend/legend.module.ts new file mode 100644 index 0000000000..30b2f6ef1c --- /dev/null +++ b/demo/src/app/geo/legend/legend.module.ts @@ -0,0 +1,36 @@ +import { NgModule } from '@angular/core'; +import { + MatCardModule, + MatButtonModule, + MatIconModule +} from '@angular/material'; + +import { IgoPanelModule } from '@igo2/common'; +import { + IgoMapModule, + IgoLayerModule, + IgoFilterModule, + IgoMetadataModule, + IgoDownloadModule +} from '@igo2/geo'; + +import { AppLegendComponent } from './legend.component'; +import { AppLegendRoutingModule } from './legend-routing.module'; + +@NgModule({ + declarations: [AppLegendComponent], + imports: [ + AppLegendRoutingModule, + MatCardModule, + MatButtonModule, + MatIconModule, + IgoPanelModule, + IgoMapModule, + IgoLayerModule, + IgoFilterModule, + IgoMetadataModule, + IgoDownloadModule + ], + exports: [AppLegendComponent] +}) +export class AppLegendModule {} diff --git a/demo/src/app/geo/ogc-filter/ogc-filter.component.ts b/demo/src/app/geo/ogc-filter/ogc-filter.component.ts index a44ea879e4..e9e3243110 100644 --- a/demo/src/app/geo/ogc-filter/ogc-filter.component.ts +++ b/demo/src/app/geo/ogc-filter/ogc-filter.component.ts @@ -62,7 +62,7 @@ export class AppOgcFilterComponent { fieldNameGeometry: 'geometry', maxFeatures: 10000, version: '2.0.0', - outputFormat: 'geojson', + outputFormat: undefined, outputFormatDownload: 'SHP' // based on service capabilities }, sourceFields: [ diff --git a/packages/context/src/lib/share-map/index.ts b/packages/context/src/lib/share-map/index.ts index 7bab4d20e9..c070cd80cb 100644 --- a/packages/context/src/lib/share-map/index.ts +++ b/packages/context/src/lib/share-map/index.ts @@ -1,3 +1,2 @@ export * from './shared'; export * from './share-map/share-map.component'; -export * from './share-map/share-map-binding.directive'; diff --git a/packages/context/src/lib/share-map/share-map.module.ts b/packages/context/src/lib/share-map/share-map.module.ts index e009eaf785..812b60c8f5 100644 --- a/packages/context/src/lib/share-map/share-map.module.ts +++ b/packages/context/src/lib/share-map/share-map.module.ts @@ -12,7 +12,6 @@ import { import { IgoLanguageModule } from '@igo2/core'; import { ShareMapComponent } from './share-map/share-map.component'; -import { ShareMapBindingDirective } from './share-map/share-map-binding.directive'; @NgModule({ imports: [ @@ -26,8 +25,8 @@ import { ShareMapBindingDirective } from './share-map/share-map-binding.directiv MatButtonModule, IgoLanguageModule ], - exports: [ShareMapComponent, ShareMapBindingDirective], - declarations: [ShareMapComponent, ShareMapBindingDirective] + exports: [ShareMapComponent], + declarations: [ShareMapComponent] }) export class IgoShareMapModule { static forRoot(): ModuleWithProviders { diff --git a/packages/context/src/lib/share-map/share-map/share-map-binding.directive.ts b/packages/context/src/lib/share-map/share-map/share-map-binding.directive.ts deleted file mode 100644 index 36e0a21946..0000000000 --- a/packages/context/src/lib/share-map/share-map/share-map-binding.directive.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Directive, Self, OnInit, Optional } from '@angular/core'; - -import { LayerListService } from '@igo2/geo'; -import { ShareMapComponent } from './share-map.component'; -import { RouteService } from '@igo2/core'; - -@Directive({ - selector: '[igoShareMapBinding]' -}) -export class ShareMapBindingDirective implements OnInit { - private component: ShareMapComponent; - - constructor( - @Self() component: ShareMapComponent, - private layerListService: LayerListService, - @Optional() private route: RouteService - ) { - this.component = component; - } - - ngOnInit() { - this.initRoutes(); - } - - private initRoutes() { - if ( - this.route && - (this.route.options.llcKKey || this.route.options.llcAKey || - this.route.options.llcVKey || this.route.options.llcVKey)) { - this.route.queryParams.subscribe(params => { - - const keywordFromUrl = params[this.route.options.llcKKey as string]; - const sortedAplhaFromUrl = params[this.route.options.llcAKey as string]; - const onlyVisibleFromUrl = params[this.route.options.llcVKey as string]; - const onlyInRangeFromUrl = params[this.route.options.llcRKey as string]; - if (keywordFromUrl && !this.layerListService.keywordInitialized) { - this.layerListService.keyword = keywordFromUrl; - this.layerListService.keywordInitialized = true; - } - if (sortedAplhaFromUrl && !this.layerListService.sortedAlphaInitialized) { - this.layerListService.sortedAlpha = sortedAplhaFromUrl === '1' ? true : false; - this.layerListService.sortedAlphaInitialized = true; - } - if (onlyVisibleFromUrl && !this.layerListService.onlyVisibleInitialized) { - this.layerListService.onlyVisible = onlyVisibleFromUrl === '1' ? true : false; - this.layerListService.onlyVisibleInitialized = true; - } - if (onlyInRangeFromUrl && !this.layerListService.onlyInRangeInitialized) { - this.layerListService.onlyInRange = onlyInRangeFromUrl === '1' ? true : false; - this.layerListService.onlyInRangeInitialized = true; - } - if (!this.component.hasApi) { - this.component.resetUrl(); - } - }); - } - } -} diff --git a/packages/context/src/lib/share-map/share-map/share-map.component.html b/packages/context/src/lib/share-map/share-map/share-map.component.html index 5083342b1f..05df5d8789 100644 --- a/packages/context/src/lib/share-map/share-map/share-map.component.html +++ b/packages/context/src/lib/share-map/share-map/share-map.component.html @@ -73,7 +73,6 @@

    {{'igo.context.shareMap.included' | translate}}

  • {{'igo.context.shareMap.zoom' | translate}}
  • {{'igo.context.shareMap.addedLayers' | translate}}
  • {{'igo.context.shareMap.visibleInvisible' | translate}}
  • -
  • {{'igo.context.shareMap.layerListControls' | translate}}
  • {{'igo.context.shareMap.excluded' | translate}}

    diff --git a/packages/context/src/lib/share-map/share-map/share-map.component.ts b/packages/context/src/lib/share-map/share-map/share-map.component.ts index d3957983c5..99cc5c5c54 100644 --- a/packages/context/src/lib/share-map/share-map/share-map.component.ts +++ b/packages/context/src/lib/share-map/share-map/share-map.component.ts @@ -4,7 +4,7 @@ import { FormBuilder, FormGroup } from '@angular/forms'; import { uuid, Clipboard } from '@igo2/utils'; import { ConfigService, MessageService, LanguageService } from '@igo2/core'; import { AuthService } from '@igo2/auth'; -import { IgoMap, LayerListService } from '@igo2/geo'; +import { IgoMap } from '@igo2/geo'; import { ShareMapService } from '../shared/share-map.service'; import { Subscription } from 'rxjs'; @@ -17,14 +17,8 @@ import { Subscription } from 'rxjs'; export class ShareMapComponent implements AfterViewInit, OnInit, OnDestroy { public form: FormGroup; private mapState$$: Subscription; - @Input() - get map(): IgoMap { - return this._map; - } - set map(value: IgoMap) { - this._map = value; - } - private _map: IgoMap; + + @Input() map: IgoMap; public url: string; public hasApi = false; @@ -40,7 +34,6 @@ export class ShareMapComponent implements AfterViewInit, OnInit, OnDestroy { private auth: AuthService, private shareMapService: ShareMapService, private formBuilder: FormBuilder, - private layerListService: LayerListService, private cdRef: ChangeDetectorRef ) { this.hasApi = this.config.getConfig('context.url') ? true : false; @@ -71,29 +64,7 @@ export class ShareMapComponent implements AfterViewInit, OnInit, OnDestroy { this.mapState$$.unsubscribe(); } - public hasLayerListControls(): boolean { - if (this.layerListService.keyword || this.layerListService.sortedAlpha || - this.layerListService.onlyVisible || this.layerListService.onlyInRange ) { - this.publicShareOption.layerlistControls.querystring = ''; - if (this.layerListService.keyword) { - this.publicShareOption.layerlistControls.querystring += '&llck=' + this.layerListService.keyword; - } - if (this.layerListService.sortedAlpha) { - this.publicShareOption.layerlistControls.querystring += '&llca=1'; - } - if (this.layerListService.onlyVisible) { - this.publicShareOption.layerlistControls.querystring += '&llcv=1'; - } - if (this.layerListService.onlyInRange) { - this.publicShareOption.layerlistControls.querystring += '&llcr=1'; - } - return true; - } - return false; - } - resetUrl(values: any = {}) { - this.hasLayerListControls(); const inputs = Object.assign({}, values); inputs.uri = this.userId ? `${this.userId}-${values.uri}` : values.uri; this.url = this.shareMapService.getUrl(this.map, inputs, this.publicShareOption); diff --git a/packages/context/src/lib/share-map/shared/share-map.service.ts b/packages/context/src/lib/share-map/shared/share-map.service.ts index 098eecd112..90684c7aeb 100644 --- a/packages/context/src/lib/share-map/shared/share-map.service.ts +++ b/packages/context/src/lib/share-map/shared/share-map.service.ts @@ -57,8 +57,8 @@ export class ShareMapService { let invisibleKey = this.route.options.visibleOffLayersKey; const layers = map.layers; - const visibleLayers = layers.filter(lay => lay.visible); - const invisibleLayers = layers.filter(lay => !lay.visible); + const visibleLayers = layers.filter(lay => lay.visible && lay.id !== 'searchPointerSummaryId'); + const invisibleLayers = layers.filter(lay => !lay.visible && lay.id !== 'searchPointerSummaryId'); if (visibleLayers.length === 0) { visibleKey = ''; diff --git a/packages/context/src/locale/en.context.json b/packages/context/src/locale/en.context.json index 0f1189f987..e0e3142f97 100644 --- a/packages/context/src/locale/en.context.json +++ b/packages/context/src/locale/en.context.json @@ -51,7 +51,7 @@ "publicContexts": "Public contexts", "save": "Save this context", "sharedContexts": "Shared contexts", - "filterPlaceHolder": "Filer the contexts list", + "filterPlaceHolder": "Filter the contexts list", "sortAlphabetically": "Sort the context's list alphabetically", "sortContextOrder": "Replace contexts according to the initial list", "userAccount": "User account" @@ -108,7 +108,6 @@ "zoom": "The zoom level of the map.", "addedLayers": "The layers added by the catalog or by the search bar.", "visibleInvisible": "Visibility status of layers.", - "layerListControls": "The restrictions applied to the table of contents (keyword, alphabetical order, visibility and scale).", "excluded": "Excluded in the shared URL :", "order": "The order of the layers.", "opacity": "Modifications made to the opacity of the layers.", diff --git a/packages/context/src/locale/fr.context.json b/packages/context/src/locale/fr.context.json index e40b4e0381..b507f305a4 100644 --- a/packages/context/src/locale/fr.context.json +++ b/packages/context/src/locale/fr.context.json @@ -108,7 +108,6 @@ "zoom": "Le niveau de zoom de la carte.", "addedLayers": "Les couches ajoutées via le catalogue ou via la barre de recherche.", "visibleInvisible": "Le statut de visibilité des couches.", - "layerListControls": "Les restrictions appliquées à la table des matières des couches (mot-clé, ordre alphabétique, visibilité et échelle).", "excluded": "Exclus dans le lien de partage :", "order": "L'ordre des couches.", "opacity": "Les modifications effectuées à l'opacité des couches.", diff --git a/packages/core/src/lib/route/route.interface.ts b/packages/core/src/lib/route/route.interface.ts index 0b9cf9ff47..bbbac40fa9 100644 --- a/packages/core/src/lib/route/route.interface.ts +++ b/packages/core/src/lib/route/route.interface.ts @@ -8,10 +8,6 @@ export interface RouteServiceOptions { visibleOffLayersKey?: boolean | string; directionsCoordKey?: boolean | string; toolKey?: boolean | string; - llcKKey?: boolean | string; - llcAKey?: boolean | string; - llcVKey?: boolean | string; - llcRKey?: boolean | string; wmsUrlKey?: boolean | string; layersKey?: boolean | string; } diff --git a/packages/core/src/lib/route/route.service.ts b/packages/core/src/lib/route/route.service.ts index df3ae92ded..b5d5c2348a 100644 --- a/packages/core/src/lib/route/route.service.ts +++ b/packages/core/src/lib/route/route.service.ts @@ -37,10 +37,6 @@ export class RouteService { visibleOffLayersKey: 'invisiblelayers', directionsCoordKey: 'routing', toolKey: 'tool', - llcKKey: 'llck', - llcAKey: 'llca', - llcVKey: 'llcv', - llcRKey: 'llcr', wmsUrlKey: 'wmsUrl', layersKey: 'layers' }; diff --git a/packages/geo/src/lib/filter/filter.module.ts b/packages/geo/src/lib/filter/filter.module.ts index 9208781fcd..b396a05a0d 100644 --- a/packages/geo/src/lib/filter/filter.module.ts +++ b/packages/geo/src/lib/filter/filter.module.ts @@ -22,7 +22,8 @@ import { MatCheckboxModule, MatTabsModule, MatRadioModule, - MatMenuModule + MatMenuModule, + MatBadgeModule } from '@angular/material'; import { @@ -93,7 +94,8 @@ import { SpatialFilterService } from './shared/spatial-filter.service'; IgoCollapsibleModule, IgoListModule, IgoKeyValueModule, - IgoGeometryModule + IgoGeometryModule, + MatBadgeModule ], exports: [ FilterableDataSourcePipe, diff --git a/packages/geo/src/lib/filter/ogc-filter-button/ogc-filter-button.component.html b/packages/geo/src/lib/filter/ogc-filter-button/ogc-filter-button.component.html index 7fdb85bb8d..5d3224c9a6 100644 --- a/packages/geo/src/lib/filter/ogc-filter-button/ogc-filter-button.component.html +++ b/packages/geo/src/lib/filter/ogc-filter-button/ogc-filter-button.component.html @@ -1,16 +1,18 @@ -
    +*ngIf="options.ogcFilters && options.ogcFilters.enabled && +(options.ogcFilters.pushButtons || options.ogcFilters.editable)"> gr.enabled); + let cntPushButtons = 0; + if (currentPushButtonGroup) { + currentPushButtonGroup.computedButtons.map(cb => cntPushButtons += cb.buttons.filter(button => button.enabled).length); + } + return cntPushButtons > 0 ? cntPushButtons : undefined; + } else { + return; + } + } else if (filter && filter.filters && !filter.filters.filters) { + return 1; + } else if (filter && filter.filters && filter.filters.filters) { + return filter.filters.filters.length; + } + } + + @Input() + get layer(): Layer { + return this._layer; + } + set layer(value: Layer) { + this._layer = value; + if (value) { + this.options = this.layer.dataSource.options as OgcFilterableDataSourceOptions; + } + } + private _layer; @Input() map: IgoMap; @@ -29,8 +60,4 @@ export class OgcFilterButtonComponent implements OnInit { ngOnInit() { this.options = this.layer.dataSource.options as OgcFilterableDataSourceOptions; } - - toggleOgcFilter() { - this.ogcFilterCollapse = !this.ogcFilterCollapse; - } } diff --git a/packages/geo/src/lib/filter/ogc-filterable-item/ogc-filterable-item.component.html b/packages/geo/src/lib/filter/ogc-filterable-item/ogc-filterable-item.component.html index 02fd90d470..4190e449cf 100644 --- a/packages/geo/src/lib/filter/ogc-filterable-item/ogc-filterable-item.component.html +++ b/packages/geo/src/lib/filter/ogc-filterable-item/ogc-filterable-item.component.html @@ -1,31 +1,30 @@ -

    {{layer.title}}

    -

    - - + + + - - -
    diff --git a/packages/geo/src/lib/filter/time-filter-button/time-filter-button.component.html b/packages/geo/src/lib/filter/time-filter-button/time-filter-button.component.html index a9727a1621..b33afb67d9 100644 --- a/packages/geo/src/lib/filter/time-filter-button/time-filter-button.component.html +++ b/packages/geo/src/lib/filter/time-filter-button/time-filter-button.component.html @@ -4,9 +4,8 @@ tooltip-position="below" matTooltipShowDelay="500" [matTooltip]="'igo.geo.filter.filterBy' | translate" - [color]="color" - (click)="toggleTimeFilter()"> - + [color]="color"> +
    { + that => { let newMinDateNumber; const maxDateNumber = new Date(that.max); @@ -336,7 +360,10 @@ export class TimeFilterFormComponent implements OnInit { } playYear(event: any) { - if (this.year + this.mySlider.step > (this.max.getFullYear() + this.mySlider.step)) { + if ( + this.year + this.mySlider.step > + this.max.getFullYear() + this.mySlider.step + ) { this.stopFilter(); this.resetFilter(event); } diff --git a/packages/geo/src/lib/filter/time-filter-item/time-filter-item.component.html b/packages/geo/src/lib/filter/time-filter-item/time-filter-item.component.html index 9b9e75affb..c6f0038c0f 100644 --- a/packages/geo/src/lib/filter/time-filter-item/time-filter-item.component.html +++ b/packages/geo/src/lib/filter/time-filter-item/time-filter-item.component.html @@ -11,8 +11,8 @@

    {{layer.title}}

    diff --git a/packages/geo/src/lib/filter/time-filter-item/time-filter-item.component.scss b/packages/geo/src/lib/filter/time-filter-item/time-filter-item.component.scss index 31b0c56497..1b2ea383fc 100644 --- a/packages/geo/src/lib/filter/time-filter-item/time-filter-item.component.scss +++ b/packages/geo/src/lib/filter/time-filter-item/time-filter-item.component.scss @@ -6,6 +6,7 @@ text-align: center; width: 100%; display: inline-block; + padding-top: 5px; } .igo-layer-legend-container { diff --git a/packages/geo/src/lib/filter/time-filter-item/time-filter-item.component.ts b/packages/geo/src/lib/filter/time-filter-item/time-filter-item.component.ts index 24b0defe4c..21bae73c1a 100644 --- a/packages/geo/src/lib/filter/time-filter-item/time-filter-item.component.ts +++ b/packages/geo/src/lib/filter/time-filter-item/time-filter-item.component.ts @@ -11,7 +11,7 @@ import { BehaviorSubject } from 'rxjs'; styleUrls: ['./time-filter-item.component.scss'] }) export class TimeFilterItemComponent { - + public color = 'primary'; showLegend$: BehaviorSubject = new BehaviorSubject(false); filtersCollapsed: boolean = false; diff --git a/packages/geo/src/lib/layer/index.ts b/packages/geo/src/lib/layer/index.ts index cbf15ce85f..623bcb77c4 100644 --- a/packages/geo/src/lib/layer/index.ts +++ b/packages/geo/src/lib/layer/index.ts @@ -1,6 +1,9 @@ export * from './shared'; export * from './layer-item'; export * from './layer-legend'; +export * from './layer-legend-item'; +export * from './layer-legend-list'; export * from './layer-list'; +export * from './layer-list-tool'; export * from './track-feature-button'; export * from './utils'; 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 4e237886c5..328f9ef9d0 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,7 +1,13 @@ + +

    {{layer.title}}

    - - + +
    -
    -
    - -
    - - - - - - - - - - - -
    -
    -
    -
    div { - text-align: center; -} - .igo-layer-legend-container { padding-left: 18px; width: calc(100% - 18px); } -#opacity-slider { - float: left; -} - -.igo-layer-button-group { - text-align: right; - padding: 0 10px 0 0; - width: 100%; -} - mat-icon.disabled { color: rgba(0, 0, 0, 0.38); } @@ -46,3 +32,7 @@ mat-icon.disabled { .mat-badge-small .mat-badge-content { font-size: 12px; } + +.selection-eye { + padding-right: 45px; +} 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 4e93f4bfc7..29a28fac9d 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 @@ -3,7 +3,11 @@ import { Input, OnInit, OnDestroy, - ChangeDetectionStrategy + ChangeDetectionStrategy, + Output, + EventEmitter, + Renderer2, + ElementRef } from '@angular/core'; import { Subscription, BehaviorSubject } from 'rxjs'; @@ -19,6 +23,25 @@ import { NetworkService, ConnectionState } from '@igo2/core'; changeDetection: ChangeDetectionStrategy.OnPush }) export class LayerItemComponent implements OnInit, OnDestroy { + + public focusedCls = 'igo-layer-item-focused'; + + @Input() + get activeLayer() { + return this._activeLayer; + } + set activeLayer(value) { + if (value && this.layer && value.id === this.layer.id && !this.selectionMode) { + this.layerTool$.next(true); + this.renderer.addClass(this.elRef.nativeElement, this.focusedCls); + } else { + this.renderer.removeClass(this.elRef.nativeElement, this.focusedCls); + } + } + private _activeLayer; + + layerTool$: BehaviorSubject = new BehaviorSubject(false); + showLegend$: BehaviorSubject = new BehaviorSubject(true); inResolutionRange$: BehaviorSubject = new BehaviorSubject(true); @@ -29,9 +52,37 @@ export class LayerItemComponent implements OnInit, OnDestroy { state: ConnectionState; + @Input() + get selectAll() { + return this._selectAll; + } + set selectAll(value: boolean) { + this._selectAll = value; + if (value) { + if (!this.layer.baseLayer) { + this.layerCheck = true; + } + } else { + this.layerCheck = false; + } + } + private _selectAll = false; + + public layerCheck = false; + private resolution$$: Subscription; - @Input() layer: Layer; + layers$: BehaviorSubject = new BehaviorSubject(undefined); + + @Input() + get layer() { + return this._layer; + } + set layer(value) { + this._layer = value; + this.layers$.next(value); + } + private _layer; @Input() toggleLegendOnVisibilityChange: boolean = false; @@ -47,6 +98,8 @@ export class LayerItemComponent implements OnInit, OnDestroy { @Input() queryBadge: boolean = false; + @Input() selectionMode; + get removable(): boolean { return this.layer.options.removable !== false; } @@ -58,7 +111,16 @@ export class LayerItemComponent implements OnInit, OnDestroy { this.layer.opacity = opacity / 100; } - constructor(private networkService: NetworkService) {} + @Output() action: EventEmitter = new EventEmitter(undefined); + @Output() checkbox = new EventEmitter<{ + layer: Layer; + check: boolean; + }>(); + + constructor( + private networkService: NetworkService, + private renderer: Renderer2, + private elRef: ElementRef) {} ngOnInit() { if ( @@ -82,6 +144,13 @@ export class LayerItemComponent implements OnInit, OnDestroy { this.state = state; this.onResolutionChange(); }); + + this.layers$.subscribe(() => { + if (this.layer && this.layer.options.active) { + this.layerTool$.next(true); + this.renderer.addClass(this.elRef.nativeElement, this.focusedCls); + } + }); } ngOnDestroy() { @@ -150,4 +219,19 @@ export class LayerItemComponent implements OnInit, OnDestroy { !layerIsQueryable(this.layer); this.queryBadgeHidden$.next(hidden); } + + toggleLayerTool() { + this.layerTool$.next(!this.layerTool$.getValue()); + if (this.layerTool$.getValue() === true) { + this.renderer.addClass(this.elRef.nativeElement, this.focusedCls); + } else { + this.renderer.removeClass(this.elRef.nativeElement, this.focusedCls); + } + this.action.emit(this.layer); + } + + public check() { + this.layerCheck = !this.layerCheck; + this.checkbox.emit({layer: this.layer, check: this.layerCheck}); + } } diff --git a/packages/geo/src/lib/layer/layer-legend-item/index.ts b/packages/geo/src/lib/layer/layer-legend-item/index.ts new file mode 100644 index 0000000000..6496340366 --- /dev/null +++ b/packages/geo/src/lib/layer/layer-legend-item/index.ts @@ -0,0 +1 @@ +export * from './layer-legend-item.component'; diff --git a/packages/geo/src/lib/layer/layer-legend-item/layer-legend-item.component.html b/packages/geo/src/lib/layer/layer-legend-item/layer-legend-item.component.html new file mode 100644 index 0000000000..64a2bf9ffd --- /dev/null +++ b/packages/geo/src/lib/layer/layer-legend-item/layer-legend-item.component.html @@ -0,0 +1,10 @@ + +

    {{layer.title}}

    +
    + +
    + + +
    diff --git a/packages/geo/src/lib/layer/layer-legend-item/layer-legend-item.component.scss b/packages/geo/src/lib/layer/layer-legend-item/layer-legend-item.component.scss new file mode 100644 index 0000000000..eefa425b06 --- /dev/null +++ b/packages/geo/src/lib/layer/layer-legend-item/layer-legend-item.component.scss @@ -0,0 +1,44 @@ +@import '../../../../../core/src/style/partial/media'; +@import '../../../../../common/src/style/partial/common.variables'; + +:host { + overflow: hidden; +} + +.igo-layer-list-item{ + height: $igo-list-item-height; + clear: both; +} + +.igo-layer-actions-container { + width: 100%; + display: inline-block; + padding-left: 10px; +} + +.igo-layer-actions-container > div { + text-align: center; +} + +.igo-layer-legend-container { + padding-left: 18px; + width: calc(100% - 18px); +} + +#opacity-slider { + float: left; +} + +.igo-layer-button-group { + text-align: right; + padding: 0 10px 0 0; + width: 100%; +} + +mat-icon.disabled { + color: rgba(0, 0, 0, 0.38); +} + +.mat-badge-small .mat-badge-content { + font-size: 12px; +} diff --git a/packages/geo/src/lib/layer/layer-legend-item/layer-legend-item.component.ts b/packages/geo/src/lib/layer/layer-legend-item/layer-legend-item.component.ts new file mode 100644 index 0000000000..c2bc0ce47e --- /dev/null +++ b/packages/geo/src/lib/layer/layer-legend-item/layer-legend-item.component.ts @@ -0,0 +1,86 @@ +import { + Component, + Input, + OnInit, + OnDestroy, + ChangeDetectionStrategy +} from '@angular/core'; +import { Subscription, BehaviorSubject } from 'rxjs'; + +import { MetadataLayerOptions } from '../../metadata/shared/metadata.interface'; +import { Layer, TooltipType } from '../shared/layers'; +import { NetworkService, ConnectionState } from '@igo2/core'; + +@Component({ + selector: 'igo-layer-legend-item', + templateUrl: './layer-legend-item.component.html', + styleUrls: ['./layer-legend-item.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class LayerLegendItemComponent implements OnInit, OnDestroy { + + inResolutionRange$: BehaviorSubject = new BehaviorSubject(true); + + tooltipText: string; + + state: ConnectionState; + + private resolution$$: Subscription; + + @Input() layer: Layer; + + @Input() updateLegendOnResolutionChange: boolean = false; + + constructor(private networkService: NetworkService) {} + + ngOnInit() { + this.layer.legendCollapsed = true; + + const resolution$ = this.layer.map.viewController.resolution$; + this.resolution$$ = resolution$.subscribe(() => { + this.onResolutionChange(); + }); + this.tooltipText = this.computeTooltip(); + + this.networkService.currentState().subscribe((state: ConnectionState) => { + this.state = state; + this.onResolutionChange(); + }); + } + + ngOnDestroy() { + this.resolution$$.unsubscribe(); + } + + computeTooltip(): string { + const layerOptions = this.layer.options; + if (!layerOptions.tooltip) { + return this.layer.title; + } + const layerTooltip = layerOptions.tooltip; + const layerMetadata = (layerOptions as MetadataLayerOptions).metadata; + switch (layerOptions.tooltip.type) { + case TooltipType.TITLE: + return this.layer.title; + case TooltipType.ABSTRACT: + if (layerMetadata && layerMetadata.abstract) { + return layerMetadata.abstract; + } else { + return this.layer.title; + } + case TooltipType.CUSTOM: + if (layerTooltip && layerTooltip.text) { + return layerTooltip.text; + } else { + return this.layer.title; + } + default: + return this.layer.title; + } + } + + private onResolutionChange() { + const inResolutionRange = this.layer.isInResolutionsRange; + this.inResolutionRange$.next(inResolutionRange); + } +} diff --git a/packages/geo/src/lib/layer/layer-legend-list/index.ts b/packages/geo/src/lib/layer/layer-legend-list/index.ts new file mode 100644 index 0000000000..715f41b60b --- /dev/null +++ b/packages/geo/src/lib/layer/layer-legend-list/index.ts @@ -0,0 +1 @@ +export * from './layer-legend-list.component'; diff --git a/packages/geo/src/lib/layer/layer-legend-list/layer-legend-list-binding.directive.ts b/packages/geo/src/lib/layer/layer-legend-list/layer-legend-list-binding.directive.ts new file mode 100644 index 0000000000..b1c5038f61 --- /dev/null +++ b/packages/geo/src/lib/layer/layer-legend-list/layer-legend-list-binding.directive.ts @@ -0,0 +1,56 @@ +import { Directive, Self, OnInit, OnDestroy, Optional } from '@angular/core'; +import { Subscription, combineLatest } from 'rxjs'; + +import { RouteService } from '@igo2/core'; +import { MapService } from '../../map/shared/map.service'; +import { Layer } from '../shared/layers/layer'; +import { debounceTime } from 'rxjs/operators'; +import { LayerLegendListComponent } from './layer-legend-list.component'; + +@Directive({ + selector: '[igoLayerLegendListBinding]' +}) +export class LayerLegendListBindingDirective implements OnInit, OnDestroy { + private component: LayerLegendListComponent; + private layersOrResolutionChange$$: Subscription; + layersVisibility$$: Subscription; + + constructor( + @Self() component: LayerLegendListComponent, + private mapService: MapService + ) { + this.component = component; + } + + ngOnInit() { + // Override input layers + this.component.layers = []; + this.layersOrResolutionChange$$ = combineLatest([ + this.mapService.getMap().layers$, + this.mapService.getMap().viewController.resolution$] + ).pipe( + debounceTime(10) + ).subscribe((bunch: [Layer[], number]) => { + const shownLayers = bunch[0].filter((layer: Layer) => { + return layer.showInLayerList === true; + }); + this.component.layers = shownLayers; + + this.layersVisibility$$ = combineLatest(shownLayers + .map((layer: Layer) => layer.visible$)) + .subscribe((r) => { + this.component.change$.next(); + } + ); + }); + } + + ngOnDestroy() { + this.layersOrResolutionChange$$.unsubscribe(); + if (this.layersVisibility$$ !== undefined) { + this.layersVisibility$$.unsubscribe(); + this.layersVisibility$$ = undefined; + } + } + +} diff --git a/packages/geo/src/lib/layer/layer-legend-list/layer-legend-list.component.html b/packages/geo/src/lib/layer/layer-legend-list/layer-legend-list.component.html new file mode 100644 index 0000000000..ab0bc2ba4d --- /dev/null +++ b/packages/geo/src/lib/layer/layer-legend-list/layer-legend-list.component.html @@ -0,0 +1,35 @@ + + + {{'igo.geo.layer.legend.showAll' | translate}} + + + + + + + + + +

    + {{'igo.geo.layer.legend.noLayersVisibleWithShowAllButton' | translate}} +

    +

    + {{'igo.geo.layer.legend.noLayersVisibleWithShowAllButtonButZoom' | translate}} +

    +

    + {{'igo.geo.layer.legend.noLayersVisible' | translate}} +

    +

    + {{'igo.geo.layer.legend.noLayersVisibleButZoom' | translate}} +

    diff --git a/packages/geo/src/lib/layer/layer-legend-list/layer-legend-list.component.scss b/packages/geo/src/lib/layer/layer-legend-list/layer-legend-list.component.scss new file mode 100644 index 0000000000..2b5e7017d6 --- /dev/null +++ b/packages/geo/src/lib/layer/layer-legend-list/layer-legend-list.component.scss @@ -0,0 +1,19 @@ +$slide-toggle-width: 60px; + +mat-slide-toggle { + width: 100%; + margin: 10px; + + ::ng-deep .mat-slide-toggle-content { + width: calc(100% - #{$slide-toggle-width}); + } +} + +igo-list { + display: contents; +} + +.layers-empty { + text-align: justify; + margin: 10px; +} diff --git a/packages/geo/src/lib/layer/layer-legend-list/layer-legend-list.component.ts b/packages/geo/src/lib/layer/layer-legend-list/layer-legend-list.component.ts new file mode 100644 index 0000000000..b1a75408e7 --- /dev/null +++ b/packages/geo/src/lib/layer/layer-legend-list/layer-legend-list.component.ts @@ -0,0 +1,89 @@ +import { Component, Input, ChangeDetectionStrategy, OnInit, OnDestroy, EventEmitter, Output } from '@angular/core'; +import { Layer } from '../shared'; +import { BehaviorSubject, ReplaySubject, Subscription, EMPTY, timer } from 'rxjs'; +import { debounce } from 'rxjs/operators'; + +@Component({ + selector: 'igo-layer-legend-list', + templateUrl: './layer-legend-list.component.html', + styleUrls: ['./layer-legend-list.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class LayerLegendListComponent implements OnInit, OnDestroy { + orderable = true; + + hasVisibleOrInRangeLayers$: BehaviorSubject = new BehaviorSubject(true); + hasVisibleAndNotInRangeLayers$: BehaviorSubject = new BehaviorSubject(true); + layersInUi$: BehaviorSubject = new BehaviorSubject([]); + layers$: BehaviorSubject = new BehaviorSubject([]); + showAllLegend: boolean = false; + public change$ = new ReplaySubject(1); + private change$$: Subscription; + @Input() + set layers(value: Layer[]) { + this._layers = value; + this.next(); + } + get layers(): Layer[] { + return this._layers; + } + private _layers: Layer[]; + @Input() excludeBaseLayers: boolean = false; + + @Input() updateLegendOnResolutionChange: boolean = false; + + @Input() allowShowAllLegends: boolean = false; + + @Input() showAllLegendsValue: boolean = false; + + @Output() allLegendsShown = new EventEmitter(false); + + constructor() { } + ngOnInit(): void { + this.change$$ = this.change$ + .pipe(debounce(() => { + return this.layers.length === 0 ? EMPTY : timer(50); + })) + .subscribe(() => { + const layers = this.computeShownLayers(this.layers.slice(0)); + this.layers$.next(layers); + this.hasVisibleOrInRangeLayers$.next( + this.layers.slice(0) + .filter(layer => layer.baseLayer !== true) + .filter(layer => layer.visible$.value && layer.isInResolutionsRange$.value).length > 0 + ); + this.hasVisibleAndNotInRangeLayers$.next( + this.layers.slice(0) + .filter(layer => layer.baseLayer !== true) + .filter(layer => layer.visible$.value && !layer.isInResolutionsRange$.value).length > 0 + ); + + this.layersInUi$.next( + this.layers.slice(0).filter(layer => layer.showInLayerList !== false && (!this.excludeBaseLayers || !layer.baseLayer)) + ); + }); + } + + ngOnDestroy() { + this.change$$.unsubscribe(); + } + private next() { + this.change$.next(); + } + private computeShownLayers(layers: Layer[]) { + let shownLayers = layers.filter((layer: Layer) => layer.visible && layer.isInResolutionsRange); + if (this.showAllLegendsValue) { + shownLayers = layers; + } + return this.sortLayersByZindex(shownLayers); + } + private sortLayersByZindex(layers: Layer[]) { + return layers.sort((layer1, layer2) => layer2.zIndex - layer1.zIndex); + } + + toggleShowAllLegends(toggle: boolean) { + this.showAllLegendsValue = toggle; + this.next(); + this.allLegendsShown.emit(toggle); + } +} diff --git a/packages/geo/src/lib/layer/layer-legend/layer-legend.component.html b/packages/geo/src/lib/layer/layer-legend/layer-legend.component.html index 600f868ea6..8a97990087 100644 --- a/packages/geo/src/lib/layer/layer-legend/layer-legend.component.html +++ b/packages/geo/src/lib/layer/layer-legend/layer-legend.component.html @@ -12,7 +12,7 @@ (toggle)="toggleLegendItem($event, item)" svgIcon="chevron-up"> -

    {{computeItemTitle(item) | async}}

    +

    {{computeItemTitle(item) | async}}

    diff --git a/packages/geo/src/lib/layer/layer-list-tool/index.ts b/packages/geo/src/lib/layer/layer-list-tool/index.ts new file mode 100644 index 0000000000..78c1aef09c --- /dev/null +++ b/packages/geo/src/lib/layer/layer-list-tool/index.ts @@ -0,0 +1,4 @@ +export * from './layer-list-tool.component'; +export * from './layer-list-tool.enum'; +export * from './layer-list-tool.interface'; +export * from './layer-list-tool.service'; diff --git a/packages/geo/src/lib/layer/layer-list-tool/layer-list-tool.component.html b/packages/geo/src/lib/layer/layer-list-tool/layer-list-tool.component.html new file mode 100644 index 0000000000..6359884715 --- /dev/null +++ b/packages/geo/src/lib/layer/layer-list-tool/layer-list-tool.component.html @@ -0,0 +1,50 @@ + + + + + + + + +
    + +
    + + +
    + +
    + +
    + +
    + +
    \ No newline at end of file diff --git a/packages/geo/src/lib/layer/layer-list-tool/layer-list-tool.component.scss b/packages/geo/src/lib/layer/layer-list-tool/layer-list-tool.component.scss new file mode 100644 index 0000000000..c9bfbe4a48 --- /dev/null +++ b/packages/geo/src/lib/layer/layer-list-tool/layer-list-tool.component.scss @@ -0,0 +1,4 @@ +mat-form-field.inputFilter { + width: calc(100% - 100px); + max-width: 200px; +} diff --git a/packages/geo/src/lib/layer/layer-list-tool/layer-list-tool.component.ts b/packages/geo/src/lib/layer/layer-list-tool/layer-list-tool.component.ts new file mode 100644 index 0000000000..b840be756c --- /dev/null +++ b/packages/geo/src/lib/layer/layer-list-tool/layer-list-tool.component.ts @@ -0,0 +1,109 @@ +import { + Component, + Input, + ChangeDetectionStrategy, + OnInit, + EventEmitter, + Output, + OnDestroy +} from '@angular/core'; +import { FloatLabelType } from '@angular/material'; + +import { + BehaviorSubject, + Subscription, +} from 'rxjs'; +import { LayerListControlsOptions } from './layer-list-tool.interface'; + +@Component({ + selector: 'igo-layer-list-tool', + templateUrl: './layer-list-tool.component.html', + styleUrls: ['./layer-list-tool.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class LayerListToolComponent implements OnInit, OnDestroy { + + public onlyVisible$: BehaviorSubject = new BehaviorSubject(false); + public sortAlpha$: BehaviorSubject = new BehaviorSubject(false); + public term$: BehaviorSubject = new BehaviorSubject(undefined); + onlyVisible$$: Subscription; + sortAlpha$$: Subscription; + term$$: Subscription; + + @Input() layersAreAllVisible: boolean = true; + + @Input() floatLabel: FloatLabelType = 'auto'; + + @Input() + set onlyVisible(value: boolean) { + this.onlyVisible$.next(value); + } + get onlyVisible(): boolean { + return this.onlyVisible$.value; + } + + @Input() + set sortAlpha(value: boolean) { + this.sortAlpha$.next(value); + } + get sortAlpha(): boolean { + return this.sortAlpha$.value; + } + + @Input() + set term(value: string) { + this.term$.next(value); + } + get term(): string { + return this.term$.value; + } + + public selectionMode = false; + + @Output() appliedFilterAndSort = new EventEmitter(); + @Output() selection = new EventEmitter(); + + ngOnInit(): void { + this.term$$ = this.term$.subscribe(keyword => { + this.appliedFilterAndSort.emit({ + keyword, + onlyVisible: this.onlyVisible, + sortAlpha: this.sortAlpha}); + }); + + this.onlyVisible$$ = this.onlyVisible$.subscribe(onlyVisible => { + this.appliedFilterAndSort.emit({ + keyword: this.term, + onlyVisible, + sortAlpha: this.sortAlpha}); + }); + this.sortAlpha$$ = this.sortAlpha$.subscribe(sortAlpha => { + this.appliedFilterAndSort.emit({ + keyword: this.term, + onlyVisible: this.onlyVisible, + sortAlpha}); + }); + } + + ngOnDestroy(): void { + this.onlyVisible$$.unsubscribe(); + this.sortAlpha$$.unsubscribe(); + this.term$$.unsubscribe(); + } + + clearTerm() { + this.term = undefined; + } + toggleSortAlpha() { + this.sortAlpha = !this.sortAlpha; + } + + toggleOnlyVisible() { + this.onlyVisible = !this.onlyVisible; + } + + toggleSelectionMode() { + this.selectionMode = !this.selectionMode; + this.selection.emit(this.selectionMode); + } +} diff --git a/packages/geo/src/lib/layer/layer-list-tool/layer-list-tool.enum.ts b/packages/geo/src/lib/layer/layer-list-tool/layer-list-tool.enum.ts new file mode 100644 index 0000000000..be055032fe --- /dev/null +++ b/packages/geo/src/lib/layer/layer-list-tool/layer-list-tool.enum.ts @@ -0,0 +1,5 @@ +export enum LayerListToolControlsEnum { + always = 'always', + never = 'never', + default = 'default' +} diff --git a/packages/integration/src/lib/map/shared/map-details-tool.interface.ts b/packages/geo/src/lib/layer/layer-list-tool/layer-list-tool.interface.ts similarity index 55% rename from packages/integration/src/lib/map/shared/map-details-tool.interface.ts rename to packages/geo/src/lib/layer/layer-list-tool/layer-list-tool.interface.ts index 56edba2a8a..e0e754b343 100644 --- a/packages/integration/src/lib/map/shared/map-details-tool.interface.ts +++ b/packages/geo/src/lib/layer/layer-list-tool/layer-list-tool.interface.ts @@ -1,11 +1,9 @@ -import { LayerListControlsEnum } from '@igo2/geo'; +import { LayerListControlsEnum } from '../layer-list/layer-list.enum'; export interface LayerListControlsOptions { excludeBaseLayers?: boolean; showToolbar?: LayerListControlsEnum; - toolbarThreshold?: number; keyword?: string; - sortedAlpha?: boolean; + sortAlpha?: boolean; onlyVisible?: boolean; - onlyInRange?: boolean; } diff --git a/packages/geo/src/lib/layer/layer-list/layer-list.service.spec.ts b/packages/geo/src/lib/layer/layer-list-tool/layer-list-tool.service.spec.ts similarity index 50% rename from packages/geo/src/lib/layer/layer-list/layer-list.service.spec.ts rename to packages/geo/src/lib/layer/layer-list-tool/layer-list-tool.service.spec.ts index e051161a92..190288c29a 100644 --- a/packages/geo/src/lib/layer/layer-list/layer-list.service.spec.ts +++ b/packages/geo/src/lib/layer/layer-list-tool/layer-list-tool.service.spec.ts @@ -1,18 +1,18 @@ import { TestBed, inject } from '@angular/core/testing'; -import { LayerListService } from './layer-list.service'; +import { LayerListToolService } from './layer-list-tool.service'; -describe('StyleService', () => { +describe('LayerListToolService', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [], providers: [ - LayerListService + LayerListToolService ] }); }); - it('should ...', inject([LayerListService], (service: LayerListService) => { + it('should ...', inject([LayerListToolService], (service: LayerListToolService) => { expect(service).toBeTruthy(); })); diff --git a/packages/geo/src/lib/layer/layer-list/layer-list.service.ts b/packages/geo/src/lib/layer/layer-list-tool/layer-list-tool.service.ts similarity index 84% rename from packages/geo/src/lib/layer/layer-list/layer-list.service.ts rename to packages/geo/src/lib/layer/layer-list-tool/layer-list-tool.service.ts index 5ecc96c51d..be88e5c1a1 100644 --- a/packages/geo/src/lib/layer/layer-list/layer-list.service.ts +++ b/packages/geo/src/lib/layer/layer-list-tool/layer-list-tool.service.ts @@ -3,9 +3,9 @@ import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) -export class LayerListService { +export class LayerListToolService { public keyword: string; - public sortedAlpha = false; + public sortAlpha = false; public onlyVisible = false; public onlyInRange = false; public keywordInitialized = false; diff --git a/packages/geo/src/lib/layer/layer-list/index.ts b/packages/geo/src/lib/layer/layer-list/index.ts index f4e119cd52..be2a712c6e 100644 --- a/packages/geo/src/lib/layer/layer-list/index.ts +++ b/packages/geo/src/lib/layer/layer-list/index.ts @@ -1,4 +1,3 @@ export * from './layer-list.component'; export * from './layer-list-binding.directive'; export * from './layer-list.enum'; -export * from './layer-list.service'; diff --git a/packages/geo/src/lib/layer/layer-list/layer-list-binding.directive.ts b/packages/geo/src/lib/layer/layer-list/layer-list-binding.directive.ts index 365bfb1d72..62db7f2b77 100644 --- a/packages/geo/src/lib/layer/layer-list/layer-list-binding.directive.ts +++ b/packages/geo/src/lib/layer/layer-list/layer-list-binding.directive.ts @@ -1,26 +1,23 @@ -import { Directive, Self, OnInit, OnDestroy, AfterViewInit, Optional } from '@angular/core'; +import { Directive, Self, OnInit, OnDestroy, Optional } from '@angular/core'; import { Subscription, combineLatest } from 'rxjs'; import { RouteService } from '@igo2/core'; import { MapService } from '../../map/shared/map.service'; import { LayerListComponent } from './layer-list.component'; -import { LayerListService } from './layer-list.service'; import { Layer } from '../shared/layers/layer'; import { map, debounceTime } from 'rxjs/operators'; @Directive({ selector: '[igoLayerListBinding]' }) -export class LayerListBindingDirective implements OnInit, AfterViewInit, OnDestroy { +export class LayerListBindingDirective implements OnInit, OnDestroy { private component: LayerListComponent; private layersOrResolutionChange$$: Subscription; layersVisibility$$: Subscription; - layersRange$$: Subscription; constructor( @Self() component: LayerListComponent, private mapService: MapService, - private layerListService: LayerListService, @Optional() private route: RouteService ) { this.component = component; @@ -39,69 +36,21 @@ export class LayerListBindingDirective implements OnInit, AfterViewInit, OnDestr return layer.showInLayerList === true; }); this.component.layers = shownLayers; - this.setLayersVisibilityRangeStatus(shownLayers, this.component.excludeBaseLayers); + this.setLayersVisibilityStatus(shownLayers, this.component.excludeBaseLayers); }); } - ngAfterViewInit(): void { - this.initRoutes(); - } - - private setLayersVisibilityRangeStatus(layers: Layer[], excludeBaseLayers: boolean) { + private setLayersVisibilityStatus(layers: Layer[], excludeBaseLayers: boolean) { if (this.layersVisibility$$ !== undefined) { this.layersVisibility$$.unsubscribe(); this.layersVisibility$$ = undefined; } - if (this.layersRange$$ !== undefined) { - this.layersRange$$.unsubscribe(); - this.layersRange$$ = undefined; - } this.layersVisibility$$ = combineLatest(layers .filter(layer => layer.baseLayer !== excludeBaseLayers ) .map((layer: Layer) => layer.visible$)) .pipe(map((visibles: boolean[]) => visibles.every(Boolean))) .subscribe((allLayersAreVisible: boolean) => this.component.layersAreAllVisible = allLayersAreVisible); - - this.layersRange$$ = combineLatest(layers.map((layer: Layer) => layer.isInResolutionsRange$)) - .pipe(map((inrange: boolean[]) => inrange.every(Boolean))) - .subscribe((layersAreAllInRange: boolean) => - this.component.layersAreAllInRange = layersAreAllInRange); - } - - private initRoutes() { - if ( - this.route && - (this.route.options.llcKKey || this.route.options.llcAKey || - this.route.options.llcVKey || this.route.options.llcVKey)) { - this.route.queryParams.subscribe(params => { - - const keywordFromUrl = params[this.route.options.llcKKey as string]; - const sortedAplhaFromUrl = params[this.route.options.llcAKey as string]; - const onlyVisibleFromUrl = params[this.route.options.llcVKey as string]; - const onlyInRangeFromUrl = params[this.route.options.llcRKey as string]; - if (keywordFromUrl && !this.layerListService.keywordInitialized) { - this.layerListService.keyword = keywordFromUrl; - this.layerListService.keywordInitialized = true; - } - if (sortedAplhaFromUrl && !this.layerListService.sortedAlphaInitialized) { - this.layerListService.sortedAlpha = sortedAplhaFromUrl === '1' ? true : false; - this.layerListService.sortedAlphaInitialized = true; - } - if (onlyVisibleFromUrl && - !this.layerListService.onlyVisibleInitialized && - !this.component.layersAreAllVisible) { - this.layerListService.onlyVisible = onlyVisibleFromUrl === '1' ? true : false; - this.layerListService.onlyVisibleInitialized = true; - } - if (onlyInRangeFromUrl && - !this.layerListService.onlyInRangeInitialized && - !this.component.layersAreAllInRange) { - this.layerListService.onlyInRange = onlyInRangeFromUrl === '1' ? true : false; - this.layerListService.onlyInRangeInitialized = true; - } - }); - } } ngOnDestroy() { @@ -110,10 +59,6 @@ export class LayerListBindingDirective implements OnInit, AfterViewInit, OnDestr this.layersVisibility$$.unsubscribe(); this.layersVisibility$$ = undefined; } - if (this.layersRange$$ !== undefined) { - this.layersRange$$.unsubscribe(); - this.layersRange$$ = undefined; - } } } 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 3c370b7d43..c8ca50b745 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 @@ -1,86 +1,180 @@ - - - - - - - - - -
    - -
    -
    - -
    -
    -
    + + + - - - - + [toggleLegendOnVisibilityChange]="toggleLegendOnVisibilityChange" + [selectionMode]="selection" + [selectAll]="selectAllCheck" + (action)="toggleLayerTool($event)" + (checkbox)="layersCheck($event)"> + + +
    + + + + + + + + +
    + + +
    +
    + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + +
    +
    diff --git a/packages/geo/src/lib/layer/layer-list/layer-list.component.scss b/packages/geo/src/lib/layer/layer-list/layer-list.component.scss index c9bfbe4a48..c2f794f527 100644 --- a/packages/geo/src/lib/layer/layer-list/layer-list.component.scss +++ b/packages/geo/src/lib/layer/layer-list/layer-list.component.scss @@ -1,4 +1,50 @@ +:host .igo-list-min-height { + height: calc(100% - 91px); + padding-top: 8px; +} + +:host .igo-list-max-height { + padding-top: 8px; +} + mat-form-field.inputFilter { width: calc(100% - 100px); max-width: 200px; } + +.igo-layer-actions-container { + width: calc(100% - 5px); + padding-left: 5px; +} + +.igo-layer-actions-container > div { + text-align: center; +} + +#opacity-slider { + float: left; + min-width: unset; + width: 110px; + left: 10px; + top: 10px +} + +.igo-layer-button-group { + display: flex; + float: right; + padding-top: 5px; +} + +:host igo-panel { + height: unset; +} + +#opacity-menu { + max-width: unset; + width: 132px; + height: 50px; + + .mat-menu-content:not(:empty) { + padding-top: 20px; + } +} diff --git a/packages/geo/src/lib/layer/layer-list/layer-list.component.ts b/packages/geo/src/lib/layer/layer-list/layer-list.component.ts index 636ace3ae9..28118ba55d 100644 --- a/packages/geo/src/lib/layer/layer-list/layer-list.component.ts +++ b/packages/geo/src/lib/layer/layer-list/layer-list.component.ts @@ -5,13 +5,14 @@ import { TemplateRef, ContentChild, OnInit, - OnDestroy + OnDestroy, + Output, + EventEmitter } from '@angular/core'; import { FloatLabelType } from '@angular/material'; import { Layer } from '../shared'; import { LayerListControlsEnum } from './layer-list.enum'; -import { LayerListService } from './layer-list.service'; import { BehaviorSubject, ReplaySubject, @@ -24,6 +25,8 @@ import { MetadataOptions, MetadataLayerOptions } from '../../metadata/shared/metadata.interface'; +import { LayerListControlsOptions } from '../layer-list-tool/layer-list-tool.interface'; +import { IgoMap } from '../../map/shared/map'; // TODO: This class could use a clean up. Also, some methods could be moved ealsewhere @Component({ @@ -42,13 +45,30 @@ export class LayerListComponent implements OnInit, OnDestroy { showToolbar$: BehaviorSubject = new BehaviorSubject(false); + public layerTool: boolean; + activeLayer$: BehaviorSubject = new BehaviorSubject(undefined); + + layersChecked: Layer[] = []; + public selection; + private change$$: Subscription; @ContentChild('igoLayerItemToolbar') templateLayerToolbar: TemplateRef; @Input() layersAreAllVisible: boolean = true; - @Input() layersAreAllInRange: boolean = true; + @Input() ogcButton: boolean = true; + + @Input() timeButton: boolean = true; + + @Input() + get map(): IgoMap { + return this._map; + } + set map(value: IgoMap) { + this._map = value; + } + private _map: IgoMap; @Input() set layers(value: Layer[]) { @@ -60,9 +80,18 @@ export class LayerListComponent implements OnInit, OnDestroy { } private _layers: Layer[]; + set activeLayer(value: Layer) { + this._activeLayer = value; + this.activeLayer$.next(value); + } + get activeLayer(): Layer { + return this._activeLayer; + } + private _activeLayer: Layer; + @Input() floatLabel: FloatLabelType = 'auto'; - @Input() layerFilterAndSortOptions: any = {}; + @Input() layerFilterAndSortOptions: LayerListControlsOptions = {}; @Input() excludeBaseLayers: boolean = false; @@ -74,67 +103,63 @@ export class LayerListComponent implements OnInit, OnDestroy { @Input() queryBadge: boolean = false; + @Output() appliedFilterAndSort = new EventEmitter(); + get keyword(): string { - return this.layerListService.keyword; + return this._keyword; } set keyword(value: string) { - this.layerListService.keyword = value; + this._keyword = value; this.next(); } - - get keywordInitialized(): boolean { - return this.layerListService.keywordInitialized; - } - set keywordInitialized(value: boolean) { - this.layerListService.keywordInitialized = value; - } + private _keyword = undefined; get onlyVisible(): boolean { - return this.layerListService.onlyVisible; + return this._onlyVisible; } set onlyVisible(value: boolean) { - this.layerListService.onlyVisible = value; + this._onlyVisible = value; this.next(); } + private _onlyVisible = false; - get onlyVisibleInitialized(): boolean { - return this.layerListService.onlyVisibleInitialized; - } - set onlyVisibleInitialized(value: boolean) { - this.layerListService.onlyVisibleInitialized = value; - } - - get onlyInRange(): boolean { - return this.layerListService.onlyInRange; + get sortAlpha(): boolean { + return this._sortedAlpha; } - set onlyInRange(value: boolean) { - this.layerListService.onlyInRange = value; + set sortAlpha(value: boolean) { + this._sortedAlpha = value; this.next(); } + private _sortedAlpha = false; - get onlyInRangeInitialized(): boolean { - return this.layerListService.onlyInRangeInitialized; + get opacity() { + return this.activeLayer$.getValue().opacity * 100; } - set onlyInRangeInitialized(value: boolean) { - this.layerListService.onlyInRangeInitialized = value; + set opacity(opacity: number) { + this.activeLayer$.getValue().opacity = opacity / 100; } - get sortedAlpha(): boolean { - return this.layerListService.sortedAlpha; - } - set sortedAlpha(value: boolean) { - this.layerListService.sortedAlpha = value; - this.next(); + get badgeOpacity() { + if (this.opacity === 100) { + return; + } + return this.opacity; } - get sortedAlphaInitialized(): boolean { - return this.layerListService.sortedAlphaInitialized; + get checkOpacity() { + return this.layersCheckedOpacity() * 100; } - set sortedAlphaInitialized(value: boolean) { - this.layerListService.sortedAlphaInitialized = value; + set checkOpacity(opacity: number) { + for (const layer of this.layersChecked) { + layer.opacity = opacity / 100; + } } - constructor(private layerListService: LayerListService) {} + public toggleOpacity = false; + + public selectAllCheck$ = new BehaviorSubject(false); + selectAllCheck$$: Subscription; + public selectAllCheck = false; /** * Subscribe to the search term stream and trigger researches @@ -150,27 +175,33 @@ export class LayerListComponent implements OnInit, OnDestroy { .subscribe(() => { this.showToolbar$.next(this.computeShowToolbar()); this.layers$.next(this.computeLayers(this.layers.slice(0))); + this.appliedFilterAndSort.emit({ + keyword: this.keyword, + sortAlpha: this.sortAlpha, + onlyVisible: this.onlyVisible + }); }); - this.initLayerFilterAndSortOptions(); + this.selectAllCheck$$ = this.selectAllCheck$.subscribe((value) => { + this.selectAllCheck = value; + }); + + this.layers$.subscribe(() => { + if (this.layers) { + for (const layer of this.layers) { + if (layer.options.active) { + this.activeLayer = layer; + this.layerTool = true; + } + } + } + }); } ngOnDestroy() { this.change$$.unsubscribe(); } - toggleOnlyVisible() { - this.onlyVisible = !this.onlyVisible; - } - - toggleOnlyInRange() { - this.onlyInRange = !this.onlyInRange; - } - - toggleSort(sortAlpha: boolean) { - this.sortedAlpha = sortAlpha; - } - clearKeyword() { this.keyword = undefined; } @@ -197,13 +228,42 @@ export class LayerListComponent implements OnInit, OnDestroy { ); } + raisableLayers(layers: Layer[]) { + let response = false; + for (const layer of layers) { + const mapIndex = this.layers.findIndex(lay => layer.id === lay.id); + const previousLayer = this.layers[mapIndex - 1]; + if (previousLayer && !previousLayer.baseLayer && !layers.find(lay => previousLayer.id === lay.id)) { + response = true; + } + } + return response; + } + + lowerableLayers(layers: Layer[]) { + let response = false; + for (const layer of layers) { + const mapIndex = this.layers.findIndex(lay => layer.id === lay.id); + const nextLayer = this.layers[mapIndex + 1]; + if (nextLayer && !nextLayer.baseLayer && !layers.find(lay => nextLayer.id === lay.id)) { + response = true; + } + } + return response; + } + + lowerLayers(layers: Layer[]) { + this.map.lowerLayers(layers); + layers.reverse(); + } + private next() { this.change$.next(); } private computeLayers(layers: Layer[]): Layer[] { let layersOut = this.filterLayers(layers); - if (this.sortedAlpha) { + if (this.sortAlpha) { layersOut = this.sortLayersByTitle(layersOut); } else { layersOut = this.sortLayersByZindex(layersOut); @@ -211,14 +271,23 @@ export class LayerListComponent implements OnInit, OnDestroy { return layersOut; } + onKeywordChange(term) { + this.keyword = term; + } + + onAppliedFilterAndSortChange(appliedFilter: LayerListControlsOptions) { + this.keyword = appliedFilter.keyword; + this.onlyVisible = appliedFilter.onlyVisible; + this.sortAlpha = appliedFilter.sortAlpha; + } + private filterLayers(layers: Layer[]): Layer[] { - const keyword = this.keyword; if ( this.layerFilterAndSortOptions.showToolbar === LayerListControlsEnum.never ) { return layers; } - if (!keyword && !this.onlyInRange && !this.onlyVisible) { + if (!this.keyword && !this.onlyVisible) { return layers; } @@ -233,8 +302,8 @@ export class LayerListComponent implements OnInit, OnDestroy { return kw.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); }); - if (keyword) { - const localKeyword = keyword + if (this.keyword) { + const localKeyword = this.keyword .normalize('NFD') .replace(/[\u0300-\u036f]/g, ''); const layerTitle = layer.title @@ -247,7 +316,7 @@ export class LayerListComponent implements OnInit, OnDestroy { undefined; if ( !keywordRegex.test(layerTitle) && - !(keyword.toLowerCase() === dataSourceType.toLowerCase()) && + !(this.keyword.toLowerCase() === dataSourceType.toLowerCase()) && !keywordInList ) { const index = keepLayerIds.indexOf(layer.id); @@ -263,13 +332,6 @@ export class LayerListComponent implements OnInit, OnDestroy { keepLayerIds.splice(index, 1); } } - - if (this.onlyInRange && layer.isInResolutionsRange === false) { - const index = keepLayerIds.indexOf(layer.id); - if (index > -1) { - keepLayerIds.splice(index, 1); - } - } }); return layers.filter( @@ -303,7 +365,6 @@ export class LayerListComponent implements OnInit, OnDestroy { if ( this.layers.length >= this.thresholdToFilterAndSort || this.keyword || - this.onlyInRange || this.onlyVisible ) { return true; @@ -312,37 +373,83 @@ export class LayerListComponent implements OnInit, OnDestroy { } } - private initLayerFilterAndSortOptions() { - if (this.layerFilterAndSortOptions.toolbarThreshold) { - this.thresholdToFilterAndSort = this.layerFilterAndSortOptions.toolbarThreshold; + toggleLayerTool(layer) { + this.toggleOpacity = false; + if (this.layerTool && layer === this.activeLayer) { + this.layerTool = false; + } else { + this.layerTool = true; } - if (this.layerFilterAndSortOptions.keyword && !this.keywordInitialized) { - this.keyword = this.layerFilterAndSortOptions.keyword; - this.keywordInitialized = true; + for (const lay of this.layers) { + lay.options.active = false; } - if ( - this.layerFilterAndSortOptions.sortedAlpha && - !this.sortedAlphaInitialized - ) { - this.sortedAlpha = this.layerFilterAndSortOptions.sortedAlpha; - this.sortedAlphaInitialized = true; + layer.options.active = true; + this.activeLayer = layer; + return; + } + + removeLayers(layers?: Layer[]) { + if (layers && layers.length > 0) { + for (const layer of layers) { + layer.map.removeLayer(layer); + } + this.layersChecked = []; + } else if (!layers) { + this.activeLayer.map.removeLayer(this.activeLayer); + this.layerTool = false; } - if ( - this.layerFilterAndSortOptions.onlyVisible && - !this.onlyVisibleInitialized && - !this.layersAreAllVisible - ) { - this.onlyVisible = this.layerFilterAndSortOptions.onlyVisible; - this.onlyVisibleInitialized = true; + return; + } + + layersCheck(event: {layer: Layer; check: boolean}) { + if (event.check === true) { + const eventMapIndex = this.layers.findIndex(layer => event.layer.id === layer.id); + for (const layer of this.layersChecked) { + const mapIndex = this.layers.findIndex(lay => layer.id === lay.id); + if (eventMapIndex < mapIndex) { + this.layersChecked.splice(this.layersChecked.findIndex(lay => layer.id === lay.id), 0, event.layer); + return; + } + } + this.layersChecked.push(event.layer); + } else { + const index = this.layersChecked.findIndex(layer => event.layer.id === layer.id); + this.layersChecked.splice(index, 1); } - if ( - this.layerFilterAndSortOptions.onlyInRange && - !this.onlyInRangeInitialized && - !this.layersAreAllInRange - ) { - this.onlyInRange = this.layerFilterAndSortOptions.onlyInRange; - this.onlyInRangeInitialized = true; + } + + toggleSelectionMode(value: boolean) { + this.selection = value; + this.activeLayer = undefined; + if (value === true) { + this.layerTool = false; + } + } + + layersCheckedOpacity(): any { + if (this.layersChecked.length > 1) { + return 1; + } else { + const opacity = []; + for (const layer of this.layersChecked) { + opacity.push(layer.opacity); + } + return opacity; + } + } + + selectAll() { + if (!this.selectAllCheck) { + for (const layer of this.layers) { + if (!layer.baseLayer) { + this.layersChecked.push(layer); + } + } + this.selectAllCheck$.next(true); + } else { + this.layersChecked = []; + this.selectAllCheck$.next(false); } } } diff --git a/packages/geo/src/lib/layer/layer.module.ts b/packages/geo/src/lib/layer/layer.module.ts index 5f7a3bd343..c1b4628df5 100644 --- a/packages/geo/src/lib/layer/layer.module.ts +++ b/packages/geo/src/lib/layer/layer.module.ts @@ -10,24 +10,33 @@ import { MatListModule, MatSliderModule, MatBadgeModule, - MatSelectModule + MatSelectModule, + MatSlideToggleModule, + MatDividerModule, + MatMenuModule, + MatCheckboxModule } from '@angular/material'; import { IgoLanguageModule } from '@igo2/core'; import { IgoListModule, IgoCollapsibleModule, - IgoImageModule + IgoImageModule, + IgoPanelModule } from '@igo2/common'; import { LayerService } from './shared/layer.service'; import { StyleService } from './shared/style.service'; -import { LayerListService } from './layer-list/layer-list.service'; +import { LayerListToolService } from './layer-list-tool/layer-list-tool.service'; import { LayerItemComponent } from './layer-item/layer-item.component'; import { LayerLegendComponent } from './layer-legend/layer-legend.component'; import { LayerListComponent } from './layer-list/layer-list.component'; +import { LayerListToolComponent } from './layer-list-tool/layer-list-tool.component'; import { LayerListBindingDirective } from './layer-list/layer-list-binding.directive'; +import { LayerLegendListBindingDirective } from './layer-legend-list/layer-legend-list-binding.directive'; import { TrackFeatureButtonComponent } from './track-feature-button/track-feature-button.component'; +import { LayerLegendListComponent } from './layer-legend-list/layer-legend-list.component'; +import { LayerLegendItemComponent } from './layer-legend-item/layer-legend-item.component'; @NgModule({ imports: [ @@ -35,30 +44,43 @@ import { TrackFeatureButtonComponent } from './track-feature-button/track-featur MatFormFieldModule, CommonModule, FormsModule, + MatDividerModule, + MatMenuModule, MatIconModule, MatButtonModule, + MatSlideToggleModule, MatSelectModule, MatTooltipModule, MatListModule, MatSliderModule, MatBadgeModule, + MatCheckboxModule, IgoLanguageModule, IgoListModule, IgoCollapsibleModule, - IgoImageModule + IgoImageModule, + IgoPanelModule ], exports: [ LayerItemComponent, + LayerLegendItemComponent, LayerLegendComponent, LayerListComponent, + LayerListToolComponent, + LayerLegendListComponent, LayerListBindingDirective, + LayerLegendListBindingDirective, TrackFeatureButtonComponent ], declarations: [ LayerItemComponent, + LayerLegendItemComponent, LayerLegendComponent, LayerListComponent, + LayerListToolComponent, + LayerLegendListComponent, LayerListBindingDirective, + LayerLegendListBindingDirective, TrackFeatureButtonComponent ] }) @@ -66,7 +88,7 @@ export class IgoLayerModule { static forRoot(): ModuleWithProviders { return { ngModule: IgoLayerModule, - providers: [LayerService, StyleService, LayerListService] + providers: [LayerService, StyleService, LayerListToolService] }; } } diff --git a/packages/geo/src/lib/layer/layer.theming.scss b/packages/geo/src/lib/layer/layer.theming.scss index 8a61d4b603..5154a5e0f1 100644 --- a/packages/geo/src/lib/layer/layer.theming.scss +++ b/packages/geo/src/lib/layer/layer.theming.scss @@ -1,5 +1,13 @@ @import './layer-legend/layer-legend.theming'; @mixin igo-layer-theming($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + @include igo-layer-legend-theming($theme); + + igo-layer-item.igo-layer-item-focused > mat-list-item { + background-color: mat-color($accent, lighter); + color: mat-color($primary, default-contrast); + } } diff --git a/packages/geo/src/lib/layer/shared/layers/layer.interface.ts b/packages/geo/src/lib/layer/shared/layers/layer.interface.ts index ffbe657c48..88881d3f92 100644 --- a/packages/geo/src/lib/layer/shared/layers/layer.interface.ts +++ b/packages/geo/src/lib/layer/shared/layers/layer.interface.ts @@ -23,6 +23,7 @@ export interface LayerOptions { ol?: olLayer; tooltip?: TooltipContent; _internal?: { [key: string]: string }; + active?: boolean; } export interface GroupLayers { diff --git a/packages/geo/src/lib/map/shared/map.ts b/packages/geo/src/lib/map/shared/map.ts index 98f5540397..675ae0f073 100644 --- a/packages/geo/src/lib/map/shared/map.ts +++ b/packages/geo/src/lib/map/shared/map.ts @@ -272,18 +272,31 @@ export class IgoMap { raiseLayer(layer: Layer) { const index = this.getLayerIndex(layer); - if (index > 0) { + if (index > 1 && !this.layers[index - 1].baseLayer) { this.moveLayer(layer, index, index - 1); } } + raiseLayers(layers: Layer[]) { + for (const layer of layers) { + this.raiseLayer(layer); + } + } + lowerLayer(layer: Layer) { const index = this.getLayerIndex(layer); - if (index < this.layers.length - 1) { + if (index < this.layers.length - 1 && !this.layers[index + 1].baseLayer) { this.moveLayer(layer, index, index + 1); } } + lowerLayers(layers: Layer[]) { + const reverseLayers = layers.reverse(); + for (const layer of reverseLayers) { + this.lowerLayer(layer); + } + } + moveLayer(layer: Layer, from: number, to: number) { const layerTo = this.layers[to]; const zIndexTo = layerTo.zIndex; diff --git a/packages/geo/src/locale/en.geo.json b/packages/geo/src/locale/en.geo.json index cbc15cc99f..4d3239eeef 100644 --- a/packages/geo/src/locale/en.geo.json +++ b/packages/geo/src/locale/en.geo.json @@ -129,9 +129,16 @@ "layer": { "hideLayer": "Hide Layer", "lowerLayer": "Bring layer backward", + "filterLowerLayer": "The backward movement is always done according to the order of the map!", "filterPlaceholder": "Filter the layers list", + "activateSelectionMode": "Activate multiple selection mode", + "deactivateSelectionMode": "Deactivate multiple selection mode", + "tools": "Tools", + "selectAll": "Select all", + "deselectAll": "Deselect all", "opacity": "Opacity", "raiseLayer": "Bring layer forward", + "filterRaiseLayer": "The forward movement is always done according to the order of the map!", "moreOptions": "More options", "removeLayer": "Remove this layer from the map", "showFeaturesList": "Show features list", @@ -149,7 +156,12 @@ "loadingLegendText": "Loading legend", "noLegendText": "No legend available for this layer", "noLegendScale": "No legend available for this scale", - "selectStyle": "Select the style of the legend" + "selectStyle": "Select the style of the legend", + "showAll" : "Show all legends (non visible and out of range)", + "noLayersVisibleWithShowAllButton": "No layer currently visible. Activate the button above to see all the legends.", + "noLayersVisibleWithShowAllButtonButZoom": "No layer currently visible. If you zoom in the map, some layers will appear OR activate the button above to see all the legends.", + "noLayersVisible": "No layers currently visible.", + "noLayersVisibleButZoom": "No layers currently visible. However, if you zoom in the map, some layers will appear." } }, "download": { diff --git a/packages/geo/src/locale/fr.geo.json b/packages/geo/src/locale/fr.geo.json index 939122f77a..19d6fd9fa5 100644 --- a/packages/geo/src/locale/fr.geo.json +++ b/packages/geo/src/locale/fr.geo.json @@ -129,9 +129,16 @@ "layer": { "hideLayer": "Masquer la couche", "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", + "activateSelectionMode": "Activer le mode de sélection multiple", + "deactivateSelectionMode": "Désactiver le mode de sélection multiple", + "tools": "Outils", + "selectAll": "Tout sélectionner", + "deselectAll": "Tout désélectionner", "opacity": "Opacité", "raiseLayer": "Mettre en avant", + "filterRaiseLayer": "Le déplacement avant se fait toujours selon l'ordre de la carte!", "moreOptions": "Options sur la couche", "removeLayer": "Retirer la couche de la carte", "showFeaturesList": "Afficher la liste des entités", @@ -149,7 +156,13 @@ "loadingLegendText": "Chargement de la légende", "noLegendText": "Aucune légende disponible pour cette couche", "noLegendScale": "Aucune légende disponible pour cette échelle", - "selectStyle": "Sélectionner le style de la légende" + "selectStyle": "Sélectionner le style de la légende", + "showAll" : "Montrer toutes les légendes (non visible et hors échelle)", + "noLayersVisibleWithShowAllButton": "Aucune couche actuellement visible. Activez le bouton ci haut pour voir toutes les légendes.", + "noLayersVisibleWithShowAllButtonButZoom": "Aucune couche actuellement visible. Si vous zoomez dans la carte, certaines couches apparaîtront OU activez le bouton ci haut pour voir toutes les légendes.", + "noLayersVisible": "Aucune couche actuellement visible.", + "noLayersVisibleButZoom": "Aucune couche actuellement visible. Par contre, si vous zoomez dans la carte, certaines couches apparaîtront." + } }, "download": { diff --git a/packages/integration/src/lib/context/context-manager-tool/context-manager-tool.component.ts b/packages/integration/src/lib/context/context-manager-tool/context-manager-tool.component.ts index b5ca175645..c24283474e 100644 --- a/packages/integration/src/lib/context/context-manager-tool/context-manager-tool.component.ts +++ b/packages/integration/src/lib/context/context-manager-tool/context-manager-tool.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, Input } from '@angular/core'; import { ToolComponent } from '@igo2/common'; @@ -17,6 +17,8 @@ import { IgoMap } from '@igo2/geo'; }) export class ContextManagerToolComponent { + @Input() toolToOpenOnContextChange: string = 'mapTools'; + get map(): IgoMap { return this.mapState.map; } constructor(private toolState: ToolState, private mapState: MapState) {} diff --git a/packages/integration/src/lib/context/context-share-tool/context-share-tool.component.html b/packages/integration/src/lib/context/context-share-tool/context-share-tool.component.html index 28291ae13c..e4a1d775e0 100644 --- a/packages/integration/src/lib/context/context-share-tool/context-share-tool.component.html +++ b/packages/integration/src/lib/context/context-share-tool/context-share-tool.component.html @@ -1,4 +1,2 @@ + [map]="map"> diff --git a/packages/integration/src/lib/context/context-share-tool/context-share-tool.component.ts b/packages/integration/src/lib/context/context-share-tool/context-share-tool.component.ts index 1121fb1723..89f52f2741 100644 --- a/packages/integration/src/lib/context/context-share-tool/context-share-tool.component.ts +++ b/packages/integration/src/lib/context/context-share-tool/context-share-tool.component.ts @@ -1,9 +1,10 @@ -import { Component, Input } from '@angular/core'; +import { Component } from '@angular/core'; import { ToolComponent } from '@igo2/common'; -import { IgoMap } from '@igo2/geo'; +import { IgoMap, LayerListControlsOptions } from '@igo2/geo'; import { MapState } from '../../map/map.state'; +import { LayerListToolState } from '../../map/layer-list-tool.state'; @ToolComponent({ name: 'shareMap', @@ -18,7 +19,10 @@ export class ContextShareToolComponent { get map(): IgoMap { return this.mapState.map; } + get layerListControls(): LayerListControlsOptions { return this.layerListToolState.getLayerListControls(); } + constructor( - private mapState: MapState + private mapState: MapState, + private layerListToolState: LayerListToolState ) {} } diff --git a/packages/integration/src/lib/filter/active-ogc-filter-tool/active-ogc-filter-tool.animation.ts b/packages/integration/src/lib/filter/active-ogc-filter-tool/active-ogc-filter-tool.animation.ts new file mode 100644 index 0000000000..f38a74b20c --- /dev/null +++ b/packages/integration/src/lib/filter/active-ogc-filter-tool/active-ogc-filter-tool.animation.ts @@ -0,0 +1,23 @@ +import { + trigger, + state, + style, + transition, + animate, + AnimationTriggerMetadata +} from '@angular/animations'; + +export function toolSlideInOut( + speed = '300ms', + type = 'ease-in-out' +): AnimationTriggerMetadata { + return trigger('toolSlideInOut', [ + state( + 'enter', + style({ + transform: 'translateX(100%)' + }) + ), + transition('void => enter', animate(speed + ' ' + type)) + ]); +} diff --git a/packages/integration/src/lib/filter/active-ogc-filter-tool/active-ogc-filter-tool.component.html b/packages/integration/src/lib/filter/active-ogc-filter-tool/active-ogc-filter-tool.component.html new file mode 100644 index 0000000000..214c2ce576 --- /dev/null +++ b/packages/integration/src/lib/filter/active-ogc-filter-tool/active-ogc-filter-tool.component.html @@ -0,0 +1,8 @@ + + + + diff --git a/packages/integration/src/lib/filter/active-ogc-filter-tool/active-ogc-filter-tool.component.ts b/packages/integration/src/lib/filter/active-ogc-filter-tool/active-ogc-filter-tool.component.ts new file mode 100644 index 0000000000..84495084cd --- /dev/null +++ b/packages/integration/src/lib/filter/active-ogc-filter-tool/active-ogc-filter-tool.component.ts @@ -0,0 +1,38 @@ +import { Component } from '@angular/core'; + +import { ToolComponent } from '@igo2/common'; +import { MapState } from '../../map/map.state'; +import { Layer, IgoMap } from '@igo2/geo'; + +import { toolSlideInOut } from './active-ogc-filter-tool.animation'; + +@ToolComponent({ + name: 'activeOgcFilter', + title: 'igo.integration.tools.ogcFilter', + icon: 'filter', + parent: 'mapTools' +}) +@Component({ + selector: 'igo-active-ogc-filter-tool', + templateUrl: './active-ogc-filter-tool.component.html', + animations: [toolSlideInOut()] +}) +export class ActiveOgcFilterToolComponent { + + get map(): IgoMap { + return this.mapState.map; + } + + get layer(): Layer { + for (const lay of this.map.layers) { + if (lay.options.active === true) { + return lay; + } + } + return; + } + + public animate = 'enter'; + + constructor(public mapState: MapState) {} +} diff --git a/packages/integration/src/lib/filter/active-ogc-filter-tool/index.ts b/packages/integration/src/lib/filter/active-ogc-filter-tool/index.ts new file mode 100644 index 0000000000..c94c3e87b6 --- /dev/null +++ b/packages/integration/src/lib/filter/active-ogc-filter-tool/index.ts @@ -0,0 +1 @@ +export * from './active-ogc-filter-tool.component'; diff --git a/packages/integration/src/lib/filter/active-time-filter-tool/active-time-filter-tool.animation.ts b/packages/integration/src/lib/filter/active-time-filter-tool/active-time-filter-tool.animation.ts new file mode 100644 index 0000000000..f38a74b20c --- /dev/null +++ b/packages/integration/src/lib/filter/active-time-filter-tool/active-time-filter-tool.animation.ts @@ -0,0 +1,23 @@ +import { + trigger, + state, + style, + transition, + animate, + AnimationTriggerMetadata +} from '@angular/animations'; + +export function toolSlideInOut( + speed = '300ms', + type = 'ease-in-out' +): AnimationTriggerMetadata { + return trigger('toolSlideInOut', [ + state( + 'enter', + style({ + transform: 'translateX(100%)' + }) + ), + transition('void => enter', animate(speed + ' ' + type)) + ]); +} diff --git a/packages/integration/src/lib/filter/active-time-filter-tool/active-time-filter-tool.component.html b/packages/integration/src/lib/filter/active-time-filter-tool/active-time-filter-tool.component.html new file mode 100644 index 0000000000..87b0e2d0ab --- /dev/null +++ b/packages/integration/src/lib/filter/active-time-filter-tool/active-time-filter-tool.component.html @@ -0,0 +1,6 @@ + + diff --git a/packages/integration/src/lib/filter/active-time-filter-tool/active-time-filter-tool.component.ts b/packages/integration/src/lib/filter/active-time-filter-tool/active-time-filter-tool.component.ts new file mode 100644 index 0000000000..3b634f8b0e --- /dev/null +++ b/packages/integration/src/lib/filter/active-time-filter-tool/active-time-filter-tool.component.ts @@ -0,0 +1,37 @@ +import { Component } from '@angular/core'; + +import { ToolComponent } from '@igo2/common'; +import { MapState } from '../../map/map.state'; +import { IgoMap, Layer } from '@igo2/geo'; +import { toolSlideInOut } from './active-time-filter-tool.animation'; + +@ToolComponent({ + name: 'activeTimeFilter', + title: 'igo.integration.tools.timeFilter', + icon: 'history', + parent: 'mapTools' +}) +@Component({ + selector: 'igo-active-time-filter-tool', + templateUrl: './active-time-filter-tool.component.html', + animations: [toolSlideInOut()] +}) +export class ActiveTimeFilterToolComponent { + + get map(): IgoMap { + return this.mapState.map; + } + + get layer(): Layer { + for (const lay of this.map.layers) { + if (lay.options.active === true) { + return lay; + } + } + return; + } + + public animate = 'enter'; + + constructor(public mapState: MapState) {} +} diff --git a/packages/integration/src/lib/filter/active-time-filter-tool/index.ts b/packages/integration/src/lib/filter/active-time-filter-tool/index.ts new file mode 100644 index 0000000000..499b2223c7 --- /dev/null +++ b/packages/integration/src/lib/filter/active-time-filter-tool/index.ts @@ -0,0 +1 @@ +export * from './active-time-filter-tool.component'; diff --git a/packages/integration/src/lib/filter/filter.module.ts b/packages/integration/src/lib/filter/filter.module.ts index 36976c4038..46826b3300 100644 --- a/packages/integration/src/lib/filter/filter.module.ts +++ b/packages/integration/src/lib/filter/filter.module.ts @@ -9,12 +9,32 @@ import { IgoFilterModule, IgoQueryModule } from '@igo2/geo'; import { OgcFilterToolComponent } from './ogc-filter-tool/ogc-filter-tool.component'; import { TimeFilterToolComponent } from './time-filter-tool/time-filter-tool.component'; import { SpatialFilterToolComponent } from './spatial-filter-tool/spatial-filter-tool.component'; +import { ActiveTimeFilterToolComponent } from './active-time-filter-tool/active-time-filter-tool.component'; +import { ActiveOgcFilterToolComponent } from './active-ogc-filter-tool/active-ogc-filter-tool.component'; @NgModule({ imports: [IgoFilterModule, IgoQueryModule, CommonModule], - declarations: [OgcFilterToolComponent, TimeFilterToolComponent, SpatialFilterToolComponent], - exports: [OgcFilterToolComponent, TimeFilterToolComponent, SpatialFilterToolComponent], - entryComponents: [OgcFilterToolComponent, TimeFilterToolComponent, SpatialFilterToolComponent], + declarations: [ + OgcFilterToolComponent, + ActiveOgcFilterToolComponent, + TimeFilterToolComponent, + ActiveTimeFilterToolComponent, + SpatialFilterToolComponent + ], + exports: [ + OgcFilterToolComponent, + ActiveOgcFilterToolComponent, + TimeFilterToolComponent, + ActiveTimeFilterToolComponent, + SpatialFilterToolComponent + ], + entryComponents: [ + OgcFilterToolComponent, + ActiveOgcFilterToolComponent, + TimeFilterToolComponent, + ActiveTimeFilterToolComponent, + SpatialFilterToolComponent + ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class IgoAppFilterModule { diff --git a/packages/integration/src/lib/filter/index.ts b/packages/integration/src/lib/filter/index.ts index 16d650759c..75d08cac70 100644 --- a/packages/integration/src/lib/filter/index.ts +++ b/packages/integration/src/lib/filter/index.ts @@ -1,4 +1,6 @@ export * from './ogc-filter-tool'; +export * from './active-ogc-filter-tool'; export * from './spatial-filter-tool'; export * from './time-filter-tool'; +export * from './active-time-filter-tool'; export * from './filter.module'; diff --git a/packages/integration/src/lib/map/index.ts b/packages/integration/src/lib/map/index.ts index 257aa80f34..4bff6f7888 100644 --- a/packages/integration/src/lib/map/index.ts +++ b/packages/integration/src/lib/map/index.ts @@ -1,4 +1,6 @@ export * from './map-details-tool'; +export * from './map-legend'; export * from './map-tool'; +export * from './map-tools'; export * from './map.state'; export * from './map.module'; diff --git a/packages/integration/src/lib/map/layer-list-tool.state.ts b/packages/integration/src/lib/map/layer-list-tool.state.ts new file mode 100644 index 0000000000..a69c2d4187 --- /dev/null +++ b/packages/integration/src/lib/map/layer-list-tool.state.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; +import { LayerListControlsOptions } from '@igo2/geo'; + +/** + * Service that holds the state of layer list tool values + */ +@Injectable({ + providedIn: 'root' +}) +export class LayerListToolState { + + readonly keyword$: BehaviorSubject = new BehaviorSubject(''); + readonly sortAlpha$: BehaviorSubject = new BehaviorSubject(undefined); + readonly onlyVisible$: BehaviorSubject = new BehaviorSubject(undefined); + readonly selectedTab$: BehaviorSubject = new BehaviorSubject(undefined); + + setKeyword(keyword: string) { + this.keyword$.next(keyword); + } + + setSortAlpha(sort: boolean) { + this.sortAlpha$.next(sort); + } + + setOnlyVisible(onlyVisible: boolean) { + this.onlyVisible$.next(onlyVisible); + } + + setSelectedTab(tab: number) { + this.selectedTab$.next(tab); + } + + getLayerListControls(): LayerListControlsOptions { + return { + keyword: this.keyword$.value, + onlyVisible: this.onlyVisible$.value, + sortAlpha: this.sortAlpha$.value + }; + } +} diff --git a/packages/integration/src/lib/map/map-details-tool/map-details-tool.component.html b/packages/integration/src/lib/map/map-details-tool/map-details-tool.component.html index e530ad4ea0..63165edeb1 100644 --- a/packages/integration/src/lib/map/map-details-tool/map-details-tool.component.html +++ b/packages/integration/src/lib/map/map-details-tool/map-details-tool.component.html @@ -1,4 +1,5 @@ - -

    {{'igo.integration.mapTool.empty' | translate}}

    - + +

    + {{'igo.integration.mapTool.empty' | translate}}

    +

    + {{'igo.integration.mapTool.customize' | translate}}

    +

    {{'igo.integration.mapTool.search-tool' | translate}}

    - +

    {{'igo.integration.mapTool.catalog-tool' | translate}}

    - +

    {{'igo.integration.mapTool.context-tool' | translate}} diff --git a/packages/integration/src/lib/map/map-details-tool/map-details-tool.component.ts b/packages/integration/src/lib/map/map-details-tool/map-details-tool.component.ts index 4c5e6f4dae..113fc4b553 100644 --- a/packages/integration/src/lib/map/map-details-tool/map-details-tool.component.ts +++ b/packages/integration/src/lib/map/map-details-tool/map-details-tool.component.ts @@ -1,13 +1,12 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input, OnInit, ChangeDetectorRef } from '@angular/core'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { ToolComponent } from '@igo2/common'; -import { LayerListControlsEnum, Layer, IgoMap } from '@igo2/geo'; +import { LayerListControlsEnum, Layer, IgoMap, LayerListControlsOptions, SearchSourceService, sourceCanSearch } from '@igo2/geo'; import { ToolState } from './../../tool/tool.state'; import { MapState } from './../map.state'; -import { LayerListControlsOptions } from '../shared/map-details-tool.interface'; @ToolComponent({ name: 'mapDetails', @@ -19,7 +18,10 @@ import { LayerListControlsOptions } from '../shared/map-details-tool.interface'; templateUrl: './map-details-tool.component.html', styleUrls: ['./map-details-tool.component.scss'] }) -export class MapDetailsToolComponent { +export class MapDetailsToolComponent implements OnInit { + + public delayedShowEmptyMapContent: boolean = false; + @Input() toggleLegendOnVisibilityChange: boolean = false; @Input() expandLegendOfVisibleLayers: boolean = false; @@ -34,6 +36,8 @@ export class MapDetailsToolComponent { @Input() queryBadge: boolean = false; + @Input() layerAdditionAllowed: boolean = true; + get map(): IgoMap { return this.mapState.map; } @@ -60,11 +64,9 @@ export class MapDetailsToolComponent { switch (this.layerListControls.showToolbar) { case LayerListControlsEnum.always: filterSortOptions.showToolbar = LayerListControlsEnum.always; - filterSortOptions.toolbarThreshold = undefined; break; case LayerListControlsEnum.never: filterSortOptions.showToolbar = LayerListControlsEnum.never; - filterSortOptions.toolbarThreshold = undefined; break; default: break; @@ -73,7 +75,12 @@ export class MapDetailsToolComponent { } get searchToolInToolbar(): boolean { - return this.toolState.toolbox.getToolbar().indexOf('searchResults') !== -1; + return this.toolState.toolbox.getToolbar().indexOf('searchResults') !== -1 + && + this.searchSourceService + .getSources() + .filter(sourceCanSearch) + .filter(s => s.available && s.getType() === 'Layer').length > 0; } get catalogToolInToolbar(): boolean { @@ -84,7 +91,19 @@ export class MapDetailsToolComponent { return this.toolState.toolbox.getToolbar().indexOf('contextManager') !== -1; } - constructor(private mapState: MapState, private toolState: ToolState) {} + constructor( + private mapState: MapState, + private toolState: ToolState, + private searchSourceService: SearchSourceService, + private cdRef: ChangeDetectorRef) {} + + ngOnInit(): void { + // prevent message to be shown too quickly. Waiting for layers + setTimeout(() => { + this.delayedShowEmptyMapContent = true; + this.cdRef.detectChanges(); + }, 50); + } searchEmit() { this.toolState.toolbox.activateTool('searchResults'); diff --git a/packages/integration/src/lib/map/map-legend/index.ts b/packages/integration/src/lib/map/map-legend/index.ts new file mode 100644 index 0000000000..46b6cc431f --- /dev/null +++ b/packages/integration/src/lib/map/map-legend/index.ts @@ -0,0 +1 @@ +export * from './map-legend-tool.component'; diff --git a/packages/integration/src/lib/map/map-legend/map-legend-tool.component.html b/packages/integration/src/lib/map/map-legend/map-legend-tool.component.html new file mode 100644 index 0000000000..235ff1c521 --- /dev/null +++ b/packages/integration/src/lib/map/map-legend/map-legend-tool.component.html @@ -0,0 +1,43 @@ + + + + +

    + {{ ((visibleLayers$ | async).length) ? ('igo.integration.mapTool.noLayersInRange' | translate) : ('igo.integration.mapTool.noLayersVisible' | translate) }} +

    + + + + +

    + {{'igo.integration.mapTool.empty' | translate}}

    +

    + {{'igo.integration.mapTool.customize' | translate}}

    + + + +

    + {{'igo.integration.mapTool.search-tool' | translate}} +

    +
    + + +

    + {{'igo.integration.mapTool.catalog-tool' | translate}} +

    +
    + + +

    + {{'igo.integration.mapTool.context-tool' | translate}} +

    +
    +
    +
    diff --git a/packages/integration/src/lib/map/map-legend/map-legend-tool.component.scss b/packages/integration/src/lib/map/map-legend/map-legend-tool.component.scss new file mode 100644 index 0000000000..06c68bf10f --- /dev/null +++ b/packages/integration/src/lib/map/map-legend/map-legend-tool.component.scss @@ -0,0 +1,23 @@ +.map-empty, .search-tool, .catalog-tool, .context-tool { + margin: 10px; +} + +.map-empty { + text-align: justify; +} + +.search-tool, .catalog-tool, .context-tool { + cursor: pointer; +} + +mat-list mat-list-item h4 { + font-weight: bold; +} + +mat-list-item ::ng-deep .mat-list-text { + font-size: smaller; +} + +mat-list.mat-list-base mat-list-item.mat-list-item ::ng-deep div.mat-list-item-content div.mat-list-text { + padding-left: 5px; +} diff --git a/packages/integration/src/lib/map/map-legend/map-legend-tool.component.ts b/packages/integration/src/lib/map/map-legend/map-legend-tool.component.ts new file mode 100644 index 0000000000..546d3f0ee4 --- /dev/null +++ b/packages/integration/src/lib/map/map-legend/map-legend-tool.component.ts @@ -0,0 +1,141 @@ +import { Component, Input, OnInit, OnDestroy, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core'; +import { Observable, Subscription, BehaviorSubject, ReplaySubject } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { ToolComponent } from '@igo2/common'; +import { Layer, IgoMap, LayerListControlsOptions, SearchSourceService, sourceCanSearch } from '@igo2/geo'; + +import { ToolState } from './../../tool/tool.state'; +import { MapState } from './../map.state'; + +@ToolComponent({ + name: 'mapLegend', + title: 'igo.integration.tools.legend', + icon: 'format-list-bulleted-type' +}) +@Component({ + selector: 'igo-map-legend-tool', + templateUrl: './map-legend-tool.component.html', + styleUrls: ['./map-legend-tool.component.scss'] +}) +export class MapLegendToolComponent implements OnInit, OnDestroy { + + public delayedShowEmptyMapContent: boolean = false; + + layers$: BehaviorSubject = new BehaviorSubject([]); + showAllLegendsValue$: BehaviorSubject = new BehaviorSubject(false); + change$ = new ReplaySubject(1); + + private resolution$$: Subscription; + + @Input() updateLegendOnResolutionChange: boolean = false; + + @Input() layerAdditionAllowed: boolean = true; + + @Input() allowShowAllLegends: boolean = false; + + @Input() showAllLegendsValue: boolean = false; + + @Input() layerListControls: LayerListControlsOptions = {}; + + get map(): IgoMap { + return this.mapState.map; + } + + get visibleOrInRangeLayers$(): Observable { + return this.layers$.pipe( + map( + layers => layers + .filter(layer => layer.visible$.value && layer.isInResolutionsRange$.value) + )); + } + + get visibleLayers$(): Observable { + return this.layers$.pipe( + map( + layers => layers + .filter(layer => layer.visible$.value) + )); + } + + get excludeBaseLayers(): boolean { + return this.layerListControls.excludeBaseLayers || false; + } + + get searchToolInToolbar(): boolean { + return this.toolState.toolbox.getToolbar().indexOf('searchResults') !== -1 + && + this.searchSourceService + .getSources() + .filter(sourceCanSearch) + .filter(s => s.available && s.getType() === 'Layer').length > 0; + } + + get catalogToolInToolbar(): boolean { + return this.toolState.toolbox.getToolbar().indexOf('catalog') !== -1; + } + + get contextToolInToolbar(): boolean { + return this.toolState.toolbox.getToolbar().indexOf('contextManager') !== -1; + } + constructor( + private mapState: MapState, + private toolState: ToolState, + private searchSourceService: SearchSourceService, + private cdRef: ChangeDetectorRef) {} + + ngOnInit(): void { + this.resolution$$ = this.map.viewController.resolution$.subscribe(r => + this.layers$.next( + this.map.layers.filter(layer => layer.showInLayerList !== false && (!this.excludeBaseLayers || !layer.baseLayer)) + )); + + this.mapState.showAllLegendsValue = this.mapState.showAllLegendsValue !== undefined ? + this.mapState.showAllLegendsValue : this.showAllLegendsValue || false; + this.showAllLegendsValue$.next(this.mapState.showAllLegendsValue); + + // prevent message to be shown too quickly. Waiting for layers + setTimeout(() => { + this.delayedShowEmptyMapContent = true; + this.cdRef.detectChanges(); + }, 50); + + } + + onShowAllLegends(event) { + this.mapState.showAllLegendsValue = event; + this.showAllLegendsValue$.next(event); + } + + showAllLegend() { + if (this.layers$.getValue().length === 0) { + return false; + } else if (this.layers$.getValue().length !== 0 && this.allowShowAllLegends === false) { + let visibleOrInRangeLayers; + this.visibleOrInRangeLayers$.subscribe((value) => { + value.length === 0 ? visibleOrInRangeLayers = false : visibleOrInRangeLayers = true; + }); + + if (visibleOrInRangeLayers === false) { + return false; + } + } + return true; + } + + ngOnDestroy(): void { + this.resolution$$.unsubscribe(); + } + + searchEmit() { + this.toolState.toolbox.activateTool('searchResults'); + } + + catalogEmit() { + this.toolState.toolbox.activateTool('catalog'); + } + + contextEmit() { + this.toolState.toolbox.activateTool('contextManager'); + } +} diff --git a/packages/integration/src/lib/map/map-tool/map-tool.component.html b/packages/integration/src/lib/map/map-tool/map-tool.component.html index e25994da67..45ecdebab4 100644 --- a/packages/integration/src/lib/map/map-tool/map-tool.component.html +++ b/packages/integration/src/lib/map/map-tool/map-tool.component.html @@ -2,6 +2,7 @@ + + + + + + + + + + + + + + + + + + + + +

    + {{ ((visibleLayers$ | async).length) ? ('igo.integration.mapTool.noLayersInRange' | translate) : ('igo.integration.mapTool.noLayersVisible' | translate) }} +

    +
    + + + +

    + {{'igo.integration.mapTool.empty' | translate}}

    +

    + {{'igo.integration.mapTool.customize' | translate}}

    + + + + + + +

    + {{'igo.integration.mapTool.search-tool' | translate}} +

    +
    + + + + + +

    + {{'igo.integration.mapTool.catalog-tool' | translate}} +

    +
    + + + + + +

    + {{'igo.integration.mapTool.context-tool' | translate}} +

    +
    +
    +
    + + + diff --git a/packages/integration/src/lib/map/map-tools/map-tools.component.scss b/packages/integration/src/lib/map/map-tools/map-tools.component.scss new file mode 100644 index 0000000000..3ff830f496 --- /dev/null +++ b/packages/integration/src/lib/map/map-tools/map-tools.component.scss @@ -0,0 +1,28 @@ +.map-empty, .search-tool, .catalog-tool, .context-tool { + margin: 10px; +} + +.map-empty { + text-align: justify; +} + +.search-tool, .catalog-tool, .context-tool { + cursor: pointer; +} + +mat-list mat-list-item h4 { + font-weight: bold; +} + +mat-list-item ::ng-deep .mat-list-text { + font-size: smaller; +} + +mat-list.mat-list-base mat-list-item.mat-list-item ::ng-deep div.mat-list-item-content div.mat-list-text { + padding-left: 5px; +} + +mat-tab-group, +mat-tab-group ::ng-deep .mat-tab-body-wrapper { + height: 100%; +} diff --git a/packages/integration/src/lib/map/map-tools/map-tools.component.ts b/packages/integration/src/lib/map/map-tools/map-tools.component.ts new file mode 100644 index 0000000000..8a59904bf4 --- /dev/null +++ b/packages/integration/src/lib/map/map-tools/map-tools.component.ts @@ -0,0 +1,235 @@ +import { Component, ChangeDetectionStrategy, Input, OnInit, ViewChild, OnDestroy } from '@angular/core'; + +import { ToolComponent } from '@igo2/common'; +import { LayerListControlsEnum, LayerListControlsOptions, IgoMap, SearchSourceService, sourceCanSearch, Layer } from '@igo2/geo'; + +import { LayerListToolState } from '../layer-list-tool.state'; +import { MatTabChangeEvent } from '@angular/material'; +import { ToolState } from '../../tool/tool.state'; +import { MapState } from '../map.state'; +import { BehaviorSubject, Observable, Subscription, combineLatest } from 'rxjs'; +import { map, debounceTime } from 'rxjs/operators'; +/** + * Tool to browse a map's layers or to choose a different map + */ +@ToolComponent({ + name: 'mapTools', + title: 'igo.integration.tools.map', + icon: 'map' +}) +@Component({ + selector: 'igo-map-tools', + templateUrl: './map-tools.component.html', + styleUrls: ['./map-tools.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class MapToolsComponent implements OnInit, OnDestroy { + + layers$: BehaviorSubject = new BehaviorSubject([]); + showAllLegendsValue$: BehaviorSubject = new BehaviorSubject(false); + + private resolution$$: Subscription; + public delayedShowEmptyMapContent: boolean = false; + + @Input() allowShowAllLegends: boolean = false; + + @Input() showAllLegendsValue: boolean = false; + + @Input() toggleLegendOnVisibilityChange: boolean = false; + + @Input() expandLegendOfVisibleLayers: boolean = false; + + @Input() updateLegendOnResolutionChange: boolean = false; + + @Input() selectedTabAtOpening: string; + + @Input() ogcButton: boolean = true; + + @Input() timeButton: boolean = true; + + @Input() layerAdditionAllowed: boolean = true; + + @Input() + get layerListControls(): LayerListControlsOptions { + return this._layerListControls; + } + set layerListControls(value: LayerListControlsOptions) { + + const stateOptions = this.layerListToolState.getLayerListControls(); + const stateKeyword = stateOptions.keyword; + const stateOnlyVisible = stateOptions.onlyVisible; + const stateSortAlpha = stateOptions.sortAlpha; + + value.keyword = stateKeyword !== '' ? stateKeyword : value.keyword; + value.onlyVisible = stateOnlyVisible !== undefined ? stateOnlyVisible : value.onlyVisible; + value.sortAlpha = stateSortAlpha !== undefined ? stateSortAlpha : value.sortAlpha; + + value.onlyVisible = value.onlyVisible === undefined ? false : value.onlyVisible; + value.sortAlpha = value.sortAlpha === undefined ? false : value.sortAlpha; + + this._layerListControls = value; + } + private _layerListControls = {}; + + get map(): IgoMap { + return this.mapState.map; + } + + @Input() queryBadge: boolean = false; + + get visibleOrInRangeLayers$(): Observable { + return this.layers$.pipe( + map( + layers => layers + .filter(layer => layer.visible$.value && layer.isInResolutionsRange$.value) + )); + } + + get visibleLayers$(): Observable { + return this.layers$.pipe( + map( + layers => layers + .filter(layer => layer.visible$.value) + )); + } + + get excludeBaseLayers(): boolean { + return this.layerListControls.excludeBaseLayers || false; + } + + get layerFilterAndSortOptions(): LayerListControlsOptions { + const filterSortOptions = Object.assign({ + showToolbar: LayerListControlsEnum.default + }, this.layerListControls); + + switch (this.layerListControls.showToolbar) { + case LayerListControlsEnum.always: + filterSortOptions.showToolbar = LayerListControlsEnum.always; + break; + case LayerListControlsEnum.never: + filterSortOptions.showToolbar = LayerListControlsEnum.never; + break; + default: + break; + } + return filterSortOptions; + } + + @ViewChild('tabGroup') tabGroup; + + get searchToolInToolbar(): boolean { + return this.toolState.toolbox.getToolbar().indexOf('searchResults') !== -1 + && + this.searchSourceService + .getSources() + .filter(sourceCanSearch) + .filter(s => s.available && s.getType() === 'Layer').length > 0; + } + + get catalogToolInToolbar(): boolean { + return this.toolState.toolbox.getToolbar().indexOf('catalog') !== -1; + } + + get contextToolInToolbar(): boolean { + return this.toolState.toolbox.getToolbar().indexOf('contextManager') !== -1; + } + + constructor( + public layerListToolState: LayerListToolState, + private toolState: ToolState, + public mapState: MapState, + private searchSourceService: SearchSourceService + ) { } + + ngOnInit(): void { + this.selectedTab(); + this.resolution$$ = combineLatest([ + this.map.layers$, + this.map.viewController.resolution$] + ).pipe( + debounceTime(10) + ).subscribe((bunch: [Layer[], number]) => { + this.layers$.next( + bunch[0].filter(layer => layer.showInLayerList !== false && (!this.excludeBaseLayers || !layer.baseLayer)) + ); + }); + + this.mapState.showAllLegendsValue = this.mapState.showAllLegendsValue !== undefined ? + this.mapState.showAllLegendsValue : this.showAllLegendsValue || false; + this.showAllLegendsValue$.next(this.mapState.showAllLegendsValue); + + // prevent message to be shown too quickly. Waiting for layers + setTimeout(() => this.delayedShowEmptyMapContent = true, 50); + } + + ngOnDestroy(): void { + this.resolution$$.unsubscribe(); + } + + onShowAllLegends(event) { + this.mapState.showAllLegendsValue = event; + this.showAllLegendsValue$.next(event); + } + + private selectedTab() { + const userSelectedTab = this.layerListToolState.selectedTab$.value; + if (userSelectedTab !== undefined) { + this.layerListToolState.setSelectedTab(userSelectedTab); + } else { + if (this.selectedTabAtOpening === 'legend') { + this.layerListToolState.setSelectedTab(1); + } else { + this.layerListToolState.setSelectedTab(0); + } + } + } + + public tabChanged(tab: MatTabChangeEvent) { + this.layerListToolState.setSelectedTab(tab.index); + this.layers$.next( + this.map.layers.filter(layer => layer.showInLayerList !== false && (!this.excludeBaseLayers || !layer.baseLayer)) + ); + } + + onLayerListChange(appliedFilters: LayerListControlsOptions) { + this.layerListToolState.setKeyword(appliedFilters.keyword); + this.layerListToolState.setSortAlpha(appliedFilters.sortAlpha); + this.layerListToolState.setOnlyVisible(appliedFilters.onlyVisible); + } + + showAllLegend() { + if (this.layers$.getValue().length === 0) { + return false; + } else if (this.layers$.getValue().length !== 0 && this.allowShowAllLegends === false) { + let visibleOrInRangeLayers; + this.visibleOrInRangeLayers$.subscribe((value) => { + value.length === 0 ? visibleOrInRangeLayers = false : visibleOrInRangeLayers = true; + }); + + if (visibleOrInRangeLayers === false) { + return false; + } + } + return true; + } + + activateTimeFilter() { + this.toolState.toolbox.activateTool('activeTimeFilter'); + } + + activateOgcFilter() { + this.toolState.toolbox.activateTool('activeOgcFilter'); + } + + searchEmit() { + this.toolState.toolbox.activateTool('searchResults'); + } + + catalogEmit() { + this.toolState.toolbox.activateTool('catalog'); + } + + contextEmit() { + this.toolState.toolbox.activateTool('contextManager'); + } +} diff --git a/packages/integration/src/lib/map/map.module.ts b/packages/integration/src/lib/map/map.module.ts index b0696d85f1..33739f7066 100644 --- a/packages/integration/src/lib/map/map.module.ts +++ b/packages/integration/src/lib/map/map.module.ts @@ -20,6 +20,8 @@ import { IgoContextModule } from '@igo2/context'; import { MapDetailsToolComponent } from './map-details-tool/map-details-tool.component'; import { MapToolComponent } from './map-tool/map-tool.component'; +import { MapToolsComponent } from './map-tools/map-tools.component'; +import { MapLegendToolComponent } from './map-legend/map-legend-tool.component'; @NgModule({ imports: [ @@ -34,9 +36,9 @@ import { MapToolComponent } from './map-tool/map-tool.component'; IgoFilterModule, IgoContextModule ], - declarations: [MapToolComponent, MapDetailsToolComponent], - exports: [MapToolComponent, MapDetailsToolComponent], - entryComponents: [MapToolComponent, MapDetailsToolComponent], + declarations: [MapToolComponent, MapToolsComponent, MapDetailsToolComponent, MapLegendToolComponent], + exports: [MapToolComponent, MapToolsComponent, MapDetailsToolComponent, MapLegendToolComponent], + entryComponents: [MapToolComponent, MapToolsComponent, MapDetailsToolComponent, MapLegendToolComponent], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class IgoAppMapModule { diff --git a/packages/integration/src/lib/map/map.state.ts b/packages/integration/src/lib/map/map.state.ts index 1a28a9f79a..49cbe7ab86 100644 --- a/packages/integration/src/lib/map/map.state.ts +++ b/packages/integration/src/lib/map/map.state.ts @@ -10,6 +10,15 @@ import { IgoMap, MapService, ProjectionService } from '@igo2/geo'; }) export class MapState { + get showAllLegendsValue(): boolean { + return this._legendToolShowAll; + } + + set showAllLegendsValue(value) { + this._legendToolShowAll = value; + } + private _legendToolShowAll: boolean; + /** * Active map */ diff --git a/packages/integration/src/locale/en.integration.json b/packages/integration/src/locale/en.integration.json index 5f0e6a475e..7eb14869f9 100644 --- a/packages/integration/src/locale/en.integration.json +++ b/packages/integration/src/locale/en.integration.json @@ -8,6 +8,8 @@ "importExport": "Import & export", "ogcFilter": "Filter by", "map": "Map", + "layers": "Layers", + "legend": "Legend", "measurer": "Measure", "print": "Print", "searchResults": "Search Results", @@ -22,10 +24,13 @@ }, "about.html": "
    Features overview
    Search Results
    Located in the upper left portion of the interface, the search bar allows you to locate yourself using an address, location or GPS coordinates. The tool can also be used to search for a layer.

    Contexts
    Visualize groupings of layers made by theme or by work context. When you select a Context, you will find in Map each of the layers it contains. You can add other layers to it from the Catalog or the Search tool.
    WARNING : If you select another Context, all the layers displayed in the Map will be replaced.
    Map
    Personalize your map using the Catalog or the Search tool. This space allows you to add, delete and interrogate layers.

    Measure
    Measure distances or areas on the map.

    Directions
    Build a route between two points (by placing the two points on the map or by looking for addresses). You can then add intermediate points, copy the description of the route to the clipboard or share the link.

    Catalog
    Find all the layers that can be added to the Map. Click on the desired layer to add it.

    Import & export
    Import or export a layer.

    Print
    Print the map displayed on the screen.

    Share
    Create a URL that redirects to an exact representation of your map on the screen. This link can be shared or distributed in a document and allows you to \"save\" your map for later use.

    IGOIGO2 is a free web solution in geomatics, developed in collaborative mode by specialists from several departments and agencies of the government of Quebec.

    Version :{{version.lib}}

    ", "mapTool": { - "empty": "No layer is currently active on this map. Customize it by adding layer from the : ", + "empty": "No layer is currently active on this map. ", + "customize": "Customize it by adding layer from the : ", "search-tool": "Search tool", "catalog-tool": "Data catalog", - "context-tool": "Context manager" + "context-tool": "Context manager", + "noLayersVisible": "There is no visible layers.", + "noLayersInRange": "There is no visible layers at the current scale. Please zoom in or out to discover theses layers." } } } diff --git a/packages/integration/src/locale/fr.integration.json b/packages/integration/src/locale/fr.integration.json index 7290589dca..dc6f77c8fe 100644 --- a/packages/integration/src/locale/fr.integration.json +++ b/packages/integration/src/locale/fr.integration.json @@ -8,6 +8,8 @@ "importExport": "Importer et exporter", "ogcFilter": "Filtrer par", "map": "Carte", + "layers": "Couches", + "legend": "Légende", "measurer": "Mesure", "print": "Imprimer", "searchResults": "Résultats de recherche", @@ -22,10 +24,13 @@ }, "about.html": "
    Aperçu des fonctionnalités
    Recherche
    Située dans la portion supérieure gauche de l’interface, la barre de recherche permet de vous positionner à l’aide d’une adresse, d’un lieu ou de coordonnées GPS et peut également servir à chercher une couche. Il est aussi possible de rechercher un élément par type de résultats en utilisant le hashtag « # » (par exemple, #municipalites québec). Les accents et les lettres majuscules n'importe pas lors de l'utilisation du hashtag.

    Contextes
    Visualisez des regroupements de couches faits par thème ou par contexte de travail. Lorsque vous sélectionnez un Contexte, vous trouverez dans Carte chacune des couches qu’il contient. Vous pouvez y ajouter d’autres couches à partir du Catalogue ou de l’outil Recherche.

    ATTENTION : Si vous sélectionnez un autre Contexte, toutes les couches affichées dans Carte seront remplacées.
    Carte
    Personnalisez votre carte à partir du Catalogue ou de l’outil Recherche. Cet espace permet d’ajouter, de supprimer et d’interroger des couches.

    Outil de mesure
    Mesurez des distances ou des superficies sur la carte.

    Itinéraire
    Construisez un itinéraire entre deux points (en placant les deux points dans la carte ou en cherchant des adresses). Vous pourrez ensuite ajouter des points intermédiaires, copier la description de l'itinéraire dans le presse papier ou partager le lien.

    Catalogue
    Trouvez toutes les couches qui peuvent être ajoutées à la Carte. Cliquez sur la couche désirée pour l’ajouter.

    Importer/Exporter
    Importez ou exportez un fichier de forme (shp).

    Imprimer
    Imprimez la carte affichée à l’écran.

    Partager
    Créez une adresse URL qui redirige vers une représentation exacte de votre carte qui se trouve à l’écran. Ce lien peut être partagé ou diffusé dans un document et permet de « sauvegarder » votre carte pour une utilisation ultérieure.

    IGOIGO2 est une solution Web libre en géomatique, développée en mode collaboratif par des spécialistes provenant de plusieurs ministères et organismes du gouvernement du Québec.

    Version :{{version.lib}}

    ", "mapTool": { - "empty": "La carte ne contient aucune couche de données. Personnalisez-là en ajoutant des couches à partir des outils suivants : ", + "empty": "La carte ne contient aucune couche de données.", + "customize": "Personnalisez-là en ajoutant des couches à partir des outils suivants : ", "search-tool": "Recherche", "catalog-tool": "Catalogue de données", - "context-tool": "Gestionnaire de contextes" + "context-tool": "Gestionnaire de contextes", + "noLayersVisible": "Aucune couche visible.", + "noLayersInRange": "Aucune couche visible dans la plage d'échelle active. Veuillez zoomer ou dézoomer pour découvrir ces couches." } } }