From 18f5c12598d8da6ae3a3c6756c65709d3ba528ef Mon Sep 17 00:00:00 2001 From: QuCMGisaia Date: Mon, 8 Jul 2024 16:11:48 +0200 Subject: [PATCH 01/19] feat: externalize map component --- src/app/app.module.ts | 4 +- .../arlas-map/arlas-map.component.html | 76 ++ .../arlas-map/arlas-map.component.scss | 104 +++ .../arlas-map/arlas-map.component.spec.ts | 35 + .../arlas-map/arlas-map.component.ts | 488 ++++++++++++ .../arlas-wui-root.component.html | 86 +-- .../arlas-wui-root.component.scss | 151 ++-- .../arlas-wui-root.component.spec.ts | 12 +- .../arlas-wui-root.component.ts | 729 +++--------------- src/app/services/map.service.spec.ts | 16 + src/app/services/map.service.ts | 164 ++++ src/app/services/resultlist.service.spec.ts | 10 +- src/app/services/resultlist.service.ts | 121 ++- src/assets/i18n/en.json | 4 +- src/assets/i18n/es.json | 4 +- src/assets/i18n/fr.json | 4 +- src/assets/i18n/template.json | 3 + src/config.json | 186 +++-- src/config.map.json | 257 +++--- src/styles/map.scss | 2 +- 20 files changed, 1450 insertions(+), 1006 deletions(-) create mode 100644 src/app/components/arlas-map/arlas-map.component.html create mode 100644 src/app/components/arlas-map/arlas-map.component.scss create mode 100644 src/app/components/arlas-map/arlas-map.component.spec.ts create mode 100644 src/app/components/arlas-map/arlas-map.component.ts create mode 100644 src/app/services/map.service.spec.ts create mode 100644 src/app/services/map.service.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index bf89c5d9..2d54f578 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -76,6 +76,7 @@ import { VisualizeService } from './services/visualize.service'; import { ArlasTranslateLoader, ArlasWalkthroughLoader } from './tools/customLoader'; import { LazyLoadImageHooks } from './tools/lazy-loader'; import { LAZYLOAD_IMAGE_HOOKS, LazyLoadImageModule } from 'ng-lazyload-image'; +import { ArlasMapComponent } from './components/arlas-map/arlas-map.component'; @NgModule({ @@ -87,7 +88,8 @@ import { LAZYLOAD_IMAGE_HOOKS, LazyLoadImageModule } from 'ng-lazyload-image'; ConfigsListComponent, RoundKilometer, SquareKilometer, - GeocodingComponent + GeocodingComponent, + ArlasMapComponent ], exports: [ AoiDimensionComponent, diff --git a/src/app/components/arlas-map/arlas-map.component.html b/src/app/components/arlas-map/arlas-map.component.html new file mode 100644 index 00000000..1e401144 --- /dev/null +++ b/src/app/components/arlas-map/arlas-map.component.html @@ -0,0 +1,76 @@ +
+ + +
+
+
+ +
+
+ public +
+
+ travel_explore +
+
+ location_searching + +
+
+
+
+ +
+
+ +
+
+ +
+
+ edit +
+
+ +
+
+
° ' "
+
+
+
+ + + + + + +
+ +
+
\ No newline at end of file diff --git a/src/app/components/arlas-map/arlas-map.component.scss b/src/app/components/arlas-map/arlas-map.component.scss new file mode 100644 index 00000000..e75ea2c9 --- /dev/null +++ b/src/app/components/arlas-map/arlas-map.component.scss @@ -0,0 +1,104 @@ +/* + * Licensed to Gisaïa under one or more contributor + * license agreements. See the NOTICE.txt file distributed with + * this work for additional information regarding copyright + * ownership. Gisaïa licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +@import "../../../styles/variables.scss"; +@import "../../../styles/app/sizes.scss"; +@import "../../../styles/app/mixin.scss"; + +.arlas-map__container { + height: 100%; + + .arlas-map-settings { + position: absolute; + top: calc( $sm-spacing + $map-attributions-height + $sm-spacing + + $map-actions-width + $sm-spacing + + $map-actions-length + $sm-spacing); + right: $sm-spacing; + z-index: 2; + + .arlas-map-settings-container { + display: flex; + flex-direction: column; + @include box-border(); + background-color: white; + + .arlas-map-settings-items { + height: $map-actions-width; + width: $map-actions-width; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + border-top: 1px solid #ddd; + + &:hover { + background-color: rgba(0, 0, 0, 0.05); + } + + &:first-child { + border-top: none !important; + } + } + } + } + + .if-geocoding-button { + bottom: calc($map-actions-width + $xs-border) !important; + } + + .arlas-map-action-container { + position: absolute; + right: calc($map-actions-width + $xs-border); + bottom: 0; + display: flex; + flex-direction: row-reverse; + background-color: white; + @include box-border(); + + .arlas-map-action-items { + height: $map-actions-width; + width: $map-actions-width; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + border-right: $xs-border solid #ddd; + color: black; + + &:hover { + background-color: rgba(0, 0, 0, 0.05); + } + + &:first-child { + border-right: unset; + } + } + } + + .aoi-dimensions { + position: absolute; + + right: calc($sm-spacing + $map-actions-width + $sm-spacing); + top: calc($sm-spacing + $map-attributions-height + + $sm-spacing + $map-actions-width + + $sm-spacing + $map-actions-length + + $sm-spacing + $map-actions-width); + z-index: 3; + } +} \ No newline at end of file diff --git a/src/app/components/arlas-map/arlas-map.component.spec.ts b/src/app/components/arlas-map/arlas-map.component.spec.ts new file mode 100644 index 00000000..6ab97c0f --- /dev/null +++ b/src/app/components/arlas-map/arlas-map.component.spec.ts @@ -0,0 +1,35 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TranslateFakeLoader, TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { VisualizeService } from 'app/services/visualize.service'; +import { ArlasToolKitModule, ArlasToolkitSharedModule } from 'arlas-wui-toolkit'; +import { ArlasMapComponent } from './arlas-map.component'; + +describe('ArlasMapComponent', () => { + let component: ArlasMapComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ArlasMapComponent ], + imports: [ + ArlasToolKitModule, + TranslateModule.forRoot({ + loader: { provide: TranslateLoader, useClass: TranslateFakeLoader } }), + ArlasToolkitSharedModule + ], + providers: [ + VisualizeService + ], + teardown: { destroyAfterEach: false } + }) + .compileComponents(); + + fixture = TestBed.createComponent(ArlasMapComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/arlas-map/arlas-map.component.ts b/src/app/components/arlas-map/arlas-map.component.ts new file mode 100644 index 00000000..d5b26237 --- /dev/null +++ b/src/app/components/arlas-map/arlas-map.component.ts @@ -0,0 +1,488 @@ +/* + * Licensed to Gisaïa under one or more contributor + * license agreements. See the NOTICE.txt file distributed with + * this work for additional information regarding copyright + * ownership. Gisaïa licenses this file to you under + * the Apache License, Version 2.0 (the 'License'); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Component, OnInit, ViewChild } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { MatIconRegistry } from '@angular/material/icon'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { DomSanitizer } from '@angular/platform-browser'; +import { ActivatedRoute, Router } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { GeocodingResult } from 'app/services/geocoding.service'; +import { MapService } from 'app/services/map.service'; +import { ResultlistService } from 'app/services/resultlist.service'; +import { VisualizeService } from 'app/services/visualize.service'; +import { + AoiEdition, BboxGeneratorComponent, GeoQuery, MapglComponent, MapglImportComponent, MapglSettingsComponent, SCROLLABLE_ARLAS_ID +} from 'arlas-web-components'; +import { ElementIdentifier, MapContributor } from 'arlas-web-contributors'; +import { LegendData } from 'arlas-web-contributors/contributors/MapContributor'; +import { + ArlasCollaborativesearchService, ArlasConfigService, ArlasMapService, ArlasMapSettings, ArlasSettingsService, ArlasStartupService, getParamValue +} from 'arlas-wui-toolkit'; +import * as mapboxgl from 'mapbox-gl'; +import { BehaviorSubject, debounceTime, fromEvent, merge, mergeMap, Observable, of, Subject, takeUntil, takeWhile } from 'rxjs'; + +// TODO: update with working one +const DEFAULT_BASEMAP = { + styleFile: 'http://demo.arlas.io:82/styles/positron/style.json', + name: 'Positron' +}; + +@Component({ + selector: 'arlas-map', + templateUrl: './arlas-map.component.html', + styleUrls: ['./arlas-map.component.scss'] +}) +export class ArlasMapComponent implements OnInit { + /** Map definition */ + public mapComponentConfig: any; + public mapId = 'mapgl'; + // TODO: add typing + public defaultBasemap; + public mainMapContributor: MapContributor; + public mainCollection: string; + public mapAttributionPosition: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' = 'top-right'; + public mapLoaded = false; + + /** Map interactions */ + public featuresToSelect: Array = []; + + /** Map move */ + public centerLatLng: { lat: number; lng: number; } = { lat: 0, lng: 0 }; + public fitbounds: Array> = []; + public recalculateExtend = true; + public zoomChanged = false; + public zoomStart: number; + private disableRecalculateExtend = false; + + /** Extent in url */ + private allowMapExtend: boolean; + private mapBounds: mapboxgl.LngLatBounds; + private mapEventListener = new Subject(); + private mapExtendTimer: number; + private MAP_EXTEND_PARAM = 'extend'; + + /** Map data */ + public mapDataSources; + public mapRedrawSources; + public mapLegendUpdater = new Subject>>(); + public mapVisibilityUpdater; + /** Visibility status of layers on the map */ + public layersVisibilityStatus: Map = new Map(); + + /** Geo-filters */ + public isMapMenuOpen = false; + public shouldCloseMapMenu = true; + public aoiEdition: AoiEdition; + public geojsondraw: { type: string; features: Array; } = { + 'type': 'FeatureCollection', + 'features': [] + }; + + /** Import geometries */ + public nbVerticesLimit = 50; + public maxFeatures = 100; + + /** Geocoding */ + protected showGeocodingPopup = new BehaviorSubject(false); + protected enableGeocodingFeature = !!this.settingsService.getGeocodingSettings()?.enabled; + + /** Destroy subscriptions */ + private _onDestroy$ = new Subject(); + + @ViewChild('map', { static: false }) public mapglComponent: MapglComponent; + @ViewChild('import', { static: false }) public mapImportComponent: MapglImportComponent; + @ViewChild('mapSettings', { static: false }) public mapSettings: MapglSettingsComponent; + + public constructor( + protected mapService: MapService, + private toolkitMapService: ArlasMapService, + protected visualizeService: VisualizeService, + private configService: ArlasConfigService, + private collaborativeService: ArlasCollaborativesearchService, + protected arlasStartupService: ArlasStartupService, + private settingsService: ArlasSettingsService, + private generateAoiDialog: MatDialog, + private activatedRoute: ActivatedRoute, + private router: Router, + private mapSettingsService: ArlasMapSettings, + private resultlistService: ResultlistService, + private translate: TranslateService, + private snackbar: MatSnackBar, + private iconRegistry: MatIconRegistry, + private domSanitizer: DomSanitizer, + ) { + if (this.arlasStartupService.shouldRunApp && !this.arlasStartupService.emptyMode) { + /** resize the map */ + fromEvent(window, 'resize') + .pipe(debounceTime(100), takeUntil(this._onDestroy$)) + .subscribe((event: Event) => { + this.mapService.resize(); + }); + + this.mapComponentConfig = this.configService.getValue('arlas.web.components.mapgl.input'); + if (!!this.mapComponentConfig) { + this.mapService.setMapConfig(this.mapComponentConfig); + this.defaultBasemap = this.mapComponentConfig.defaultBasemapStyle ?? DEFAULT_BASEMAP; + const mapExtendTimer = this.configService.getValue('arlas.web.components.mapgl.mapExtendTimer'); + this.mapExtendTimer = (mapExtendTimer !== undefined) ? mapExtendTimer : 4000; + this.allowMapExtend = this.configService.getValue('arlas.web.components.mapgl.allowMapExtend'); + this.nbVerticesLimit = this.configService.getValue('arlas.web.components.mapgl.nbVerticesLimit'); + + /** init from url */ + const queryParamVisibleVisualisations = getParamValue('vs'); + if (queryParamVisibleVisualisations) { + const visibleVisuSet = new Set(queryParamVisibleVisualisations.split(';').map(n => decodeURI(n))); + this.mapComponentConfig.visualisations_sets.forEach(v => v.enabled = visibleVisuSet.has(v.name)); + } + } else { + this.defaultBasemap = DEFAULT_BASEMAP; + } + } else { + this.defaultBasemap = DEFAULT_BASEMAP; + } + } + + public ngOnInit(): void { + if (this.arlasStartupService.shouldRunApp && !this.arlasStartupService.emptyMode) { + /** Prepare map data */ + this.mainCollection = this.configService.getValue('arlas.server.collection.name'); + this.mainMapContributor = this.mapService.mapContributors.filter(m => !!m.collection || m.collection === this.mainCollection)[0]; + this.mapDataSources = this.mapService.mapContributors.map(c => c.dataSources).length > 0 ? + this.mapService.mapContributors.map(c => c.dataSources).reduce((set1, set2) => new Set([...set1, ...set2])) : new Set(); + this.mapRedrawSources = merge(...this.mapService.mapContributors.map(c => c.redrawSource)); + + const legendUpdaters: Observable<{ collection: string; legendData: Map; }> = + merge(...this.mapService.mapContributors + .map(c => c.legendUpdater + .pipe(mergeMap(m => of({ collection: c.collection, legendData: m }))) + )); + const legendData = new Map>(); + legendUpdaters + .pipe(takeUntil(this._onDestroy$)) + .subscribe(lg => { + legendData.set(lg.collection, lg.legendData); + this.mapLegendUpdater.next(legendData); + }); + + this.mapVisibilityUpdater = merge(...this.mapService.mapContributors.map(c => c.visibilityUpdater)); + this.mapService.mapContributors.forEach(contrib => contrib.drawingsUpdate.subscribe(() => { + this.geojsondraw = { + 'type': 'FeatureCollection', + 'features': this.mapService.mapContributors.map(c => c.geojsondraw.features).reduce((a, b) => a.concat(b)) + .filter((v, i, a) => a.findIndex(t => (t.properties.arlas_id === v.properties.arlas_id)) === i) + }; + })); + + if (this.allowMapExtend) { + const extendValue = getParamValue(this.MAP_EXTEND_PARAM); + if (extendValue) { + const stringBounds = extendValue.split(','); + if (stringBounds.length === 4) { + this.mapBounds = new mapboxgl.LngLatBounds( + new mapboxgl.LngLat(+stringBounds[0], +stringBounds[1]), + new mapboxgl.LngLat(+stringBounds[2], +stringBounds[3]) + ); + } + } + } + + // eslint-disable-next-line max-len + this.iconRegistry.addSvgIconLiteral('bbox', this.domSanitizer.bypassSecurityTrustHtml('')); + + // eslint-disable-next-line max-len + this.iconRegistry.addSvgIconLiteral('draw_polygon', this.domSanitizer.bypassSecurityTrustHtml(' image/svg+xml ')); + + // eslint-disable-next-line max-len + this.iconRegistry.addSvgIconLiteral('remove_polygon', this.domSanitizer.bypassSecurityTrustHtml(' image/svg+xml ')); + + // eslint-disable-next-line max-len + this.iconRegistry.addSvgIconLiteral('import_polygon', this.domSanitizer.bypassSecurityTrustHtml('')); + + // eslint-disable-next-line max-len + this.iconRegistry.addSvgIconLiteral('map_settings', this.domSanitizer.bypassSecurityTrustHtml('')); + } + } + + public ngAfterViewInit() { + if (!this.arlasStartupService.emptyMode) { + this.mapService.adjustCoordinates(); + + this.mapEventListener + .pipe( + takeUntil(this._onDestroy$), + debounceTime(this.mapExtendTimer)) + .subscribe(() => { + /** Change map extent in the url */ + const bounds = (this.mapglComponent.map).getBounds(); + const extend = bounds.getWest() + ',' + bounds.getSouth() + ',' + bounds.getEast() + ',' + bounds.getNorth(); + const queryParams = Object.assign({}, this.activatedRoute.snapshot.queryParams); + queryParams[this.MAP_EXTEND_PARAM] = extend; + this.router.navigate([], { replaceUrl: true, queryParams: queryParams }); + }); + } + } + + /** + * Wait until the map component is loaded before fetching the data + * @param isLoaded Whether the map has loaded + */ + public onMapLoaded(isLoaded: boolean): void { + if (isLoaded && !this.arlasStartupService.emptyMode) { + this.mapLoaded = true; + this.mapService.setMapComponent(this.mapglComponent); + this.toolkitMapService.setMap(this.mapglComponent.map); + this.visualizeService.setMap(this.mapglComponent.map); + if (this.mapBounds && this.allowMapExtend) { + (this.mapglComponent.map).fitBounds(this.mapBounds, { duration: 0 }); + this.mapBounds = null; + } + this.mapglComponent.map.on('movestart', (e) => { + this.zoomStart = this.mapglComponent.map.getZoom(); + }); + this.mapglComponent.map.on('moveend', (e) => { + if (Math.abs(this.mapglComponent.map.getZoom() - this.zoomStart) > 1) { + this.zoomChanged = true; + } + if (this.allowMapExtend) { + this.mapEventListener.next(null); + } + }); + this.adjustMapOffset(); + this.mapService.adjustCoordinates(); + this.mapService.mapContributors.forEach(mapglContributor => { + mapglContributor.updateData = true; + mapglContributor.fetchData(null); + mapglContributor.setSelection(null, this.collaborativeService.getCollaboration(mapglContributor.identifier)); + }); + + // TODO: add that back when the resultlist is in its own component + // if (!!this.previewListContrib && this.previewListContrib.data.length > 0 && + // this.mapComponentConfig.mapLayers.events.onHover.filter(l => this.mapglComponent.map.getLayer(l)).length > 0) { + // this.resultlistService.updateVisibleItems(); + // } + } + } + + public ngOnDestroy(): void { + this._onDestroy$.next(true); + this._onDestroy$.complete(); + } + + public openAoiGenerator() { + this.generateAoiDialog.open(BboxGeneratorComponent, { + data: { + initCorner: { + lat: this.mapComponentConfig.initCenter ? this.mapComponentConfig.initCenter[1] : 0, + lng: this.mapComponentConfig.initCenter ? this.mapComponentConfig.initCenter[0] : 0, + } + } + }); + } + + public downloadLayerSource(d) { + const mc = this.mapService.mapContributors.find(mc => mc.collection === d.collection); + if (mc) { + mc.downloadLayerSource(d.sourceName, d.layerName, d.downloadType); + } + } + + public openMapSettings(): void { + this.mapSettingsService.mapContributors = this.mapService.mapContributors; + this.mapSettings.openDialog(this.mapSettingsService); + } + + /** + * Applies the selected geo query + */ + public applySelectedGeoQuery(geoQueries: Map) { + const configDebounceTime = this.configService.getValue('arlas.server.debounceCollaborationTime'); + const debounceDuration = configDebounceTime !== undefined ? configDebounceTime : 750; + const changedMapContributors = this.mapService.mapContributors.filter(mc => !!geoQueries.has(mc.collection)); + for (let i = 0; i < changedMapContributors.length; i++) { + setTimeout(() => { + const collection = changedMapContributors[i].collection; + const geoQuery = geoQueries.get(collection); + changedMapContributors[i].setGeoQueryOperation(geoQuery.operation); + changedMapContributors[i].setGeoQueryField(geoQuery.geometry_path); + changedMapContributors[i].onChangeGeoQuery(); + this.snackbar.open(this.translate.instant('Updating Geo-query of') + ' ' + changedMapContributors[i].collection); + if (i === changedMapContributors.length - 1) { + setTimeout(() => this.snackbar.dismiss(), 1000); + } + + }, (i) * (debounceDuration * 1.5)); + } + + } + + public setLayersVisibilityStatus(event) { + this.layersVisibilityStatus = event; + } + + public reloadMapImages() { + this.visualizeService.setMap(this.mapglComponent.map); + } + + public onAoiEdit(aoiEdit: AoiEdition) { + this.aoiEdition = aoiEdit; + } + + public onMove(event) { + // Update data only when the collections info are presents + if (this.resultlistService.collectionToDescription.size > 0) { + /** Change map extend in the url */ + const bounds = (this.mapglComponent.map).getBounds(); + const extend = bounds.getWest() + ',' + bounds.getSouth() + ',' + bounds.getEast() + ',' + bounds.getNorth(); + const queryParams = Object.assign({}, this.activatedRoute.snapshot.queryParams); + const visibileVisus = this.mapglComponent.visualisationSetsConfig.filter(v => v.enabled).map(v => v.name).join(';'); + queryParams[this.MAP_EXTEND_PARAM] = extend; + queryParams['vs'] = visibileVisus; + this.router.navigate([], { replaceUrl: true, queryParams: queryParams }); + localStorage.setItem('currentExtent', JSON.stringify(bounds)); + + const ratioToAutoSort = 0.1; + this.centerLatLng['lat'] = event.centerWithOffset[1]; + this.centerLatLng['lng'] = event.centerWithOffset[0]; + if ((event.xMoveRatio > ratioToAutoSort || event.yMoveRatio > ratioToAutoSort || this.zoomChanged)) { + this.recalculateExtend = true; + } + const newMapExtent = event.extendWithOffset; + const newMapExtentRaw = event.rawExtendWithOffset; + const pwithin = newMapExtent[1] + ',' + newMapExtent[2] + ',' + newMapExtent[3] + ',' + newMapExtent[0]; + const pwithinRaw = newMapExtentRaw[1] + ',' + newMapExtentRaw[2] + ',' + newMapExtentRaw[3] + ',' + newMapExtentRaw[0]; + if (this.recalculateExtend && !this.disableRecalculateExtend) { + this.resultlistService.applyMapExtent(pwithinRaw, pwithin); + + this.mapService.mapContributors.forEach(c => { + if (!!this.resultlistService.resultlistContributors) { + const resultlistContrbutor = this.resultlistService.resultlistContributors.find(v => v.collection === c.collection); + if (!!resultlistContrbutor) { + if (this.resultlistService.isGeoSortActivated.get(c.identifier)) { + c.searchSort = resultlistContrbutor.geoOrderSort; + } else { + c.searchSort = resultlistContrbutor.sort; + } + this.collaborativeService.registry.set(c.identifier, c); + } + } + this.mapService.clearWindowData(c); + }); + this.zoomChanged = false; + } + event.extendForTest = newMapExtent; + event.rawExtendForTest = newMapExtentRaw; + this.mapService.mapContributors.forEach(contrib => contrib.onMove(event, this.recalculateExtend)); + this.recalculateExtend = false; + } + } + + public onChangeAoi(event) { + const configDebounceTime = this.configService.getValue('arlas.server.debounceCollaborationTime'); + const debounceDuration = configDebounceTime !== undefined ? configDebounceTime : 750; + for (let i = 0; i < this.mapService.mapContributors.length; i++) { + setTimeout(() => { + this.snackbar.open(this.translate.instant('Loading data of') + ' ' + this.mapService.mapContributors[i].collection); + this.mapService.mapContributors[i].onChangeAoi(event); + if (i === this.mapService.mapContributors.length - 1) { + setTimeout(() => this.snackbar.dismiss(), 1000); + } + }, (i) * ((debounceDuration + 100) * 1.5)); + } + } + + public changeVisualisation(event) { + this.mapService.mapContributors.forEach(contrib => contrib.changeVisualisation(event)); + const queryParams = Object.assign({}, this.activatedRoute.snapshot.queryParams); + const visibileVisus = this.mapglComponent.visualisationSetsConfig.filter(v => v.enabled).map(v => v.name).join(';'); + queryParams['vs'] = visibileVisus; + this.router.navigate([], { replaceUrl: true, queryParams: queryParams }); + } + + public emitFeaturesOnHover(event) { + if (event.features) { + this.mapService.setCursor('pointer'); + this.resultlistService.highlightItems(event.features); + } else { + this.mapService.setCursor(''); + this.resultlistService.clearHighlightedItems(); + } + } + + public emitFeaturesOnClic(event) { + if (event.features) { + const feature = event.features[0]; + const resultListContributor = this.resultlistService.resultlistContributors + .filter(c => feature.layer.metadata.collection === c.collection + && !feature.layer.id.includes(SCROLLABLE_ARLAS_ID))[0]; + if (!!resultListContributor) { + const idFieldName = this.resultlistService.collectionToDescription.get(resultListContributor.collection).id_path; + const id = feature.properties[idFieldName.replace(/\./g, '_')]; + // Open the list panel if it's closed + this.disableRecalculateExtend = true; + if (!this.resultlistService.listOpen) { + this.resultlistService.toggleList(); + } + + // Get index of list to display + const contributorIds = this.resultlistService.resultlistContributors.map(c => c.identifier); + const listIdx = contributorIds.findIndex(id => id === resultListContributor.identifier); + this.resultlistService.selectedListTabIndex = listIdx; + + // Set Timeout to wait for the detail to be open + setTimeout(() => { + let isDone = false; + this.resultlistService.openDetail$(id) + .pipe(takeUntil(this._onDestroy$), takeWhile(() => !isDone)) + .subscribe(r => { + if (r) { + this.disableRecalculateExtend = false; + isDone = true; + } + }); + }, 250); + } + } + } + + public closeMapMenu() { + setTimeout(() => { + if (this.shouldCloseMapMenu) { + this.isMapMenuOpen = false; + } + }, 100); + } + + protected goToLocation(event: GeocodingResult) { + const bbox = this.visualizeService.getBbox(event.geojson); + this.visualizeService.handleGeojsonPreview(event.geojson); + if (event.geojson.type === 'Point') { + const zoom = this.settingsService.getGeocodingSettings().find_place_zoom_to; + this.mapglComponent.map.fitBounds(bbox, { maxZoom: zoom }); + } else { + this.mapglComponent.map.fitBounds(bbox); + } + } + + private adjustMapOffset() { + this.recalculateExtend = true; + this.mapglComponent.map.fitBounds(this.mapglComponent.map.getBounds()); + } +} diff --git a/src/app/components/arlas-wui-root/arlas-wui-root.component.html b/src/app/components/arlas-wui-root/arlas-wui-root.component.html index cfc34d77..83913cfa 100644 --- a/src/app/components/arlas-wui-root/arlas-wui-root.component.html +++ b/src/app/components/arlas-wui-root/arlas-wui-root.component.html @@ -21,7 +21,7 @@ - @@ -31,9 +31,6 @@
- -
-
-
-
- -
-
- public -
-
- travel_explore -
-
- location_searching - -
-
-
-
- -
-
- -
-
- -
-
- edit -
-
- -
-
-
° ' "
-
-
-
@@ -107,7 +66,9 @@
-
+
keyboard_arrow_left @@ -118,32 +79,9 @@ [showIndicators]="showIndicators" [diameterSpinner]="+spinner.diameter" [colorSpinner]="spinner.color" [strokeWidthSpinner]="spinner.strokeWidth">
- - + + +
- - -
- -
diff --git a/src/app/components/arlas-wui-root/arlas-wui-root.component.scss b/src/app/components/arlas-wui-root/arlas-wui-root.component.scss index 8f2e2045..32eb9e20 100644 --- a/src/app/components/arlas-wui-root/arlas-wui-root.component.scss +++ b/src/app/components/arlas-wui-root/arlas-wui-root.component.scss @@ -31,17 +31,37 @@ padding: 0; } - ::ng-deep.current-coordinate, - ::ng-deep.current-coordinate-edition { - right: calc($sm-spacing + $map-scale-max-width + $default-spacing); - bottom: calc($timeline-height + $sm-spacing); - } + .arlas-map { + display: flex; + width: 100%; + height: 100%; - .map-container-tight-coordinates { ::ng-deep.current-coordinate, ::ng-deep.current-coordinate-edition { - right: $sm-spacing; - bottom: calc($timeline-height + $sm-spacing + $map-scale-height + $sm-spacing); + right: calc($sm-spacing + $map-scale-max-width + $default-spacing); + bottom: calc($timeline-height + $sm-spacing); + } + + &--tight { + ::ng-deep.current-coordinate, + ::ng-deep.current-coordinate-edition { + right: $sm-spacing; + bottom: calc($timeline-height + $sm-spacing + $map-scale-height + $sm-spacing); + } + } + + &--tight-reduce { + ::ng-deep.current-coordinate, + ::ng-deep.current-coordinate-edition { + right: calc($sm-spacing + $result-list-width); + } + } + + &--tight-with-list { + ::ng-deep.current-coordinate, + ::ng-deep.current-coordinate-edition { + right: calc($sm-spacing + $preview-result-list-width); + } } } } @@ -52,12 +72,6 @@ top: 0; } -.arlas-map { - display: flex; - width: 100%; - height: 100%; -} - .side-result-list-toggle { z-index: 2; position: absolute; @@ -227,11 +241,11 @@ .app-container-reduce-analytics, .app-container-reduce { - .arlas-map-action, - .arlas-map-settings { - right: calc($result-list-width + $sm-spacing); + ::ng-deep.arlas-map-action, + ::ng-deep.arlas-map-settings { + right: calc($result-list-width + $sm-spacing) !important; } - .aoi-dimensions { + ::ng-deep.aoi-dimensions { right: calc($result-list-width + $sm-spacing + $map-actions-width + $sm-spacing); } ::ng-deep.basemap-container { @@ -242,10 +256,10 @@ ::ng-deep.current-coordinate-edition { right: calc($result-list-width + $sm-spacing + $map-scale-max-width + $default-spacing); } - .map-container-tight-coordinates { + .arlas-map--tight { ::ng-deep.current-coordinate, ::ng-deep.current-coordinate-edition { - right: calc($result-list-width + $sm-spacing); + right: calc($result-list-width + $sm-spacing) !important; bottom: calc($timeline-height + $sm-spacing + $map-scale-height + $sm-spacing); } } @@ -253,11 +267,11 @@ .app-container-with-list-analytics, .app-container-with-list { - .arlas-map-action, - .arlas-map-settings { - right: calc($preview-result-list-width + $sm-spacing); + ::ng-deep.arlas-map-action, + ::ng-deep.arlas-map-settings { + right: calc($preview-result-list-width + $sm-spacing) !important; } - .aoi-dimensions { + ::ng-deep.aoi-dimensions { right: calc($preview-result-list-width + $sm-spacing + $map-actions-width + $sm-spacing); } @@ -269,10 +283,10 @@ ::ng-deep.current-coordinate-edition { right: calc($preview-result-list-width + $sm-spacing + $map-scale-max-width + $default-spacing); } - .map-container-tight-coordinates { + .arlas-map--tight { ::ng-deep.current-coordinate, ::ng-deep.current-coordinate-edition { - right: calc($preview-result-list-width + $sm-spacing); + right: calc($preview-result-list-width + $sm-spacing) !important; bottom: calc($timeline-height + $sm-spacing + $map-scale-height + $sm-spacing); } } @@ -308,7 +322,8 @@ // Center regarding to timeline tools bottom: calc(($timeline-tools-height - $map-scale-height) / 2); } - .map-container-tight-coordinates { + + .arlas-map--tight { ::ng-deep.current-coordinate, ::ng-deep.current-coordinate-edition { bottom: calc(($timeline-tools-height - $map-scale-height) / 2 + $map-scale-height + $sm-spacing); @@ -341,7 +356,7 @@ bottom: calc($timeline-with-legend-height + ($timeline-tools-height - $map-scale-height) / 2); } - .map-container-tight-coordinates { + .arlas-map--tight { ::ng-deep.current-coordinate, ::ng-deep.current-coordinate-edition { // Center regarding to timeline tools @@ -364,74 +379,6 @@ } } -.arlas-map-action-container { - position: absolute; - right: 30px; - bottom: 0; - display: flex; - flex-direction: row-reverse; - background-color: white; - @include box-border(); - - .arlas-map-action-items { - height: $map-actions-width; - width: $map-actions-width; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - border-right: $xs-border solid #ddd; - color: black; - - &:hover { - background-color: rgba(0, 0, 0, 0.05); - } - - &:first-child { - border-right: unset; - } - } -} - -.if-geocoding-button { - bottom: 30px; -} - -.arlas-map-settings { - position: absolute; - top: calc( - $sm-spacing + $map-attributions-height + $sm-spacing + $map-actions-width + $sm-spacing + $map-actions-length + - $sm-spacing - ); - right: $sm-spacing; - z-index: 2; - - .arlas-map-settings-container { - display: flex; - flex-direction: column; - @include box-border(); - background-color: white; - - .arlas-map-settings-items { - height: $map-actions-width; - width: $map-actions-width; - cursor: pointer; - display: flex; - justify-content: center; - align-items: center; - border-top: 1px solid #ddd; - - &:hover { - background-color: rgba(0, 0, 0, 0.05); - } - - &:first-child { - border-top: none !important; - } - } - } -} - .arlas-map ::ng-deep.basemap-container { top: calc( $sm-spacing + $map-attributions-height + $sm-spacing + $map-actions-width + $sm-spacing + $map-actions-length + @@ -441,6 +388,7 @@ z-index: 1; } +// TODO: still useful ? .arlas-zoom-to-data { margin: 0 4px; background-color: #ff4081; @@ -469,17 +417,6 @@ width: 100%; height: calc(100vh - $top-menu-height); } - -.aoi-dimensions { - position: absolute; - - right: calc($sm-spacing + $map-actions-width + $sm-spacing); - top: calc( - $sm-spacing + $map-attributions-height + $sm-spacing + $map-actions-width + $sm-spacing + $map-actions-length + - $sm-spacing + $map-actions-width - ); - z-index: 3; -} ::ng-deep.bookmark-shortcut { top: 30px !important; } diff --git a/src/app/components/arlas-wui-root/arlas-wui-root.component.spec.ts b/src/app/components/arlas-wui-root/arlas-wui-root.component.spec.ts index f34e5295..59773a5f 100644 --- a/src/app/components/arlas-wui-root/arlas-wui-root.component.spec.ts +++ b/src/app/components/arlas-wui-root/arlas-wui-root.component.spec.ts @@ -1,5 +1,5 @@ import { APP_BASE_HREF } from '@angular/common'; -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatChipsModule } from '@angular/material/chips'; @@ -17,10 +17,10 @@ import { } from 'arlas-wui-toolkit'; import { LeftMenuComponent } from '../left-menu/left-menu.component'; -import { HistogramModule, MapglImportModule, MapglModule, MapglSettingsModule } from 'arlas-web-components'; -import { ArlasWuiRootComponent } from './arlas-wui-root.component'; import { ContributorService } from 'app/services/contributors.service'; import { ResultlistService } from 'app/services/resultlist.service'; +import { HistogramModule } from 'arlas-web-components'; +import { ArlasWuiRootComponent } from './arlas-wui-root.component'; describe('ArlasWuiRootComponent', () => { let component: ArlasWuiRootComponent; @@ -34,8 +34,7 @@ describe('ArlasWuiRootComponent', () => { FormsModule, MatChipsModule, MatTooltipModule, RouterModule, HistogramModule, MatSelectModule, MatMenuModule, MatProgressBarModule, MatRadioModule, TranslateModule.forRoot({ loader: { provide: TranslateLoader, useClass: TranslateFakeLoader } }), - MapglModule, - ArlasTaggerModule, MapglImportModule, MapglSettingsModule, ArlasToolkitSharedModule, + ArlasTaggerModule, ArlasToolkitSharedModule, ], declarations: [ ArlasWuiRootComponent, LeftMenuComponent @@ -47,7 +46,8 @@ describe('ArlasWuiRootComponent', () => { ArlasStartupService, { provide: APP_BASE_HREF, useValue: '/' }, ResultlistService - ] + ], + teardown: { destroyAfterEach: false } }).compileComponents(); }); diff --git a/src/app/components/arlas-wui-root/arlas-wui-root.component.ts b/src/app/components/arlas-wui-root/arlas-wui-root.component.ts index 4bb2142a..e515270c 100644 --- a/src/app/components/arlas-wui-root/arlas-wui-root.component.ts +++ b/src/app/components/arlas-wui-root/arlas-wui-root.component.ts @@ -27,42 +27,35 @@ import { ViewChild } from '@angular/core'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; -import { MatIconRegistry } from '@angular/material/icon'; import { MatSnackBar } from '@angular/material/snack-bar'; import { MatTabGroup } from '@angular/material/tabs'; -import { DomSanitizer, Title } from '@angular/platform-browser'; +import { Title } from '@angular/platform-browser'; import { ActivatedRoute, Router } from '@angular/router'; +import { marker } from '@biesbjerg/ngx-translate-extract-marker'; import { TranslateService } from '@ngx-translate/core'; +import { MapService } from 'app/services/map.service'; +import { ResultlistService } from 'app/services/resultlist.service'; import { CollectionReferenceParameters } from 'arlas-api'; import { - AoiEdition, ArlasColorService, - BboxGeneratorComponent, CellBackgroundStyleEnum, ChartType, Column, DataType, - GeoQuery, Item, - MapglComponent, - MapglImportComponent, - MapglSettingsComponent, ModeEnum, PageQuery, Position, ResultListComponent, - SCROLLABLE_ARLAS_ID, SortEnum } from 'arlas-web-components'; import { AnalyticsContributor, ChipsSearchContributor, ElementIdentifier, - FeatureRenderMode, MapContributor, ResultListContributor } from 'arlas-web-contributors'; -import { LegendData } from 'arlas-web-contributors/contributors/MapContributor'; import { AnalyticsService, ArlasCollaborativesearchService, @@ -80,16 +73,14 @@ import { TimelineComponent } from 'arlas-wui-toolkit'; import * as mapboxgl from 'mapbox-gl'; -import { BehaviorSubject, fromEvent, merge, Observable, of, Subject, zip } from 'rxjs'; -import { debounceTime, finalize, mergeMap, takeUntil } from 'rxjs/operators'; +import { fromEvent, Subject, timer, zip } from 'rxjs'; +import { debounceTime, finalize, takeUntil, takeWhile } from 'rxjs/operators'; import { environment } from '../../../environments/environment'; import { ContributorService } from '../../services/contributors.service'; import { DynamicComponentService } from '../../services/dynamicComponent.service'; import { VisualizeService } from '../../services/visualize.service'; +import { ArlasMapComponent } from '../arlas-map/arlas-map.component'; import { MenuState } from '../left-menu/left-menu.component'; -import { GeocodingResult } from '../../services/geocoding.service'; -import { ResultlistService } from 'app/services/resultlist.service'; -import { marker } from '@biesbjerg/ngx-translate-extract-marker'; @Component({ selector: 'arlas-wui-root', @@ -110,15 +101,11 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { }>(); @Output() public actionOnList = new Subject<{ origin: string; event: string; data?: any; }>(); - public coordinatesHaveSpace = true; public modeEnum = ModeEnum; - public mapglContributors: Array = new Array(); public chipsSearchContributor: ChipsSearchContributor; public resultlistContributors: Array = new Array(); public analyticsContributor: AnalyticsContributor; - public sortOutput = new Map(); - public analytics: Array; public dataType = DataType; public chartType = ChartType; @@ -129,7 +116,6 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { public appNameBackgroundColor: string; // component config - public mapComponentConfig: any; public timelineComponentConfig: any; public detailedTimelineComponentConfig: any; /** @@ -141,19 +127,8 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { public resultListConfigPerContId = new Map(); public resultlistIsExporting = false; - public fitbounds: Array> = []; - public featureToHightLight: { - isleaving: boolean; - elementidentifier: ElementIdentifier; - }; - public featuresToSelect: Array = []; - public nbVerticesLimit = 50; - public isMapMenuOpen = false; - public shouldCloseMapMenu = true; public menuState: MenuState; public searchOpen = true; - public mapId = 'mapgl'; - public centerLatLng: { lat: number; lng: number; } = { lat: 0, lng: 0 }; public previewListContrib: ResultListContributor = null; public rightListContributors: Array = new Array(); /* Options */ @@ -162,27 +137,10 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { public showZoomToData = false; public showIndicators = false; public onSideNavChange: boolean; - public defaultBaseMap; - public mapDataSources; - public mapRedrawSources; - public mapLegendUpdater = new Subject>>(); - public mapVisibilityUpdater; - /** Visibility status of layers on the map */ - public layersVisibilityStatus: Map = new Map(); - public mainMapContributor: MapContributor; public mainCollection; - public geojsondraw: { type: string; features: Array; } = { - 'type': 'FeatureCollection', - 'features': [] - }; public isTimelineOpen = true; - public recalculateExtend = true; - public zoomChanged = false; - public zoomStart: number; - public aoiEdition: AoiEdition; - private disableRecalculateExtend = false; - private currentClickedFeatureId: string = undefined; + public mapglContributors = new Array(); @Input() public hiddenAnalyticsTabs: string[] = []; @Input() public hiddenResultlistTabs: string[] = []; @@ -201,15 +159,14 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { * @description Number of columns in the grid result list */ @Input() public resultListGridColumns = 4; - public isGeoSortActivated = new Map(); public collectionToDescription = new Map(); public collections: string[]; - @ViewChild('map', { static: false }) public mapglComponent: MapglComponent; - @ViewChild('import', { static: false }) public mapImportComponent: MapglImportComponent; - @ViewChild('mapSettings', { static: false }) public mapSettings: MapglSettingsComponent; + public apploading = true; + @ViewChild('tabsList', { static: false }) public tabsList: MatTabGroup; @ViewChild('timeline', { static: false }) public timelineComponent: TimelineComponent; @ViewChild('resultsidenav', { static: false }) public resultListComponent: ResultListComponent; + @ViewChild('arlasmap', { static: false }) public arlasMapComponent: ArlasMapComponent; /** Shortcuts */ public shortcuts = new Array(); @@ -234,17 +191,6 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { */ public spacing = 5; - public mapAttributionPosition: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' = 'top-right'; - private allowMapExtend: boolean; - private mapBounds: mapboxgl.LngLatBounds; - private mapEventListener = new Subject(); - private mapExtendTimer: number; - private MAP_EXTEND_PARAM = 'extend'; - - /** Geocoding */ - protected showGeocodingPopup = new BehaviorSubject(false); - protected enableGeocodingFeature = !!this.arlasSettingsService.getGeocodingSettings()?.enabled; - /* Process */ private downloadDialogRef: MatDialogRef; @@ -258,13 +204,10 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { private contributorService: ContributorService, public arlasStartUpService: ArlasStartupService, private mapSettingsService: ArlasMapSettings, - private iconRegistry: MatIconRegistry, - private domSanitizer: DomSanitizer, private cdr: ChangeDetectorRef, - private mapService: ArlasMapService, + private toolkitMapService: ArlasMapService, private colorService: ArlasColorService, private titleService: Title, - private arlasSettingsService: ArlasSettingsService, private dynamicComponentService: DynamicComponentService, public visualizeService: VisualizeService, private translate: TranslateService, @@ -273,10 +216,10 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { private router: Router, public analyticsService: AnalyticsService, private dialog: MatDialog, - private generateAoiDialog: MatDialog, private processService: ProcessService, - private resultlistService: ResultlistService, - private exportService: ArlasExportCsvService + protected resultlistService: ResultlistService, + private exportService: ArlasExportCsvService, + protected mapService: MapService ) { this.menuState = { configs: false @@ -285,7 +228,6 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { /** resize the map */ fromEvent(window, 'resize').pipe(debounceTime(100)) .subscribe((event: Event) => { - this.mapglComponent.map.resize(); this.resizeCollectionCounts(); this.adjustVisibleShortcuts(); this.adjustComponentsSize(); @@ -308,20 +250,10 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { this.appNameBackgroundColor = this.configService.getValue('arlas-wui.web.app.name_background_color') ? this.configService.getValue('arlas-wui.web.app.name_background_color') : '#FF4081'; this.analyticsContributor = this.arlasStartUpService.contributorRegistry.get('analytics') as AnalyticsContributor; - this.mapComponentConfig = this.configService.getValue('arlas.web.components.mapgl.input'); - const mapExtendTimer = this.configService.getValue('arlas.web.components.mapgl.mapExtendTimer'); - this.mapExtendTimer = (mapExtendTimer !== undefined) ? mapExtendTimer : 4000; - this.allowMapExtend = this.configService.getValue('arlas.web.components.mapgl.allowMapExtend'); - this.nbVerticesLimit = this.configService.getValue('arlas.web.components.mapgl.nbVerticesLimit'); this.timelineComponentConfig = this.configService.getValue('arlas.web.components.timeline'); this.detailedTimelineComponentConfig = this.configService.getValue('arlas.web.components.detailedTimeline'); this.mainCollection = this.configService.getValue('arlas.server.collection.name'); - this.defaultBaseMap = !!this.mapComponentConfig.defaultBasemapStyle ? this.mapComponentConfig.defaultBasemapStyle : - { - styleFile: 'http://demo.arlas.io:82/styles/positron/style.json', - name: 'Positron' - }; if (this.configService.getValue('arlas.web.options.spinner')) { this.spinner = Object.assign(this.spinner, this.configService.getValue('arlas.web.options.spinner')); @@ -334,12 +266,6 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { } /** init from url */ - const queryParamVisibleVisualisations = this.getParamValue('vs'); - if (queryParamVisibleVisualisations) { - const visibleVisuSet = new Set(queryParamVisibleVisualisations.split(';').map(n => decodeURI(n))); - this.mapComponentConfig.visualisations_sets.forEach(v => v.enabled = visibleVisuSet.has(v.name)); - } - this.isTimelineOpen = this.getParamValue('to') === 'true'; let wasTabSelected = this.getParamValue('at') !== null; @@ -351,38 +277,14 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { } this.updateTimelineLegendVisibility(); }); - } else { - // Update default basemap style ? - this.defaultBaseMap = { - styleFile: 'http://demo.arlas.io:82/styles/positron/style.json', - name: 'Positron' - }; } } - public openAoiGenerator() { - this.generateAoiDialog.open(BboxGeneratorComponent, { - data: { - initCorner: { - lat: this.mapComponentConfig.initCenter ? this.mapComponentConfig.initCenter[1] : 0, - lng: this.mapComponentConfig.initCenter ? this.mapComponentConfig.initCenter[0] : 0, - } - } - }); - } - public ngOnDestroy(): void { this._onDestroy$.next(true); this._onDestroy$.complete(); } - public downloadLayerSource(d) { - const mc = this.mapglContributors.find(mc => mc.collection === d.collection); - if (mc) { - mc.downloadLayerSource(d.sourceName, d.layerName, d.downloadType); - } - } - public ngOnInit() { if (!this.version) { @@ -403,34 +305,6 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { const tab = allContributors.find(c => c.identifier === contId).name; return !hiddenListsTabsSet.has(tab); }) : []; - /** Prepare map data */ - this.mapglContributors = this.contributorService.getMapContributors(); - this.mainMapContributor = this.mapglContributors.filter(m => !!m.collection || m.collection === this.mainCollection)[0]; - this.mapDataSources = this.mapglContributors.map(c => c.dataSources).length > 0 ? - this.mapglContributors.map(c => c.dataSources).reduce((set1, set2) => new Set([...set1, ...set2])) : new Set(); - this.mapRedrawSources = merge(...this.mapglContributors.map(c => c.redrawSource)); - - const legendUpdaters: Observable<{ collection: string; legendData: Map; }> = - merge(...this.mapglContributors - .map(c => c.legendUpdater - .pipe(mergeMap(m => of({ collection: c.collection, legendData: m }))) - )); - const legendData = new Map>(); - legendUpdaters - .pipe(takeUntil(this._onDestroy$)) - .subscribe(lg => { - legendData.set(lg.collection, lg.legendData); - this.mapLegendUpdater.next(legendData); - }); - - this.mapVisibilityUpdater = merge(...this.mapglContributors.map(c => c.visibilityUpdater)); - this.mapglContributors.forEach(contrib => contrib.drawingsUpdate.subscribe(() => { - this.geojsondraw = { - 'type': 'FeatureCollection', - 'features': this.mapglContributors.map(c => c.geojsondraw.features).reduce((a, b) => a.concat(b)) - .filter((v, i, a) => a.findIndex(t => (t.properties.arlas_id === v.properties.arlas_id)) === i) - }; - })); this.chipsSearchContributor = this.contributorService.getChipSearchContributor(); const ids = new Set(this.resultListsConfig.map(c => c.contributorId)); @@ -449,7 +323,7 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { (rlcontrib as any).name = rlcontrib.getName(); const sortColumn = rlcontrib.fieldsList.find(c => !!(c as any).sort && (c as any).sort !== ''); if (!!sortColumn) { - this.sortOutput.set(rlcontrib.identifier, { + this.resultlistService.sortOutput.set(rlcontrib.identifier, { columnName: sortColumn.columnName, fieldName: sortColumn.fieldName, sortDirection: (sortColumn as any).sort === 'asc' ? SortEnum.asc : SortEnum.desc @@ -464,26 +338,8 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { this.resultListConfigPerContId.set(rlConf.contributorId, rlConf.input); }); - this.resultlistContributors.forEach(c => { - const listActionsId = c.actionToTriggerOnClick.map(a => a.id); - const mapcontributor = this.mapglContributors.find(mc => mc.collection === c.collection); - if (!!mapcontributor && !listActionsId.includes('zoomToFeature')) { - c.addAction({ id: 'zoomToFeature', label: 'Zoom to', cssClass: '', tooltip: 'Zoom to product' }); - } - if (!!this.resultListConfigPerContId.get(c.identifier)) { - if (!!this.resultListConfigPerContId.get(c.identifier).visualisationLink && !listActionsId.includes('visualize')) { - c.addAction({ id: 'visualize', label: 'Visualize', cssClass: '', tooltip: 'Visualize on the map' }); - } - if (!!this.resultListConfigPerContId.get(c.identifier).downloadLink && !listActionsId.includes('download')) { - c.addAction({ id: 'download', label: 'Download', cssClass: '', tooltip: 'Download' }); - } - } - - }); - this.declareResultlistExportCsv(); - // Check if the user can access process endpoint - const processSettings = this.arlasSettingsService.getProcessSettings(); + const processSettings = this.settingsService.getProcessSettings(); const externalNode = this.configService.getValue('arlas.web.externalNode'); if ( !!processSettings && !!processSettings.url @@ -529,47 +385,61 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { this.actionOnItemEvent(data, mapContributor, listContributor, collection); }); - if (this.allowMapExtend) { - const extendValue = this.getParamValue(this.MAP_EXTEND_PARAM); - if (extendValue) { - const stringBounds = extendValue.split(','); - if (stringBounds.length === 4) { - this.mapBounds = new mapboxgl.LngLatBounds( - new mapboxgl.LngLat(+stringBounds[0], +stringBounds[1]), - new mapboxgl.LngLat(+stringBounds[2], +stringBounds[3]) - ); + this.collections = [...new Set(Array.from(this.collaborativeService.registry.values()).map(c => c.collection))]; + + // Set MapContributors + const mapContributors = []; + this.contributorService.getMapContributors().forEach(mapContrib => { + mapContrib.colorGenerator = this.colorService.colorGenerator; + if (!!this.resultlistContributors) { + const resultlistContrbutor: ResultListContributor = this.resultlistContributors + .find(resultlistContrib => resultlistContrib.collection === mapContrib.collection); + if (!!resultlistContrbutor) { + mapContrib.searchSize = resultlistContrbutor.pageSize; + mapContrib.searchSort = resultlistContrbutor.sort; + } else { + mapContrib.searchSize = 50; } } - } - this.collections = [...new Set(Array.from(this.collaborativeService.registry.values()).map(c => c.collection))]; + mapContributors.push(mapContrib); + }); + this.mapService.setContributors(mapContributors); + zip(...this.collections.map(c => this.collaborativeService.describe(c))) .pipe(takeUntil(this._onDestroy$)) .subscribe(cdrs => { cdrs.forEach(cdr => { this.collectionToDescription.set(cdr.collection_name, cdr.params); }); - const bounds = (this.mapglComponent.map)?.getBounds(); + this.resultlistService.setCollectionsDescription(this.collectionToDescription); + const bounds = (this.arlasMapComponent.mapglComponent?.map)?.getBounds(); if (!!bounds) { - (this.mapglComponent.map).fitBounds(bounds, { duration: 0 }); + (this.arlasMapComponent.mapglComponent?.map).fitBounds(bounds, { duration: 0 }); } if (this.resultlistContributors.length > 0) { this.resultlistContributors.forEach(c => c.sort = this.collectionToDescription.get(c.collection).id_path); } - this.mapglContributors.forEach(mapContrib => { - mapContrib.colorGenerator = this.colorService.colorGenerator; - if (!!this.resultlistContributors) { - const resultlistContrbutor: ResultListContributor = this.resultlistContributors - .find(resultlistContrib => resultlistContrib.collection === mapContrib.collection); - if (!!resultlistContrbutor) { - mapContrib.searchSize = resultlistContrbutor.pageSize; - mapContrib.searchSort = resultlistContrbutor.sort; - } else { - mapContrib.searchSize = 50; - } - } - }); }); + // Add actions to resultlist contributors + this.resultlistContributors.forEach(c => { + const listActionsId = c.actionToTriggerOnClick.map(a => a.id); + const mapcontributor = mapContributors.find(mc => mc.collection === c.collection); + if (!!mapcontributor && !listActionsId.includes('zoomToFeature')) { + c.addAction({ id: 'zoomToFeature', label: 'Zoom to', cssClass: '', tooltip: marker('Zoom to product') }); + } + if (!!this.resultListConfigPerContId.get(c.identifier)) { + if (!!this.resultListConfigPerContId.get(c.identifier).visualisationLink && !listActionsId.includes('visualize')) { + c.addAction({ id: 'visualize', label: 'Visualize', cssClass: '', tooltip: marker('Visualize on the map') }); + } + if (!!this.resultListConfigPerContId.get(c.identifier).downloadLink && !listActionsId.includes('download')) { + c.addAction({ id: 'download', label: 'Download', cssClass: '', tooltip: marker('Download') }); + } + } + + }); + this.declareResultlistExportCsv(); + this.shortcuts = this.arlasStartUpService.filtersShortcuts; this.collaborativeService.ongoingSubscribe @@ -591,24 +461,9 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { } }); } - - // eslint-disable-next-line max-len - this.iconRegistry.addSvgIconLiteral('bbox', this.domSanitizer.bypassSecurityTrustHtml('')); - - // eslint-disable-next-line max-len - this.iconRegistry.addSvgIconLiteral('draw_polygon', this.domSanitizer.bypassSecurityTrustHtml(' image/svg+xml ')); - - // eslint-disable-next-line max-len - this.iconRegistry.addSvgIconLiteral('remove_polygon', this.domSanitizer.bypassSecurityTrustHtml(' image/svg+xml ')); - - // eslint-disable-next-line max-len - this.iconRegistry.addSvgIconLiteral('import_polygon', this.domSanitizer.bypassSecurityTrustHtml('')); - - // eslint-disable-next-line max-len - this.iconRegistry.addSvgIconLiteral('map_settings', this.domSanitizer.bypassSecurityTrustHtml('')); } - public isElementInViewport(el) { + public isElementInViewport(el: HTMLElement) { if (el) { const rect = el.getBoundingClientRect(); return (rect.top >= 0 && @@ -655,63 +510,28 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { }); } - this.mapEventListener - .pipe( - takeUntil(this._onDestroy$), - debounceTime(this.mapExtendTimer)) - .subscribe(() => { - /** Change map extend in the url */ - const bounds = (this.mapglComponent.map).getBounds(); - const extend = bounds.getWest() + ',' + bounds.getSouth() + ',' + bounds.getEast() + ',' + bounds.getNorth(); - const queryParams = Object.assign({}, this.activatedRoute.snapshot.queryParams); - queryParams[this.MAP_EXTEND_PARAM] = extend; - this.router.navigate([], { replaceUrl: true, queryParams: queryParams }); - }); - - this.cdr.detectChanges(); - } - } - - public onMapLoaded(isLoaded: boolean): void { - /** wait until the map component loading is finished before fetching the data */ - if (isLoaded && !this.arlasStartUpService.emptyMode) { - - this.mapService.setMap(this.mapglComponent.map); - this.visualizeService.setMap(this.mapglComponent.map); - if (this.mapBounds && this.allowMapExtend) { - (this.mapglComponent.map).fitBounds(this.mapBounds, { duration: 0 }); - this.mapBounds = null; - } - this.mapglComponent.map.on('movestart', (e) => { - this.zoomStart = this.mapglComponent.map.getZoom(); - }); - this.mapglComponent.map.on('moveend', (e) => { - if (Math.abs(this.mapglComponent.map.getZoom() - this.zoomStart) > 1) { - this.zoomChanged = true; - } - if (this.allowMapExtend) { - this.mapEventListener.next(null); - } - }); - this.adjustMapOffset(); - this.adjustCoordinates(); - this.mapglContributors.forEach(mapglContributor => { - mapglContributor.updateData = true; - mapglContributor.fetchData(null); - mapglContributor.setSelection(null, this.collaborativeService.getCollaboration(mapglContributor.identifier)); - }); - - // If there is a list displayed, sync window layers' data - if (!!this.previewListContrib && this.previewListContrib.data.length > 0 && - this.mapComponentConfig.mapLayers.events.onHover.filter(l => this.mapglComponent.map.getLayer(l)).length > 0) { - this.updateVisibleItems(); + // TODO: remove when the resultlist is externalised + if (!!this.previewListContrib) { + timer(0, 200) + .pipe( + takeUntil(this._onDestroy$), + takeWhile(() => this.apploading)) + .subscribe(() => { + if (this.previewListContrib.data.length > 0 && + this.mapService.getMapConfig().mapLayers.events.onHover.filter(l => this.mapService.mapComponent?.map.getLayer(l)).length > 0) { + this.updateVisibleItems(); + this.apploading = false; + } + }); } + this.resultlistService.setResultlistComponent(this.resultListComponent); + this.cdr.detectChanges(); } } public setAppTitle() { - const prefixTitle = (!!this.arlasSettingsService.settings.tab_name && this.arlasSettingsService.settings.tab_name !== NOT_CONFIGURED) ? - this.arlasSettingsService.settings.tab_name : ''; + const prefixTitle = (!!this.settingsService.settings.tab_name && this.settingsService.settings.tab_name !== NOT_CONFIGURED) ? + this.settingsService.settings.tab_name : ''; this.titleService.setTitle(prefixTitle === '' ? this.appName : prefixTitle.concat(' - ').concat(this.appName)); } @@ -724,43 +544,12 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { const idFieldName = this.collectionToDescription.get(this.previewListContrib.collection).id_path; const visibleItems = this.previewListContrib.data.map(i => (i.get(idFieldName) as number | string)) .filter(i => i !== undefined && this.isElementInViewport(document.getElementById(i.toString()))); - this.updateMapStyle(visibleItems, this.previewListContrib.collection); - } - } - - public updateMapStyle(ids: Array, collection: string) { - // use always this.previewListContrib because it's the current resultlist contributor - if (!!this.mapComponentConfig.mapLayers.events.onHover) { - this.mapComponentConfig.mapLayers.events.onHover.forEach(l => { - const layer = this.mapglComponent.map.getLayer(l); - if (ids && ids.length > 0) { - if (!!layer && layer.source.indexOf(collection) >= 0 && ids.length > 0 && - // Tests value in camel and kebab case due to an unknown issue on other projects - (layer.metadata.isScrollableLayer || layer.metadata['is-scrollable-layer'])) { - this.mapglComponent.map.setFilter(l, this.getVisibleElementLayerFilter(l, ids)); - const strokeLayerId = l.replace('_id:', '-fill_stroke-'); - const strokeLayer = this.mapglComponent.map.getLayer(strokeLayerId); - if (!!strokeLayer) { - this.mapglComponent.map.setFilter(strokeLayerId, this.getVisibleElementLayerFilter(strokeLayerId, ids)); - } - } - } else { - if (!!layer && layer.source.indexOf(collection) >= 0) { - this.mapglComponent.map.setFilter(l, this.mapglComponent.layersMap.get(l).filter); - const strokeLayerId = l.replace('_id:', '-fill_stroke-'); - const strokeLayer = this.mapglComponent.map.getLayer(strokeLayerId); - if (!!strokeLayer) { - this.mapglComponent.map.setFilter(strokeLayerId, - this.mapglComponent.layersMap.get(strokeLayerId).filter); - } - } - } - }); + this.mapService.updateMapStyle(visibleItems, this.previewListContrib.collection); } } public updateMapStyleFromScroll(items: Array, collection: string) { - this.updateMapStyle(items.map(i => i.identifier), collection); + this.mapService.updateMapStyle(items.map(i => i.identifier), collection); } /** @@ -773,7 +562,7 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { setTimeout(() => { const visibleItems = items.map(item => item.get(idFieldName)) .filter(id => id !== undefined && this.isElementInViewport(document.getElementById(id.toString()))); - this.updateMapStyle(visibleItems, collection); + this.mapService.updateMapStyle(visibleItems, collection); }, 200); } } @@ -782,55 +571,21 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { this.menuState = states; } - public openMapSettings(): void { - this.mapSettingsService.mapContributors = this.mapglContributors; - this.mapSettings.openDialog(this.mapSettingsService); - } - - - /** - * Applies the selected geo query - */ - public applySelectedGeoQuery(geoQueries: Map) { - const configDebounceTime = this.configService.getValue('arlas.server.debounceCollaborationTime'); - const debounceDuration = configDebounceTime !== undefined ? configDebounceTime : 750; - const changedMapContributors = this.mapglContributors.filter(mc => !!geoQueries.has(mc.collection)); - for (let i = 0; i < changedMapContributors.length; i++) { - setTimeout(() => { - const collection = changedMapContributors[i].collection; - const geoQuery = geoQueries.get(collection); - changedMapContributors[i].setGeoQueryOperation(geoQuery.operation); - changedMapContributors[i].setGeoQueryField(geoQuery.geometry_path); - changedMapContributors[i].onChangeGeoQuery(); - this.snackbar.open(this.translate.instant('Updating Geo-query of') + ' ' + changedMapContributors[i].collection); - if (i === changedMapContributors.length - 1) { - setTimeout(() => this.snackbar.dismiss(), 1000); - } - - }, (i) * (debounceDuration * 1.5)); - } - - } - - public setLyersVisibilityStatus(event) { - this.layersVisibilityStatus = event; - } - public zoomToData(collection: string): void { if (!this.mapSettingsService.mapContributors || this.mapSettingsService.mapContributors.length === 0) { this.mapSettingsService.mapContributors = this.mapglContributors; } const centroidPath = this.collectionToDescription.get(collection).centroid_path; - this.mapService.zoomToData(collection, centroidPath, this.mapglComponent.map, 0.2); + this.toolkitMapService.zoomToData(collection, centroidPath, this.arlasMapComponent.mapglComponent.map, 0.2); } /** This method sorts the list on the given column. The features are also sorted if the `Simple mode` is activated in mapContributor */ public sortColumnEvent(contributorId: string, sortOutput: Column) { const resultlistContributor = (this.collaborativeService.registry.get(contributorId) as ResultListContributor); - this.isGeoSortActivated.set(contributorId, false); + this.resultlistService.isGeoSortActivated.set(contributorId, false); /** Save the sorted column */ - this.sortOutput.set(contributorId, sortOutput); + this.resultlistService.sortOutput.set(contributorId, sortOutput); /** Sort the list by the selected column and the id field name */ resultlistContributor.sortColumn(sortOutput, true); /** set mapcontritbutor sort */ @@ -853,7 +608,7 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { c.searchSize = resultlistContributor.getConfigValue('search_size'); /** Redraw features with setted sort in case of window mode */ /** Remove old features */ - this.clearWindowData(c); + this.mapService.clearWindowData(c); /** Set new features */ c.drawGeoSearch(0, true); }); @@ -866,7 +621,7 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { */ public paginate(contributor, eventPaginate: PageQuery): void { contributor.getPage(eventPaginate.reference, eventPaginate.whichPage); - const sort = this.isGeoSortActivated.get(contributor.identifier) ? contributor.geoOrderSort : contributor.sort; + const sort = this.resultlistService.isGeoSortActivated.get(contributor.identifier) ? contributor.geoOrderSort : contributor.sort; this.mapglContributors .filter(c => c.collection === contributor.collection) .forEach(c => c.getPage(eventPaginate.reference, sort, eventPaginate.whichPage, contributor.maxPages)); @@ -892,14 +647,10 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { }, 0); } - public reloadMapImages() { - this.visualizeService.setMap(this.mapglComponent.map); - } - public getBoardEvents(event: { origin: string; event: string; data?: any; }) { const resultListContributor = this.collaborativeService.registry.get(event.origin) as ResultListContributor; const currentCollection = resultListContributor.collection; - const mapContributor: MapContributor = this.mapglContributors.filter(c => c.collection === currentCollection)[0]; + const mapContributor: MapContributor = this.mapService.getContributorByCollection(currentCollection); switch (event.event) { case 'paginationEvent': this.paginate(resultListContributor, event.data); @@ -913,35 +664,20 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { if (mapContributor) { f.elementidentifier.idFieldName = f.elementidentifier.idFieldName.replace(/\./g, '_'); } - this.featureToHightLight = f; + this.mapService.featureToHightLight = f; } break; case 'selectedItemsEvent': - /** TODO : manage features to select when we have multiple collections */ - if (event.data.length > 0 && this.mapComponentConfig && mapContributor) { - this.featuresToSelect = event.data.map(id => { - let idFieldName = this.collectionToDescription.get(currentCollection).id_path; - if (mapContributor.isFlat) { - idFieldName = idFieldName.replace(/\./g, '_'); - } - return { - idFieldName: idFieldName, - idValue: id - }; - }); - this.mapglComponent.selectFeaturesByCollection(this.featuresToSelect, currentCollection); - } else { - if (!!this.mapglComponent) { - this.mapglComponent.selectFeaturesByCollection([], currentCollection); - } - } + const ids = event.data; + const idPath = this.collectionToDescription.get(currentCollection).id_path; + this.mapService.selectFeatures(idPath, ids, mapContributor); break; case 'actionOnItemEvent': this.actionOnItemEvent(event.data, mapContributor, resultListContributor, currentCollection); break; case 'globalActionEvent': if (event.data.id === 'production') { - const idsItemSelected: ElementIdentifier[] = this.featuresToSelect; + const idsItemSelected: ElementIdentifier[] = this.mapService.mapComponent.featuresToSelect; this.process(idsItemSelected.map(i => i.idValue), currentCollection); } else if (event.data.id === 'export_csv') { this.resultlistIsExporting = true; @@ -956,230 +692,40 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { case 'geoSortEvent': break; case 'geoAutoSortEvent': - this.onActiveOnGeosort(event.data, resultListContributor, mapContributor, this.centerLatLng.lat, this.centerLatLng.lng); + this.onActiveOnGeosort(event.data, resultListContributor); break; } this.actionOnList.next(event); } - public onActiveOnGeosort(data, resultListContributor: ResultListContributor, mapContributor: MapContributor, lat, lng): void { - this.isGeoSortActivated.set(resultListContributor.identifier, data); + public onActiveOnGeosort(data, resultListContributor: ResultListContributor): void { + this.resultlistService.isGeoSortActivated.set(resultListContributor.identifier, data); + const mapContributor = this.mapService.getContributorByCollection(resultListContributor.collection); if (data) { /** Apply geosort in list */ + const lat = this.mapService.centerLatLng.lat; + const lng = this.mapService.centerLatLng.lng; resultListContributor.geoSort(lat, lng, true); - this.sortOutput.delete(resultListContributor.identifier); + this.resultlistService.sortOutput.delete(resultListContributor.identifier); // this.resultListComponent.columns.filter(c => !c.isIdField).forEach(c => c.sortDirection = SortEnum.none); /** Apply geosort in map (for simple mode) */ - this.clearWindowData(mapContributor); + this.mapService.clearWindowData(mapContributor); mapContributor.searchSort = resultListContributor.geoOrderSort; mapContributor.searchSize = resultListContributor.pageSize; mapContributor.drawGeoSearch(0, true); } else { const idFieldName = resultListContributor.getConfigValue('fieldsConfiguration')['idFieldName']; - this.sortOutput.set(resultListContributor.identifier, + this.resultlistService.sortOutput.set(resultListContributor.identifier, { fieldName: idFieldName, sortDirection: SortEnum.none }); /** Sort the list by the selected column and the id field name */ resultListContributor.sortColumn({ fieldName: idFieldName, sortDirection: SortEnum.none }, true); mapContributor.searchSort = resultListContributor.sort; mapContributor.searchSize = resultListContributor.pageSize; - this.clearWindowData(mapContributor); + this.mapService.clearWindowData(mapContributor); mapContributor.drawGeoSearch(0, true); } } - public onChangeAoi(event) { - const configDebounceTime = this.configService.getValue('arlas.server.debounceCollaborationTime'); - const debounceDuration = configDebounceTime !== undefined ? configDebounceTime : 750; - for (let i = 0; i < this.mapglContributors.length; i++) { - setTimeout(() => { - this.snackbar.open(this.translate.instant('Loading data of') + ' ' + this.mapglContributors[i].collection); - this.mapglContributors[i].onChangeAoi(event); - if (i === this.mapglContributors.length - 1) { - setTimeout(() => this.snackbar.dismiss(), 1000); - } - }, (i) * ((debounceDuration + 100) * 1.5)); - } - } - - public onAoiEdit(aoiEdit: AoiEdition) { - this.aoiEdition = aoiEdit; - } - - public onMove(event) { - // Update data only when the collections info are presents - if (this.collectionToDescription.size > 0) { - /** Change map extend in the url */ - const bounds = (this.mapglComponent.map).getBounds(); - const extend = bounds.getWest() + ',' + bounds.getSouth() + ',' + bounds.getEast() + ',' + bounds.getNorth(); - const queryParams = Object.assign({}, this.activatedRoute.snapshot.queryParams); - const visibileVisus = this.mapglComponent.visualisationSetsConfig.filter(v => v.enabled).map(v => v.name).join(';'); - queryParams[this.MAP_EXTEND_PARAM] = extend; - queryParams['vs'] = visibileVisus; - this.router.navigate([], { replaceUrl: true, queryParams: queryParams }); - localStorage.setItem('currentExtent', JSON.stringify(bounds)); - const ratioToAutoSort = 0.1; - this.centerLatLng['lat'] = event.centerWithOffset[1]; - this.centerLatLng['lng'] = event.centerWithOffset[0]; - if ((event.xMoveRatio > ratioToAutoSort || event.yMoveRatio > ratioToAutoSort || this.zoomChanged)) { - this.recalculateExtend = true; - } - const newMapExtent = event.extendWithOffset; - const newMapExtentRaw = event.rawExtendWithOffset; - const pwithin = newMapExtent[1] + ',' + newMapExtent[2] + ',' + newMapExtent[3] + ',' + newMapExtent[0]; - const pwithinRaw = newMapExtentRaw[1] + ',' + newMapExtentRaw[2] + ',' + newMapExtentRaw[3] + ',' + newMapExtentRaw[0]; - if (this.recalculateExtend && !this.disableRecalculateExtend) { - this.resultlistContributors - .forEach(c => { - const centroidPath = this.collectionToDescription.get(c.collection).centroid_path; - const mapContrib = this.mapglContributors.find(mc => mc.collection === c.collection); - if (!!mapContrib) { - c.filter = mapContrib.getFilterForCount(pwithinRaw, pwithin, centroidPath); - } else { - MapContributor.getFilterFromExtent(pwithinRaw, pwithin, centroidPath); - } - this.collaborativeService.registry.set(c.identifier, c); - }); - this.resultlistContributors.forEach(c => { - if (this.isGeoSortActivated.get(c.identifier)) { - c.geoSort(this.centerLatLng.lat, this.centerLatLng.lng, true); - } else { - c.sortColumn(this.sortOutput.get(c.identifier), true); - } - }); - this.mapglContributors.forEach(c => { - if (!!this.resultlistContributors) { - const resultlistContrbutor: ResultListContributor = this.resultlistContributors.find(v => v.collection === c.collection); - if (!!resultlistContrbutor) { - if (this.isGeoSortActivated.get(c.identifier)) { - c.searchSort = resultlistContrbutor.geoOrderSort; - } else { - c.searchSort = resultlistContrbutor.sort; - } - this.collaborativeService.registry.set(c.identifier, c); - } - } - this.clearWindowData(c); - }); - this.zoomChanged = false; - } - event.extendForTest = newMapExtent; - event.rawExtendForTest = newMapExtentRaw; - this.mapglContributors.forEach(contrib => contrib.onMove(event, this.recalculateExtend)); - this.recalculateExtend = false; - - } - } - - public changeVisualisation(event) { - this.mapglContributors.forEach(contrib => contrib.changeVisualisation(event)); - const queryParams = Object.assign({}, this.activatedRoute.snapshot.queryParams); - const visibileVisus = this.mapglComponent.visualisationSetsConfig.filter(v => v.enabled).map(v => v.name).join(';'); - queryParams['vs'] = visibileVisus; - this.router.navigate([], { replaceUrl: true, queryParams: queryParams }); - } - - - public emitFeaturesOnOver(event) { - if (event.features) { - this.mapglComponent.map.getCanvas().style.cursor = 'pointer'; - // Get feature by collection - this.resultlistContributors.forEach(c => { - const idFieldName = this.collectionToDescription.get(c.collection).id_path; - const highLightItems = event.features - .filter(f => f.layer.metadata.collection === c.collection) - .map(f => f.properties[idFieldName.replace(/\./g, '_')]) - .filter(id => id !== undefined) - .map(id => id.toString()); - c.setHighlightItems(highLightItems); - }); - } else { - this.mapglComponent.map.getCanvas().style.cursor = ''; - this.resultlistContributors.forEach(c => { - c.setHighlightItems([]); - }); - } - } - - public emitFeaturesOnClic(event) { - if (event.features) { - const feature = event.features[0]; - const resultListContributor = this.resultlistContributors - .filter(c => feature.layer.metadata.collection === c.collection && !feature.layer.id.includes(SCROLLABLE_ARLAS_ID))[0]; - if (!!resultListContributor) { - const idFieldName = this.collectionToDescription.get(resultListContributor.collection).id_path; - const id = feature.properties[idFieldName.replace(/\./g, '_')]; - // Open the list panel if it's closed - this.disableRecalculateExtend = true; - if (!this.resultlistService.listOpen) { - this.toggleList(); - } - // Select the good tab if we have several - // No tabs case - if (this.resultlistContributors.length === 1) { - this.waitFor(this.resultListComponent,() => this.openDetail(id)); - } else { - this.waitFor(this.resultListComponent,() => { - // retrieve list - const tab = document.querySelector('[aria-label="' + resultListContributor.identifier + '"]') as any; - tab.click(); - // Set Timeout to wait the new tab - setTimeout(() => this.openDetail(id), 250); - this.disableRecalculateExtend = false; - }); - } - } - } - } - - private openDetail(id: any) { - const isListMode = this.resultListComponent.resultMode === this.modeEnum.list; - if (isListMode) { - const detailListButton = document.getElementById('open-detail-' + id); - if (!!detailListButton) { - // close previous if exists - if (this.currentClickedFeatureId) { - const closeButtonElement = document.getElementById('close-detail-' + this.currentClickedFeatureId); - if (closeButtonElement) { - closeButtonElement.click(); - } - } - detailListButton.click(); - this.currentClickedFeatureId = id; - this.disableRecalculateExtend = false; - } - } else { - const productTile = document.getElementById('grid-tile-' + id); - const isDetailledGridOpen = this.resultListComponent.isDetailledGridOpen; - if (!!productTile) { - productTile.click(); - if (!isDetailledGridOpen) { - setTimeout(() => { - const detailGridButton = document.getElementById('show_details_gridmode_btn'); - if (!!detailGridButton) { - detailGridButton.click(); - this.disableRecalculateExtend = false; - } - }, 250); - } else { - // If image is displayed switch to detail data - const gridDivs = document.getElementsByClassName('resultgrid__img'); - if (gridDivs.length > 0) { - const imgDiv = gridDivs[0].parentElement; - if (window.getComputedStyle(imgDiv).display === 'block') { - setTimeout(() => { - const detailGridButton = document.getElementById('show_details_gridmode_btn'); - if (!!detailGridButton) { - detailGridButton.click(); - this.disableRecalculateExtend = false; - } - }, 1); - } - } - } - } - } - } - public toggleList() { this.tabsList.realignInkBar(); this.resultlistService.toggleList(); @@ -1204,14 +750,6 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { this.analyticsService.selectTab(undefined); } - public closeMapMenu() { - setTimeout(() => { - if (this.shouldCloseMapMenu) { - this.isMapMenuOpen = false; - } - }, 100); - } - public onOpenShortcut(state: boolean, shortcutIdx: number) { if (state) { this.shortcutOpen = shortcutIdx; @@ -1234,23 +772,12 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { } public goToArlasHub() { - const hubUrl = this.arlasSettingsService.getArlasHubUrl(); + const hubUrl = this.settingsService.getArlasHubUrl(); if (!!hubUrl) { window.open(hubUrl); } } - protected goToLocation(event: GeocodingResult) { - const bbox = this.visualizeService.getBbox(event.geojson); - this.visualizeService.handleGeojsonPreview(event.geojson); - if (event.geojson.type === 'Point') { - const zoom = this.settingsService.getGeocodingSettings().find_place_zoom_to; - this.mapglComponent.map.fitBounds(bbox, { maxZoom: zoom }); - } else { - this.mapglComponent.map.fitBounds(bbox); - } - } - /** * Compute the space available between the divider after the search and the title of the application */ @@ -1286,28 +813,13 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { this.timelineComponent.detailedTimelineHistogramComponent.resizeHistogram(); } } - this.mapglComponent.map?.resize(); - this.adjustCoordinates(); + this.mapService.mapComponent?.map?.resize(); + this.mapService.adjustCoordinates(); this.updateVisibleItems(); }, 0); } - private adjustCoordinates(): void { - const timelineToolsMaxWidth = 420; - const scaleMaxWidth = 100; - const toggleButtonWidth = 24; - const smMargin = 5; - const mapCanvas = document.getElementsByClassName('mapboxgl-canvas'); - if (mapCanvas && mapCanvas.length > 0) { - const bbox = mapCanvas[0].getBoundingClientRect(); - if (bbox) { - const width = bbox.width; - this.coordinatesHaveSpace = (width - timelineToolsMaxWidth - scaleMaxWidth - toggleButtonWidth - 3 * smMargin) > 230; - } - } - } - /** * Transfer shortcuts from the visible ones to the extra ones based on the space available in the window */ @@ -1360,39 +872,6 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { this.showShortcuts = false; } - private adjustMapOffset() { - this.recalculateExtend = true; - this.mapglComponent.map.fitBounds(this.mapglComponent.map.getBounds()); - } - - private getVisibleElementLayerFilter(l, ids) { - const lFilter = this.mapglComponent.layersMap.get(l).filter; - const filters = []; - if (lFilter) { - lFilter.forEach(f => { - filters.push(f); - }); - } - if (filters.length === 0) { - filters.push('all'); - } - filters.push([ - 'match', - ['get', 'id'], - Array.from(new Set(ids)), - true, - false - ]); - return filters; - } - - private clearWindowData(contributor: MapContributor) { - contributor.getConfigValue('layers_sources') - .filter(ls => ls.source.startsWith('feature-') && ls.render_mode === FeatureRenderMode.window) - .map(ls => ls.source) - .forEach(source => contributor.clearData(source)); - } - private getParamValue(param: string): string { let paramValue = null; const url = window.location.href; @@ -1440,7 +919,7 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { } private process(ids: string[], collection: string) { - const maxItems = this.arlasSettingsService.getProcessSettings().max_items; + const maxItems = this.settingsService.getProcessSettings().max_items; if (ids.length <= maxItems) { this.processService.load().subscribe({ next: () => { @@ -1454,7 +933,7 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { this.downloadDialogRef = this.dialog.open(ProcessComponent, { minWidth: '520px', maxWidth: '60vw' }); this.downloadDialogRef.componentInstance.nbProducts = ids.length; this.downloadDialogRef.componentInstance.matchingAdditionalParams = item as Map; - this.downloadDialogRef.componentInstance.wktAoi = this.mapglComponent.getAllPolygon('wkt'); + this.downloadDialogRef.componentInstance.wktAoi = this.mapService.mapComponent.getAllPolygon('wkt'); this.downloadDialogRef.componentInstance.ids = ids; this.downloadDialogRef.componentInstance.collection = collection; } diff --git a/src/app/services/map.service.spec.ts b/src/app/services/map.service.spec.ts new file mode 100644 index 00000000..9a1a1ec5 --- /dev/null +++ b/src/app/services/map.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { MapService } from './map.service'; + +describe('MapService', () => { + let service: MapService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(MapService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/map.service.ts b/src/app/services/map.service.ts new file mode 100644 index 00000000..8956b311 --- /dev/null +++ b/src/app/services/map.service.ts @@ -0,0 +1,164 @@ +import { Injectable } from '@angular/core'; +import { MapglComponent } from 'arlas-web-components'; +import { ElementIdentifier, FeatureRenderMode, MapContributor } from 'arlas-web-contributors'; + +export interface FeatureHover { + isleaving: boolean; + elementidentifier: ElementIdentifier; +}; + +@Injectable({ + providedIn: 'root' +}) +export class MapService { + public mapComponent: MapglComponent; + private mapComponentConfig: any; + public mapContributors: Array = new Array(); + public centerLatLng: { lat: number; lng: number; } = { lat: 0, lng: 0 }; + public featureToHightLight: FeatureHover; + public coordinatesHaveSpace: boolean; + + public constructor() { } + + public setContributors(mapContributors: Array) { + this.mapContributors = mapContributors; + } + + public selectFeatures(idPath: string, ids: string[] | number[], mapContributor: MapContributor) { + /** TODO : manage features to select when we have multiple collections */ + if (!!this.mapComponent && !!mapContributor) { + if (ids.length > 0) { + const featuresToSelect = ids.map(id => { + let idFieldName = idPath; + if (mapContributor.isFlat) { + idFieldName = idFieldName.replace(/\./g, '_'); + } + return { + idFieldName: idFieldName, + idValue: id + }; + }); + this.mapComponent.selectFeaturesByCollection(featuresToSelect, mapContributor.collection); + } else { + this.mapComponent.selectFeaturesByCollection([], mapContributor.collection); + } + } + } + + public getFeatureToHover(id: ElementIdentifier, mapContributor: MapContributor): FeatureHover { + const f = mapContributor.getFeatureToHightLight(id); + if (mapContributor) { + f.elementidentifier.idFieldName = f.elementidentifier.idFieldName.replace(/\./g, '_'); + } + return f; + } + + public setMapConfig(mapComponentConfig) { + this.mapComponentConfig = mapComponentConfig; + } + + public getMapConfig() { + return this.mapComponentConfig; + } + + public setCursor(cursor: string) { + this.mapComponent.map.getCanvas().style.cursor = cursor; + } + + public setMapComponent(mapComponent: MapglComponent) { + this.mapComponent = mapComponent; + } + + public clearWindowData(contributor: MapContributor) { + contributor.getConfigValue('layers_sources') + .filter(ls => ls.source.startsWith('feature-') && ls.render_mode === FeatureRenderMode.window) + .map(ls => ls.source) + .forEach(source => contributor.clearData(source)); + } + + public updateMapStyle(ids: Array, collection: string) { + if (!!this.mapComponent && !!this.mapComponent.map && !!this.mapComponentConfig && !!this.mapComponentConfig.mapLayers.events.onHover) { + this.mapComponentConfig.mapLayers.events.onHover.forEach(l => { + const layer = this.mapComponent.map.getLayer(l); + if (ids && ids.length > 0) { + if (!!layer && layer.source.indexOf(collection) >= 0 && ids.length > 0 && + (layer.metadata.isScrollableLayer || layer.metadata['is-scrollable-layer'])) { + this.mapComponent.map.setFilter(l, this.getVisibleElementLayerFilter(l, ids)); + const strokeLayerId = l.replace('_id:', '-fill_stroke-'); + const strokeLayer = this.mapComponent.map.getLayer(strokeLayerId); + if (!!strokeLayer) { + this.mapComponent.map.setFilter(strokeLayerId, this.getVisibleElementLayerFilter(strokeLayerId, ids)); + } + } + } else { + if (!!layer && layer.source.indexOf(collection) >= 0) { + this.mapComponent.map.setFilter(l, this.mapComponent.layersMap.get(l).filter); + const strokeLayerId = l.replace('_id:', '-fill_stroke-'); + const strokeLayer = this.mapComponent.map.getLayer(strokeLayerId); + if (!!strokeLayer) { + this.mapComponent.map.setFilter(strokeLayerId, + this.mapComponent.layersMap.get(strokeLayerId).filter); + } + } + } + }); + } + } + + public resize() { + this.mapComponent.map?.resize(); + this.adjustCoordinates(); + } + + public adjustCoordinates(): void { + const timelineToolsMaxWidth = 420; + const scaleMaxWidth = 100; + const toggleButtonWidth = 24; + const smMargin = 5; + const mapCanvas = document.getElementsByClassName('mapboxgl-canvas'); + if (mapCanvas && mapCanvas.length > 0) { + const bbox = mapCanvas[0].getBoundingClientRect(); + if (bbox) { + const width = bbox.width; + this.coordinatesHaveSpace = (width - timelineToolsMaxWidth - scaleMaxWidth - toggleButtonWidth - 3 * smMargin) > 230; + } + } + } + + private getVisibleElementLayerFilter(l, ids) { + const lFilter = this.mapComponent.layersMap.get(l).filter; + const filters = []; + if (lFilter) { + lFilter.forEach(f => { + filters.push(f); + }); + } + if (filters.length === 0) { + filters.push('all'); + } + filters.push([ + 'match', + ['get', 'id'], + Array.from(new Set(ids)), + true, + false + ]); + return filters; + } + + public getContributorByCollection(collection: string): MapContributor { + let mapContributor: MapContributor; + if (this.mapContributors) { + mapContributor = this.mapContributors.find(mc => mc.collection === collection); + } + return mapContributor; + } + + public getContributorById(identifier: string): MapContributor { + let mapContributor: MapContributor; + if (this.mapContributors) { + mapContributor = this.mapContributors.find(mc => mc.identifier === identifier); + } + return mapContributor; + } +} diff --git a/src/app/services/resultlist.service.spec.ts b/src/app/services/resultlist.service.spec.ts index 922f2f9b..8e943174 100644 --- a/src/app/services/resultlist.service.spec.ts +++ b/src/app/services/resultlist.service.spec.ts @@ -1,8 +1,8 @@ import { TestBed } from '@angular/core/testing'; -import { ResultlistService } from './resultlist.service'; -import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; +import { ArlasCollaborativesearchService } from 'arlas-wui-toolkit'; +import { ResultlistService } from './resultlist.service'; describe('ResultlistService', () => { @@ -12,7 +12,11 @@ describe('ResultlistService', () => { TestBed.configureTestingModule({ imports: [ RouterTestingModule - ] + ], + providers: [ + ArlasCollaborativesearchService + ], + teardown: { destroyAfterEach: false } }); service = TestBed.inject(ResultlistService); }); diff --git a/src/app/services/resultlist.service.ts b/src/app/services/resultlist.service.ts index bf4236f1..b0c52cc7 100644 --- a/src/app/services/resultlist.service.ts +++ b/src/app/services/resultlist.service.ts @@ -1,8 +1,11 @@ import { Injectable } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { CellBackgroundStyleEnum } from 'arlas-web-components'; -import { ResultListContributor } from 'arlas-web-contributors'; -import { getParamValue } from 'arlas-wui-toolkit'; +import { CollectionReferenceParameters } from 'arlas-api'; +import { CellBackgroundStyleEnum, ModeEnum, ResultListComponent, SortEnum } from 'arlas-web-components'; +import { MapContributor, ResultListContributor } from 'arlas-web-contributors'; +import { ArlasCollaborativesearchService, getParamValue } from 'arlas-wui-toolkit'; +import { BehaviorSubject } from 'rxjs'; +import { MapService } from './map.service'; @Injectable({ @@ -14,13 +17,21 @@ export class ResultlistService { public resultlistConfigs = []; public resultlistConfigPerContId = new Map(); public previewlistContrib: ResultListContributor = null; + public collectionToDescription = new Map(); + public isGeoSortActivated = new Map(); + public sortOutput = new Map(); + public resultListComponent: ResultListComponent; public selectedListTabIndex = 0; public listOpen = false; + private currentClickedFeatureId: string = undefined; + public constructor( private activatedRoute: ActivatedRoute, - private router: Router + private router: Router, + private mapService: MapService, + private collaborativeService: ArlasCollaborativesearchService ) { const resultlistOpenString = getParamValue('ro'); if (resultlistOpenString) { @@ -28,6 +39,10 @@ export class ResultlistService { } } + public setResultlistComponent(c: ResultListComponent) { + this.resultListComponent = c; + } + public setContributors(resultlistContributors: Array, resultlistConfigs: string[]) { this.resultlistContributors = resultlistContributors; if (this.resultlistContributors.length > 0) { @@ -41,14 +56,110 @@ export class ResultlistService { } } + public setCollectionsDescription(collectionToDescription: Map) { + this.collectionToDescription = collectionToDescription; + } + public toggleList() { this.listOpen = !this.listOpen; const queryParams = Object.assign({}, this.activatedRoute.snapshot.queryParams); queryParams['ro'] = this.listOpen + ''; - this.router.navigate([], {replaceUrl: true, queryParams: queryParams}); + this.router.navigate([], { replaceUrl: true, queryParams: queryParams }); } public isThumbnailProtected(): boolean { return this.resultlistContributors[this.selectedListTabIndex].fieldsConfiguration?.useHttpThumbnails ?? false; } + + public applyMapExtent(pwithinRaw: string, pwithin: string) { + this.resultlistContributors + .forEach(c => { + const centroidPath = this.collectionToDescription.get(c.collection).centroid_path; + const mapContrib = this.mapService.getContributorByCollection(c.collection); + if (!!mapContrib) { + c.filter = mapContrib.getFilterForCount(pwithinRaw, pwithin, centroidPath); + } else { + MapContributor.getFilterFromExtent(pwithinRaw, pwithin, centroidPath); + } + this.collaborativeService.registry.set(c.identifier, c); + }); + this.resultlistContributors.forEach(c => { + if (this.isGeoSortActivated.get(c.identifier)) { + c.geoSort(this.mapService.centerLatLng.lat, this.mapService.centerLatLng.lng, true); + } else { + c.sortColumn(this.sortOutput.get(c.identifier), true); + } + }); + } + + public highlightItems(hoveredFeatures: any[]) { + this.resultlistContributors.forEach(c => { + const idFieldName = this.collectionToDescription.get(c.collection).id_path; + const highLightItems = hoveredFeatures + .filter(f => f.layer.metadata.collection === c.collection) + .map(f => f.properties[idFieldName.replace(/\./g, '_')]) + .filter(id => id !== undefined) + .map(id => id.toString()); + c.setHighlightItems(highLightItems); + }); + } + + public clearHighlightedItems() { + this.resultlistContributors.forEach(c => { + c.setHighlightItems([]); + }); + } + + public openDetail$(id: any): BehaviorSubject { + const isOpen = new BehaviorSubject(false); + // If does not work add a variable ? + const isListMode = this.resultListComponent.resultMode === ModeEnum.list; + if (isListMode) { + const detailListButton = document.getElementById('open-detail-' + id); + if (!!detailListButton) { + // close previous if exists + if (this.currentClickedFeatureId) { + const closeButtonElement = document.getElementById('close-detail-' + this.currentClickedFeatureId); + if (closeButtonElement) { + closeButtonElement.click(); + } + } + detailListButton.click(); + this.currentClickedFeatureId = id; + isOpen.next(true); + } + } else { + const productTile = document.getElementById('grid-tile-' + id); + const isDetailledGridOpen = this.resultListComponent.isDetailledGridOpen; + if (!!productTile) { + productTile.click(); + if (!isDetailledGridOpen) { + setTimeout(() => { + const detailGridButton = document.getElementById('show_details_gridmode_btn'); + if (!!detailGridButton) { + detailGridButton.click(); + isOpen.next(true); + } + }, 250); + } else { + // If image is displayed switch to detail data + const gridDivs = document.getElementsByClassName('resultgrid__img'); + if (gridDivs.length > 0) { + const imgDiv = gridDivs[0].parentElement; + if (window.getComputedStyle(imgDiv).display === 'block') { + setTimeout(() => { + const detailGridButton = document.getElementById('show_details_gridmode_btn'); + if (!!detailGridButton) { + detailGridButton.click(); + isOpen.next(true); + } + }, 1); + } + } + } + } + + return isOpen; + } + } } diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index ac9fa2b4..6697a5ee 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -41,6 +41,9 @@ "Updating Geo-query of": "Updating Geo-query of", "Loading data of": "Loading collection", "You have exceeded the number of products authorised for a single download": "You have exceeded the number of products authorised for a single download", + "Zoom to product": "Zoom to product", + "Visualize on the map": "Visualize on the map", + "Download": "Download", "An error occured exporting the list": "An error occured exporting the list", "reduce": "Reduce", "expand": "Expand", @@ -66,7 +69,6 @@ "Share": "Share", "Tag": "Tag", "Menu": "Menu", - "Download": "Download", "Today": "Today", "This week": "This week", "This month": "This month", diff --git a/src/assets/i18n/es.json b/src/assets/i18n/es.json index 03a5ac14..d3d615ae 100644 --- a/src/assets/i18n/es.json +++ b/src/assets/i18n/es.json @@ -41,6 +41,9 @@ "Updating Geo-query of": "Carga de la petición geográfica de", "Loading data of": "Carga de la collección", "You have exceeded the number of products authorised for a single download": "Ha superado el número de productos autorizados para una sola descarga", + "Zoom to product": "Zoom al producto", + "Visualize on the map": "Visualizar en el mapa", + "Download": "Descargar", "An error occured exporting the list": "Se ha producido un error al exportar la lista", "reduce": "Reducir", "expand": "Abrir", @@ -66,7 +69,6 @@ "Share": "Compartir", "Tag": "Etiquetar", "Menu": "Menú", - "Download": "Descargar", "Today": "Hoy", "This week": "Esta semana", "This month": "Este mes", diff --git a/src/assets/i18n/fr.json b/src/assets/i18n/fr.json index 763ff00d..38344bbe 100644 --- a/src/assets/i18n/fr.json +++ b/src/assets/i18n/fr.json @@ -41,6 +41,9 @@ "Updating Geo-query of": "Chargement de la requête géographique de", "Loading data of": "Chargement de la collection", "You have exceeded the number of products authorised for a single download": "Vous avez dépassé le nombre de produits autorisés pour un téléchargement unique", + "Zoom to product": "Zoomer sur le produit", + "Visualize on the map": "Visualiser sur la carte", + "Download": "Télécharger", "An error occured exporting the list": "Une erreur s'est produite lors de l'exportation de la liste", "reduce": "Réduire", "expand": "Ouvrir", @@ -66,7 +69,6 @@ "Share": "Partager", "Tag": "Taguer", "Menu": "Menu", - "Download": "Télécharger", "Today": "Aujourd'hui", "This week": "Cette semaine", "This month": "Ce mois", diff --git a/src/assets/i18n/template.json b/src/assets/i18n/template.json index 0ef17e1b..92997bf4 100644 --- a/src/assets/i18n/template.json +++ b/src/assets/i18n/template.json @@ -41,6 +41,9 @@ "Updating Geo-query of": "Updating Geo-query of", "Loading data of": "Loading data of", "You have exceeded the number of products authorised for a single download": "You have exceeded the number of products authorised for a single download", + "Zoom to product": "Zoom to product", + "Visualize on the map": "Visualize on the map", + "Download": "Download", "An error occured exporting the list": "An error occured exporting the list", "reduce": "reduce", "expand": "expand", diff --git a/src/config.json b/src/config.json index b6f5a3ce..93c3a34d 100644 --- a/src/config.json +++ b/src/config.json @@ -12,11 +12,11 @@ "icon": "check_box_outline_blank", "layers_sources": [ { - "id": "arlas_id:Latest products:1677155839933", + "id": "arlas_id:Latest products:1696925765098", "name": "Latest products", "source": "feature-_geometry_wkt-window-demo_eo", "minzoom": 0, - "maxzoom": 23, + "maxzoom": 22, "include_fields": [ "metadata.ObservationContext.processusUsed.platform" ], @@ -31,27 +31,25 @@ "render_mode": "window" }, { - "id": "arlas_id:Products:1677155960359", + "id": "arlas_id:Products:1696926758346", "name": "Products", "source": "feature-_geometry_wkt-wide-demo_eo", "minzoom": 0, - "maxzoom": 23, + "maxzoom": 22, "include_fields": [ "metadata.ObservationContext.processusUsed.platform" ], "short_form_fields": [], - "colors_from_fields": [ - "metadata.ObservationContext.processusUsed.platform" - ], + "colors_from_fields": [], "provided_fields": [], "normalization_fields": [], "metrics": [], - "maxfeatures": 1000, + "maxfeatures": 5000, "returned_geometry": "_geometry_wkt", "render_mode": "wide" }, { - "id": "arlas_id:Density of products:1677155972496", + "id": "arlas_id:Density of products:1696925833059", "name": "Density of products", "source": "cluster-_centroid_wkt-Coarse-tile-centroid-demo_eo", "minzoom": 0, @@ -86,11 +84,11 @@ "agg_geo_field": "_centroid_wkt", "aggType": "tile", "granularity": "Coarse", - "minfeatures": 1000, + "minfeatures": 5000, "aggregated_geometry": "centroid" }, { - "id": "arlas_id:Product density grid:1677155981981", + "id": "arlas_id:Product density grid:1697197580589", "name": "Product density grid", "source": "cluster-_centroid_wkt-Medium-tile-cell-demo_eo", "minzoom": 0, @@ -149,9 +147,9 @@ "aggregated_geometry": "centroid" }, { - "id": "arlas_id:Heatmap:1689091401394", + "id": "arlas_id:Heatmap:1697197287204", "name": "Heatmap", - "source": "cluster-_centroid_wkt-Medium-tile-cell_center-demo_eo", + "source": "cluster-_centroid_wkt-Fine-tile-cell_center-demo_eo", "minzoom": 1, "maxzoom": 23, "include_fields": [], @@ -168,7 +166,7 @@ ], "agg_geo_field": "_centroid_wkt", "aggType": "tile", - "granularity": "Medium", + "granularity": "Fine", "minfeatures": 1000, "aggregated_geometry": "cell_center" } @@ -202,13 +200,13 @@ "additionalCollections": [] }, { - "identifier": "demo_eo-metadata.core.identity.identifier-150-Capteur-Date-Couverture nuageuse-Angle d'incidence-Toponymie-Identification-Fiche technique-Niveaux de produits et de processing-Mode d'acquisition-", + "identifier": "demo_eo-metadata.core.identity.identifier-80-Capteur-Date-Couverture nuageuse-Angle d'incidence-Toponymie-Identification-Fiche technique-Niveaux de produits et de processing-Mode d'acquisition-", "name": "DATA", "title": "DATA", "collection": "demo_eo", "icon": "table_chart", "type": "resultlist", - "search_size": 150, + "search_size": 80, "fieldsConfiguration": { "idFieldName": "metadata.core.identity.identifier", "titleFieldNames": [ @@ -223,10 +221,14 @@ "process": "" } ], - "icon": "fiber_manual_record", "iconColorFieldName": "metadata.ObservationContext.processusUsed.platform", "urlThumbnailTemplate": "{metadata.core.graphics.thumbnail}", - "urlImageTemplate": "{metadata.core.graphics.quicklook}" + "urlImageTemplates": [ + { + "url": "{metadata.core.graphics.quicklook}", + "description": "" + } + ] }, "columns": [ { @@ -243,7 +245,7 @@ "dataType": "", "process": "(new Date(result)).toUTCString().split(',')[1] !== undefined ? (new Date(result)).toUTCString().split(',')[1].replace('GMT', '') :'undefined'", "useColorService": false, - "sort": "" + "sort": "desc" }, { "columnName": "Couverture nuageuse", @@ -374,11 +376,7 @@ ] } ], - "includeMetadata": [ - "metadata.ObservationContext.processusUsed.platform", - "{metadata.core.graphics.thumbnail}", - "{metadata.core.graphics.quicklook}" - ] + "includeMetadata": [] }, { "type": "detailedhistogram", @@ -654,6 +652,7 @@ "timeline": { "contributorId": "timeline", "componentType": "histogram", + "uuid": "", "input": { "id": "histogram-timeline", "xTicks": 9, @@ -692,21 +691,41 @@ "nbVerticesLimit": 100, "input": { "defaultBasemapStyle": { - "name": "Streets-dark", - "styleFile": "https://api.maptiler.com/maps/a1e62ee0-aca6-451a-a4b8-42250422a212/style.json?key=xIhbu1RwgdbxfZNmoXn4" + "name": "Streets-light", + "styleFile": "https://api.maptiler.com/maps/208a41eb-368f-4003-8e3c-2dba0d90b3cb/style.json?key=E7xmD2y9rRj1z4TcNnj9", + "image": "https://api.maptiler.com/maps/208a41eb-368f-4003-8e3c-2dba0d90b3cb/0/0/0.png?key=E7xmD2y9rRj1z4TcNnj9", + "type": null }, "basemapStyles": [ { "name": "Basic", - "styleFile": "https://api.maptiler.com/maps/basic/style.json?key=xIhbu1RwgdbxfZNmoXn4" + "styleFile": "https://api.maptiler.com/maps/basic/style.json?key=E7xmD2y9rRj1z4TcNnj9", + "image": "https://cloud.maptiler.com/static/img/maps/basic.png", + "type": null + }, + { + "name": "Satellite Hybrid", + "styleFile": "https://api.maptiler.com/maps/hybrid/style.json?key=E7xmD2y9rRj1z4TcNnj9", + "image": "https://cloud.maptiler.com/static/img/maps/hybrid.png", + "type": null }, { "name": "Streets-dark", - "styleFile": "https://api.maptiler.com/maps/a1e62ee0-aca6-451a-a4b8-42250422a212/style.json?key=xIhbu1RwgdbxfZNmoXn4" + "styleFile": "https://api.maptiler.com/maps/a1e62ee0-aca6-451a-a4b8-42250422a212/style.json?key=E7xmD2y9rRj1z4TcNnj9", + "image": "https://api.maptiler.com/maps/a1e62ee0-aca6-451a-a4b8-42250422a212/0/0/0.png?key=E7xmD2y9rRj1z4TcNnj9", + "type": null }, { "name": "Streets-light", - "styleFile": "https://api.maptiler.com/maps/208a41eb-368f-4003-8e3c-2dba0d90b3cb/style.json?key=xIhbu1RwgdbxfZNmoXn4" + "styleFile": "https://api.maptiler.com/maps/208a41eb-368f-4003-8e3c-2dba0d90b3cb/style.json?key=E7xmD2y9rRj1z4TcNnj9", + "image": "https://api.maptiler.com/maps/208a41eb-368f-4003-8e3c-2dba0d90b3cb/0/0/0.png?key=E7xmD2y9rRj1z4TcNnj9", + "type": null + }, + { + "name": "Streets-light - No label", + "styleFile": "https://api.maptiler.com/maps/bfbf9c99-aa66-4830-8b6c-2251d177f2f5/style.json?key=E7xmD2y9rRj1z4TcNnj9", + "image": "https://api.maptiler.com/maps/bfbf9c99-aa66-4830-8b6c-2251d177f2f5/0/0/0.png?key=E7xmD2y9rRj1z4TcNnj9", + "type": null } ], "margePanForLoad": 5, @@ -724,12 +743,12 @@ "events": { "zoomOnClick": [], "emitOnClick": [ - "arlas_id:Latest products:1677155839933", - "scrollable_arlas_id:Latest products:1677155839933" + "arlas_id:Latest products:1696925765098", + "scrollable_arlas_id:Latest products:1696925765098" ], "onHover": [ - "arlas_id:Latest products:1677155839933", - "scrollable_arlas_id:Latest products:1677155839933" + "arlas_id:Latest products:1696925765098", + "scrollable_arlas_id:Latest products:1696925765098" ] }, "externalEventLayers": [] @@ -738,7 +757,7 @@ { "name": "Latest products", "layers": [ - "arlas_id:Latest products:1677155839933" + "arlas_id:Latest products:1696925765098" ], "enabled": true }, @@ -746,10 +765,8 @@ "name": "All products", "layers": [ "arlas_id:Number of products:1677155990578", - "arlas_id:Density of products:1677155972496", - "arlas_id:Products:1677155960359", - "arlas_id:Product density grid:1677155981981", - "arlas_id:Heatmap:1689091401394" + "arlas_id:Products:1696926758346", + "arlas_id:Product density grid:1697197580589" ], "enabled": true } @@ -758,13 +775,12 @@ }, "resultlists": [ { - "contributorId": "demo_eo-metadata.core.identity.identifier-150-Capteur-Date-Couverture nuageuse-Angle d'incidence-Toponymie-Identification-Fiche technique-Niveaux de produits et de processing-Mode d'acquisition-", "componentType": "resultlist", "input": { - "id": "demo_eo-metadata.core.identity.identifier-150-Capteur-Date-Couverture nuageuse-Angle d'incidence-Toponymie-Identification-Fiche technique-Niveaux de produits et de processing-Mode d'acquisition-", + "id": "demo_eo-metadata.core.identity.identifier-80-Capteur-Date-Couverture nuageuse-Angle d'incidence-Toponymie-Identification-Fiche technique-Niveaux de produits et de processing-Mode d'acquisition-", "tableWidth": 445, "globalActionsList": [], - "searchSize": 150, + "searchSize": 80, "nLastLines": 3, "detailedGridHeight": 300, "nbGridColumns": 3, @@ -772,7 +788,7 @@ "hasGridMode": true, "defautMode": "grid", "visualisationLink": "{_services_WMS_0_url}", - "downloadLink": "https://demo.cloud.arlas.io/arlas/server/explore/demo_eo/_search?f=metadata.core.identity.identifier:eq:{metadata_core_identity_identifier}", + "downloadLink": "https://cloud.arlas.io/arlas/server/explore/demo_eo/_search?f=metadata.core.identity.identifier:eq:{metadata_core_identity_identifier}", "isBodyHidden": false, "isGeoSortActived": true, "isAutoGeoSortActived": null, @@ -790,12 +806,16 @@ "showName": true, "showIcon": true } - } + }, + "contributorId": "demo_eo-metadata.core.identity.identifier-80-Capteur-Date-Couverture nuageuse-Angle d'incidence-Toponymie-Identification-Fiche technique-Niveaux de produits et de processing-Mode d'acquisition-", + "uuid": "9f0bd987-6cbd-42df-aed2-efccfce6bb5c", + "usage": "analytics" } ], "detailedTimeline": { "contributorId": "detailedTimeline", "componentType": "histogram", + "uuid": "", "input": { "id": "histogram-detailed-timeline", "xTicks": 5, @@ -848,7 +868,6 @@ "icon": "camera", "components": [ { - "contributorId": "demo_eo-_sensorType-10-metadata.ObservationContext.processusUsed.platform-10-", "componentType": "donut", "showExportCsv": false, "input": { @@ -858,10 +877,12 @@ "containerWidth": 223, "multiselectable": true, "opacity": 0.4 - } + }, + "contributorId": "demo_eo-_sensorType-10-metadata.ObservationContext.processusUsed.platform-10-", + "uuid": "e08c80a9-4979-4a29-8a9e-f8fb89e3fbf3", + "usage": "analytics" }, { - "contributorId": "demo_eo-metadata.ObservationContext.processusUsed.platform-6-Count-desc-count", "showExportCsv": false, "componentType": "powerbars", "input": { @@ -877,7 +898,10 @@ "useColorFromData": false, "unit": "", "chartWidth": 217 - } + }, + "contributorId": "demo_eo-metadata.ObservationContext.processusUsed.platform-6-Count-desc-count", + "uuid": "f7ae6c0f-ce87-496e-8861-66676dc6e280", + "usage": "analytics" } ] }, @@ -888,7 +912,6 @@ "icon": "assistant_photo", "components": [ { - "contributorId": "demo_eo-metadata.core.identity.lifecycle.created-time-bucket-Count--50_keywords.country", "input": { "id": "demo_eo-metadata.core.identity.lifecycle.created-time-bucket-Count--50_keywords.country", "isHistogramSelectable": true, @@ -931,7 +954,10 @@ } }, "componentType": "swimlane", - "showExportCsv": false + "showExportCsv": false, + "contributorId": "demo_eo-metadata.core.identity.lifecycle.created-time-bucket-Count--50_keywords.country", + "uuid": "123db10c-4820-4f3a-907e-4bcf7595085a", + "usage": "analytics" } ] }, @@ -942,7 +968,6 @@ "icon": "add_location", "components": [ { - "contributorId": "demo_eo-_keywords.country-10-Count-desc-count", "showExportCsv": "", "componentType": "powerbars", "input": { @@ -958,10 +983,12 @@ "useColorFromData": false, "unit": "", "chartWidth": 217 - } + }, + "contributorId": "demo_eo-_keywords.country-10-Count-desc-count", + "uuid": "a5cb8c78-c5a6-4af8-8803-82eb97aa13c5", + "usage": "analytics" }, { - "contributorId": "demo_eo-_keywords.city-10-Count-desc-count", "showExportCsv": "", "componentType": "powerbars", "input": { @@ -977,7 +1004,10 @@ "useColorFromData": false, "unit": "", "chartWidth": 217 - } + }, + "contributorId": "demo_eo-_keywords.city-10-Count-desc-count", + "uuid": "fd0d4347-4cd2-4edb-a98f-5ae9fae0342d", + "usage": "analytics" } ] }, @@ -988,7 +1018,6 @@ "icon": "cloud_queue", "components": [ { - "contributorId": "demo_eo-metadata.ObservationContext.eo.opt.cloudCoverPercentage-numeric-bucket-Count--50", "input": { "id": "demo_eo-metadata.ObservationContext.eo.opt.cloudCoverPercentage-numeric-bucket-Count--50", "isHistogramSelectable": true, @@ -1022,10 +1051,12 @@ "isSmoothedCurve": false }, "componentType": "histogram", - "showExportCsv": true + "showExportCsv": true, + "contributorId": "demo_eo-metadata.ObservationContext.eo.opt.cloudCoverPercentage-numeric-bucket-Count--50", + "uuid": "bec69072-6e9b-49a3-baac-66b8597a7d85", + "usage": "analytics" }, { - "contributorId": "demo_eo-metadata.core.temporalCoverage.begin-time-bucket-AVG-metadata.ObservationContext.eo.opt.snowCoverPercentage-50", "input": { "id": "demo_eo-metadata.core.temporalCoverage.begin-time-bucket-AVG-metadata.ObservationContext.eo.opt.snowCoverPercentage-50", "isHistogramSelectable": true, @@ -1060,7 +1091,10 @@ "isSmoothedCurve": false }, "componentType": "histogram", - "showExportCsv": "" + "showExportCsv": "", + "contributorId": "demo_eo-metadata.core.temporalCoverage.begin-time-bucket-AVG-metadata.ObservationContext.eo.opt.snowCoverPercentage-50", + "uuid": "738133aa-dee7-46bc-9671-55617b160963", + "usage": "analytics" } ] }, @@ -1071,7 +1105,6 @@ "icon": "ac_unit", "components": [ { - "contributorId": "demo_eo-metadata.ObservationContext.eo.opt.snowCoverPercentage-numeric-bucket-Count--50", "input": { "id": "demo_eo-metadata.ObservationContext.eo.opt.snowCoverPercentage-numeric-bucket-Count--50", "isHistogramSelectable": true, @@ -1105,10 +1138,12 @@ "isSmoothedCurve": false }, "componentType": "histogram", - "showExportCsv": "" + "showExportCsv": "", + "contributorId": "demo_eo-metadata.ObservationContext.eo.opt.snowCoverPercentage-numeric-bucket-Count--50", + "uuid": "3ae8b440-8f8f-45c1-8137-4c75c7b2ef94", + "usage": "analytics" }, { - "contributorId": "demo_eo-metadata.ObservationContext.phenomenonTime.begin-time-bucket-AVG-metadata.ObservationContext.eo.opt.snowCoverPercentage-50", "input": { "id": "demo_eo-metadata.ObservationContext.phenomenonTime.begin-time-bucket-AVG-metadata.ObservationContext.eo.opt.snowCoverPercentage-50", "isHistogramSelectable": true, @@ -1143,7 +1178,10 @@ "isSmoothedCurve": false }, "componentType": "histogram", - "showExportCsv": "" + "showExportCsv": "", + "contributorId": "demo_eo-metadata.ObservationContext.phenomenonTime.begin-time-bucket-AVG-metadata.ObservationContext.eo.opt.snowCoverPercentage-50", + "uuid": "e4aeb25c-52c3-4e36-8b74-ae2e91e3cc3f", + "usage": "analytics" } ] }, @@ -1154,7 +1192,6 @@ "icon": "brightness_5", "components": [ { - "contributorId": "demo_eo-metadata.ObservationContext.eo.acquisition.illuminationAzimuthAngle-numeric-bucket-Count--50", "input": { "id": "demo_eo-metadata.ObservationContext.eo.acquisition.illuminationAzimuthAngle-numeric-bucket-Count--50", "isHistogramSelectable": true, @@ -1188,7 +1225,10 @@ "isSmoothedCurve": false }, "componentType": "histogram", - "showExportCsv": true + "showExportCsv": true, + "contributorId": "demo_eo-metadata.ObservationContext.eo.acquisition.illuminationAzimuthAngle-numeric-bucket-Count--50", + "uuid": "b9d8b69f-cf34-4dcc-9602-1b5e2e5b3265", + "usage": "analytics" } ] }, @@ -1199,7 +1239,6 @@ "icon": "call_missed_outgoing", "components": [ { - "contributorId": "demo_eo-metadata.ObservationContext.eo.acquisition.incidenceAngle-numeric-bucket-Count--50", "input": { "id": "demo_eo-metadata.ObservationContext.eo.acquisition.incidenceAngle-numeric-bucket-Count--50", "isHistogramSelectable": true, @@ -1233,7 +1272,10 @@ "isSmoothedCurve": false }, "componentType": "histogram", - "showExportCsv": true + "showExportCsv": true, + "contributorId": "demo_eo-metadata.ObservationContext.eo.acquisition.incidenceAngle-numeric-bucket-Count--50", + "uuid": "2791576c-c207-4354-b070-5615ebb5cba1", + "usage": "analytics" } ] }, @@ -1244,7 +1286,6 @@ "icon": "apps", "components": [ { - "contributorId": "demo_eo-_resolution-numeric-bucket-Count--50", "input": { "id": "demo_eo-_resolution-numeric-bucket-Count--50", "isHistogramSelectable": true, @@ -1278,11 +1319,15 @@ "isSmoothedCurve": false }, "componentType": "histogram", - "showExportCsv": "" + "showExportCsv": "", + "contributorId": "demo_eo-_resolution-numeric-bucket-Count--50", + "uuid": "6630b480-8c03-4f39-8c87-a95d919499a4", + "usage": "analytics" } ] } ], + "filters_shortcuts": [], "colorGenerator": { "keysToColors": [ [ @@ -1424,7 +1469,7 @@ "externalNode": {} }, "server": { - "url": "http://localhost:81/server", + "url": "https://cloud.arlas.io/arlas/server", "max_age_cache": 120, "collection": { "name": "demo_eo" @@ -1463,5 +1508,6 @@ "replacedAttribute": "arlas.web.components.mapgl.input.mapLayers.externalEventLayers", "replacer": "external-event-layers" } - ] -} + ], + "resources": {} +} \ No newline at end of file diff --git a/src/config.map.json b/src/config.map.json index 15f46454..9e8b7eae 100644 --- a/src/config.map.json +++ b/src/config.map.json @@ -1,16 +1,16 @@ { "layers": [ { - "id": "arlas_id:Latest products:1677155839933", + "id": "arlas_id:Latest products:1696925765098", "type": "fill", "source": "feature-_geometry_wkt-window-demo_eo", "minzoom": 0, - "maxzoom": 23, + "maxzoom": 22, "layout": { "visibility": "visible" }, "paint": { - "fill-opacity": 0, + "fill-opacity": 0.1, "fill-color": [ "match", [ @@ -23,20 +23,20 @@ "#ec4040", "SPOT 6", "#0087e9", + "SPOT 7", + "#1102c6", "SPOT 5", "#0041ff", "SPOT 4", "#00b4ff", - "SPOT 7", - "#1102c6", "TerraSAR-X 1", "#5e5e5e", + "SPOT 1", + "#f941ff", "ALOS2", "#00c926", "SENTINEL 2A", "#ff0094", - "DRONE", - "#ffe300", "#9d9ca9" ] }, @@ -49,7 +49,7 @@ "metadata_ObservationContext_processusUsed_platform_arlas__color" ], "width": 3, - "opacity": 0.7 + "opacity": 0.8 }, "is-scrollable-layer": true }, @@ -61,17 +61,19 @@ ] }, { - "id": "arlas_id:Products:1677155960359", - "type": "fill", + "id": "arlas_id:Products:1696926758346", + "type": "line", "source": "feature-_geometry_wkt-wide-demo_eo", "minzoom": 0, - "maxzoom": 23, + "maxzoom": 22, "layout": { - "visibility": "visible" + "visibility": "visible", + "line-cap": "round", + "line-join": "round" }, "paint": { - "fill-opacity": 0, - "fill-color": [ + "line-opacity": 0.4, + "line-color": [ "match", [ "get", @@ -83,34 +85,31 @@ "#ec4040", "SPOT 6", "#0087e9", + "SPOT 7", + "#1102c6", "SPOT 5", "#0041ff", "SPOT 4", "#00b4ff", - "SPOT 7", - "#1102c6", "TerraSAR-X 1", "#5e5e5e", + "SPOT 1", + "#f941ff", "ALOS2", "#00c926", "SENTINEL 2A", "#ff0094", - "DRONE", - "#ffe300", "#9d9ca9" + ], + "line-width": 0.5, + "line-dasharray": [ + 0.1, + 5 ] }, "metadata": { "collection": "demo_eo", "collection-display-name": "demo_eo", - "stroke": { - "color": [ - "get", - "metadata_ObservationContext_processusUsed_platform_arlas__color" - ], - "width": 0.5, - "opacity": 0.5 - }, "is-scrollable-layer": false }, "filter": [ @@ -121,7 +120,7 @@ ] }, { - "id": "arlas_id:Density of products:1677155972496", + "id": "arlas_id:Density of products:1696925833059", "type": "circle", "source": "cluster-_centroid_wkt-Coarse-tile-centroid-demo_eo", "minzoom": 0, @@ -223,7 +222,7 @@ ] }, { - "id": "arlas_id:Product density grid:1677155981981", + "id": "arlas_id:Product density grid:1697197580589", "type": "fill", "source": "cluster-_centroid_wkt-Medium-tile-cell-demo_eo", "minzoom": 0, @@ -242,17 +241,17 @@ "count_:normalized" ], 0, - 0, + 0.1, 0.2, - 0.04, + 0.16000000000000003, 0.4, - 0.08, + 0.22000000000000003, 0.6, - 0.12000000000000002, + 0.28, 0.8, - 0.16, + 0.3400000000000001, 1, - 0.2 + 0.4 ], "fill-color": [ "interpolate", @@ -355,9 +354,9 @@ ] }, { - "id": "arlas_id:Heatmap:1689091401394", + "id": "arlas_id:Heatmap:1697197287204", "type": "heatmap", - "source": "cluster-_centroid_wkt-Medium-tile-cell_center-demo_eo", + "source": "cluster-_centroid_wkt-Fine-tile-cell_center-demo_eo", "minzoom": 1, "maxzoom": 23, "layout": { @@ -431,58 +430,67 @@ ] }, { - "source": "feature-_geometry_wkt-window-demo_eo", - "id": "arlas-select-arlas-fill_stroke-Latest products:1677155839933", + "id": "arlas-select-arlas_id:Products:1696926758346", "type": "line", - "maxzoom": 23, - "filter": [ - "all", - [ - "all" - ] - ], + "source": "feature-_geometry_wkt-wide-demo_eo", "minzoom": 0, + "maxzoom": 22, "layout": { "visibility": "none" }, "paint": { + "line-opacity": 0.4, "line-color": [ - "get", - "metadata_ObservationContext_processusUsed_platform_arlas__color" + "match", + [ + "get", + "metadata_ObservationContext_processusUsed_platform" + ], + "SENTINEL 2", + "#ff61ec", + "PLEIADES", + "#ec4040", + "SPOT 6", + "#0087e9", + "SPOT 7", + "#1102c6", + "SPOT 5", + "#0041ff", + "SPOT 4", + "#00b4ff", + "TerraSAR-X 1", + "#5e5e5e", + "SPOT 1", + "#f941ff", + "ALOS2", + "#00c926", + "SENTINEL 2A", + "#ff0094", + "#9d9ca9" ], - "line-opacity": 0.7, - "line-width": 15 - } - }, - { - "source": "feature-_geometry_wkt-wide-demo_eo", - "id": "arlas-select-arlas-fill_stroke-Products:1677155960359", - "type": "line", - "maxzoom": 23, + "line-width": 12.5, + "line-dasharray": [ + 0.1, + 5 + ] + }, + "metadata": { + "collection": "demo_eo", + "collection-display-name": "demo_eo", + "is-scrollable-layer": false + }, "filter": [ "all", [ "all" ] - ], - "minzoom": 0, - "layout": { - "visibility": "none" - }, - "paint": { - "line-color": [ - "get", - "metadata_ObservationContext_processusUsed_platform_arlas__color" - ], - "line-opacity": 0.5, - "line-width": 12.5 - } + ] }, { "source": "feature-_geometry_wkt-window-demo_eo", - "id": "arlas-hover-arlas-fill_stroke-Latest products:1677155839933", + "id": "arlas-select-arlas-fill_stroke-Latest products:1696925765098", "type": "line", - "maxzoom": 23, + "maxzoom": 22, "filter": [ "all", [ @@ -498,39 +506,72 @@ "get", "metadata_ObservationContext_processusUsed_platform_arlas__color" ], - "line-opacity": 0.7, + "line-opacity": 0.8, "line-width": 15 } }, { - "source": "feature-_geometry_wkt-wide-demo_eo", - "id": "arlas-hover-arlas-fill_stroke-Products:1677155960359", + "id": "arlas-hover-arlas_id:Products:1696926758346", "type": "line", - "maxzoom": 23, - "filter": [ - "all", - [ - "all" - ] - ], + "source": "feature-_geometry_wkt-wide-demo_eo", "minzoom": 0, + "maxzoom": 22, "layout": { "visibility": "none" }, "paint": { + "line-opacity": 0.4, "line-color": [ - "get", - "metadata_ObservationContext_processusUsed_platform_arlas__color" + "match", + [ + "get", + "metadata_ObservationContext_processusUsed_platform" + ], + "SENTINEL 2", + "#ff61ec", + "PLEIADES", + "#ec4040", + "SPOT 6", + "#0087e9", + "SPOT 7", + "#1102c6", + "SPOT 5", + "#0041ff", + "SPOT 4", + "#00b4ff", + "TerraSAR-X 1", + "#5e5e5e", + "SPOT 1", + "#f941ff", + "ALOS2", + "#00c926", + "SENTINEL 2A", + "#ff0094", + "#9d9ca9" ], - "line-opacity": 0.5, - "line-width": 12.5 - } + "line-width": 12.5, + "line-dasharray": [ + 0.1, + 5 + ] + }, + "metadata": { + "collection": "demo_eo", + "collection-display-name": "demo_eo", + "is-scrollable-layer": false + }, + "filter": [ + "all", + [ + "all" + ] + ] }, { "source": "feature-_geometry_wkt-window-demo_eo", - "id": "arlas-fill_stroke-Latest products:1677155839933", + "id": "arlas-hover-arlas-fill_stroke-Latest products:1696925765098", "type": "line", - "maxzoom": 23, + "maxzoom": 22, "filter": [ "all", [ @@ -539,22 +580,22 @@ ], "minzoom": 0, "layout": { - "visibility": "visible" + "visibility": "none" }, "paint": { "line-color": [ "get", "metadata_ObservationContext_processusUsed_platform_arlas__color" ], - "line-opacity": 0.7, - "line-width": 3 + "line-opacity": 0.8, + "line-width": 15 } }, { - "source": "feature-_geometry_wkt-wide-demo_eo", - "id": "arlas-fill_stroke-Products:1677155960359", + "source": "feature-_geometry_wkt-window-demo_eo", + "id": "arlas-fill_stroke-Latest products:1696925765098", "type": "line", - "maxzoom": 23, + "maxzoom": 22, "filter": [ "all", [ @@ -570,13 +611,13 @@ "get", "metadata_ObservationContext_processusUsed_platform_arlas__color" ], - "line-opacity": 0.5, - "line-width": 0.5 + "line-opacity": 0.8, + "line-width": 3 } }, { "source": "cluster-_centroid_wkt-Medium-tile-cell-demo_eo", - "id": "arlas-fill_stroke-Product density grid:1677155981981", + "id": "arlas-fill_stroke-Product density grid:1697197580589", "type": "line", "maxzoom": 15, "filter": [ @@ -597,9 +638,9 @@ }, { "source": "feature-_geometry_wkt-window-demo_eo", - "id": "scrollable_arlas_id:Latest products:1677155839933", + "id": "scrollable_arlas_id:Latest products:1696925765098", "type": "fill", - "maxzoom": 23, + "maxzoom": 22, "minzoom": 0, "metadata": { "collection": "demo_eo", @@ -616,7 +657,7 @@ "visibility": "visible" }, "paint": { - "fill-opacity": 0, + "fill-opacity": 0.1, "fill-color": [ "match", [ @@ -629,20 +670,20 @@ "#ec4040", "SPOT 6", "#0087e9", + "SPOT 7", + "#1102c6", "SPOT 5", "#0041ff", "SPOT 4", "#00b4ff", - "SPOT 7", - "#1102c6", "TerraSAR-X 1", "#5e5e5e", + "SPOT 1", + "#f941ff", "ALOS2", "#00c926", "SENTINEL 2A", "#ff0094", - "DRONE", - "#ffe300", "#9d9ca9" ] } @@ -650,19 +691,19 @@ ], "external-event-layers": [ { - "id": "arlas-hover-arlas-fill_stroke-Latest products:1677155839933", + "id": "arlas-hover-arlas_id:Products:1696926758346", "on": "hover" }, { - "id": "arlas-hover-arlas-fill_stroke-Products:1677155960359", + "id": "arlas-hover-arlas-fill_stroke-Latest products:1696925765098", "on": "hover" }, { - "id": "arlas-select-arlas-fill_stroke-Latest products:1677155839933", + "id": "arlas-select-arlas_id:Products:1696926758346", "on": "select" }, { - "id": "arlas-select-arlas-fill_stroke-Products:1677155960359", + "id": "arlas-select-arlas-fill_stroke-Latest products:1696925765098", "on": "select" } ] diff --git a/src/styles/map.scss b/src/styles/map.scss index 4e13460e..769c93f3 100644 --- a/src/styles/map.scss +++ b/src/styles/map.scss @@ -116,7 +116,7 @@ } } -.arlas-map { +arlas-coordinates { .current-coordinate, .current-coordinate-edition { position: absolute; From 731c021852265afe3d87f1f97b2705fd03594b6c Mon Sep 17 00:00:00 2001 From: QuCMGisaia Date: Wed, 10 Jul 2024 10:30:07 +0200 Subject: [PATCH 02/19] feat: externalise list component --- src/app/app.component.ts | 90 ++- src/app/app.module.ts | 11 +- .../arlas-list/arlas-list.component.html | 54 ++ .../arlas-list/arlas-list.component.scss | 31 + .../arlas-list/arlas-list.component.spec.ts | 35 ++ .../arlas-list/arlas-list.component.ts | 118 ++++ .../arlas-map/arlas-map.component.ts | 9 +- .../arlas-wui-root.component.html | 157 ++--- .../arlas-wui-root.component.scss | 215 +++---- .../arlas-wui-root.component.ts | 569 ++---------------- src/app/pipes/get-resultlist-config.pipe.ts | 18 + src/app/services/resultlist.service.ts | 404 ++++++++++++- src/app/tools/utils.ts | 30 + src/styles.scss | 24 +- src/styles/resultlist.css | 8 +- 15 files changed, 999 insertions(+), 774 deletions(-) create mode 100644 src/app/components/arlas-list/arlas-list.component.html create mode 100644 src/app/components/arlas-list/arlas-list.component.scss create mode 100644 src/app/components/arlas-list/arlas-list.component.spec.ts create mode 100644 src/app/components/arlas-list/arlas-list.component.ts create mode 100644 src/app/pipes/get-resultlist-config.pipe.ts create mode 100644 src/app/tools/utils.ts diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 077a3c32..cc224721 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -16,7 +16,15 @@ * specific language governing permissions and limitations * under the License. */ -import { Component, OnInit } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; +import { CollectionReferenceParameters } from 'arlas-api'; +import { ResultListContributor } from 'arlas-web-contributors'; +import { ArlasCollaborativesearchService, ArlasConfigService, ArlasStartupService } from 'arlas-wui-toolkit'; +import { ResultlistService } from './services/resultlist.service'; +import { ContributorService } from 'public-api'; +import { MapService } from './services/map.service'; +import { ArlasColorService } from 'arlas-web-components'; +import { Subject, takeUntil, zip } from 'rxjs'; @Component({ selector: 'arlas-root', @@ -25,11 +33,91 @@ import { Component, OnInit } from '@angular/core'; }) export class ArlasWuiComponent implements OnInit { + public collections = new Array(); + public collectionToDescription = new Map(); + public resultlistContributors: Array = new Array(); + @Input() public hiddenResultlistTabs: string[] = []; + + /** Destroy subscriptions */ + private _onDestroy$ = new Subject(); + + public constructor( + private arlasStartupService: ArlasStartupService, + private configService: ArlasConfigService, + private resultlistService: ResultlistService, + private contributorService: ContributorService, + private mapService: MapService, + private colorService: ArlasColorService, + private collaborativeService: ArlasCollaborativesearchService + ) { } + public ngOnInit(): void { const loadingGif = document.querySelector('.gif'); if (!!loadingGif) { loadingGif.remove(); } + + // Initialize the contributors and app wide services + if (this.arlasStartupService.shouldRunApp && !this.arlasStartupService.emptyMode) { + this.collections = [...new Set(Array.from(this.collaborativeService.registry.values()).map(c => c.collection))]; + + /** Resultlist */ + /** Retrieve displayable resultlists */ + const hiddenListsTabsSet = new Set(this.hiddenResultlistTabs); + const allResultlists = this.configService.getValue('arlas.web.components.resultlists'); + const allContributors = this.configService.getValue('arlas.web.contributors'); + const resultListsConfig = !!allResultlists ? allResultlists.filter(a => { + const contId = a.contributorId; + const tab = allContributors.find(c => c.identifier === contId).name; + return !hiddenListsTabsSet.has(tab); + }) : []; + + const ids = new Set(resultListsConfig.map(c => c.contributorId)); + this.arlasStartupService.contributorRegistry.forEach((v, k) => { + if (v instanceof ResultListContributor) { + v.updateData = ids.has(v.identifier); + this.resultlistContributors.push(v); + } + }); + this.resultlistService.setContributors(this.resultlistContributors, resultListsConfig); + + /** Map */ + // Set MapContributors + const mapContributors = []; + this.contributorService.getMapContributors().forEach(mapContrib => { + mapContrib.colorGenerator = this.colorService.colorGenerator; + if (!!this.resultlistContributors) { + const resultlistContrbutor: ResultListContributor = this.resultlistContributors + .find(resultlistContrib => resultlistContrib.collection === mapContrib.collection); + if (!!resultlistContrbutor) { + mapContrib.searchSize = resultlistContrbutor.pageSize; + mapContrib.searchSort = resultlistContrbutor.sort; + } else { + mapContrib.searchSize = 50; + } + } + mapContributors.push(mapContrib); + }); + this.mapService.setContributors(mapContributors); + + zip(...this.collections.map(c => this.collaborativeService.describe(c))) + .pipe(takeUntil(this._onDestroy$)) + .subscribe(cdrs => { + cdrs.forEach(cdr => { + this.collectionToDescription.set(cdr.collection_name, cdr.params); + }); + this.resultlistService.setCollectionsDescription(this.collectionToDescription); + if (!!this.mapService.mapComponent) { + const bounds = (this.mapService.mapComponent.map).getBounds(); + if (!!bounds) { + (this.mapService.mapComponent.map).fitBounds(bounds, { duration: 0 }); + } + } + if (this.resultlistContributors.length > 0) { + this.resultlistContributors.forEach(c => c.sort = this.collectionToDescription.get(c.collection).id_path); + } + }); + } } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 2d54f578..b02b2ad8 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -45,7 +45,7 @@ import { RouterModule } from '@angular/router'; import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; import { HistogramModule, MapglImportModule, MapglModule, MapglSettingsModule, - ResultsModule, FormatNumberModule, BboxGeneratorModule + ResultsModule, FormatNumberModule, BboxGeneratorModule, GetValueModule } from 'arlas-web-components'; import { ArlasConfigService, @@ -77,6 +77,8 @@ import { ArlasTranslateLoader, ArlasWalkthroughLoader } from './tools/customLoad import { LazyLoadImageHooks } from './tools/lazy-loader'; import { LAZYLOAD_IMAGE_HOOKS, LazyLoadImageModule } from 'ng-lazyload-image'; import { ArlasMapComponent } from './components/arlas-map/arlas-map.component'; +import { ArlasListComponent } from './components/arlas-list/arlas-list.component'; +import { GetResultlistConfigPipe } from './pipes/get-resultlist-config.pipe'; @NgModule({ @@ -89,7 +91,9 @@ import { ArlasMapComponent } from './components/arlas-map/arlas-map.component'; RoundKilometer, SquareKilometer, GeocodingComponent, - ArlasMapComponent + ArlasMapComponent, + ArlasListComponent, + GetResultlistConfigPipe ], exports: [ AoiDimensionComponent, @@ -153,7 +157,8 @@ import { ArlasMapComponent } from './components/arlas-map/arlas-map.component'; }), ArlasTaggerModule, LoginModule, - LazyLoadImageModule + LazyLoadImageModule, + GetValueModule ], providers: [ ContributorService, diff --git a/src/app/components/arlas-list/arlas-list.component.html b/src/app/components/arlas-list/arlas-list.component.html new file mode 100644 index 00000000..ab29b621 --- /dev/null +++ b/src/app/components/arlas-list/arlas-list.component.html @@ -0,0 +1,54 @@ + + + + + + {{(list | getResultlistConfig)?.options?.icon}} + + + {{list.name | translate}} + + + +
+ + +
+
+
\ No newline at end of file diff --git a/src/app/components/arlas-list/arlas-list.component.scss b/src/app/components/arlas-list/arlas-list.component.scss new file mode 100644 index 00000000..bf025c48 --- /dev/null +++ b/src/app/components/arlas-list/arlas-list.component.scss @@ -0,0 +1,31 @@ +@import "../../../styles/variables.scss"; + +.arlas-progression { + width: 100%; + position: absolute; + top: 0; +} + +.result-list-tab-group { + ::ng-deep .mat-tab-label { + min-width: 60px; + } + + ::ng-deep .mat-tab-label-content { + display: flex; + justify-content: space-between; + width: 100%; + gap: 0 10px; + } + + ::ng-deep .mat-tab-label-content .name { + flex: 5; + } + + .list__container { + height: calc(100vh - 50px - $top-menu-height); + overflow: hidden; + } +} + + diff --git a/src/app/components/arlas-list/arlas-list.component.spec.ts b/src/app/components/arlas-list/arlas-list.component.spec.ts new file mode 100644 index 00000000..2f4eee3c --- /dev/null +++ b/src/app/components/arlas-list/arlas-list.component.spec.ts @@ -0,0 +1,35 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TranslateFakeLoader, TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { VisualizeService } from 'app/services/visualize.service'; +import { ArlasToolKitModule, ArlasToolkitSharedModule } from 'arlas-wui-toolkit'; +import { ArlasListComponent } from './arlas-list.component'; + +describe('ArlasListComponent', () => { + let component: ArlasListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ArlasListComponent ], + imports: [ + ArlasToolKitModule, + TranslateModule.forRoot({ + loader: { provide: TranslateLoader, useClass: TranslateFakeLoader } }), + ArlasToolkitSharedModule + ], + providers: [ + VisualizeService + ], + teardown: { destroyAfterEach: false } + }) + .compileComponents(); + + fixture = TestBed.createComponent(ArlasListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/arlas-list/arlas-list.component.ts b/src/app/components/arlas-list/arlas-list.component.ts new file mode 100644 index 00000000..9c6a3831 --- /dev/null +++ b/src/app/components/arlas-list/arlas-list.component.ts @@ -0,0 +1,118 @@ +/* + * Licensed to Gisaïa under one or more contributor + * license agreements. See the NOTICE.txt file distributed with + * this work for additional information regarding copyright + * ownership. Gisaïa licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { MatTabGroup } from '@angular/material/tabs'; +import { ResultlistService } from 'app/services/resultlist.service'; +import { Action, Column, ElementIdentifier, Item, ModeEnum, PageQuery, ResultListComponent } from 'arlas-web-components'; +import { ResultListContributor } from 'arlas-web-contributors'; +import { Subject } from 'rxjs'; + +@Component({ + selector: 'arlas-list', + templateUrl: './arlas-list.component.html', + styleUrls: ['./arlas-list.component.scss'] +}) +export class ArlasListComponent implements OnInit, OnDestroy { + + /** + * @Input : Angular + * @description Width in pixels of the preview result list + */ + @Input() public previewListWidth = 125; + /** + * @Input : Angular + * @description Width in pixels of the result list + */ + @Input() public listWidth = 500; + /** + * @Input : Angular + * @description Number of columns in the grid result list + */ + @Input() public resultListGridColumns = 4; + + @ViewChild('resultList', { static: false }) public resultListComponent: ResultListComponent; + @ViewChild('tabsList', { static: false }) public tabsList: MatTabGroup; + + /** Destroy subscriptions */ + private _onDestroy$ = new Subject(); + + public constructor( + protected resultlistService: ResultlistService + ) { } + + public ngOnInit(): void { } + + public ngOnDestroy(): void { + this._onDestroy$.next(true); + this._onDestroy$.complete(); + } + + public ngAfterViewInit() { } + + public changeListResultMode(mode: ModeEnum, identifier: string) { + const config = this.resultlistService.resultlistConfigPerContId.get(identifier); + config.defautMode = mode; + this.resultlistService.resultlistConfigPerContId.set(identifier, config); + setTimeout(() => { + this.resultlistService.updateVisibleItems(); + }, 0); + } + + public sortColumn(listContributor: ResultListContributor, column: Column) { + this.resultlistService.getBoardEvents({ origin: listContributor.identifier, event: 'sortColumnEvent', data: column }); + } + + public geoAutoSort(listContributor: ResultListContributor, enabled: boolean) { + this.resultlistService.getBoardEvents({ origin: listContributor.identifier, event: 'geoAutoSortEvent', data: enabled }); + } + + // TODO: find out what the event is + public geoSort(listContributor: ResultListContributor, event: string) { + this.resultlistService.getBoardEvents({ origin: listContributor.identifier, event: 'geoSortEvent', data: event }); + } + + public applyActionOnItem(listContributor: ResultListContributor, action: { action: Action; elementidentifier: ElementIdentifier; }) { + this.resultlistService.getBoardEvents({ origin: listContributor.identifier, event: 'actionOnItemEvent', data: action }); + } + + public consultItem(listContributor: ResultListContributor, data: ElementIdentifier) { + this.resultlistService.getBoardEvents({ origin: listContributor.identifier, event: 'consultedItemEvent', data }); + } + + public selectItems(listContributor: ResultListContributor, data: string[]) { + this.resultlistService.getBoardEvents({ origin: listContributor.identifier, event: 'selectedItemsEvent', data }); + } + + public paginate(listContributor: ResultListContributor, pageQuery: PageQuery) { + this.resultlistService.getBoardEvents({ origin: listContributor.identifier, event: 'paginationEvent', data: pageQuery }); + } + + public applyGlobalAction(listContributor: ResultListContributor, action: Action) { + this.resultlistService.getBoardEvents({ origin: listContributor.identifier, event: 'globalActionEvent', data: action }); + } + + public updateMapStyleFromScroll(items: Item[], collection: string) { + this.resultlistService.updateMapStyleFromScroll(items, collection); + } + + public updateMapStyleFromChange(items: Map[], collection: string) { + this.resultlistService.updateMapStyleFromChange(items, collection); + } +} diff --git a/src/app/components/arlas-map/arlas-map.component.ts b/src/app/components/arlas-map/arlas-map.component.ts index d5b26237..47af3327 100644 --- a/src/app/components/arlas-map/arlas-map.component.ts +++ b/src/app/components/arlas-map/arlas-map.component.ts @@ -273,11 +273,10 @@ export class ArlasMapComponent implements OnInit { mapglContributor.setSelection(null, this.collaborativeService.getCollaboration(mapglContributor.identifier)); }); - // TODO: add that back when the resultlist is in its own component - // if (!!this.previewListContrib && this.previewListContrib.data.length > 0 && - // this.mapComponentConfig.mapLayers.events.onHover.filter(l => this.mapglComponent.map.getLayer(l)).length > 0) { - // this.resultlistService.updateVisibleItems(); - // } + if (!!this.resultlistService.previewListContrib && this.resultlistService.previewListContrib.data.length > 0 && + this.mapComponentConfig.mapLayers.events.onHover.filter(l => this.mapglComponent.map.getLayer(l)).length > 0) { + this.resultlistService.updateVisibleItems(); + } } } diff --git a/src/app/components/arlas-wui-root/arlas-wui-root.component.html b/src/app/components/arlas-wui-root/arlas-wui-root.component.html index 83913cfa..5654d487 100644 --- a/src/app/components/arlas-wui-root/arlas-wui-root.component.html +++ b/src/app/components/arlas-wui-root/arlas-wui-root.component.html @@ -23,21 +23,33 @@ + [isEmptyMode]="arlasStartupService.emptyMode" [showIndicators]="showIndicators"> - +
-
-
+ +
+ [class.app-container-timeline-legend]="isTimelineOpen && timelineComponent + && timelineComponent.timelineLegend && timelineComponent.timelineLegend.length > 1">
@@ -47,7 +59,7 @@
-
{{ isExtraShortcutsOpen ? 'See less' : 'See more' | translate }}
+
{{ isExtraShortcutsOpen ? ('See less' | translate) : ('See more' | translate) }}
{{ extraShortcutsFiltered }} @@ -64,112 +76,69 @@ [displayFilterFirstValue]="false">
+
-
+
+ [class.arlas-map--tight-with-list]="!mapService.coordinatesHaveSpace && !resultlistService.listOpen + && resultlistService.rightListContributors.length > 0 + && (resultlistService.previewListContrib | getResultlistConfig)?.hasGridMode">
keyboard_arrow_left
-
+
- + -
+
{{ resultlistService.listOpen ? 'keyboard_arrow_right' : 'keyboard_arrow_left' }}
-
- + + (paginationEvent)="resultlistService.getBoardEvents({ origin: resultlistService.previewListContrib.identifier, event: 'paginationEvent', data: $event })" + (geoSortEvent)="resultlistService.getBoardEvents({ origin: resultlistService.previewListContrib.identifier, event: 'geoSortEvent', data: $event })" + (geoAutoSortEvent)="resultlistService.getBoardEvents({ origin: resultlistService.previewListContrib.identifier, event: 'geoAutoSortEvent', data: $event })" + (selectedItemsEvent)="resultlistService.getBoardEvents({ origin: resultlistService.previewListContrib.identifier, event: 'selectedItemsEvent', data: $event })" + (consultedItemEvent)="resultlistService.getBoardEvents({ origin: resultlistService.previewListContrib.identifier, event: 'consultedItemEvent', data: $event })" + (actionOnItemEvent)="resultlistService.getBoardEvents({ origin: resultlistService.previewListContrib.identifier, event: 'actionOnItemEvent', data: $event })" + (globalActionEvent)="resultlistService.getBoardEvents({ origin: resultlistService.previewListContrib.identifier, event: 'globalActionEvent', data: $event })">
-
- - - - - - {{resultListConfigPerContId.get(list.identifier)?.options?.icon}} - - - {{list.name | translate}} - - -
- - -
-
-
+
+
diff --git a/src/app/components/arlas-wui-root/arlas-wui-root.component.scss b/src/app/components/arlas-wui-root/arlas-wui-root.component.scss index 32eb9e20..b03eb558 100644 --- a/src/app/components/arlas-wui-root/arlas-wui-root.component.scss +++ b/src/app/components/arlas-wui-root/arlas-wui-root.component.scss @@ -66,13 +66,7 @@ } } -.arlas-progression { - width: 100%; - position: absolute; - top: 0; -} - -.side-result-list-toggle { +.resultlist__toggle { z-index: 2; position: absolute; top: 50%; @@ -83,35 +77,31 @@ justify-content: center; flex-direction: column; @include toggle(); -} -.side-result-list-toggle-open { - // Have same spacing between toggle and list as with toggle and timeline - right: calc($result-list-width + ($timeline-tools-height - $toggle-height) / 2); -} + &--open { + // Have same spacing between toggle and list as with toggle and timeline + right: calc($result-list-width + ($timeline-tools-height - $toggle-height) / 2); + } -.side-result-list-toggle-open-no-grid { - right: 0px; + &--no-grid { + right: 0; + } } -.side-result-list-preview { +.resultlist__preview { width: $preview-result-list-width; height: 100%; position: absolute; right: 0; display: block; background-color: white; -} - -.side-result-list-preview-close { - display: none; -} -.side-result-list-preview .resultgrid__icon_check { - display: none; + &--closed { + display: none; + } } -.side-result-list { +.resultlist__wrapper { width: 0; height: calc(100vh - $top-menu-height); position: absolute; @@ -119,32 +109,28 @@ visibility: hidden; background-color: white; overflow: hidden; -} -.one-tab ::ng-deep.mat-tab-header { - display: none; + &--open { + display: block; + width: $result-list-width; + visibility: visible; + } } -.side-result-list-open { - display: block; - width: $result-list-width; - visibility: visible; +.one-tab { + ::ng-deep.list__container { + height: calc(100vh - $top-menu-height) !important; + } + + ::ng-deep.mat-tab-header { + display: none; + } } .rotate-icon { transform: rotate(180deg); } -.left_resultlist_wrapper { - height: calc(100vh - 50px - $top-menu-height); - overflow: hidden; -} - -.one-tab .left_resultlist_wrapper { - height: calc(100vh - $top-menu-height); - overflow: hidden; -} - .arlas-analytics-toggle { position: absolute; // Have same spacing between toggle and list as with toggle and timeline @@ -241,53 +227,57 @@ .app-container-reduce-analytics, .app-container-reduce { - ::ng-deep.arlas-map-action, - ::ng-deep.arlas-map-settings { - right: calc($result-list-width + $sm-spacing) !important; - } - ::ng-deep.aoi-dimensions { - right: calc($result-list-width + $sm-spacing + $map-actions-width + $sm-spacing); - } - ::ng-deep.basemap-container { - right: calc($result-list-width + $sm-spacing + $map-actions-width + $sm-spacing) !important; - } + .arlas-map { + ::ng-deep.arlas-map-action, + ::ng-deep.arlas-map-settings { + right: calc($result-list-width + $sm-spacing) !important; + } + ::ng-deep.aoi-dimensions { + right: calc($result-list-width + $sm-spacing + $map-actions-width + $sm-spacing); + } + ::ng-deep.basemap-container { + right: calc($result-list-width + $sm-spacing + $map-actions-width + $sm-spacing) !important; + } - ::ng-deep.current-coordinate, - ::ng-deep.current-coordinate-edition { - right: calc($result-list-width + $sm-spacing + $map-scale-max-width + $default-spacing); - } - .arlas-map--tight { ::ng-deep.current-coordinate, ::ng-deep.current-coordinate-edition { - right: calc($result-list-width + $sm-spacing) !important; - bottom: calc($timeline-height + $sm-spacing + $map-scale-height + $sm-spacing); + right: calc($result-list-width + $sm-spacing + $map-scale-max-width + $default-spacing); + } + &--tight { + ::ng-deep.current-coordinate, + ::ng-deep.current-coordinate-edition { + right: calc($result-list-width + $sm-spacing) !important; + bottom: calc($timeline-height + $sm-spacing + $map-scale-height + $sm-spacing); + } } } } .app-container-with-list-analytics, .app-container-with-list { - ::ng-deep.arlas-map-action, - ::ng-deep.arlas-map-settings { - right: calc($preview-result-list-width + $sm-spacing) !important; - } - ::ng-deep.aoi-dimensions { - right: calc($preview-result-list-width + $sm-spacing + $map-actions-width + $sm-spacing); - } + .arlas-map { + ::ng-deep.arlas-map-action, + ::ng-deep.arlas-map-settings { + right: calc($preview-result-list-width + $sm-spacing) !important; + } + ::ng-deep.aoi-dimensions { + right: calc($preview-result-list-width + $sm-spacing + $map-actions-width + $sm-spacing); + } - ::ng-deep.basemap-container { - right: calc($preview-result-list-width + $sm-spacing + $map-actions-width + $sm-spacing) !important; - } + ::ng-deep.basemap-container { + right: calc($preview-result-list-width + $sm-spacing + $map-actions-width + $sm-spacing) !important; + } - ::ng-deep.current-coordinate, - ::ng-deep.current-coordinate-edition { - right: calc($preview-result-list-width + $sm-spacing + $map-scale-max-width + $default-spacing); - } - .arlas-map--tight { ::ng-deep.current-coordinate, ::ng-deep.current-coordinate-edition { - right: calc($preview-result-list-width + $sm-spacing) !important; - bottom: calc($timeline-height + $sm-spacing + $map-scale-height + $sm-spacing); + right: calc($preview-result-list-width + $sm-spacing + $map-scale-max-width + $default-spacing); + } + &--tight { + ::ng-deep.current-coordinate, + ::ng-deep.current-coordinate-edition { + right: calc($preview-result-list-width + $sm-spacing) !important; + bottom: calc($timeline-height + $sm-spacing + $map-scale-height + $sm-spacing); + } } } } @@ -312,21 +302,23 @@ } .app-container-timeline-hidden { - ::ng-deep.mapboxgl-ctrl-scale { - // Center regarding to timeline tools - bottom: calc(($timeline-tools-height - $map-scale-height) / 2 - $map-scale-border-width); - } - - ::ng-deep.current-coordinate, - ::ng-deep.current-coordinate-edition { - // Center regarding to timeline tools - bottom: calc(($timeline-tools-height - $map-scale-height) / 2); - } + .arlas-map { + ::ng-deep.mapboxgl-ctrl-scale { + // Center regarding to timeline tools + bottom: calc(($timeline-tools-height - $map-scale-height) / 2 - $map-scale-border-width); + } - .arlas-map--tight { ::ng-deep.current-coordinate, ::ng-deep.current-coordinate-edition { - bottom: calc(($timeline-tools-height - $map-scale-height) / 2 + $map-scale-height + $sm-spacing); + // Center regarding to timeline tools + bottom: calc(($timeline-tools-height - $map-scale-height) / 2); + } + + &--tight { + ::ng-deep.current-coordinate, + ::ng-deep.current-coordinate-edition { + bottom: calc(($timeline-tools-height - $map-scale-height) / 2 + $map-scale-height + $sm-spacing); + } } } @@ -343,27 +335,29 @@ } .app-container-timeline-legend { - ::ng-deep.mapboxgl-ctrl-scale { - // Center regarding to timeline tools - bottom: calc( - $timeline-with-legend-height + ($timeline-tools-height - $map-scale-height) / 2 - $map-scale-border-width - ); - } - - ::ng-deep.current-coordinate, - ::ng-deep.current-coordinate-edition { - // Center regarding to timeline tools - bottom: calc($timeline-with-legend-height + ($timeline-tools-height - $map-scale-height) / 2); - } + .arlas-map { + ::ng-deep.mapboxgl-ctrl-scale { + // Center regarding to timeline tools + bottom: calc( + $timeline-with-legend-height + ($timeline-tools-height - $map-scale-height) / 2 - $map-scale-border-width + ); + } - .arlas-map--tight { ::ng-deep.current-coordinate, ::ng-deep.current-coordinate-edition { // Center regarding to timeline tools - bottom: calc( - $timeline-with-legend-height + ($timeline-tools-height - $map-scale-height) / 2 + $map-scale-height + - $sm-spacing - ); + bottom: calc($timeline-with-legend-height + ($timeline-tools-height - $map-scale-height) / 2); + } + + &--tight { + ::ng-deep.current-coordinate, + ::ng-deep.current-coordinate-edition { + // Center regarding to timeline tools + bottom: calc( + $timeline-with-legend-height + ($timeline-tools-height - $map-scale-height) / 2 + $map-scale-height + + $sm-spacing + ); + } } } @@ -421,21 +415,6 @@ top: 30px !important; } -.result-list-tab-group ::ng-deep .mat-tab-label { - min-width: 60px; -} - -.result-list-tab-group ::ng-deep .mat-tab-label-content { - display: flex; - justify-content: space-between; - width: 100%; - gap: 0 10px; -} - -.result-list-tab-group ::ng-deep .mat-tab-label-content .name { - flex: 5; -} - .top-left-menu { display: flex; align-items: center; diff --git a/src/app/components/arlas-wui-root/arlas-wui-root.component.ts b/src/app/components/arlas-wui-root/arlas-wui-root.component.ts index e515270c..75ce86bb 100644 --- a/src/app/components/arlas-wui-root/arlas-wui-root.component.ts +++ b/src/app/components/arlas-wui-root/arlas-wui-root.component.ts @@ -26,28 +26,17 @@ import { Output, ViewChild } from '@angular/core'; -import { MatDialog, MatDialogRef } from '@angular/material/dialog'; -import { MatSnackBar } from '@angular/material/snack-bar'; -import { MatTabGroup } from '@angular/material/tabs'; import { Title } from '@angular/platform-browser'; import { ActivatedRoute, Router } from '@angular/router'; -import { marker } from '@biesbjerg/ngx-translate-extract-marker'; -import { TranslateService } from '@ngx-translate/core'; import { MapService } from 'app/services/map.service'; import { ResultlistService } from 'app/services/resultlist.service'; import { CollectionReferenceParameters } from 'arlas-api'; import { - ArlasColorService, - CellBackgroundStyleEnum, ChartType, - Column, DataType, - Item, - ModeEnum, - PageQuery, Position, - ResultListComponent, - SortEnum + Item, + ModeEnum } from 'arlas-web-components'; import { AnalyticsContributor, @@ -60,25 +49,22 @@ import { AnalyticsService, ArlasCollaborativesearchService, ArlasConfigService, - ArlasExportCsvService, ArlasMapService, ArlasMapSettings, ArlasSettingsService, ArlasStartupService, CollectionUnit, FilterShortcutConfiguration, + getParamValue, NOT_CONFIGURED, - ProcessComponent, - ProcessService, TimelineComponent } from 'arlas-wui-toolkit'; -import * as mapboxgl from 'mapbox-gl'; -import { fromEvent, Subject, timer, zip } from 'rxjs'; -import { debounceTime, finalize, takeUntil, takeWhile } from 'rxjs/operators'; +import { fromEvent, Subject, zip } from 'rxjs'; +import { debounceTime, takeUntil } from 'rxjs/operators'; import { environment } from '../../../environments/environment'; import { ContributorService } from '../../services/contributors.service'; -import { DynamicComponentService } from '../../services/dynamicComponent.service'; import { VisualizeService } from '../../services/visualize.service'; +import { ArlasListComponent } from '../arlas-list/arlas-list.component'; import { ArlasMapComponent } from '../arlas-map/arlas-map.component'; import { MenuState } from '../left-menu/left-menu.component'; @@ -101,9 +87,7 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { }>(); @Output() public actionOnList = new Subject<{ origin: string; event: string; data?: any; }>(); - public modeEnum = ModeEnum; public chipsSearchContributor: ChipsSearchContributor; - public resultlistContributors: Array = new Array(); public analyticsContributor: AnalyticsContributor; public analytics: Array; @@ -123,27 +107,18 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { */ public isTimelineLegend = true; - public resultListsConfig = []; - public resultListConfigPerContId = new Map(); - public resultlistIsExporting = false; - public menuState: MenuState; public searchOpen = true; - public previewListContrib: ResultListContributor = null; - public rightListContributors: Array = new Array(); + /* Options */ public spinner: { show: boolean; diameter: string; color: string; strokeWidth: number; } = { show: false, diameter: '60', color: 'accent', strokeWidth: 5 }; public showZoomToData = false; public showIndicators = false; - public onSideNavChange: boolean; public mainCollection; public isTimelineOpen = true; - public mapglContributors = new Array(); - @Input() public hiddenAnalyticsTabs: string[] = []; - @Input() public hiddenResultlistTabs: string[] = []; /** * @Input : Angular * @description Width in pixels of the preview result list @@ -163,10 +138,9 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { public collections: string[]; public apploading = true; - @ViewChild('tabsList', { static: false }) public tabsList: MatTabGroup; @ViewChild('timeline', { static: false }) public timelineComponent: TimelineComponent; - @ViewChild('resultsidenav', { static: false }) public resultListComponent: ResultListComponent; - @ViewChild('arlasmap', { static: false }) public arlasMapComponent: ArlasMapComponent; + @ViewChild('arlasMap', { static: false }) public arlasMapComponent: ArlasMapComponent; + @ViewChild('arlasList', { static: false }) public arlasListComponent: ArlasListComponent; /** Shortcuts */ public shortcuts = new Array(); @@ -191,9 +165,6 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { */ public spacing = 5; - /* Process */ - private downloadDialogRef: MatDialogRef; - /** Destroy subscriptions */ private _onDestroy$ = new Subject(); @@ -202,29 +173,23 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { protected settingsService: ArlasSettingsService, public collaborativeService: ArlasCollaborativesearchService, private contributorService: ContributorService, - public arlasStartUpService: ArlasStartupService, + public arlasStartupService: ArlasStartupService, private mapSettingsService: ArlasMapSettings, private cdr: ChangeDetectorRef, private toolkitMapService: ArlasMapService, - private colorService: ArlasColorService, private titleService: Title, - private dynamicComponentService: DynamicComponentService, public visualizeService: VisualizeService, - private translate: TranslateService, - private snackbar: MatSnackBar, private activatedRoute: ActivatedRoute, private router: Router, public analyticsService: AnalyticsService, - private dialog: MatDialog, - private processService: ProcessService, protected resultlistService: ResultlistService, - private exportService: ArlasExportCsvService, protected mapService: MapService ) { this.menuState = { configs: false }; - if (this.arlasStartUpService.shouldRunApp && !this.arlasStartUpService.emptyMode) { + + if (this.arlasStartupService.shouldRunApp && !this.arlasStartupService.emptyMode) { /** resize the map */ fromEvent(window, 'resize').pipe(debounceTime(100)) .subscribe((event: Event) => { @@ -249,7 +214,7 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { /** end of retrocompatibility code */ this.appNameBackgroundColor = this.configService.getValue('arlas-wui.web.app.name_background_color') ? this.configService.getValue('arlas-wui.web.app.name_background_color') : '#FF4081'; - this.analyticsContributor = this.arlasStartUpService.contributorRegistry.get('analytics') as AnalyticsContributor; + this.analyticsContributor = this.arlasStartupService.contributorRegistry.get('analytics') as AnalyticsContributor; this.timelineComponentConfig = this.configService.getValue('arlas.web.components.timeline'); this.detailedTimelineComponentConfig = this.configService.getValue('arlas.web.components.detailedTimeline'); @@ -264,20 +229,20 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { if (this.configService.getValue('arlas.web.options.indicators')) { this.showIndicators = true; } + } - /** init from url */ - this.isTimelineOpen = this.getParamValue('to') === 'true'; + /** init from url */ + this.isTimelineOpen = getParamValue('to') === 'true'; - let wasTabSelected = this.getParamValue('at') !== null; - this.analyticsService.tabChange.subscribe(tab => { - // If there is a change in the state of the analytics (open/close), resize - if (wasTabSelected !== (tab !== undefined)) { - this.adjustComponentsSize(); - wasTabSelected = (tab !== undefined); - } - this.updateTimelineLegendVisibility(); - }); - } + let wasTabSelected = getParamValue('at') !== null; + this.analyticsService.tabChange.subscribe(tab => { + // If there is a change in the state of the analytics (open/close), resize + if (wasTabSelected !== (tab !== undefined)) { + this.adjustComponentsSize(); + wasTabSelected = (tab !== undefined); + } + this.updateTimelineLegendVisibility(); + }); } public ngOnDestroy(): void { @@ -291,156 +256,26 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { this.version = environment.VERSION; } this.setAppTitle(); - if (this.arlasStartUpService.shouldRunApp && !this.arlasStartUpService.emptyMode) { + if (this.arlasStartupService.shouldRunApp && !this.arlasStartupService.emptyMode) { /** Retrieve displayable analytics */ const hiddenAnalyticsTabsSet = new Set(this.hiddenAnalyticsTabs); - const allAnalytics = this.arlasStartUpService.analytics; + const allAnalytics = this.arlasStartupService.analytics; this.analyticsService.initializeGroups(!!allAnalytics ? allAnalytics.filter(a => !hiddenAnalyticsTabsSet.has(a.tab)) : []); - /** Retrieve displayable resultlists */ - const hiddenListsTabsSet = new Set(this.hiddenResultlistTabs); - const allResultlists = this.configService.getValue('arlas.web.components.resultlists'); - const allContributors = this.configService.getValue('arlas.web.contributors'); - this.resultListsConfig = !!allResultlists ? allResultlists.filter(a => { - const contId = a.contributorId; - const tab = allContributors.find(c => c.identifier === contId).name; - return !hiddenListsTabsSet.has(tab); - }) : []; this.chipsSearchContributor = this.contributorService.getChipSearchContributor(); - const ids = new Set(this.resultListsConfig.map(c => c.contributorId)); - this.arlasStartUpService.contributorRegistry.forEach((v, k) => { - if (v instanceof ResultListContributor) { - v.updateData = ids.has(v.identifier); - this.resultlistContributors.push(v); - } - }); - this.resultlistService.setContributors(this.resultlistContributors, this.resultListsConfig); - - if (this.resultlistContributors.length > 0) { - this.rightListContributors = this.resultlistContributors - .filter(c => this.resultListsConfig.some((rc) => c.identifier === rc.contributorId)) - .map(rlcontrib => { - (rlcontrib as any).name = rlcontrib.getName(); - const sortColumn = rlcontrib.fieldsList.find(c => !!(c as any).sort && (c as any).sort !== ''); - if (!!sortColumn) { - this.resultlistService.sortOutput.set(rlcontrib.identifier, { - columnName: sortColumn.columnName, - fieldName: sortColumn.fieldName, - sortDirection: (sortColumn as any).sort === 'asc' ? SortEnum.asc : SortEnum.desc - }); - } - return rlcontrib; - }); - - this.resultListsConfig.forEach(rlConf => { - rlConf.input.cellBackgroundStyle = !!rlConf.input.cellBackgroundStyle ? - CellBackgroundStyleEnum[rlConf.input.cellBackgroundStyle] : undefined; - this.resultListConfigPerContId.set(rlConf.contributorId, rlConf.input); - }); - - // Check if the user can access process endpoint - const processSettings = this.settingsService.getProcessSettings(); - const externalNode = this.configService.getValue('arlas.web.externalNode'); - if ( - !!processSettings && !!processSettings.url - && !!externalNode && !!externalNode.download && externalNode.download === true - ) { - this.processService.check() - .pipe(takeUntil(this._onDestroy$)) - .subscribe({ - next: () => { - this.resultlistContributors.forEach(c => { - const listActionsId = c.actionToTriggerOnClick.map(a => a.id); - if (!listActionsId.includes('production')) { - c.addAction({ id: 'production', label: 'Download', cssClass: '', tooltip: 'Download' }); - const resultConfig = this.resultListConfigPerContId.get(c.identifier); - if (resultConfig) { - if (!resultConfig.globalActionsList) { - resultConfig.globalActionsList = []; - } - resultConfig.globalActionsList.push({ 'id': 'production', 'label': 'Download' }); - } - } - - }); - } - }); - } - - const selectedResultlistTab = this.getParamValue('rt'); - const previewListContrib = this.rightListContributors.find(r => r.getName() === decodeURI(selectedResultlistTab)); - if (previewListContrib) { - this.previewListContrib = previewListContrib; - } else { - this.previewListContrib = this.rightListContributors[0]; - } - } this.actionOnPopup .pipe(takeUntil(this._onDestroy$)) .subscribe(data => { const collection = data.action.collection; - const mapContributor = this.mapglContributors.filter(m => m.collection === collection)[0]; - const listContributor = this.resultlistContributors.filter(m => m.collection === collection)[0]; - this.actionOnItemEvent(data, mapContributor, listContributor, collection); + const mapContributor = this.mapService.mapContributors.filter(m => m.collection === collection)[0]; + const listContributor = this.resultlistService.resultlistContributors.filter(m => m.collection === collection)[0]; + this.resultlistService.actionOnItemEvent(data, mapContributor, listContributor, collection); }); this.collections = [...new Set(Array.from(this.collaborativeService.registry.values()).map(c => c.collection))]; - // Set MapContributors - const mapContributors = []; - this.contributorService.getMapContributors().forEach(mapContrib => { - mapContrib.colorGenerator = this.colorService.colorGenerator; - if (!!this.resultlistContributors) { - const resultlistContrbutor: ResultListContributor = this.resultlistContributors - .find(resultlistContrib => resultlistContrib.collection === mapContrib.collection); - if (!!resultlistContrbutor) { - mapContrib.searchSize = resultlistContrbutor.pageSize; - mapContrib.searchSort = resultlistContrbutor.sort; - } else { - mapContrib.searchSize = 50; - } - } - mapContributors.push(mapContrib); - }); - this.mapService.setContributors(mapContributors); - - zip(...this.collections.map(c => this.collaborativeService.describe(c))) - .pipe(takeUntil(this._onDestroy$)) - .subscribe(cdrs => { - cdrs.forEach(cdr => { - this.collectionToDescription.set(cdr.collection_name, cdr.params); - }); - this.resultlistService.setCollectionsDescription(this.collectionToDescription); - const bounds = (this.arlasMapComponent.mapglComponent?.map)?.getBounds(); - if (!!bounds) { - (this.arlasMapComponent.mapglComponent?.map).fitBounds(bounds, { duration: 0 }); - } - if (this.resultlistContributors.length > 0) { - this.resultlistContributors.forEach(c => c.sort = this.collectionToDescription.get(c.collection).id_path); - } - }); - - // Add actions to resultlist contributors - this.resultlistContributors.forEach(c => { - const listActionsId = c.actionToTriggerOnClick.map(a => a.id); - const mapcontributor = mapContributors.find(mc => mc.collection === c.collection); - if (!!mapcontributor && !listActionsId.includes('zoomToFeature')) { - c.addAction({ id: 'zoomToFeature', label: 'Zoom to', cssClass: '', tooltip: marker('Zoom to product') }); - } - if (!!this.resultListConfigPerContId.get(c.identifier)) { - if (!!this.resultListConfigPerContId.get(c.identifier).visualisationLink && !listActionsId.includes('visualize')) { - c.addAction({ id: 'visualize', label: 'Visualize', cssClass: '', tooltip: marker('Visualize on the map') }); - } - if (!!this.resultListConfigPerContId.get(c.identifier).downloadLink && !listActionsId.includes('download')) { - c.addAction({ id: 'download', label: 'Download', cssClass: '', tooltip: marker('Download') }); - } - } - - }); - this.declareResultlistExportCsv(); - - this.shortcuts = this.arlasStartUpService.filtersShortcuts; + this.shortcuts = this.arlasStartupService.filtersShortcuts; this.collaborativeService.ongoingSubscribe .pipe(takeUntil(this._onDestroy$)) @@ -463,69 +298,31 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { } } - public isElementInViewport(el: HTMLElement) { - if (el) { - const rect = el.getBoundingClientRect(); - return (rect.top >= 0 && - rect.left >= 0 && - rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && - rect.right <= (window.innerWidth || document.documentElement.clientWidth)); - } else { - return false; - } - } - - public declareResultlistExportCsv() { - if (this.settingsService.isResultListExportEnabled()) { - this.resultlistContributors.forEach(c => { - const resultConfig = this.resultListConfigPerContId.get(c.identifier); - if (resultConfig) { - if (!resultConfig.globalActionsList) { - resultConfig.globalActionsList = []; - } - resultConfig.globalActionsList.push({ 'id': 'export_csv', 'label': 'Export csv', 'alwaysEnabled': true }); - } - }); - } - } - public ngAfterViewInit(): void { - if (!this.arlasStartUpService.emptyMode) { + if (!this.arlasStartupService.emptyMode) { + const isListOpen = getParamValue('ro') === 'true'; + if (isListOpen) { + this.resultlistService.toggleList(); + } this.resizeCollectionCounts(); this.adjustVisibleShortcuts(); this.adjustComponentsSize(); + + // TODO: move it to list with an added output ? // Keep the last displayed list as preview when closing the right panel - if (!!this.tabsList) { - this.tabsList.selectedIndexChange + if (!!this.arlasListComponent.tabsList) { + this.arlasListComponent.tabsList.selectedIndexChange .pipe(takeUntil(this._onDestroy$)) .subscribe(index => { - this.resultlistService.selectedListTabIndex = index; - this.previewListContrib = this.resultlistContributors[index]; + this.resultlistService.selectList(index); const queryParams = Object.assign({}, this.activatedRoute.snapshot.queryParams); - queryParams['rt'] = this.previewListContrib.getName(); + queryParams['rt'] = this.resultlistService.previewListContrib.getName(); this.router.navigate([], { replaceUrl: true, queryParams: queryParams }); this.adjustGrids(); this.adjustComponentsSize(); }); } - - // TODO: remove when the resultlist is externalised - if (!!this.previewListContrib) { - timer(0, 200) - .pipe( - takeUntil(this._onDestroy$), - takeWhile(() => this.apploading)) - .subscribe(() => { - if (this.previewListContrib.data.length > 0 && - this.mapService.getMapConfig().mapLayers.events.onHover.filter(l => this.mapService.mapComponent?.map.getLayer(l)).length > 0) { - this.updateVisibleItems(); - this.apploading = false; - } - }); - } - this.resultlistService.setResultlistComponent(this.resultListComponent); - this.cdr.detectChanges(); } } @@ -536,198 +333,30 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { prefixTitle.concat(' - ').concat(this.appName)); } - /** - * Update which elements from the list are visible on the map - */ - public updateVisibleItems() { - if (this.previewListContrib && !!this.collectionToDescription.get(this.previewListContrib.collection)) { - const idFieldName = this.collectionToDescription.get(this.previewListContrib.collection).id_path; - const visibleItems = this.previewListContrib.data.map(i => (i.get(idFieldName) as number | string)) - .filter(i => i !== undefined && this.isElementInViewport(document.getElementById(i.toString()))); - this.mapService.updateMapStyle(visibleItems, this.previewListContrib.collection); - } - } - - public updateMapStyleFromScroll(items: Array, collection: string) { - this.mapService.updateMapStyle(items.map(i => i.identifier), collection); - } - - /** - * Updates features style on map after repopulating the resultlist with data - * @param items List of items constituting the resultlist - */ - public updateMapStyleFromChange(items: Array>, collection: string) { - if (this.collectionToDescription.size > 0) { - const idFieldName = this.collectionToDescription.get(collection).id_path; - setTimeout(() => { - const visibleItems = items.map(item => item.get(idFieldName)) - .filter(id => id !== undefined && this.isElementInViewport(document.getElementById(id.toString()))); - this.mapService.updateMapStyle(visibleItems, collection); - }, 200); - } - } - public consumeMenuEvents(states: MenuState) { this.menuState = states; } public zoomToData(collection: string): void { if (!this.mapSettingsService.mapContributors || this.mapSettingsService.mapContributors.length === 0) { - this.mapSettingsService.mapContributors = this.mapglContributors; + this.mapSettingsService.mapContributors = this.mapService.mapContributors; } const centroidPath = this.collectionToDescription.get(collection).centroid_path; this.toolkitMapService.zoomToData(collection, centroidPath, this.arlasMapComponent.mapglComponent.map, 0.2); } - - /** This method sorts the list on the given column. The features are also sorted if the `Simple mode` is activated in mapContributor */ - public sortColumnEvent(contributorId: string, sortOutput: Column) { - const resultlistContributor = (this.collaborativeService.registry.get(contributorId) as ResultListContributor); - this.resultlistService.isGeoSortActivated.set(contributorId, false); - /** Save the sorted column */ - this.resultlistService.sortOutput.set(contributorId, sortOutput); - /** Sort the list by the selected column and the id field name */ - resultlistContributor.sortColumn(sortOutput, true); - /** set mapcontritbutor sort */ - let sortOrder = null; - if (sortOutput.sortDirection.toString() === '0') { - sortOrder = ''; - } else if (sortOutput.sortDirection.toString() === '1') { - sortOrder = '-'; - } - let sort = ''; - if (sortOrder !== null) { - sort = sortOrder + sortOutput.fieldName; - } - - this.mapglContributors - .filter(c => c.collection === resultlistContributor.collection) - .forEach(c => { - // Could have some problems if we put 2 lists with the same collection and different sort ? - c.searchSort = resultlistContributor.sort; - c.searchSize = resultlistContributor.getConfigValue('search_size'); - /** Redraw features with setted sort in case of window mode */ - /** Remove old features */ - this.mapService.clearWindowData(c); - /** Set new features */ - c.drawGeoSearch(0, true); - }); - } - - /** - * Called at the end of scrolling the list - * @param contributor ResultlistContributor instance that fetches the data - * @param eventPaginate Which page is queried - */ - public paginate(contributor, eventPaginate: PageQuery): void { - contributor.getPage(eventPaginate.reference, eventPaginate.whichPage); - const sort = this.resultlistService.isGeoSortActivated.get(contributor.identifier) ? contributor.geoOrderSort : contributor.sort; - this.mapglContributors - .filter(c => c.collection === contributor.collection) - .forEach(c => c.getPage(eventPaginate.reference, sort, eventPaginate.whichPage, contributor.maxPages)); - } - public clickOnTile(item: Item) { - this.tabsList.realignInkBar(); - const config = this.resultListConfigPerContId.get(this.previewListContrib.identifier); - config.defautMode = this.modeEnum.grid; + this.arlasListComponent.tabsList.realignInkBar(); + const config = this.resultlistService.resultlistConfigPerContId.get(this.resultlistService.previewListContrib.identifier); + config.defautMode = ModeEnum.grid; config.selectedGridItem = item; config.isDetailledGridOpen = true; - this.resultListConfigPerContId.set(this.previewListContrib.identifier, config); - this.resultlistService.toggleList(); - setTimeout(() => this.timelineComponent.timelineHistogramComponent.resizeHistogram(), 100); - } - - public changeListResultMode(mode: ModeEnum, identifier: string) { - const config = this.resultListConfigPerContId.get(identifier); - config.defautMode = mode; - this.resultListConfigPerContId.set(identifier, config); - setTimeout(() => { - this.updateVisibleItems(); - }, 0); - } - - public getBoardEvents(event: { origin: string; event: string; data?: any; }) { - const resultListContributor = this.collaborativeService.registry.get(event.origin) as ResultListContributor; - const currentCollection = resultListContributor.collection; - const mapContributor: MapContributor = this.mapService.getContributorByCollection(currentCollection); - switch (event.event) { - case 'paginationEvent': - this.paginate(resultListContributor, event.data); - break; - case 'sortColumnEvent': - this.sortColumnEvent(event.origin, event.data); - break; - case 'consultedItemEvent': - if (!!mapContributor) { - const f = mapContributor.getFeatureToHightLight(event.data); - if (mapContributor) { - f.elementidentifier.idFieldName = f.elementidentifier.idFieldName.replace(/\./g, '_'); - } - this.mapService.featureToHightLight = f; - } - break; - case 'selectedItemsEvent': - const ids = event.data; - const idPath = this.collectionToDescription.get(currentCollection).id_path; - this.mapService.selectFeatures(idPath, ids, mapContributor); - break; - case 'actionOnItemEvent': - this.actionOnItemEvent(event.data, mapContributor, resultListContributor, currentCollection); - break; - case 'globalActionEvent': - if (event.data.id === 'production') { - const idsItemSelected: ElementIdentifier[] = this.mapService.mapComponent.featuresToSelect; - this.process(idsItemSelected.map(i => i.idValue), currentCollection); - } else if (event.data.id === 'export_csv') { - this.resultlistIsExporting = true; - this.exportService.fetchResultlistData$(resultListContributor, undefined) - .pipe(finalize(() => this.resultlistIsExporting = false)) - .subscribe({ - next: (h) => this.exportService.exportResultlist(resultListContributor, h), - error: (e) => this.snackbar.open(marker('An error occured exporting the list')) - }); - } - break; - case 'geoSortEvent': - break; - case 'geoAutoSortEvent': - this.onActiveOnGeosort(event.data, resultListContributor); - break; - } - this.actionOnList.next(event); - } - - public onActiveOnGeosort(data, resultListContributor: ResultListContributor): void { - this.resultlistService.isGeoSortActivated.set(resultListContributor.identifier, data); - const mapContributor = this.mapService.getContributorByCollection(resultListContributor.collection); - if (data) { - /** Apply geosort in list */ - const lat = this.mapService.centerLatLng.lat; - const lng = this.mapService.centerLatLng.lng; - resultListContributor.geoSort(lat, lng, true); - this.resultlistService.sortOutput.delete(resultListContributor.identifier); - // this.resultListComponent.columns.filter(c => !c.isIdField).forEach(c => c.sortDirection = SortEnum.none); - /** Apply geosort in map (for simple mode) */ - this.mapService.clearWindowData(mapContributor); - mapContributor.searchSort = resultListContributor.geoOrderSort; - mapContributor.searchSize = resultListContributor.pageSize; - mapContributor.drawGeoSearch(0, true); - } else { - const idFieldName = resultListContributor.getConfigValue('fieldsConfiguration')['idFieldName']; - this.resultlistService.sortOutput.set(resultListContributor.identifier, - { fieldName: idFieldName, sortDirection: SortEnum.none }); - /** Sort the list by the selected column and the id field name */ - resultListContributor.sortColumn({ fieldName: idFieldName, sortDirection: SortEnum.none }, true); - mapContributor.searchSort = resultListContributor.sort; - mapContributor.searchSize = resultListContributor.pageSize; - this.mapService.clearWindowData(mapContributor); - mapContributor.drawGeoSearch(0, true); - } + this.resultlistService.resultlistConfigPerContId.set(this.resultlistService.previewListContrib.identifier, config); + this.toggleList(); } public toggleList() { - this.tabsList.realignInkBar(); + this.arlasListComponent.tabsList.realignInkBar(); this.resultlistService.toggleList(); this.updateTimelineLegendVisibility(); this.adjustGrids(); @@ -790,10 +419,11 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { private adjustGrids() { if (!this.resultlistService.listOpen) { - const config = this.resultListConfigPerContId.get(this.previewListContrib.identifier); + const config = this.resultlistService.resultlistConfigPerContId.get(this.resultlistService.previewListContrib.identifier); config.isDetailledGridOpen = false; } else { - this.resultlistService.selectedListTabIndex = this.rightListContributors.indexOf(this.previewListContrib); + this.resultlistService.selectedListTabIndex = + this.resultlistService.rightListContributors.indexOf(this.resultlistService.previewListContrib); } } @@ -816,7 +446,7 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { this.mapService.mapComponent?.map?.resize(); this.mapService.adjustCoordinates(); - this.updateVisibleItems(); + this.resultlistService.updateVisibleItems(); }, 0); } @@ -837,8 +467,8 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { // The threshold is based on the window inner size and the available size for the shortcuts // The shortcuts are spaced on the left from the menu, and must not overflow on the legend on the right, // with a minimum spacing equal to the one on the left. - const previewListOpen = !!this.previewListContrib && !this.resultlistService.listOpen - && this.resultListConfigPerContId.get(this.previewListContrib.identifier)?.hasGridMode; + const previewListOpen = !!this.resultlistService.previewListContrib && !this.resultlistService.listOpen + && this.resultlistService.resultlistConfigPerContId.get(this.resultlistService.previewListContrib.identifier)?.hasGridMode; const mapActionsAndLegendWidth = 270; const leftMenuWidth = 48; this.showMoreShortcutsWidth = document.getElementById('extra-shortcuts-title').getBoundingClientRect().width; @@ -871,94 +501,5 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { this.showShortcuts = false; } - - private getParamValue(param: string): string { - let paramValue = null; - const url = window.location.href; - const regex = new RegExp('[?&]' + param + '(=([^&#]*)|&|#|$)'); - const results = regex.exec(url); - if (results && results[2]) { - paramValue = results[2]; - } - return paramValue; - } - - private actionOnItemEvent(data, mapContributor, listContributor, collection) { - switch (data.action.id) { - case 'zoomToFeature': - if (!!mapContributor) { - mapContributor.getBoundsToFit(data.elementidentifier, collection) - .subscribe(bounds => this.visualizeService.fitbounds = bounds); - } - break; - case 'visualize': - if (!!this.resultListConfigPerContId.get(listContributor.identifier)) { - const urlVisualisationTemplate = this.resultListConfigPerContId.get(listContributor.identifier).visualisationLink; - this.visualizeService.getVisuInfo(data.elementidentifier, collection, urlVisualisationTemplate).subscribe(url => { - this.visualizeService.displayDataOnMap(url, - data.elementidentifier, this.collectionToDescription.get(collection).geometry_path, - this.collectionToDescription.get(collection).centroid_path, collection); - }); - } - break; - case 'download': - if (!!this.resultListConfigPerContId.get(listContributor.identifier)) { - const urlDownloadTemplate = this.resultListConfigPerContId.get(listContributor.identifier).downloadLink; - if (urlDownloadTemplate) { - this.visualizeService.getVisuInfo(data.elementidentifier, collection, urlDownloadTemplate).subscribe(url => { - const win = window.open(url, '_blank'); - win.focus(); - }); - } - } - break; - case 'production': - this.process([data.elementidentifier.idValue], collection); - break; - } - } - - private process(ids: string[], collection: string) { - const maxItems = this.settingsService.getProcessSettings().max_items; - if (ids.length <= maxItems) { - this.processService.load().subscribe({ - next: () => { - this.processService.getItemsDetail( - this.collectionToDescription.get(collection).id_path, - ids, - this.processService.getProcessDescription().additionalParameters?.parameters, - collection - ).subscribe({ - next: (item: any) => { - this.downloadDialogRef = this.dialog.open(ProcessComponent, { minWidth: '520px', maxWidth: '60vw' }); - this.downloadDialogRef.componentInstance.nbProducts = ids.length; - this.downloadDialogRef.componentInstance.matchingAdditionalParams = item as Map; - this.downloadDialogRef.componentInstance.wktAoi = this.mapService.mapComponent.getAllPolygon('wkt'); - this.downloadDialogRef.componentInstance.ids = ids; - this.downloadDialogRef.componentInstance.collection = collection; - } - }); - } - }); - } else { - this.snackbar.open( - this.translate.instant('You have exceeded the number of products authorised for a single download') + ' (' + maxItems + ')', 'X', - { - horizontalPosition: 'center', - verticalPosition: 'top', - duration: 5000 - } - ); - } - } - - private waitFor(variable, callback) { - const interval = setInterval(() => { - if (variable !== undefined) { - clearInterval(interval); - callback(); - } - }, 100); - } } diff --git a/src/app/pipes/get-resultlist-config.pipe.ts b/src/app/pipes/get-resultlist-config.pipe.ts new file mode 100644 index 00000000..0c293a31 --- /dev/null +++ b/src/app/pipes/get-resultlist-config.pipe.ts @@ -0,0 +1,18 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { ResultlistService } from 'app/services/resultlist.service'; +import { ResultListContributor } from 'arlas-web-contributors'; + +@Pipe({ + name: 'getResultlistConfig' +}) +export class GetResultlistConfigPipe implements PipeTransform { + + public constructor(private resultlistService: ResultlistService) { } + + public transform(resultlistContributor: ResultListContributor): any { + // console.log(resultlistContributor.identifier); + // console.log(this.resultlistService.resultlistConfigPerContId.get(resultlistContributor.identifier)); + return this.resultlistService.resultlistConfigPerContId.get(resultlistContributor.identifier); + } + +} diff --git a/src/app/services/resultlist.service.ts b/src/app/services/resultlist.service.ts index b0c52cc7..7e8edbbc 100644 --- a/src/app/services/resultlist.service.ts +++ b/src/app/services/resultlist.service.ts @@ -1,11 +1,42 @@ +/* + * Licensed to Gisaïa under one or more contributor + * license agreements. See the NOTICE.txt file distributed with + * this work for additional information regarding copyright + * ownership. Gisaïa licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + import { Injectable } from '@angular/core'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; import { ActivatedRoute, Router } from '@angular/router'; +import { marker } from '@biesbjerg/ngx-translate-extract-marker'; +import { TranslateService } from '@ngx-translate/core'; +import { isElementInViewport } from 'app/tools/utils'; import { CollectionReferenceParameters } from 'arlas-api'; -import { CellBackgroundStyleEnum, ModeEnum, ResultListComponent, SortEnum } from 'arlas-web-components'; +import { + CellBackgroundStyleEnum, Column, ElementIdentifier, Item, ModeEnum, PageQuery, + SortEnum +} from 'arlas-web-components'; import { MapContributor, ResultListContributor } from 'arlas-web-contributors'; -import { ArlasCollaborativesearchService, getParamValue } from 'arlas-wui-toolkit'; -import { BehaviorSubject } from 'rxjs'; +import { + ArlasCollaborativesearchService, ArlasConfigService, ArlasExportCsvService, ArlasSettingsService, + getParamValue, ProcessComponent, ProcessService +} from 'arlas-wui-toolkit'; +import { BehaviorSubject, finalize, Subject } from 'rxjs'; import { MapService } from './map.service'; +import { VisualizeService } from './visualize.service'; @Injectable({ @@ -13,38 +44,46 @@ import { MapService } from './map.service'; }) export class ResultlistService { - public resultlistContributors: Array = new Array(); + /** Resultlist configs */ public resultlistConfigs = []; public resultlistConfigPerContId = new Map(); - public previewlistContrib: ResultListContributor = null; public collectionToDescription = new Map(); + + /** Resultlist contributors */ + public resultlistContributors: Array = new Array(); + public rightListContributors: Array = new Array(); + public previewListContrib: ResultListContributor = null; + + /** Resultlist state */ public isGeoSortActivated = new Map(); public sortOutput = new Map(); - - public resultListComponent: ResultListComponent; public selectedListTabIndex = 0; public listOpen = false; - private currentClickedFeatureId: string = undefined; + public resultlistIsExporting = false; + public actionOnList = new Subject<{ origin: string; event: string; data?: any; }>(); + + /* Process */ + private downloadDialogRef: MatDialogRef; public constructor( private activatedRoute: ActivatedRoute, private router: Router, private mapService: MapService, - private collaborativeService: ArlasCollaborativesearchService - ) { - const resultlistOpenString = getParamValue('ro'); - if (resultlistOpenString) { - this.listOpen = (resultlistOpenString === 'true'); - } - } - - public setResultlistComponent(c: ResultListComponent) { - this.resultListComponent = c; - } + private collaborativeService: ArlasCollaborativesearchService, + private settingsService: ArlasSettingsService, + private configService: ArlasConfigService, + private processService: ProcessService, + private exportService: ArlasExportCsvService, + private snackbar: MatSnackBar, + private visualizeService: VisualizeService, + private translate: TranslateService, + private dialog: MatDialog + ) { } public setContributors(resultlistContributors: Array, resultlistConfigs: string[]) { this.resultlistContributors = resultlistContributors; + if (this.resultlistContributors.length > 0) { this.resultlistConfigs = resultlistConfigs; @@ -53,6 +92,33 @@ export class ResultlistService { CellBackgroundStyleEnum[rlConf.input.cellBackgroundStyle] : undefined; this.resultlistConfigPerContId.set(rlConf.contributorId, rlConf.input); }); + + this.rightListContributors = this.resultlistContributors + .filter(c => this.resultlistConfigs.some((rc) => c.identifier === rc.contributorId)) + .map(rlcontrib => { + (rlcontrib as any).name = rlcontrib.getName(); + // TODO: update c type + const sortColumn = rlcontrib.fieldsList.find(c => !!(c as any).sort && (c as any).sort !== ''); + if (!!sortColumn) { + this.sortOutput.set(rlcontrib.identifier, { + columnName: sortColumn.columnName, + fieldName: sortColumn.fieldName, + sortDirection: (sortColumn as any).sort === 'asc' ? SortEnum.asc : SortEnum.desc + }); + } + return rlcontrib; + }); + + const selectedResultlistTab = getParamValue('rt'); + const listIdx = this.rightListContributors.findIndex(r => r.getName() === decodeURI(selectedResultlistTab)); + if (listIdx >= 0) { + this.selectList(listIdx); + } else { + this.selectList(0); + } + + this.addActions(); + this.declareResultlistExportCsv(); } } @@ -67,10 +133,19 @@ export class ResultlistService { this.router.navigate([], { replaceUrl: true, queryParams: queryParams }); } + public selectList(index: number) { + this.selectedListTabIndex = index; + this.previewListContrib = this.resultlistContributors[index]; + } + public isThumbnailProtected(): boolean { return this.resultlistContributors[this.selectedListTabIndex].fieldsConfiguration?.useHttpThumbnails ?? false; } + public updateMapStyleFromScroll(items: Array, collection: string) { + this.mapService.updateMapStyle(items.map(i => i.identifier), collection); + } + public applyMapExtent(pwithinRaw: string, pwithin: string) { this.resultlistContributors .forEach(c => { @@ -110,10 +185,38 @@ export class ResultlistService { }); } + /** + * Update which elements from the list are visible on the map + */ + public updateVisibleItems() { + if (this.previewListContrib && !!this.collectionToDescription.get(this.previewListContrib.collection)) { + const idFieldName = this.collectionToDescription.get(this.previewListContrib.collection).id_path; + const visibleItems = this.previewListContrib.data.map(i => (i.get(idFieldName) as number | string)) + .filter(i => i !== undefined && isElementInViewport(document.getElementById(i.toString()))); + this.mapService.updateMapStyle(visibleItems, this.previewListContrib.collection); + } + } + + /** + * Updates features style on map after repopulating the resultlist with data + * @param items List of items constituting the resultlist + */ + public updateMapStyleFromChange(items: Array>, collection: string) { + if (this.collectionToDescription.size > 0) { + const idFieldName = this.collectionToDescription.get(collection).id_path; + setTimeout(() => { + const visibleItems = items.map(item => item.get(idFieldName)) + .filter(id => id !== undefined && isElementInViewport(document.getElementById(id.toString()))); + this.mapService.updateMapStyle(visibleItems, collection); + }, 200); + } + } + public openDetail$(id: any): BehaviorSubject { const isOpen = new BehaviorSubject(false); // If does not work add a variable ? - const isListMode = this.resultListComponent.resultMode === ModeEnum.list; + const listConfig = this.resultlistConfigPerContId.get(this.previewListContrib.identifier); + const isListMode = listConfig.defautMode === ModeEnum.list; if (isListMode) { const detailListButton = document.getElementById('open-detail-' + id); if (!!detailListButton) { @@ -130,7 +233,7 @@ export class ResultlistService { } } else { const productTile = document.getElementById('grid-tile-' + id); - const isDetailledGridOpen = this.resultListComponent.isDetailledGridOpen; + const isDetailledGridOpen = listConfig.isDetailledGridOpen; if (!!productTile) { productTile.click(); if (!isDetailledGridOpen) { @@ -162,4 +265,263 @@ export class ResultlistService { return isOpen; } } + + public onActiveOnGeosort(data, resultListContributor: ResultListContributor): void { + this.isGeoSortActivated.set(resultListContributor.identifier, data); + const mapContributor = this.mapService.getContributorByCollection(resultListContributor.collection); + if (data) { + /** Apply geosort in list */ + const lat = this.mapService.centerLatLng.lat; + const lng = this.mapService.centerLatLng.lng; + resultListContributor.geoSort(lat, lng, true); + this.sortOutput.delete(resultListContributor.identifier); + // TODO: why commented + // this.resultListComponent.columns.filter(c => !c.isIdField).forEach(c => c.sortDirection = SortEnum.none); + /** Apply geosort in map (for simple mode) */ + this.mapService.clearWindowData(mapContributor); + mapContributor.searchSort = resultListContributor.geoOrderSort; + mapContributor.searchSize = resultListContributor.pageSize; + mapContributor.drawGeoSearch(0, true); + } else { + const idFieldName = resultListContributor.getConfigValue('fieldsConfiguration')['idFieldName']; + this.sortOutput.set(resultListContributor.identifier, + { fieldName: idFieldName, sortDirection: SortEnum.none }); + /** Sort the list by the selected column and the id field name */ + resultListContributor.sortColumn({ fieldName: idFieldName, sortDirection: SortEnum.none }, true); + mapContributor.searchSort = resultListContributor.sort; + mapContributor.searchSize = resultListContributor.pageSize; + this.mapService.clearWindowData(mapContributor); + mapContributor.drawGeoSearch(0, true); + } + } + + public getBoardEvents(event: { origin: string; event: string; data?: any; }) { + const resultListContributor = this.collaborativeService.registry.get(event.origin) as ResultListContributor; + const currentCollection = resultListContributor.collection; + const mapContributor = this.mapService.getContributorByCollection(currentCollection); + switch (event.event) { + case 'paginationEvent': + this.paginate(resultListContributor, event.data); + break; + case 'sortColumnEvent': + this.sortColumnEvent(event.origin, event.data); + break; + case 'consultedItemEvent': + if (!!mapContributor) { + const f = mapContributor.getFeatureToHightLight(event.data); + if (mapContributor) { + f.elementidentifier.idFieldName = f.elementidentifier.idFieldName.replace(/\./g, '_'); + } + this.mapService.featureToHightLight = f; + } + break; + case 'selectedItemsEvent': + const ids = event.data; + const idPath = this.collectionToDescription.get(currentCollection)?.id_path; + if (!!idPath) { + this.mapService.selectFeatures(idPath, ids, mapContributor); + } + break; + case 'actionOnItemEvent': + this.actionOnItemEvent(event.data, mapContributor, resultListContributor, currentCollection); + break; + case 'globalActionEvent': + if (event.data.id === 'production') { + const idsItemSelected: ElementIdentifier[] = this.mapService.mapComponent.featuresToSelect; + this.process(idsItemSelected.map(i => i.idValue), currentCollection); + } else if (event.data.id === 'export_csv') { + this.resultlistIsExporting = true; + this.exportService.fetchResultlistData$(resultListContributor, undefined) + .pipe(finalize(() => this.resultlistIsExporting = false)) + .subscribe({ + next: (h) => this.exportService.exportResultlist(resultListContributor, h), + error: (e) => this.snackbar.open(marker('An error occured exporting the list')) + }); + } + break; + case 'geoSortEvent': + break; + case 'geoAutoSortEvent': + this.onActiveOnGeosort(event.data, resultListContributor); + break; + } + this.actionOnList.next(event); + } + + /** + * Called at the end of scrolling the list + * @param contributor ResultlistContributor instance that fetches the data + * @param eventPaginate Which page is queried + */ + private paginate(contributor: ResultListContributor, eventPaginate: PageQuery): void { + contributor.getPage(eventPaginate.reference, eventPaginate.whichPage); + const sort = this.isGeoSortActivated.get(contributor.identifier) ? contributor.geoOrderSort : contributor.sort; + this.mapService.mapContributors + .filter(c => c.collection === contributor.collection) + .forEach(c => c.getPage(eventPaginate.reference, sort, eventPaginate.whichPage, contributor.maxPages)); + } + + public actionOnItemEvent(data, mapContributor, listContributor, collection) { + switch (data.action.id) { + case 'zoomToFeature': + if (!!mapContributor) { + mapContributor.getBoundsToFit(data.elementidentifier, collection) + .subscribe(bounds => this.visualizeService.fitbounds = bounds); + } + break; + case 'visualize': + if (!!this.resultlistConfigPerContId.get(listContributor.identifier)) { + const urlVisualisationTemplate = this.resultlistConfigPerContId.get(listContributor.identifier).visualisationLink; + this.visualizeService.getVisuInfo(data.elementidentifier, collection, urlVisualisationTemplate).subscribe(url => { + this.visualizeService.displayDataOnMap(url, + data.elementidentifier, this.collectionToDescription.get(collection).geometry_path, + this.collectionToDescription.get(collection).centroid_path, collection); + }); + } + break; + case 'download': + if (!!this.resultlistConfigPerContId.get(listContributor.identifier)) { + const urlDownloadTemplate = this.resultlistConfigPerContId.get(listContributor.identifier).downloadLink; + if (urlDownloadTemplate) { + this.visualizeService.getVisuInfo(data.elementidentifier, collection, urlDownloadTemplate).subscribe(url => { + const win = window.open(url, '_blank'); + win.focus(); + }); + } + } + break; + case 'production': + this.process([data.elementidentifier.idValue], collection); + break; + } + } + + private process(ids: string[], collection: string) { + const maxItems = this.settingsService.getProcessSettings().max_items; + if (ids.length <= maxItems) { + this.processService.load().subscribe({ + next: () => { + this.processService.getItemsDetail( + this.collectionToDescription.get(collection).id_path, + ids, + this.processService.getProcessDescription().additionalParameters?.parameters, + collection + ).subscribe({ + next: (item: any) => { + this.downloadDialogRef = this.dialog.open(ProcessComponent, { minWidth: '520px', maxWidth: '60vw' }); + this.downloadDialogRef.componentInstance.nbProducts = ids.length; + this.downloadDialogRef.componentInstance.matchingAdditionalParams = item as Map; + this.downloadDialogRef.componentInstance.wktAoi = this.mapService.mapComponent.getAllPolygon('wkt'); + this.downloadDialogRef.componentInstance.ids = ids; + this.downloadDialogRef.componentInstance.collection = collection; + } + }); + } + }); + } else { + this.snackbar.open( + this.translate.instant('You have exceeded the number of products authorised for a single download') + ' (' + maxItems + ')', 'X', + { + horizontalPosition: 'center', + verticalPosition: 'top', + duration: 5000 + } + ); + } + } + + /** This method sorts the list on the given column. The features are also sorted if the `Simple mode` is activated in mapContributor */ + private sortColumnEvent(contributorId: string, sortOutput: Column) { + const resultlistContributor = (this.collaborativeService.registry.get(contributorId) as ResultListContributor); + this.isGeoSortActivated.set(contributorId, false); + /** Save the sorted column */ + this.sortOutput.set(contributorId, sortOutput); + /** Sort the list by the selected column and the id field name */ + resultlistContributor.sortColumn(sortOutput, true); + + // TODO: why here when does nothing + /** set mapcontritbutor sort */ + let sortOrder = null; + if (sortOutput.sortDirection.toString() === '0') { + sortOrder = ''; + } else if (sortOutput.sortDirection.toString() === '1') { + sortOrder = '-'; + } + let sort = ''; + if (sortOrder !== null) { + sort = sortOrder + sortOutput.fieldName; + } + + this.mapService.mapContributors + .filter(c => c.collection === resultlistContributor.collection) + .forEach(c => { + // Could have some problems if we put 2 lists with the same collection and different sort ? + c.searchSort = resultlistContributor.sort; + c.searchSize = resultlistContributor.getConfigValue('search_size'); + /** Redraw features with setted sort in case of window mode */ + /** Remove old features */ + this.mapService.clearWindowData(c); + /** Set new features */ + c.drawGeoSearch(0, true); + }); + } + + private addActions() { + this.resultlistContributors.forEach(c => { + const listActionsId = c.actionToTriggerOnClick.map(a => a.id); + const mapcontributor = this.mapService.mapContributors.find(mc => mc.collection === c.collection); + if (!!mapcontributor && !listActionsId.includes('zoomToFeature')) { + c.addAction({ id: 'zoomToFeature', label: 'Zoom to', cssClass: '', tooltip: marker('Zoom to product') }); + } + if (!!this.resultlistConfigPerContId.get(c.identifier)) { + if (!!this.resultlistConfigPerContId.get(c.identifier).visualisationLink && !listActionsId.includes('visualize')) { + c.addAction({ id: 'visualize', label: 'Visualize', cssClass: '', tooltip: marker('Visualize on the map') }); + } + if (!!this.resultlistConfigPerContId.get(c.identifier).downloadLink && !listActionsId.includes('download')) { + c.addAction({ id: 'download', label: 'Download', cssClass: '', tooltip: marker('Download') }); + } + } + }); + + // Check if the user can access process endpoint + const processSettings = this.settingsService.getProcessSettings(); + const externalNode = this.configService.getValue('arlas.web.externalNode'); + if (!!processSettings && !!processSettings.url + && !!externalNode && !!externalNode.download && externalNode.download === true) { + + this.processService.check() + .subscribe({ + next: () => { + this.resultlistContributors.forEach(c => { + const listActionsId = c.actionToTriggerOnClick.map(a => a.id); + if (!listActionsId.includes('production')) { + c.addAction({ id: 'production', label: 'Download', cssClass: '', tooltip: marker('Download') }); + const resultConfig = this.resultlistConfigPerContId.get(c.identifier); + if (resultConfig) { + if (!resultConfig.globalActionsList) { + resultConfig.globalActionsList = []; + } + resultConfig.globalActionsList.push({ 'id': 'production', 'label': 'Download' }); + } + } + + }); + } + }); + } + } + + private declareResultlistExportCsv() { + if (this.settingsService.isResultListExportEnabled()) { + this.resultlistContributors.forEach(c => { + const resultConfig = this.resultlistConfigPerContId.get(c.identifier); + if (resultConfig) { + if (!resultConfig.globalActionsList) { + resultConfig.globalActionsList = []; + } + resultConfig.globalActionsList.push({ 'id': 'export_csv', 'label': 'Export csv', 'alwaysEnabled': true }); + } + }); + } + } } diff --git a/src/app/tools/utils.ts b/src/app/tools/utils.ts new file mode 100644 index 00000000..2ed32fc8 --- /dev/null +++ b/src/app/tools/utils.ts @@ -0,0 +1,30 @@ +/* + * Licensed to Gisaïa under one or more contributor + * license agreements. See the NOTICE.txt file distributed with + * this work for additional information regarding copyright + * ownership. Gisaïa licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export function isElementInViewport(el: HTMLElement) { + if (el) { + const rect = el.getBoundingClientRect(); + return (rect.top >= 0 && + rect.left >= 0 && + rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && + rect.right <= (window.innerWidth || document.documentElement.clientWidth)); + } else { + return false; + } +} diff --git a/src/styles.scss b/src/styles.scss index 9072f646..9458ff81 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -106,23 +106,19 @@ button:focus { } -.side-result-list-preview table.resultlist>tbody { - height: calc(100vh - $top-menu-height) !important; -} - -.side-result-list-preview table.resultlist>thead { - display: none; -} - -.side-result-list-preview table.resultlist .resultgrid__icon_check { - display: none; -} +.resultlist__preview { + table.resultlist>tbody { + height: calc(100vh - $top-menu-height) !important; + } -.side-result-list-preview table.resultlist .resultlist-thumbnails-fit { - display: none; + table.resultlist>thead, + table.resultlist .resultgrid__icon_check, + table.resultlist .resultlist-thumbnails-fit { + display: none; + } } -.side-result-list .resultgrid__title-highlight- { +.resultlist__wrapper .resultgrid__title-highlight- { margin: unset !important; display: flex; padding: 0 5px; diff --git a/src/styles/resultlist.css b/src/styles/resultlist.css index 5158ad49..96cec5ca 100644 --- a/src/styles/resultlist.css +++ b/src/styles/resultlist.css @@ -27,23 +27,23 @@ } .analytics-board-content .group-item-title, -.side-result-list .group-item-title { +.resultlist__wrapper .group-item-title { color: #656565 !important; } .analytics-board-content .detail-item, -.side-result-list .detail-item { +.resultlist__wrapper .detail-item { color: #ff4081 !important; display: inline-block; } .analytics-board-content .resultdetaileditem-key, -.side-result-list .resultdetaileditem-key { +.resultlist__wrapper .resultdetaileditem-key { color: #656565; } .analytics-board-content .resultdetaileditem-value, -.side-result-list .resultdetaileditem-value { +.resultlist__wrapper .resultdetaileditem-value { font-family: "Roboto", "Helvetica Neue", sans-serif; font-size: 0.9em; font-weight: normal; From 719888bd5f3fde49a73e87df68da1469082ee179 Mon Sep 17 00:00:00 2001 From: QuCMGisaia Date: Fri, 12 Jul 2024 09:42:24 +0200 Subject: [PATCH 03/19] fix: karma tests --- package-lock.json | 602 ++++++++++++++++-- package.json | 1 + src/app/app.component.spec.ts | 90 ++- src/app/app.component.ts | 11 +- src/app/app.module.ts | 14 +- .../arlas-wui-root.component.html | 5 +- .../arlas-wui-root.component.spec.ts | 81 ++- .../arlas-wui-root.component.ts | 10 +- .../left-menu/left-menu.component.html | 8 +- .../left-menu/left-menu.component.ts | 24 +- src/app/pipes/get-resultlist-config.pipe.ts | 4 +- src/app/services/contributors.service.ts | 13 - src/app/services/resultlist.service.spec.ts | 18 +- src/public-api.ts | 19 +- src/test.ts | 23 +- 15 files changed, 756 insertions(+), 167 deletions(-) diff --git a/package-lock.json b/package-lock.json index 11bc0320..e94377fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "@typescript-eslint/eslint-plugin-tslint": "^5.6.0", "@typescript-eslint/parser": "5.3.0", "eslint": "^8.2.0", + "jasmine": "^5.1.0", "jasmine-core": "~3.10.0", "jasmine-spec-reporter": "~4.2.1", "karma": "^6.3.14", @@ -3167,6 +3168,50 @@ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -3586,6 +3631,16 @@ "typescript": "^3 || ^4" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@rollup/plugin-json": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz", @@ -9875,6 +9930,34 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreground-child": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -11559,15 +11642,29 @@ "resolved": "https://registry.npmjs.org/iv-viewer/-/iv-viewer-2.0.1.tgz", "integrity": "sha512-dPwFs3fHGJAwE9APaJB3UpEaxoTAG6N1wraKv48SjDvOP+CMzUmFbW+TWpyKVSpjRmpCsMdCoBFiLRAFsTkulA==" }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jasmine": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", - "integrity": "sha512-KbdGQTf5jbZgltoHs31XGiChAPumMSY64OZMWLNYnEnMfG5uwGBhffePwuskexjT+/Jea/gU3qAU8344hNohSw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-5.1.0.tgz", + "integrity": "sha512-prmJlC1dbLhti4nE4XAPDWmfJesYO15sjGXVp7Cs7Ym5I9Xtwa/hUHxxJXjnpfLO72+ySttA0Ztf8g/RiVnUKw==", "dev": true, "dependencies": { - "exit": "^0.1.2", - "glob": "^7.0.6", - "jasmine-core": "~2.8.0" + "glob": "^10.2.2", + "jasmine-core": "~5.1.0" }, "bin": { "jasmine": "bin/jasmine.js" @@ -11588,52 +11685,54 @@ "colors": "1.1.2" } }, - "node_modules/jasmine/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/jasmine/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": "*" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/jasmine/node_modules/jasmine-core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", - "integrity": "sha512-SNkOkS+/jMZvLhuSx1fjhcNWUC/KG6oVyFUGkSBEr9n1axSNduWU8GlI7suaHXr4yxjet6KjrUZxUTE5WzzWwQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.1.2.tgz", + "integrity": "sha512-2oIUMGn00FdUiqz6epiiJr7xcFyNYj3rDcfmnzfkBnHyBQ3cBQUs4mmyGsOb7TTLb9kxk7dBcmEmqhDKkBoDyA==", "dev": true }, "node_modules/jasmine/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jasmine/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" } }, "node_modules/jasminewd2": { @@ -13852,6 +13951,12 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true + }, "node_modules/pacote": { "version": "13.6.2", "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.6.2.tgz", @@ -14294,6 +14399,37 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -15338,6 +15474,26 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/protractor/node_modules/jasmine": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha512-KbdGQTf5jbZgltoHs31XGiChAPumMSY64OZMWLNYnEnMfG5uwGBhffePwuskexjT+/Jea/gU3qAU8344hNohSw==", + "dev": true, + "dependencies": { + "exit": "^0.1.2", + "glob": "^7.0.6", + "jasmine-core": "~2.8.0" + }, + "bin": { + "jasmine": "bin/jasmine.js" + } + }, + "node_modules/protractor/node_modules/jasmine-core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", + "integrity": "sha512-SNkOkS+/jMZvLhuSx1fjhcNWUC/KG6oVyFUGkSBEr9n1axSNduWU8GlI7suaHXr4yxjet6KjrUZxUTE5WzzWwQ==", + "dev": true + }, "node_modules/protractor/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -17179,6 +17335,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "node_modules/string-width/node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", @@ -17215,6 +17392,28 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -19049,6 +19248,77 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", @@ -21361,6 +21631,37 @@ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, + "@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "requires": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -21691,6 +21992,13 @@ "esquery": "^1.0.1" } }, + "@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true + }, "@rollup/plugin-json": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz", @@ -26375,6 +26683,24 @@ "is-callable": "^1.1.3" } }, + "foreground-child": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "dependencies": { + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + } + } + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -27614,55 +27940,60 @@ "resolved": "https://registry.npmjs.org/iv-viewer/-/iv-viewer-2.0.1.tgz", "integrity": "sha512-dPwFs3fHGJAwE9APaJB3UpEaxoTAG6N1wraKv48SjDvOP+CMzUmFbW+TWpyKVSpjRmpCsMdCoBFiLRAFsTkulA==" }, + "jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, "jasmine": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", - "integrity": "sha512-KbdGQTf5jbZgltoHs31XGiChAPumMSY64OZMWLNYnEnMfG5uwGBhffePwuskexjT+/Jea/gU3qAU8344hNohSw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-5.1.0.tgz", + "integrity": "sha512-prmJlC1dbLhti4nE4XAPDWmfJesYO15sjGXVp7Cs7Ym5I9Xtwa/hUHxxJXjnpfLO72+ySttA0Ztf8g/RiVnUKw==", "dev": true, "requires": { - "exit": "^0.1.2", - "glob": "^7.0.6", - "jasmine-core": "~2.8.0" + "glob": "^10.2.2", + "jasmine-core": "~5.1.0" }, "dependencies": { - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" } }, "jasmine-core": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", - "integrity": "sha512-SNkOkS+/jMZvLhuSx1fjhcNWUC/KG6oVyFUGkSBEr9n1axSNduWU8GlI7suaHXr4yxjet6KjrUZxUTE5WzzWwQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.1.2.tgz", + "integrity": "sha512-2oIUMGn00FdUiqz6epiiJr7xcFyNYj3rDcfmnzfkBnHyBQ3cBQUs4mmyGsOb7TTLb9kxk7dBcmEmqhDKkBoDyA==", "dev": true }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" } + }, + "minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true } } }, @@ -29362,6 +29693,12 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, + "package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true + }, "pacote": { "version": "13.6.2", "resolved": "https://registry.npmjs.org/pacote/-/pacote-13.6.2.tgz", @@ -29711,6 +30048,30 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "requires": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true + } + } + }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -30369,6 +30730,23 @@ "path-is-absolute": "^1.0.0" } }, + "jasmine": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz", + "integrity": "sha512-KbdGQTf5jbZgltoHs31XGiChAPumMSY64OZMWLNYnEnMfG5uwGBhffePwuskexjT+/Jea/gU3qAU8344hNohSw==", + "dev": true, + "requires": { + "exit": "^0.1.2", + "glob": "^7.0.6", + "jasmine-core": "~2.8.0" + } + }, + "jasmine-core": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz", + "integrity": "sha512-SNkOkS+/jMZvLhuSx1fjhcNWUC/KG6oVyFUGkSBEr9n1axSNduWU8GlI7suaHXr4yxjet6KjrUZxUTE5WzzWwQ==", + "dev": true + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -31825,6 +32203,25 @@ } } }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + } + } + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -31840,6 +32237,23 @@ } } }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + } + } + }, "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -33206,6 +33620,60 @@ } } }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 825a8fb2..5af141eb 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "@typescript-eslint/eslint-plugin-tslint": "^5.6.0", "@typescript-eslint/parser": "5.3.0", "eslint": "^8.2.0", + "jasmine": "^5.1.0", "jasmine-core": "~3.10.0", "jasmine-spec-reporter": "~4.2.1", "karma": "^6.3.14", diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 9fb6ae76..5bd440cb 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -17,31 +17,101 @@ * under the License. */ -import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { Dialog, DIALOG_SCROLL_STRATEGY } from '@angular/cdk/dialog'; +import { HttpClientModule } from '@angular/common/http'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MAT_DIALOG_SCROLL_STRATEGY, MatDialog } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { RouterTestingModule } from '@angular/router/testing'; +import { TranslateFakeLoader, TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { ArlasColorService, ColorGeneratorLoader } from 'arlas-web-components'; +import { ArlasCollaborativesearchService, ArlasConfigService, ArlasSettingsService, ArlasStartupService } from 'arlas-wui-toolkit'; +import { of } from 'rxjs'; import { ArlasWuiComponent } from './app.component'; +import { ContributorService } from './services/contributors.service'; +import { MapService } from './services/map.service'; +import { ResultlistService } from './services/resultlist.service'; +import { VisualizeService } from './services/visualize.service'; describe('ArlasWuiComponent', () => { let component: ArlasWuiComponent; let fixture: ComponentFixture; - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ + beforeEach(async () => { + const mockArlasStartupService = jasmine.createSpyObj('ArlasStartupService', [], { + shouldRunApp: true, + emptyMode: false, + contributorRegistry: new Map() + }); - ], - declarations: [ + const mockSettingsService = jasmine.createSpyObj('ArlasSettingsService', ['getHistogramMaxBucket']); + mockSettingsService.getHistogramMaxBucket.and.returnValue(); + + const mockContributorService = jasmine.createSpyObj('ContributorService', ['getMapContributors']); + mockContributorService.getMapContributors.and.returnValue([]); + + const mockColorGeneratorLoader = jasmine.createSpyObj('ColorGeneratorLoader', [], { + changekeysToColors$: of() + }); + + await TestBed.configureTestingModule({ + declarations: [ArlasWuiComponent], + imports: [ + TranslateModule.forRoot({ + loader: { provide: TranslateLoader, useClass: TranslateFakeLoader } + }), + MatTooltipModule, + /** Needed for ResultlistService */ + RouterTestingModule, + HttpClientModule + /** End */ ], providers: [ + ResultlistService, + MapService, + ArlasColorService, + ArlasCollaborativesearchService, + ArlasConfigService, + { + provide: ArlasStartupService, + useValue: mockArlasStartupService + }, + { + provide: ArlasSettingsService, + useValue: mockSettingsService + }, + { + provide: ContributorService, + useValue: mockContributorService + }, + /** Needed for ResultlistService */ + MatSnackBar, + VisualizeService, + MatDialog, + { + provide: MAT_DIALOG_SCROLL_STRATEGY, + useValue: {} + }, + Dialog, + { + provide: DIALOG_SCROLL_STRATEGY, + useValue: {} + }, + /** End */ + { + provide: ColorGeneratorLoader, + useValue: mockColorGeneratorLoader + } ] }).compileComponents(); + }); - })); - - beforeEach(async(() => { + beforeEach(() => { fixture = TestBed.createComponent(ArlasWuiComponent); component = fixture.componentInstance; fixture.detectChanges(); - })); + }); it('should create', () => { expect(component).toBeTruthy(); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index cc224721..e47bc48e 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -18,13 +18,13 @@ */ import { Component, Input, OnInit } from '@angular/core'; import { CollectionReferenceParameters } from 'arlas-api'; +import { ArlasColorService } from 'arlas-web-components'; import { ResultListContributor } from 'arlas-web-contributors'; import { ArlasCollaborativesearchService, ArlasConfigService, ArlasStartupService } from 'arlas-wui-toolkit'; -import { ResultlistService } from './services/resultlist.service'; -import { ContributorService } from 'public-api'; -import { MapService } from './services/map.service'; -import { ArlasColorService } from 'arlas-web-components'; import { Subject, takeUntil, zip } from 'rxjs'; +import { ContributorService } from './services/contributors.service'; +import { MapService } from './services/map.service'; +import { ResultlistService } from './services/resultlist.service'; @Component({ selector: 'arlas-root', @@ -119,7 +119,4 @@ export class ArlasWuiComponent implements OnInit { }); } } - } - - diff --git a/src/app/app.module.ts b/src/app/app.module.ts index b02b2ad8..b19ff485 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -79,6 +79,8 @@ import { LAZYLOAD_IMAGE_HOOKS, LazyLoadImageModule } from 'ng-lazyload-image'; import { ArlasMapComponent } from './components/arlas-map/arlas-map.component'; import { ArlasListComponent } from './components/arlas-list/arlas-list.component'; import { GetResultlistConfigPipe } from './pipes/get-resultlist-config.pipe'; +import { MapService } from './services/map.service'; +import { ResultlistService } from './services/resultlist.service'; @NgModule({ @@ -102,7 +104,11 @@ import { GetResultlistConfigPipe } from './pipes/get-resultlist-config.pipe'; LeftMenuComponent, ConfigsListComponent, RoundKilometer, - SquareKilometer + SquareKilometer, + GeocodingComponent, + ArlasMapComponent, + ArlasListComponent, + GetResultlistConfigPipe ], imports: [ BrowserModule, @@ -161,9 +167,10 @@ import { GetResultlistConfigPipe } from './pipes/get-resultlist-config.pipe'; GetValueModule ], providers: [ - ContributorService, DynamicComponentService, VisualizeService, + MapService, + ResultlistService, { provide: LAZYLOAD_IMAGE_HOOKS, useClass: LazyLoadImageHooks @@ -173,7 +180,8 @@ import { GetResultlistConfigPipe } from './pipes/get-resultlist-config.pipe'; useClass: JwtInterceptor, deps: [AuthentificationService, ArlasIamService, ArlasSettingsService], multi: true - } + }, + ContributorService ], bootstrap: [ArlasWuiComponent], entryComponents: [] diff --git a/src/app/components/arlas-wui-root/arlas-wui-root.component.html b/src/app/components/arlas-wui-root/arlas-wui-root.component.html index 5654d487..fc075095 100644 --- a/src/app/components/arlas-wui-root/arlas-wui-root.component.html +++ b/src/app/components/arlas-wui-root/arlas-wui-root.component.html @@ -21,9 +21,8 @@ - + diff --git a/src/app/components/arlas-wui-root/arlas-wui-root.component.spec.ts b/src/app/components/arlas-wui-root/arlas-wui-root.component.spec.ts index 59773a5f..1c88ebd9 100644 --- a/src/app/components/arlas-wui-root/arlas-wui-root.component.spec.ts +++ b/src/app/components/arlas-wui-root/arlas-wui-root.component.spec.ts @@ -1,3 +1,22 @@ +/* + * Licensed to Gisaïa under one or more contributor + * license agreements. See the NOTICE.txt file distributed with + * this work for additional information regarding copyright + * ownership. Gisaïa licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + import { APP_BASE_HREF } from '@angular/common'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; @@ -12,23 +31,39 @@ import { MatSelectModule } from '@angular/material/select'; import { MatTooltipModule } from '@angular/material/tooltip'; import { RouterModule } from '@angular/router'; import { TranslateFakeLoader, TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { - ArlasCollaborativesearchService, ArlasConfigService, ArlasStartupService, ArlasTaggerModule, ArlasToolKitModule, ArlasToolkitSharedModule -} from 'arlas-wui-toolkit'; -import { LeftMenuComponent } from '../left-menu/left-menu.component'; - +import { GetResultlistConfigPipe } from 'app/pipes/get-resultlist-config.pipe'; import { ContributorService } from 'app/services/contributors.service'; import { ResultlistService } from 'app/services/resultlist.service'; +import { VisualizeService } from 'app/services/visualize.service'; import { HistogramModule } from 'arlas-web-components'; +import { + ArlasCollaborativesearchService, ArlasConfigService, ArlasSettingsService, + ArlasStartupService, ArlasTaggerModule, ArlasToolKitModule, ArlasToolkitSharedModule +} from 'arlas-wui-toolkit'; import { ArlasWuiRootComponent } from './arlas-wui-root.component'; describe('ArlasWuiRootComponent', () => { let component: ArlasWuiRootComponent; let fixture: ComponentFixture; - let arlasStartupService: ArlasStartupService; - beforeEach(() => { - TestBed.configureTestingModule({ + beforeEach(async () => { + const mockSettingsService = jasmine.createSpyObj('ArlasSettingsService', + ['settings', 'getAuthentSettings', 'getPersistenceSettings', 'getPermissionSettings', 'getSettings', 'getArlasHubUrl', 'setSettings', + 'getLinksSettings', 'getTicketingKey']); + mockSettingsService.settings = { tab_name: 'Test' }; + mockSettingsService.getAuthentSettings.and.returnValue(); + mockSettingsService.getPersistenceSettings.and.returnValue(); + mockSettingsService.getPermissionSettings.and.returnValue(); + mockSettingsService.getSettings.and.returnValue(); + mockSettingsService.getArlasHubUrl.and.returnValue(); + mockSettingsService.setSettings.and.returnValue(); + mockSettingsService.getLinksSettings.and.returnValue(); + mockSettingsService.getTicketingKey.and.returnValue(); + + const mockContributorService = jasmine.createSpyObj('ContributorService', ['getChipSearchContributor']); + mockContributorService.getChipSearchContributor.and.returnValue(); + + await TestBed.configureTestingModule({ imports: [ MatIconModule, MatAutocompleteModule, MatInputModule, ReactiveFormsModule, ArlasToolKitModule, FormsModule, MatChipsModule, MatTooltipModule, RouterModule, HistogramModule, @@ -37,29 +72,35 @@ describe('ArlasWuiRootComponent', () => { ArlasTaggerModule, ArlasToolkitSharedModule, ], declarations: [ - ArlasWuiRootComponent, LeftMenuComponent + ArlasWuiRootComponent, + GetResultlistConfigPipe ], providers: [ ArlasCollaborativesearchService, ArlasConfigService, - ContributorService, + { + provide: ContributorService, + useValue: mockContributorService + }, ArlasStartupService, { provide: APP_BASE_HREF, useValue: '/' }, - ResultlistService + ResultlistService, + VisualizeService, + ArlasSettingsService, + { + provide: ArlasSettingsService, + useValue: mockSettingsService + } ], teardown: { destroyAfterEach: false } }).compileComponents(); + fixture = TestBed.createComponent(ArlasWuiRootComponent); + component = fixture.componentInstance; + fixture.detectChanges(); }); - beforeEach(() => { - arlasStartupService = TestBed.get(ArlasStartupService); - arlasStartupService.arlasIsUp.subscribe(isUp => { - if (isUp) { - fixture = TestBed.createComponent(ArlasWuiRootComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - } - }); + it('should create', () => { + expect(component).toBeTruthy(); }); }); diff --git a/src/app/components/arlas-wui-root/arlas-wui-root.component.ts b/src/app/components/arlas-wui-root/arlas-wui-root.component.ts index 75ce86bb..899f96d5 100644 --- a/src/app/components/arlas-wui-root/arlas-wui-root.component.ts +++ b/src/app/components/arlas-wui-root/arlas-wui-root.component.ts @@ -310,7 +310,7 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { // TODO: move it to list with an added output ? // Keep the last displayed list as preview when closing the right panel - if (!!this.arlasListComponent.tabsList) { + if (!!this.arlasListComponent && !!this.arlasListComponent.tabsList) { this.arlasListComponent.tabsList.selectedIndexChange .pipe(takeUntil(this._onDestroy$)) .subscribe(index => { @@ -412,9 +412,11 @@ export class ArlasWuiRootComponent implements OnInit, AfterViewInit, OnDestroy { */ private resizeCollectionCounts() { // Add padding to the left of the divider and right of the title - const start = document.getElementById('menuDivider').getBoundingClientRect().right + this.spacing; - const end = document.getElementById('title').getBoundingClientRect().left - this.spacing; - this.availableSpaceCounts = end - start; + const start = document.getElementById('menuDivider')?.getBoundingClientRect().right + this.spacing; + const end = document.getElementById('title')?.getBoundingClientRect().left - this.spacing; + if (!!start && !!end) { + this.availableSpaceCounts = end - start; + } } private adjustGrids() { diff --git a/src/app/components/left-menu/left-menu.component.html b/src/app/components/left-menu/left-menu.component.html index b90e1f16..2b865273 100644 --- a/src/app/components/left-menu/left-menu.component.html +++ b/src/app/components/left-menu/left-menu.component.html @@ -1,5 +1,5 @@