From 8198ab325e0e4cd8a8b68b4261f15e6d82cdf636 Mon Sep 17 00:00:00 2001 From: Florent Gravin Date: Tue, 19 Nov 2024 14:07:47 +0100 Subject: [PATCH 01/13] gpf - use geoadmin.ch iframe for records which refers a map.geoadmin.ch link --- .../record-metadata.component.html | 14 ++++++++++++-- .../record-metadata.component.ts | 19 +++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/apps/datahub/src/app/record/record-metadata/record-metadata.component.html b/apps/datahub/src/app/record/record-metadata/record-metadata.component.html index 8f5225f4e..15bd95ba5 100644 --- a/apps/datahub/src/app/record/record-metadata/record-metadata.component.html +++ b/apps/datahub/src/app/record/record-metadata/record-metadata.component.html @@ -81,7 +81,14 @@ style="height: 500px" *ngIf="displayMap$ | async" > - + + @@ -106,7 +113,10 @@
{ + return mapApiLinks?.find((link) => + link?.url.toString().startsWith('https://map.geo.admin.ch/') + ) + }), + filter((geoAdminUrl) => !!geoAdminUrl), + map((link) => + this.sanitizer.bypassSecurityTrustResourceUrl( + link.url.toString().replace('?layers=', '#/embed?layers=') + ) + ), + startWith(null) + ) + displayData$ = combineLatest([ this.metadataViewFacade.dataLinks$, this.metadataViewFacade.geoDataLinks$, @@ -149,6 +163,7 @@ export class RecordMetadataComponent { public metadataViewFacade: MdViewFacade, private searchService: SearchService, private sourceService: SourcesService, + private sanitizer: DomSanitizer, private orgsService: OrganizationsServiceInterface ) {} From 051ec0ffcf408180040602cbc761850ba5fc056b Mon Sep 17 00:00:00 2001 From: Olivia Guyot Date: Fri, 29 Nov 2024 09:47:15 +0100 Subject: [PATCH 02/13] TEMP: commit geocat config --- conf/default.toml | 101 ++++++++++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 43 deletions(-) diff --git a/conf/default.toml b/conf/default.toml index 2586bd086..a01878c72 100644 --- a/conf/default.toml +++ b/conf/default.toml @@ -6,25 +6,21 @@ [global] # This URL (relative or absolute) must point to the API endpoint of a GeoNetwork4 instance geonetwork4_api_url = "/geonetwork/srv/api" -datahub_url = "/datahub" # This should point to a proxy to avoid CORS errors on some requests (data preview, OGC capabilities etc.) # The actual URL will be appended after this path, e.g. : https://my.proxy/?url=http%3A%2F%2Fencoded.url%2Fows` # This is an optional parameter: leave empty to disable proxy usage -proxy_path = "" +proxy_path = "/geonetwork/proxy?url=" # This optional parameter defines, in which language metadata should be queried in elasticsearch. # Use ISO 639-2/B (https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) format to indicate the language of the metadata. -# Setting to "current" will use the current language of the User Interface. # If not indicated, a wildcard is used and no language preference is applied for the search. -# metadata_language = "current" +metadata_language = "current" # This optional URL should point to the login page that allows authentication to the datahub. # If not indicated, the default geonetwork login page is used. # The following three placeholders can be part of this URL: # - ${current_url}: indicates where the current location should be injected in the constructed login URL (eg. start, end) # - ${lang2}, ${lang3}: indicates if and where the current language should be part of the login URL in language 2 or 3 letter code # Example to use the georchestra login page: -# login_url = "/cas/login?service=${current_url}" -# logout_url = "/geonetwork/signout" -# settings_url = "/geonetwork/srv/\${lang3}/admin.console#/organization/users?userOrGroup=" +login_url = "/geonetwork/srv/${lang3}/catalog.signin?redirect=${current_url}" # This optional URL should point to the static html page wc-embedder.html which allows to display a web component (like chart and table) via a permalink. # URLs can be indicated from the root of the same server starting with a "/" or as an external URL. Be conscious of potential CORS issues when using an external URL. # The default location in the dockerized datahub app for example is "/datahub/wc-embedder.html". @@ -34,10 +30,7 @@ proxy_path = "" # This optional parameter defines the languages that will be provided in a dropdown for the user to translate the UI. # Available languages are listed here: (https://github.com/geonetwork/geonetwork-ui/blob/main/libs/util/i18n/src/lib/i18n.constants.ts). # More information about the translation can be found in the docs (https://geonetwork.github.io/geonetwork-ui/main/docs/reference/i18n.html) -# languages = ['en', 'fr', 'de'] - -# Enables displaying a "contact block" wherever relevant in applications -# contact_email = "opendata@mycompany.com" +languages = ['en', 'fr', 'de', 'it'] ### VISUAL THEME @@ -46,34 +39,30 @@ proxy_path = "" # - for font families: https://developer.mozilla.org/en-US/docs/Web/CSS/font-family # - for background: https://developer.mozilla.org/en-US/docs/Web/CSS/background [theme] -primary_color = "#c82850" -secondary_color = "#001638" -main_color = "#555" # All-purpose text color +primary_color = "#f19330" +secondary_color = "#0071ae" +main_color = "#212029" # All-purpose text color background_color = "#fdfbff" # These optional parameters indicate which background should be used for the main header and the text color used # on top of the background. The color should be chosen to contrast well with the background (defaults to white). # Note: The search header does not use the header_foreground_color as it allows futher customisation via HTML. -# header_background = "center /cover url('assets/img/header_bg.webp')" or "var(--color-gray-500)" -# header_foreground_color = 'white' +header_background = "linear-gradient(to bottom, #fff 0%, var(--color-secondary) 100%)" +header_foreground_color = 'inherit' # This optional parameter allows to override the fallback image that should be used for thumbnails, # if the metadata record has no thumbnail image url or it is broken. # thumbnail_placeholder = 'assets/img/my_custom_placeholder.png' # These optional parameters allow changing fonts used in the app -# main_font = "'My Custom Font', fallback-font" -# title_font = "'My Custom Title Font', fallback-font-title" -# fonts_stylesheet_url = "https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&family=Permanent+Marker&display=swap" - -# Use it to set custom location for the favicon; by default, the path `/favicon.ico` will be used -# favicon = "assets/favicon.ico" +main_font = "'Source Sans Pro', sans-serif" +title_font = "'Public Sans', sans-serif" +fonts_stylesheet_url = "https://fonts.googleapis.com/css2?family=Public+Sans:wght@600&family=Source+Sans+Pro&display=swap" ### SEARCH SETTINGS # This section contains settings used for fine-tuning the search experience [search] - # Optional; specify a GeoJSON object to be used as filter: all records contained inside the geometry will be boosted on top, # all records which do not intersect with the geometry will be shown with lower priority; can be specified as URL or inline # Note: if the GeoJSON object contains multiple features, only the geometry of the first one will be kept! @@ -81,19 +70,18 @@ background_color = "#fdfbff" # filter_geometry_data = '{ "coordinates": [...], "type": "Polygon" }' # The advanced search filters available to the user can be customized with this setting. -# The following fields can be used for filtering: 'organization', 'format', 'publicationYear', 'standard', 'inspireKeyword', 'keyword', 'topic', 'isSpatial', 'license', 'resourceType', 'representationType', 'producerOrg', 'publisherOrg' -# If not configured, the default values are 'organization', 'format', 'publicationYear', 'topic', 'isSpatial', 'license' -# Note that some links in the datahub app set an 'organization' filter. So this field is needed to allow unsetting such filters. -# advanced_filters = ['organization', 'format', 'publicationYear', 'topic', 'isSpatial', 'license'] +# The following fields can be used for filtering: 'publisher', 'format', 'publicationYear', 'standard', 'inspireKeyword', 'topic', 'isSpatial', 'license' +# any other field will be ignored +advanced_filters = ['topic', 'publisher', 'contact', 'keyword', 'resourceType', 'representationType', 'format'] # One or several search presets can be defined here; every search preset is composed of: # - a name (which can be a translation key) -# - a sort criteria: either `createDate`, `userSavedCount` or `_score` (prepend with `-` for descending sort) (optionnal) +# - a sort criteria: either `createDate`, `userSavedCount` or `_score` (prepend with `-` for descending sort) # - filters which can be expressed like so: # [[search_preset]] # name = 'filterByName' # filters.q = 'Full text search' -# filters.organization = ['Org 1', 'Org 2'] +# filters.publisher = ['Org 1', 'Org 2'] # filters.format = ['format 1', 'format 2'] # filters.documentStandard = ['iso19115-3.2018'] # filters.inspireKeyword = ['keyword 1', 'keyword 2'] @@ -101,7 +89,6 @@ background_color = "#fdfbff" # filters.publicationYear = ['2023', '2022'] # filters.isSpatial = ['yes'] # filters.license = ['unknown'] -# sort = 'createDate' # [[search_preset]] # name = 'otherFilterName' # filters.q = 'Other Full text search' @@ -109,14 +96,13 @@ background_color = "#fdfbff" # Search presets will be advertised to the user along the main search field. - -### METADATA QUALITY SETTINGS - -# This section contains settings used for fine-tuning the metadata quality experience -[metadata-quality] -# By default the widget is not activated to enable it, just add this parameter. -# enabled = true -# If u want to use metadata quality widget this configuration is required +# [[search_preset]] +# sort = "-createDate" +# name = 'filterByOrgs' +# filters.publisher = ['DREAL', 'atmo Hauts-de-France', 'blargz'] +# [[search_preset]] +# name = 'Wind turbines' +# filters.q = 'wind' ### MAP SETTINGS @@ -131,8 +117,8 @@ background_color = "#fdfbff" # Optional; URL template enabling to open map layers in an external viewer; if set, displays a button next to the map's layer drop down # The template must include the following placeholders, which allow the datahub to inject the correct values when adding a layer to a viewer: -# ${service_url}: URL of the OWS or geojson file -# ${service_type}: Type of the OWS or geojson file; currently supported WMS, WFS, GEOJSON +# ${service_url}: URL of the OWS +# ${service_type}: Type of the OWS; currently supported WMS, WFS # ${layer_name}: Name of the layer # Be careful to use englobing single quotes, if your template syntax includes JSON (with double quotes) # Examples: @@ -144,9 +130,6 @@ background_color = "#fdfbff" # Optional; if true, opens external viewer in new tab (default false) # external_viewer_open_new_tab = false -# Optional; Will not tile WMS. False by default. -# do_not_tile_wms = false - # Optional; if true, the default basemap will not be added to the map. # Use [[map_layer]] sections to define your own custom layers (see below) # do_not_use_default_basemap = false @@ -192,3 +175,35 @@ background_color = "#fdfbff" # Welcome to Organization's
# wonderful data catalogue # """ +[translations.fr] +datahub.header.title.html = '
' +search.field.location.placeholder = 'ex: Berne' +datahub.header.organisations = 'Catalogues' +search.filters.publisher = 'Catalogues' +datahub.header.documentation = 'Documentation' +datahub.header.admin = 'Admin' +datahub.header.geonetwork = 'Geocat PRO' +[translations.en] +datahub.header.title.html = '
' +search.field.location.placeholder = 'ex: Berne' +datahub.header.organisations = 'Catalogues' +search.filters.publisher = 'Catalogues' +datahub.header.documentation = 'Documentation' +datahub.header.admin = 'Admin' +datahub.header.geonetwork = 'Geocat PRO' +[translations.de] +datahub.header.title.html = '
' +search.field.location.placeholder = 'ex: Berne' +datahub.header.organisations = 'Kataloge' +search.filters.publisher = 'Kataloge' +datahub.header.documentation = 'Dokumentation' +datahub.header.admin = 'Admin' +datahub.header.geonetwork = 'Geocat PRO' +[translations.it] +datahub.header.title.html = '
' +search.field.location.placeholder = 'ex: Berne' +datahub.header.organisations = 'Cataloghi' +search.filters.publisher = 'Cataloghi' +datahub.header.documentation = "Documentazione" +datahub.header.admin = 'Admin' +datahub.header.geonetwork = 'Geocat PRO' From 2946b8f6706245e3de178422138c197a6c5e47cb Mon Sep 17 00:00:00 2001 From: Olivia Guyot Date: Fri, 29 Nov 2024 11:18:52 +0100 Subject: [PATCH 03/13] show geoadmin iframe in map-container component --- .../record-metadata.component.html | 14 +- .../record-metadata.component.ts | 19 +- .../src/lib/map-view/map-view.component.ts | 9 +- .../map-container.component.html | 20 +- .../map-container.component.spec.ts | 229 +---------------- .../map-container/map-container.component.ts | 235 ++++-------------- 6 files changed, 64 insertions(+), 462 deletions(-) diff --git a/apps/datahub/src/app/record/record-metadata/record-metadata.component.html b/apps/datahub/src/app/record/record-metadata/record-metadata.component.html index 15bd95ba5..8f5225f4e 100644 --- a/apps/datahub/src/app/record/record-metadata/record-metadata.component.html +++ b/apps/datahub/src/app/record/record-metadata/record-metadata.component.html @@ -81,14 +81,7 @@ style="height: 500px" *ngIf="displayMap$ | async" > - - +
@@ -113,10 +106,7 @@
{ - return mapApiLinks?.find((link) => - link?.url.toString().startsWith('https://map.geo.admin.ch/') - ) - }), - filter((geoAdminUrl) => !!geoAdminUrl), - map((link) => - this.sanitizer.bypassSecurityTrustResourceUrl( - link.url.toString().replace('?layers=', '#/embed?layers=') - ) - ), - startWith(null) - ) - displayData$ = combineLatest([ this.metadataViewFacade.dataLinks$, this.metadataViewFacade.geoDataLinks$, @@ -163,7 +149,6 @@ export class RecordMetadataComponent { public metadataViewFacade: MdViewFacade, private searchService: SearchService, private sourceService: SourcesService, - private sanitizer: DomSanitizer, private orgsService: OrganizationsServiceInterface ) {} diff --git a/libs/feature/record/src/lib/map-view/map-view.component.ts b/libs/feature/record/src/lib/map-view/map-view.component.ts index 9b16ef2ab..81ebcbbc0 100644 --- a/libs/feature/record/src/lib/map-view/map-view.component.ts +++ b/libs/feature/record/src/lib/map-view/map-view.component.ts @@ -36,7 +36,6 @@ import { import { FeatureDetailComponent, MapContainerComponent, - prioritizePageScroll, } from '@geonetwork-ui/ui/map' import { Feature } from 'geojson' import { NgIconComponent, provideIcons } from '@ng-icons/core' @@ -158,9 +157,11 @@ export class MapViewComponent implements AfterViewInit { private changeRef: ChangeDetectorRef ) {} - async ngAfterViewInit() { - const map = await this.mapContainer.openlayersMap - prioritizePageScroll(map.getInteractions()) + // eslint-disable-next-line @angular-eslint/no-empty-lifecycle-method + ngAfterViewInit() { + // SPECIFIC GEOCAT + // const map = await this.mapContainer.openlayersMap + // prioritizePageScroll(map.getInteractions()) } onMapFeatureSelect(features: Feature[]): void { diff --git a/libs/ui/map/src/lib/components/map-container/map-container.component.html b/libs/ui/map/src/lib/components/map-container/map-container.component.html index 18d6d4648..3cdc5bda1 100644 --- a/libs/ui/map/src/lib/components/map-container/map-container.component.html +++ b/libs/ui/map/src/lib/components/map-container/map-container.component.html @@ -1,16 +1,4 @@ -
-
-
- -

map.navigation.message

-
+ + + + diff --git a/libs/ui/map/src/lib/components/map-container/map-container.component.spec.ts b/libs/ui/map/src/lib/components/map-container/map-container.component.spec.ts index 5ab227acd..abb7fb7f1 100644 --- a/libs/ui/map/src/lib/components/map-container/map-container.component.spec.ts +++ b/libs/ui/map/src/lib/components/map-container/map-container.component.spec.ts @@ -1,228 +1,3 @@ -import { - ComponentFixture, - discardPeriodicTasks, - fakeAsync, - TestBed, - tick, -} from '@angular/core/testing' -import { MockBuilder } from 'ng-mocks' -import { - mapCtxFixture, - mapCtxLayerWmsFixture, - mapCtxLayerXyzFixture, -} from '@geonetwork-ui/common/fixtures' -import { applyContextDiffToMap } from '@geospatial-sdk/openlayers' -import { MapContainerComponent } from './map-container.component' -import { computeMapContextDiff } from '@geospatial-sdk/core' - -jest.mock('@geospatial-sdk/core', () => ({ - computeMapContextDiff: jest.fn(() => ({ - 'this is': 'a diff', - })), -})) - -jest.mock('@geospatial-sdk/openlayers', () => ({ - applyContextDiffToMap: jest.fn(), - createMapFromContext: jest.fn(() => Promise.resolve(new OpenLayersMapMock())), - listen: jest.fn(), -})) - -let mapmutedCallback -let movestartCallback -let singleclickCallback -class OpenLayersMapMock { - _size = undefined - setTarget = jest.fn() - updateSize() { - this._size = [100, 100] - } - getSize() { - return this._size - } - on(type, callback) { - if (type === 'mapmuted') { - mapmutedCallback = callback - } - if (type === 'movestart') { - movestartCallback = callback - } - if (type === 'singleclick') { - singleclickCallback = callback - } - } - off() { - // do nothing! - } -} - -const defaultBaseMap = { - attributions: - '© OpenStreetMap contributors, © Carto', - type: 'xyz', - url: 'https://{a-c}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png', -} - -describe('MapContainerComponent', () => { - let component: MapContainerComponent - let fixture: ComponentFixture - - beforeEach(() => { - jest.clearAllMocks() - }) - - beforeEach(() => { - return MockBuilder(MapContainerComponent) - }) - - beforeEach(async () => { - await TestBed.configureTestingModule({}).compileComponents() - }) - - beforeEach(() => { - fixture = TestBed.createComponent(MapContainerComponent) - component = fixture.componentInstance - fixture.detectChanges() - }) - - it('creates', () => { - expect(component).toBeTruthy() - }) - - describe('#processContext', () => { - it('returns a default context if null provided', () => { - expect(component.processContext(null)).toEqual({ - layers: [defaultBaseMap], - view: { - center: [0, 15], - zoom: 2, - }, - }) - }) - it('adds base layers to context', () => { - const context = { - layers: [mapCtxLayerWmsFixture()], - view: null, - } - expect(component.processContext(context)).toEqual({ - layers: [defaultBaseMap, mapCtxLayerWmsFixture()], - view: { - center: [0, 15], - zoom: 2, - }, - }) - }) - it('uses provided basemaps if any', () => { - component['basemapLayers'] = [mapCtxLayerXyzFixture()] - const context = { layers: [], view: null } - expect(component.processContext(context)).toEqual({ - layers: [defaultBaseMap, mapCtxLayerXyzFixture()], - view: { - center: [0, 15], - zoom: 2, - }, - }) - }) - it('does not use the default base layer if specified', () => { - component['doNotUseDefaultBasemap'] = true - const context = { layers: [mapCtxLayerXyzFixture()], view: null } - expect(component.processContext(context)).toEqual({ - layers: [mapCtxLayerXyzFixture()], - view: { - center: [0, 15], - zoom: 2, - }, - }) - }) - it('applies map constraints if any', () => { - component['mapViewConstraints'] = { - maxZoom: 18, - maxExtent: [10, 20, 30, 40], - } - const context = { layers: [mapCtxLayerXyzFixture()], view: null } - expect(component.processContext(context)).toEqual({ - layers: [defaultBaseMap, mapCtxLayerXyzFixture()], - view: { - center: [0, 15], - zoom: 2, - maxExtent: [10, 20, 30, 40], - maxZoom: 18, - }, - }) - }) - }) - - describe('#afterViewInit', () => { - beforeEach(async () => { - await component.ngAfterViewInit() - }) - it('creates a map', () => { - expect(component.olMap).toBeInstanceOf(OpenLayersMapMock) - }) - describe('display message that map navigation has been muted', () => { - let messageDisplayed - beforeEach(() => { - messageDisplayed = null - component.displayMessage$.subscribe( - (value) => (messageDisplayed = value) - ) - }) - it('mapmuted event displays message after 300ms (delay for eventually hiding message)', fakeAsync(() => { - mapmutedCallback() - tick(400) - expect(messageDisplayed).toEqual(true) - discardPeriodicTasks() - })) - it('message goes away after 2s', fakeAsync(() => { - mapmutedCallback() - tick(2500) - expect(messageDisplayed).toEqual(false) - discardPeriodicTasks() - })) - it('message does not display if map fires movestart event', fakeAsync(() => { - movestartCallback() - tick(300) - expect(messageDisplayed).toEqual(false) - discardPeriodicTasks() - })) - it('message does not display if map fires singleclick event', fakeAsync(() => { - singleclickCallback() - tick(300) - expect(messageDisplayed).toEqual(false) - discardPeriodicTasks() - })) - }) - }) - - describe('#ngOnChanges', () => { - beforeEach(async () => { - await component.ngAfterViewInit() - }) - it('updates the map with the new context', async () => { - const newContext = { - ...mapCtxFixture(), - layers: [mapCtxLayerWmsFixture()], - } - await component.ngOnChanges({ - context: { - currentValue: mapCtxFixture(), - previousValue: newContext, - firstChange: false, - isFirstChange: () => false, - }, - }) - expect(computeMapContextDiff).toHaveBeenCalledWith( - { - layers: [defaultBaseMap, ...mapCtxFixture().layers], - view: mapCtxFixture().view, - }, - { - layers: [defaultBaseMap, mapCtxLayerWmsFixture()], - view: mapCtxFixture().view, - } - ) - expect(applyContextDiffToMap).toHaveBeenCalledWith(component.olMap, { - 'this is': 'a diff', - }) - }) - }) +describe.skip('MapContainerComponent', () => { + // empty }) diff --git a/libs/ui/map/src/lib/components/map-container/map-container.component.ts b/libs/ui/map/src/lib/components/map-container/map-container.component.ts index e26076bb5..bdefb840f 100644 --- a/libs/ui/map/src/lib/components/map-container/map-container.component.ts +++ b/libs/ui/map/src/lib/components/map-container/map-container.component.ts @@ -1,62 +1,19 @@ -import { - AfterViewInit, - ChangeDetectionStrategy, - Component, - ElementRef, - EventEmitter, - Inject, - Input, - OnChanges, - Output, - SimpleChanges, - ViewChild, -} from '@angular/core' -import { fromEvent, merge, Observable, of, timer } from 'rxjs' -import { delay, map, startWith, switchMap } from 'rxjs/operators' +import { ChangeDetectionStrategy, Component, Input } from '@angular/core' import { CommonModule } from '@angular/common' import { TranslateModule } from '@ngx-translate/core' -import { - computeMapContextDiff, - Extent, - FeaturesClickEvent, - FeaturesClickEventType, - FeaturesHoverEvent, - FeaturesHoverEventType, - MapClickEvent, - MapClickEventType, - MapContext, - MapContextLayer, - MapContextLayerXyz, - MapContextView, -} from '@geospatial-sdk/core' -import { - applyContextDiffToMap, - createMapFromContext, - listen, -} from '@geospatial-sdk/openlayers' -import type OlMap from 'ol/Map' -import type { Feature } from 'geojson' -import { - BASEMAP_LAYERS, - DO_NOT_USE_DEFAULT_BASEMAP, - MAP_VIEW_CONSTRAINTS, -} from './map-settings.token' -import { - NgIconComponent, - provideIcons, - provideNgIconsConfig, -} from '@ng-icons/core' +import { MapContext } from '@geospatial-sdk/core' +import { provideIcons, provideNgIconsConfig } from '@ng-icons/core' import { matSwipeOutline } from '@ng-icons/material-icons/outline' +import { LangService } from '@geonetwork-ui/util/i18n' +import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser' -const DEFAULT_BASEMAP_LAYER: MapContextLayerXyz = { - type: 'xyz', - url: `https://{a-c}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png`, - attributions: `© OpenStreetMap contributors, © Carto`, -} +// https://map.geo.admin.ch/#/embed?lang=fr¢er=2580700.82,1249405.91&z=0.761&bgLayer=ch.swisstopo.pixelkarte-farbe&topic=ech&layers=ch.bafu.luftreinhaltung-stickstoff_kritischer_eintrag@year=2020,f;WMS%7Chttps://geo.so.ch/api/wms%7Cch.so.afu.abbaustellen;WMS%7Chttps://geo.so.ch/api/wms%7Cch.so.arp.agglomerationsprogramme,f;WMTS%7Chttps://geo.so.ch/api/wmts?%7Cch.so.agi.hintergrundkarte_farbig&catalogNodes=ech + +const BASE_GEOADMIN_URL = + 'https://map.geo.admin.ch/?bgLayer=ch.swisstopo.pixelkarte-grau' -const DEFAULT_VIEW: MapContextView = { - center: [0, 15], - zoom: 2, +function isGeoAdminLayerUrl(url: string): boolean { + return /https:\/\/[a-z]+\.geo\.admin\.ch/.test(url) } @Component({ @@ -65,7 +22,7 @@ const DEFAULT_VIEW: MapContextView = { styleUrls: ['./map-container.component.css'], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [CommonModule, TranslateModule, NgIconComponent], + imports: [CommonModule, TranslateModule], providers: [ provideIcons({ matSwipeOutline }), provideNgIconsConfig({ @@ -73,142 +30,48 @@ const DEFAULT_VIEW: MapContextView = { }), ], }) -export class MapContainerComponent implements AfterViewInit, OnChanges { +export class MapContainerComponent { @Input() context: MapContext | null - // these events only get registered on the map if they are used - _featuresClick: EventEmitter - @Output() get featuresClick() { - if (!this._featuresClick) { - this.openlayersMap.then((olMap) => { - listen( - olMap, - FeaturesClickEventType, - ({ features }: FeaturesClickEvent) => - this._featuresClick.emit(features) - ) - }) - this._featuresClick = new EventEmitter() - } - return this._featuresClick - } - _featuresHover: EventEmitter - @Output() get featuresHover() { - if (!this._featuresHover) { - this.openlayersMap.then((olMap) => { - listen( - olMap, - FeaturesHoverEventType, - ({ features }: FeaturesHoverEvent) => - this._featuresHover.emit(features) - ) - }) - this._featuresHover = new EventEmitter() - } - return this._featuresHover - } - _mapClick: EventEmitter<[number, number]> - @Output() get mapClick() { - if (!this._mapClick) { - this.openlayersMap.then((olMap) => { - listen(olMap, MapClickEventType, ({ coordinate }: MapClickEvent) => - this._mapClick.emit(coordinate) - ) - }) - this._mapClick = new EventEmitter<[number, number]>() - } - return this._mapClick - } + get geoadminUrl(): SafeResourceUrl | null { + const url = new URL(BASE_GEOADMIN_URL) + if (!this.context) return null - @ViewChild('map') container: ElementRef - displayMessage$: Observable - olMap: OlMap - - constructor( - @Inject(DO_NOT_USE_DEFAULT_BASEMAP) private doNotUseDefaultBasemap: boolean, - @Inject(BASEMAP_LAYERS) private basemapLayers: MapContextLayer[], - @Inject(MAP_VIEW_CONSTRAINTS) - private mapViewConstraints: { - maxZoom?: number - maxExtent?: Extent - } - ) {} - - private olMapResolver - openlayersMap = new Promise((resolve) => { - this.olMapResolver = resolve - }) - - async ngAfterViewInit() { - this.olMap = await createMapFromContext( - this.processContext(this.context), - this.container.nativeElement - ) - this.displayMessage$ = merge( - fromEvent(this.olMap, 'mapmuted').pipe(map(() => true)), - fromEvent(this.olMap, 'movestart').pipe(map(() => false)), - fromEvent(this.olMap, 'singleclick').pipe(map(() => false)) - ).pipe( - switchMap((muted) => - muted - ? timer(2000).pipe( - map(() => false), - startWith(true), - delay(400) - ) - : of(false) - ) - ) - this.olMapResolver(this.olMap) - } - - async ngOnChanges(changes: SimpleChanges) { - if ('context' in changes && !changes['context'].isFirstChange()) { - const diff = computeMapContextDiff( - this.processContext(changes['context'].currentValue), - this.processContext(changes['context'].previousValue) - ) - await applyContextDiffToMap(this.olMap, diff) - } - } - - // This will apply basemap layers & view constraints - processContext(context: MapContext): MapContext { - const processed = context - ? { ...context, view: context.view ?? DEFAULT_VIEW } - : { layers: [], view: DEFAULT_VIEW } - if (this.basemapLayers.length) { - processed.layers = [...this.basemapLayers, ...processed.layers] - } - if (!this.doNotUseDefaultBasemap) { - processed.layers = [DEFAULT_BASEMAP_LAYER, ...processed.layers] - } - if (this.mapViewConstraints.maxZoom) { - processed.view = { - maxZoom: this.mapViewConstraints.maxZoom, - ...processed.view, - } - } - if (this.mapViewConstraints.maxExtent) { - processed.view = { - maxExtent: this.mapViewConstraints.maxExtent, - ...processed.view, - } - } - if ( - processed.view && - !('zoom' in processed.view) && - !('center' in processed.view) - ) { - if (this.mapViewConstraints.maxExtent) { - processed.view = { - extent: this.mapViewConstraints.maxExtent, - ...processed.view, + const layers: string[] = [] + for (const layer of this.context?.layers || []) { + if (layer.type === 'wms') { + if (isGeoAdminLayerUrl(layer.url)) { + layers.push(layer.name) + } else { + layers.push(`WMS|${layer.url}|${layer.name}`) + } + } else if (layer.type === 'wmts') { + if (isGeoAdminLayerUrl(layer.url)) { + layers.push(layer.name) + } else { + layers.push(`WMTS|${layer.url}|${layer.name}`) } - } else { - processed.view = { ...DEFAULT_VIEW, ...processed.view } + } else if (layer.type === 'wfs') { + // not supported + // layers.push(`WFS|${layer.url}|${layer.featureType}`) + } else if (layer.type === 'xyz') { + // not supported + //layers.push(layer.url) + } else if (layer.type === 'geojson') { + // not supported + // layers.push(layer.url) } } - return processed + url.searchParams.set('layers', layers.join(',')) + url.searchParams.set('lang', this.langService.iso2) + const embedUrl = url + .toString() + .replace('map.geo.admin.ch/', 'map.geo.admin.ch/#/embed') + return this.sanitizer.bypassSecurityTrustResourceUrl(embedUrl) } + + constructor( + private langService: LangService, + private sanitizer: DomSanitizer + ) {} } From 06c72b167ef8cb15baf023df073331a83b6989f0 Mon Sep 17 00:00:00 2001 From: Olivia Guyot Date: Fri, 29 Nov 2024 12:16:10 +0100 Subject: [PATCH 04/13] publish webcomponents on PR --- .github/workflows/webcomponents.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/webcomponents.yml b/.github/workflows/webcomponents.yml index 862a1e0f7..9bc625aba 100644 --- a/.github/workflows/webcomponents.yml +++ b/.github/workflows/webcomponents.yml @@ -1,11 +1,15 @@ name: Web Components -run-name: 🧩 Build Web Components for ${{ github.event_name == 'issue_comment' && 'PR' || (github.event_name == 'release' && '🏷' || '🌱') }} ${{github.event_name == 'issue_comment' && github.event.issue.number || github.ref_name}} +run-name: 🧩 Build Web Components for ${{ github.event_name == 'issue_comment' && 'PR' || (github.event_name == 'release' && '🏷' || '🌱') }} ${{github.event_name == 'issue_comment' && github.event.issue.number || github.head_ref || github.ref_name}} # This workflow runs whenever a commit is pushed on main or a release is published on: push: branches: - - geocat + - main + tags: + - 'v*.*.*' + pull_request: + types: [opened, synchronize, ready_for_review] release: types: [published] @@ -26,7 +30,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: - ref: ${{ needs.checks.outputs.ref }} + ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false fetch-depth: 0 @@ -54,10 +58,10 @@ jobs: tag: ${{ github.ref }} overwrite: true - - name: Publish web component to ${{ env.PUBLISH_BRANCH }} branch + - name: Publish web component to ${{ env.PUBLISH_BRANCH }}-${{ github.head_ref || github.ref_name }} branch uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} force_orphan: true publish_dir: ./wc-dist - publish_branch: ${{ env.PUBLISH_BRANCH }} + publish_branch: ${{ env.PUBLISH_BRANCH }}-${{ github.head_ref || github.ref_name }} From 327592eda8061309880a81cd8ce2ed60abaa67a2 Mon Sep 17 00:00:00 2001 From: Olivia Guyot Date: Fri, 29 Nov 2024 13:56:25 +0100 Subject: [PATCH 05/13] [upstream] fix webcomponent styling --- apps/webcomponents/src/styles.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/webcomponents/src/styles.css b/apps/webcomponents/src/styles.css index 799b8b6c3..3a47f245f 100644 --- a/apps/webcomponents/src/styles.css +++ b/apps/webcomponents/src/styles.css @@ -1,3 +1,5 @@ +@import '../../../tailwind.base.css'; + @tailwind base; @tailwind components; @tailwind utilities; From cf17b30ea8698413d520267261b3cce63a56a900 Mon Sep 17 00:00:00 2001 From: Olivia Guyot Date: Fri, 6 Dec 2024 10:58:53 +0100 Subject: [PATCH 06/13] [upstream] feat(repository): include all fields if requestFields not specified --- .../elasticsearch.service.spec.ts | 31 +++++++++++++++++-- .../elasticsearch/elasticsearch.service.ts | 4 +-- ...rganizations-from-metadata.service.spec.ts | 1 - 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.spec.ts b/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.spec.ts index 154d0a120..6f2f158bb 100644 --- a/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.spec.ts +++ b/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.spec.ts @@ -91,9 +91,32 @@ describe('ElasticsearchService', () => { }) describe('#getSearchRequestBody', () => { - describe('#track_total_hits', () => { + let payload + describe('request fields', () => { + it('includes the _source property if fields are specified', () => { + payload = service.getSearchRequestBody({}, 4, 0, null, ['uuid', 'tag']) + expect(payload).toEqual({ + _source: ['uuid', 'tag'], + from: 0, + size: 4, + query: expect.any(Object), + aggregations: expect.any(Object), + track_total_hits: true, + }) + }) + it('does not include the _source property if no field specified', () => { + payload = service.getSearchRequestBody({}, 4, 0, null, null) + expect(payload).toEqual({ + from: 0, + size: 4, + query: expect.any(Object), + aggregations: expect.any(Object), + track_total_hits: true, + }) + }) + }) + describe('track_total_hits', () => { let size = 0 - let payload describe('when size is 0', () => { beforeEach(() => { payload = service.getSearchRequestBody({}, size) @@ -570,7 +593,9 @@ describe('ElasticsearchService', () => { }) describe('#injectLangInQueryStringFields - Search language', () => { - let queryStringFields = { 'resourceTitleObject.${searchLang}': 1 } + let queryStringFields: Record = { + 'resourceTitleObject.${searchLang}': 1, + } describe('When no lang from config', () => { beforeEach(() => { service['metadataLang'] = undefined diff --git a/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts b/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts index a27660285..6562b61af 100644 --- a/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +++ b/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts @@ -50,7 +50,7 @@ export class ElasticsearchService { size = 0, from = 0, sortBy: SortByField = null, - requestFields: RequestFields = [], + requestFields: RequestFields = null, searchFilters: SearchFilters = {}, configFilters: SearchFilters = {}, uuids?: string[], @@ -68,7 +68,7 @@ export class ElasticsearchService { geometry ), ...(size > 0 ? { track_total_hits: true } : {}), - _source: requestFields, + ...(requestFields && { _source: requestFields }), } this.processRuntimeFields(payload) return payload diff --git a/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.spec.ts b/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.spec.ts index 61e741eea..7760ff5dc 100644 --- a/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.spec.ts +++ b/libs/api/repository/src/lib/gn4/organizations/organizations-from-metadata.service.spec.ts @@ -286,7 +286,6 @@ describe.each(['4.2.2-00', '4.2.3-xx', '4.2.5-xx'])( filter: [{ terms: { isTemplate: ['n'] } }], }, }, - _source: [], }) ) }) From 89dd288a3b00864df66a913eae005279a641badb Mon Sep 17 00:00:00 2001 From: Olivia Guyot Date: Fri, 6 Dec 2024 14:32:37 +0100 Subject: [PATCH 07/13] [upstream] feat(search): add availableServices field This needed some modifications in the ES code to generate queries --- .../elasticsearch.service.spec.ts | 49 ++++++++++-- .../elasticsearch/elasticsearch.service.ts | 21 ++--- .../src/lib/utils/service/fields.service.ts | 2 + .../src/lib/utils/service/fields.spec.ts | 79 ++++++++++++++++++- .../search/src/lib/utils/service/fields.ts | 55 +++++++++++++ 5 files changed, 186 insertions(+), 20 deletions(-) diff --git a/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.spec.ts b/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.spec.ts index 6f2f158bb..ead303fe8 100644 --- a/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.spec.ts +++ b/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.spec.ts @@ -479,6 +479,37 @@ describe('ElasticsearchService', () => { }, }) }) + it('handle values expressed as reg exp', () => { + const query = service['buildPayloadQuery']( + { + Org: { + '/world.*/': true, + '/*country^[fr|en]/': false, + }, + }, + {}, + [] + ) + expect(query).toMatchObject({ + bool: { + filter: [ + { + terms: { + isTemplate: ['n'], + }, + }, + { + query_string: { + query: 'Org:(/world.*/ OR -/*country^[fr|en]/)', + }, + }, + { + ids: { values: [] }, + }, + ], + }, + }) + }) describe('any has special characters', () => { let query beforeEach(() => { @@ -937,14 +968,16 @@ describe('ElasticsearchService', () => { ).toStrictEqual({ myFilters: { filters: { - filter1: { - query_string: { query: 'field1:(100)' }, - }, - filter2: { - query_string: { query: 'field2:("value1" OR "value3")' }, - }, - filter3: { - query_string: { query: 'my own query' }, + filters: { + filter1: { + query_string: { query: 'field1:(100)' }, + }, + filter2: { + query_string: { query: 'field2:("value1" OR "value3")' }, + }, + filter3: { + query_string: { query: 'my own query' }, + }, }, }, }, diff --git a/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts b/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts index 6562b61af..28d9442b6 100644 --- a/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +++ b/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts @@ -220,6 +220,7 @@ export class ElasticsearchService { private filtersToQuery( filters: FieldFilters | FiltersAggregationParams | string ): FilterQuery { + const addQuote = (key: string) => (/^\/.+\/$/.test(key) ? key : `"${key}"`) const makeQuery = (filter: FieldFilter): string => { if (typeof filter === 'string') { return filter @@ -227,9 +228,9 @@ export class ElasticsearchService { return Object.keys(filter) .map((key) => { if (filter[key] === true) { - return `"${key}"` + return addQuote(key) } - return `-"${key}"` + return `-${addQuote(key)}` }) .join(' OR ') } @@ -506,13 +507,15 @@ export class ElasticsearchService { switch (aggregation.type) { case 'filters': return { - filters: Object.keys(aggregation.filters).reduce((prev, curr) => { - const filter = aggregation.filters[curr] - return { - ...prev, - [curr]: this.filtersToQuery(filter)[0], - } - }, {}), + filters: { + filters: Object.keys(aggregation.filters).reduce((prev, curr) => { + const filter = aggregation.filters[curr] + return { + ...prev, + [curr]: this.filtersToQuery(filter)[0], + } + }, {}), + }, } case 'terms': return { diff --git a/libs/feature/search/src/lib/utils/service/fields.service.ts b/libs/feature/search/src/lib/utils/service/fields.service.ts index d79ed2268..84693022a 100644 --- a/libs/feature/search/src/lib/utils/service/fields.service.ts +++ b/libs/feature/search/src/lib/utils/service/fields.service.ts @@ -1,6 +1,7 @@ import { Injectable, Injector } from '@angular/core' import { AbstractSearchField, + AvailableServicesField, DateRangeSearchField, FieldValue, FullTextSearchField, @@ -91,6 +92,7 @@ export class FieldsService { ), user: new UserSearchField(this.injector), changeDate: new DateRangeSearchField('changeDate', this.injector, 'desc'), + availableServices: new AvailableServicesField(this.injector), } as Record get supportedFields() { diff --git a/libs/feature/search/src/lib/utils/service/fields.spec.ts b/libs/feature/search/src/lib/utils/service/fields.spec.ts index 4d8ba4138..d166c8d5a 100644 --- a/libs/feature/search/src/lib/utils/service/fields.spec.ts +++ b/libs/feature/search/src/lib/utils/service/fields.spec.ts @@ -1,13 +1,14 @@ import { lastValueFrom, of } from 'rxjs' import { AbstractSearchField, + AvailableServicesField, FullTextSearchField, IsSpatialSearchField, - TranslatedSearchField, LicenseSearchField, + MultilingualSearchField, OrganizationSearchField, SimpleSearchField, - MultilingualSearchField, + TranslatedSearchField, UserSearchField, DateRangeSearchField, } from './fields' @@ -30,7 +31,6 @@ class ElasticsearchServiceMock { class RecordsRepositoryMock { aggregate = jest.fn((aggregations) => { const aggName = Object.keys(aggregations)[0] - const sortType = aggregations[aggName].sort[1] if (aggName.startsWith('is')) return of({ [aggName]: { @@ -119,6 +119,21 @@ class RecordsRepositoryMock { ], }, }) + if (aggName === 'availableServices') + return of({ + availableServices: { + buckets: [ + { + term: 'view', + count: 10, + }, + { + term: 'download', + count: 5, + }, + ], + }, + }) const buckets = [ { term: 'First value', @@ -137,6 +152,7 @@ class RecordsRepositoryMock { count: 1, }, ] + const sortType = aggregations[aggName].sort?.[1] if (sortType === 'count') { buckets.sort((a, b) => b.count - a.count) } @@ -775,6 +791,7 @@ describe('search fields implementations', () => { }) }) }) + describe('UserSearchField', () => { beforeEach(() => { searchField = new UserSearchField(injector) @@ -812,4 +829,60 @@ describe('search fields implementations', () => { }) }) }) + + describe('AvailableServicesField', () => { + beforeEach(() => { + searchField = new AvailableServicesField(injector) + }) + describe('#getAvailableValues', () => { + let values + beforeEach(async () => { + values = await lastValueFrom(searchField.getAvailableValues()) + }) + it('returns the available values', () => { + expect(values).toEqual([ + { + label: 'search.filters.availableServices.view (10)', + value: 'view', + }, + { + label: 'search.filters.availableServices.download (5)', + value: 'download', + }, + ]) + }) + }) + describe('#getFiltersForValues', () => { + let filter + beforeEach(async () => { + filter = await lastValueFrom( + searchField.getFiltersForValues(['view', 'download']) + ) + }) + it('returns filter for both values', () => { + expect(filter).toEqual({ + linkProtocol: { + '/OGC:WFS.*/': true, + '/OGC:WMT?S.*/': true, + }, + }) + }) + }) + describe('#getValuesForFilters', () => { + let values + beforeEach(async () => { + values = await lastValueFrom( + searchField.getValuesForFilter({ + linkProtocol: { + '/OGC:WFS.*/': false, + '/OGC:WMT?S.*/': true, + }, + }) + ) + }) + it('returns value with an enabled filter', () => { + expect(values).toEqual(['view']) + }) + }) + }) }) diff --git a/libs/feature/search/src/lib/utils/service/fields.ts b/libs/feature/search/src/lib/utils/service/fields.ts index 1d8422ba0..e0e2797b8 100644 --- a/libs/feature/search/src/lib/utils/service/fields.ts +++ b/libs/feature/search/src/lib/utils/service/fields.ts @@ -9,6 +9,7 @@ import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform. import { AggregationBuckets, AggregationsParams, + FieldFilter, FieldFilterByExpression, FieldFilters, TermBucket, @@ -425,3 +426,57 @@ export class DateRangeSearchField extends SimpleSearchField { return 'dateRange' } } + +marker('search.filters.availableServices.view') +marker('search.filters.availableServices.download') + +export class AvailableServicesField extends SimpleSearchField { + private translateService = this.injector.get(TranslateService) + + constructor(injector: Injector) { + super('availableServices', injector, 'asc') + } + + linkProtocolViewFilter = '/OGC:WMT?S.*/' + linkProtocolDownloadFilter = '/OGC:WFS.*/' + + protected async getBucketLabel(bucket: TermBucket) { + return firstValueFrom( + this.translateService.get( + `search.filters.availableServices.${bucket.term}` + ) + ) + } + + protected getAggregations(): AggregationsParams { + return { + availableServices: { + type: 'filters', + filters: { + view: `+linkProtocol:${this.linkProtocolViewFilter}`, + download: `+linkProtocol:${this.linkProtocolDownloadFilter}`, + }, + }, + } + } + + getFiltersForValues(values: FieldValue[]): Observable { + const filters: FieldFilter = {} + if (values.includes('view')) filters[this.linkProtocolViewFilter] = true + if (values.includes('download')) + filters[this.linkProtocolDownloadFilter] = true + + return of({ + linkProtocol: filters, + }) + } + + getValuesForFilter(filters: FieldFilters): Observable { + const linkFilter = filters.linkProtocol + if (!linkFilter) return of([]) + const values = [] + if (linkFilter[this.linkProtocolViewFilter]) values.push('view') + if (linkFilter[this.linkProtocolDownloadFilter]) values.push('download') + return of(values) + } +} From a74d8d62ab93052b60964acfa9477e8694d00c00 Mon Sep 17 00:00:00 2001 From: Olivia Guyot Date: Fri, 6 Dec 2024 14:33:53 +0100 Subject: [PATCH 08/13] geocat: define global props when importing the web components module --- .../src/app/webcomponents.module.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/webcomponents/src/app/webcomponents.module.ts b/apps/webcomponents/src/app/webcomponents.module.ts index dc9db6efa..1b29facb8 100644 --- a/apps/webcomponents/src/app/webcomponents.module.ts +++ b/apps/webcomponents/src/app/webcomponents.module.ts @@ -1,6 +1,11 @@ import { OverlayContainer } from '@angular/cdk/overlay' import { CommonModule } from '@angular/common' -import { CUSTOM_ELEMENTS_SCHEMA, Injector, NgModule } from '@angular/core' +import { + CUSTOM_ELEMENTS_SCHEMA, + inject, + Injector, + NgModule, +} from '@angular/core' import { createCustomElement } from '@angular/elements' import { BrowserModule } from '@angular/platform-browser' import { Configuration } from '@geonetwork-ui/data-access/gn4' @@ -39,6 +44,8 @@ import { provideGn4 } from '@geonetwork-ui/api/repository' import { GnFigureDatasetsComponent } from './components/gn-figure-datasets/gn-figure-datasets.component' import { UiDatavizModule } from '@geonetwork-ui/ui/dataviz' import { GnDatasetViewMapComponent } from './components/gn-dataset-view-map/gn-dataset-view-map.component' +import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface' +import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface' const CUSTOM_ELEMENTS: [new (...args) => BaseComponent, string][] = [ [GnFacetsComponent, 'gn-facets'], @@ -120,6 +127,14 @@ export class WebcomponentsModule { customElements.define(ceTagName, customElement) } }) + + // define global props + const recordsRepository = inject(RecordsRepositoryInterface) + const platformService = inject(PlatformServiceInterface) + window['GNUI'] = { + recordsRepository, + platformService, + } } // eslint-disable-next-line @angular-eslint/no-empty-lifecycle-method, @angular-eslint/use-lifecycle-interface, @typescript-eslint/no-empty-function From 7c4dccc7815828c6fcd1c40cdb0a0c2b0eceb68a Mon Sep 17 00:00:00 2001 From: Olivia Guyot Date: Fri, 29 Nov 2024 12:14:23 +0100 Subject: [PATCH 09/13] add geoadmin-demo app --- .prettierignore | 2 + apps/geoadmin-demo/.eslintrc.json | 36 +++++++++ apps/geoadmin-demo/jest.config.ts | 22 ++++++ apps/geoadmin-demo/project.json | 101 ++++++++++++++++++++++++ apps/geoadmin-demo/src/assets/.gitkeep | 0 apps/geoadmin-demo/src/favicon.ico | Bin 0 -> 15086 bytes apps/geoadmin-demo/src/index.html | 43 ++++++++++ apps/geoadmin-demo/src/main.ts | 1 + apps/geoadmin-demo/src/styles.css | 34 ++++++++ apps/geoadmin-demo/src/test-setup.ts | 8 ++ apps/geoadmin-demo/tsconfig.app.json | 10 +++ apps/geoadmin-demo/tsconfig.editor.json | 7 ++ apps/geoadmin-demo/tsconfig.json | 32 ++++++++ apps/geoadmin-demo/tsconfig.spec.json | 16 ++++ 14 files changed, 312 insertions(+) create mode 100644 apps/geoadmin-demo/.eslintrc.json create mode 100644 apps/geoadmin-demo/jest.config.ts create mode 100644 apps/geoadmin-demo/project.json create mode 100644 apps/geoadmin-demo/src/assets/.gitkeep create mode 100644 apps/geoadmin-demo/src/favicon.ico create mode 100644 apps/geoadmin-demo/src/index.html create mode 100644 apps/geoadmin-demo/src/main.ts create mode 100644 apps/geoadmin-demo/src/styles.css create mode 100644 apps/geoadmin-demo/src/test-setup.ts create mode 100644 apps/geoadmin-demo/tsconfig.app.json create mode 100644 apps/geoadmin-demo/tsconfig.editor.json create mode 100644 apps/geoadmin-demo/tsconfig.json create mode 100644 apps/geoadmin-demo/tsconfig.spec.json diff --git a/.prettierignore b/.prettierignore index 3f0fb7ea3..177e4d55a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,3 +7,5 @@ # OpenAPI generated specs **/spec.yaml /.nx + +.angular diff --git a/apps/geoadmin-demo/.eslintrc.json b/apps/geoadmin-demo/.eslintrc.json new file mode 100644 index 000000000..c9b6b193a --- /dev/null +++ b/apps/geoadmin-demo/.eslintrc.json @@ -0,0 +1,36 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts"], + "rules": { + "@angular-eslint/directive-selector": [ + "error", + { + "type": "attribute", + "prefix": "geoadmin", + "style": "camelCase" + } + ], + "@angular-eslint/component-selector": [ + "error", + { + "type": "element", + "prefix": "geoadmin-root", + "style": "kebab-case" + } + ] + }, + "extends": [ + "plugin:@nx/angular", + "plugin:@angular-eslint/template/process-inline-templates" + ] + }, + { + "files": ["*.html"], + "extends": ["plugin:@nx/angular-template"], + "rules": {} + } + ] +} diff --git a/apps/geoadmin-demo/jest.config.ts b/apps/geoadmin-demo/jest.config.ts new file mode 100644 index 000000000..c6a603e7d --- /dev/null +++ b/apps/geoadmin-demo/jest.config.ts @@ -0,0 +1,22 @@ +/* eslint-disable */ +export default { + displayName: 'geoadmin-demo', + preset: '../../jest.preset.js', + setupFilesAfterEnv: ['/src/test-setup.ts'], + coverageDirectory: '../../coverage/apps/geoadmin-demo', + transform: { + '^.+\\.(ts|mjs|js|html)$': [ + 'jest-preset-angular', + { + tsconfig: '/tsconfig.spec.json', + stringifyContentPathRegex: '\\.(html|svg)$', + }, + ], + }, + transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], + snapshotSerializers: [ + 'jest-preset-angular/build/serializers/no-ng-attributes', + 'jest-preset-angular/build/serializers/ng-snapshot', + 'jest-preset-angular/build/serializers/html-comment', + ], +} diff --git a/apps/geoadmin-demo/project.json b/apps/geoadmin-demo/project.json new file mode 100644 index 000000000..9a304ee1a --- /dev/null +++ b/apps/geoadmin-demo/project.json @@ -0,0 +1,101 @@ +{ + "name": "geoadmin-demo", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "projectType": "application", + "prefix": "geonetwork-ui", + "sourceRoot": "apps/geoadmin-demo/src", + "tags": [], + "targets": { + "build": { + "executor": "@angular-devkit/build-angular:browser", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/apps/geoadmin-demo", + "index": "apps/geoadmin-demo/src/index.html", + "main": "apps/geoadmin-demo/src/main.ts", + "polyfills": ["zone.js"], + "tsConfig": "apps/geoadmin-demo/tsconfig.app.json", + "assets": [ + "apps/geoadmin-demo/src/favicon.ico", + "apps/geoadmin-demo/src/assets" + ], + "styles": ["apps/geoadmin-demo/src/styles.css"], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "executor": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "geoadmin-demo:build:production" + }, + "development": { + "browserTarget": "geoadmin-demo:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "executor": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "geoadmin-demo:build" + } + }, + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": [ + "apps/geoadmin-demo/**/*.ts", + "apps/geoadmin-demo/**/*.html" + ] + } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "apps/geoadmin-demo/jest.config.ts", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + }, + "serve-static": { + "executor": "@nx/web:file-server", + "options": { + "buildTarget": "geoadmin-demo:build" + } + } + } +} diff --git a/apps/geoadmin-demo/src/assets/.gitkeep b/apps/geoadmin-demo/src/assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/geoadmin-demo/src/favicon.ico b/apps/geoadmin-demo/src/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..317ebcb2336e0833a22dddf0ab287849f26fda57 GIT binary patch literal 15086 zcmeI332;U^%p|z7g|#(P)qFEA@4f!_@qOK2 z_lJl}!lhL!VT_U|uN7%8B2iKH??xhDa;*`g{yjTFWHvXn;2s{4R7kH|pKGdy(7z!K zgftM+Ku7~24TLlh(!g)gz|foI94G^t2^IO$uvX$3(OR0<_5L2sB)lMAMy|+`xodJ{ z_Uh_1m)~h?a;2W{dmhM;u!YGo=)OdmId_B<%^V^{ovI@y`7^g1_V9G}*f# zNzAtvou}I!W1#{M^@ROc(BZ! z+F!!_aR&Px3_reO(EW+TwlW~tv*2zr?iP7(d~a~yA|@*a89IUke+c472NXM0wiX{- zl`UrZC^1XYyf%1u)-Y)jj9;MZ!SLfd2Hl?o|80Su%Z?To_=^g_Jt0oa#CT*tjx>BI z16wec&AOWNK<#i0Qd=1O$fymLRoUR*%;h@*@v7}wApDl^w*h}!sYq%kw+DKDY)@&A z@9$ULEB3qkR#85`lb8#WZw=@})#kQig9oqy^I$dj&k4jU&^2(M3q{n1AKeGUKPFbr z1^<)aH;VsG@J|B&l>UtU#Ejv3GIqERzYgL@UOAWtW<{p#zy`WyJgpCy8$c_e%wYJL zyGHRRx38)HyjU3y{-4z6)pzb>&Q1pR)B&u01F-|&Gx4EZWK$nkUkOI|(D4UHOXg_- zw{OBf!oWQUn)Pe(=f=nt=zkmdjpO^o8ZZ9o_|4tW1ni+Un9iCW47*-ut$KQOww!;u z`0q)$s6IZO!~9$e_P9X!hqLxu`fpcL|2f^I5d4*a@Dq28;@2271v_N+5HqYZ>x;&O z05*7JT)mUe&%S0@UD)@&8SmQrMtsDfZT;fkdA!r(S=}Oz>iP)w=W508=Rc#nNn7ym z1;42c|8($ALY8#a({%1#IXbWn9-Y|0eDY$_L&j{63?{?AH{);EzcqfydD$@-B`Y3<%IIj7S7rK_N}je^=dEk%JQ4c z!tBdTPE3Tse;oYF>cnrapWq*o)m47X1`~6@(!Y29#>-#8zm&LXrXa(3=7Z)ElaQqj z-#0JJy3Fi(C#Rx(`=VXtJ63E2_bZGCz+QRa{W0e2(m3sI?LOcUBx)~^YCqZ{XEPX)C>G>U4tfqeH8L(3|pQR*zbL1 zT9e~4Tb5p9_G}$y4t`i*4t_Mr9QYvL9C&Ah*}t`q*}S+VYh0M6GxTTSXI)hMpMpIq zD1ImYqJLzbj0}~EpE-aH#VCH_udYEW#`P2zYmi&xSPs_{n6tBj=MY|-XrA;SGA_>y zGtU$?HXm$gYj*!N)_nQ59%lQdXtQZS3*#PC-{iB_sm+ytD*7j`D*k(P&IH2GHT}Eh z5697eQECVIGQAUe#eU2I!yI&%0CP#>%6MWV z@zS!p@+Y1i1b^QuuEF*13CuB zu69dve5k7&Wgb+^s|UB08Dr3u`h@yM0NTj4h7MnHo-4@xmyr7(*4$rpPwsCDZ@2be zRz9V^GnV;;?^Lk%ynzq&K(Aix`mWmW`^152Hoy$CTYVehpD-S1-W^#k#{0^L`V6CN+E z!w+xte;2vu4AmVNEFUOBmrBL>6MK@!O2*N|2=d|Y;oN&A&qv=qKn73lDD zI(+oJAdgv>Yr}8(&@ZuAZE%XUXmX(U!N+Z_sjL<1vjy1R+1IeHt`79fnYdOL{$ci7 z%3f0A*;Zt@ED&Gjm|OFTYBDe%bbo*xXAQsFz+Q`fVBH!N2)kaxN8P$c>sp~QXnv>b zwq=W3&Mtmih7xkR$YA)1Yi?avHNR6C99!u6fh=cL|KQ&PwF!n@ud^n(HNIImHD!h87!i*t?G|p0o+eelJ?B@A64_9%SBhNaJ64EvKgD&%LjLCYnNfc; znj?%*p@*?dq#NqcQFmmX($wms@CSAr9#>hUR^=I+=0B)vvGX%T&#h$kmX*s=^M2E!@N9#m?LhMvz}YB+kd zG~mbP|D(;{s_#;hsKK9lbVK&Lo734x7SIFJ9V_}2$@q?zm^7?*XH94w5Qae{7zOMUF z^?%F%)c1Y)Q?Iy?I>knw*8gYW#ok|2gdS=YYZLiD=CW|Nj;n^x!=S#iJ#`~Ld79+xXpVmUK^B(xO_vO!btA9y7w3L3-0j-y4 z?M-V{%z;JI`bk7yFDcP}OcCd*{Q9S5$iGA7*E1@tfkyjAi!;wP^O71cZ^Ep)qrQ)N z#wqw0_HS;T7x3y|`P==i3hEwK%|>fZ)c&@kgKO1~5<5xBSk?iZV?KI6&i72H6S9A* z=U(*e)EqEs?Oc04)V-~K5AUmh|62H4*`UAtItO$O(q5?6jj+K^oD!04r=6#dsxp?~}{`?&sXn#q2 zGuY~7>O2=!u@@Kfu7q=W*4egu@qPMRM>(eyYyaIE<|j%d=iWNdGsx%c!902v#ngNg z@#U-O_4xN$s_9?(`{>{>7~-6FgWpBpqXb`Ydc3OFL#&I}Irse9F_8R@4zSS*Y*o*B zXL?6*Aw!AfkNCgcr#*yj&p3ZDe2y>v$>FUdKIy_2N~}6AbHc7gA3`6$g@1o|dE>vz z4pl(j9;kyMsjaw}lO?(?Xg%4k!5%^t#@5n=WVc&JRa+XT$~#@rldvN3S1rEpU$;XgxVny7mki3 z-Hh|jUCHrUXuLr!)`w>wgO0N%KTB-1di>cj(x3Bav`7v z3G7EIbU$z>`Nad7Rk_&OT-W{;qg)-GXV-aJT#(ozdmnA~Rq3GQ_3mby(>q6Ocb-RgTUhTN)))x>m&eD;$J5Bg zo&DhY36Yg=J=$Z>t}RJ>o|@hAcwWzN#r(WJ52^g$lh^!63@hh+dR$&_dEGu&^CR*< z!oFqSqO@>xZ*nC2oiOd0eS*F^IL~W-rsrO`J`ej{=ou_q^_(<$&-3f^J z&L^MSYWIe{&pYq&9eGaArA~*kA + + + + Geoadmin + Geocat integration demo + + + + + + + + + + + + + diff --git a/apps/geoadmin-demo/src/main.ts b/apps/geoadmin-demo/src/main.ts new file mode 100644 index 000000000..01af04e78 --- /dev/null +++ b/apps/geoadmin-demo/src/main.ts @@ -0,0 +1 @@ +// bla diff --git a/apps/geoadmin-demo/src/styles.css b/apps/geoadmin-demo/src/styles.css new file mode 100644 index 000000000..85944f45b --- /dev/null +++ b/apps/geoadmin-demo/src/styles.css @@ -0,0 +1,34 @@ +/* You can add global styles to this file, and also import other style files */ +html, +body { + height: 100%; +} +body { + margin: 0; + position: relative; +} + +#geoadmin-root { + width: 100%; + height: calc(100% - 4px); + border: 0; +} + +gn-search-input { + position: absolute; + left: 20px; + top: 55px; + width: 40%; + background: white; +} + +gn-results-list { + position: absolute; + left: 20px; + top: 125px; + bottom: 20px; + width: 40%; + padding: 8px; + background: white; + overflow: auto; +} diff --git a/apps/geoadmin-demo/src/test-setup.ts b/apps/geoadmin-demo/src/test-setup.ts new file mode 100644 index 000000000..a5e675acf --- /dev/null +++ b/apps/geoadmin-demo/src/test-setup.ts @@ -0,0 +1,8 @@ +// @ts-expect-error https://thymikee.github.io/jest-preset-angular/docs/getting-started/test-environment +globalThis.ngJest = { + testEnvironmentOptions: { + errorOnUnknownElements: true, + errorOnUnknownProperties: true, + }, +} +import 'jest-preset-angular/setup-jest' diff --git a/apps/geoadmin-demo/tsconfig.app.json b/apps/geoadmin-demo/tsconfig.app.json new file mode 100644 index 000000000..fff4a41d4 --- /dev/null +++ b/apps/geoadmin-demo/tsconfig.app.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": [] + }, + "files": ["src/main.ts"], + "include": ["src/**/*.d.ts"], + "exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"] +} diff --git a/apps/geoadmin-demo/tsconfig.editor.json b/apps/geoadmin-demo/tsconfig.editor.json new file mode 100644 index 000000000..8ae117d96 --- /dev/null +++ b/apps/geoadmin-demo/tsconfig.editor.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*.ts"], + "compilerOptions": { + "types": ["jest", "node"] + } +} diff --git a/apps/geoadmin-demo/tsconfig.json b/apps/geoadmin-demo/tsconfig.json new file mode 100644 index 000000000..e01cf19bd --- /dev/null +++ b/apps/geoadmin-demo/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "es2022", + "useDefineForClassFields": false, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + }, + { + "path": "./tsconfig.editor.json" + } + ], + "extends": "../../tsconfig.base.json", + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +} diff --git a/apps/geoadmin-demo/tsconfig.spec.json b/apps/geoadmin-demo/tsconfig.spec.json new file mode 100644 index 000000000..53fbfcdc1 --- /dev/null +++ b/apps/geoadmin-demo/tsconfig.spec.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "target": "es2016", + "types": ["jest", "node"] + }, + "files": ["src/test-setup.ts"], + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} From 3749bb3a13a625a68dc8fced9502dd65204519ec Mon Sep 17 00:00:00 2001 From: Olivia Guyot Date: Wed, 4 Dec 2024 18:19:37 +0100 Subject: [PATCH 10/13] temp: adjustements to workflow for geocat-gpf * build docker image for geocat-gpf branch * deploy geoadmin-demo app --- .github/workflows/artifacts.yml | 1 + .github/workflows/deploy.yml | 82 +++------------------------------ 2 files changed, 7 insertions(+), 76 deletions(-) diff --git a/.github/workflows/artifacts.yml b/.github/workflows/artifacts.yml index b92123156..b38607543 100644 --- a/.github/workflows/artifacts.yml +++ b/.github/workflows/artifacts.yml @@ -7,6 +7,7 @@ on: push: branches: - geocat + - geocat-gpf # TEMP release: types: [published] issue_comment: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 92a5043f8..c02a59d7c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -4,52 +4,23 @@ run-name: 🚀 Deploy to GitHub Pages for ${{ github.event_name == 'issue_commen env: NODE_VERSION: 18.16.1 -# This workflow runs whenever the "deploy affected apps" checkbox is checked (for PR) -# or on every push to main +# This workflow deploys the geoadmin demo app on: push: branches: - - geocat - issue_comment: - types: - - edited - -concurrency: - group: deploy-${{ github.ref }} - cancel-in-progress: true + - geocat-gpf jobs: - checks: - if: github.event_name != 'issue_comment' || github.event.issue.pull_request - name: Check whether a deploy was requested on a PR - runs-on: ubuntu-latest - outputs: - shouldRun: ${{ github.event_name != 'issue_comment' || (contains(github.event.changes.body.from, '- [ ] 🚀 Build and deploy storybook and demo on GitHub Pages') && contains(github.event.comment.body, '- [x] 🚀 Build and deploy storybook and demo on GitHub Pages')) || '' }} - ref: ${{ github.event_name == 'issue_comment' && steps.comment-branch.outputs.head_ref || '' }} - - steps: - - uses: xt0rted/pull-request-comment-branch@v1 - if: github.event_name == 'issue_comment' - id: comment-branch gh-pages: - needs: checks - if: github.event_name != 'issue_comment' || needs.checks.outputs.shouldRun - name: Deploy docs, apps, Storybook to GitHub Pages + name: Deploy geoadmin demo runs-on: ubuntu-latest env: - BRANCH_NAME: ${{needs.checks.outputs.ref || 'geocat'}} + BRANCH_NAME: ${{'geocat-gpf'}} steps: - - name: Dump GitHub event - env: - GITHUB_CONTEXT: ${{ toJson(github.event) }} - run: echo "$GITHUB_CONTEXT" - - name: Checkout uses: actions/checkout@v2 - with: - ref: ${{ needs.checks.outputs.ref }} - name: Use Node.js ${{ env.NODE_VERSION }} uses: actions/setup-node@v3 @@ -57,55 +28,14 @@ jobs: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - - name: add initial comment - uses: thollander/actions-comment-pull-request@v2 - if: github.event_name == 'issue_comment' - with: - message: 'GitHub Pages links: - - - 🚧 building in progress... 🚧' - comment_tag: github-links - pr_number: ${{ github.event.issue.number }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Install run: npm ci - - name: Build storybook - run: npm run build:storybook - - - name: Build demo & web components - run: npm run build:demo - - - name: Build metadata-converter app - if: github.event_name != 'issue_comment' # This is not done on PR, only on main branch - run: npx nx build metadata-converter --prod --base-href=./ - - - name: Build docs - run: npm run docs:build -- --base=/geonetwork-ui/${{env.BRANCH_NAME}}/docs/ && mkdir -p dist/docs && mv docs/.vitepress/dist/* dist/docs - - # FIXME: restore a system for testing web components but faster/lighter than storybook - # - name: Build storybook for web components - # run: npm run build:storybook-wc + - name: Build geoadmin-demo app + run: npx nx build geoadmin-demo --prod --base-href=./ - name: Deploy to directory ${{ env.BRANCH_NAME }} run: | git config --global user.name "github-actions[bot]" git config --global user.email "github-actions[bot]@users.noreply.github.com" npx gh-pages --dist dist/ --dest ${{env.BRANCH_NAME}} --remove "${{env.BRANCH_NAME}}/**" --no-history --repo "https://${GITHUB_ACTOR}:${{secrets.GITHUB_TOKEN}}@github.com/${GITHUB_REPOSITORY}.git" - - - name: update PR comment - uses: thollander/actions-comment-pull-request@v2 - if: github.event_name == 'issue_comment' - with: - message: 'GitHub Pages links: - - * (Documentation)[https://geonetwork.github.io/geonetwork-ui/${{env.BRANCH_NAME}}/docs/] - - * (Web components demo)[https://geonetwork.github.io/geonetwork-ui/${{env.BRANCH_NAME}}/demo/webcomponents/] - - * (UI components storybook)[https://geonetwork.github.io/geonetwork-ui/${{env.BRANCH_NAME}}/storybook/demo/]' - comment_tag: github-links - pr_number: ${{ github.event.issue.number }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 91e4058c0672a18e93f181f8f40ba80423a903c4 Mon Sep 17 00:00:00 2001 From: Olivia Guyot Date: Mon, 9 Dec 2024 13:46:11 +0100 Subject: [PATCH 11/13] add gn-record-view web component --- apps/geoadmin-demo/src/index.html | 23 +++++-- apps/geoadmin-demo/src/styles.css | 13 +++- apps/geoadmin-demo/src/test-setup.ts | 15 ++-- .../gn-record-view.component.css | 10 +++ .../gn-record-view.component.html | 68 +++++++++++++++++++ .../gn-record-view.component.ts | 53 +++++++++++++++ .../gn-record-view/gn-record-view.sample.html | 51 ++++++++++++++ .../src/app/webcomponents.module.ts | 33 ++++++++- 8 files changed, 253 insertions(+), 13 deletions(-) create mode 100644 apps/webcomponents/src/app/components/gn-record-view/gn-record-view.component.css create mode 100644 apps/webcomponents/src/app/components/gn-record-view/gn-record-view.component.html create mode 100644 apps/webcomponents/src/app/components/gn-record-view/gn-record-view.component.ts create mode 100644 apps/webcomponents/src/app/components/gn-record-view/gn-record-view.sample.html diff --git a/apps/geoadmin-demo/src/index.html b/apps/geoadmin-demo/src/index.html index 51f87eed0..7f230b875 100644 --- a/apps/geoadmin-demo/src/index.html +++ b/apps/geoadmin-demo/src/index.html @@ -13,19 +13,34 @@ id="geoadmin-root" src="https://map.geo.admin.ch/#embed?bgLayer=ch.swisstopo.pixelkarte-grau" > + + + + - + record-id="417e9f31-d9d7-4165-9a5c-2f12b7191926" + > diff --git a/apps/geoadmin-demo/src/styles.css b/apps/geoadmin-demo/src/styles.css index 85944f45b..bcc021bda 100644 --- a/apps/geoadmin-demo/src/styles.css +++ b/apps/geoadmin-demo/src/styles.css @@ -14,7 +14,7 @@ body { border: 0; } -gn-search-input { +.search-input { position: absolute; left: 20px; top: 55px; @@ -32,3 +32,14 @@ gn-results-list { background: white; overflow: auto; } + +gn-record-view { + position: absolute; + right: 20px; + top: 125px; + bottom: 20px; + width: 40%; + padding: 8px; + background: white; + overflow: auto; +} diff --git a/apps/geoadmin-demo/src/test-setup.ts b/apps/geoadmin-demo/src/test-setup.ts index a5e675acf..5626c49f2 100644 --- a/apps/geoadmin-demo/src/test-setup.ts +++ b/apps/geoadmin-demo/src/test-setup.ts @@ -1,8 +1,9 @@ -// @ts-expect-error https://thymikee.github.io/jest-preset-angular/docs/getting-started/test-environment -globalThis.ngJest = { - testEnvironmentOptions: { - errorOnUnknownElements: true, - errorOnUnknownProperties: true, - }, -} import 'jest-preset-angular/setup-jest' +import '../../../jest.setup' + +class ResizeObserverMock { + observe = jest.fn() + unobserve = jest.fn() +} + +;(window as any).ResizeObserver = ResizeObserverMock diff --git a/apps/webcomponents/src/app/components/gn-record-view/gn-record-view.component.css b/apps/webcomponents/src/app/components/gn-record-view/gn-record-view.component.css new file mode 100644 index 000000000..eeaf25b47 --- /dev/null +++ b/apps/webcomponents/src/app/components/gn-record-view/gn-record-view.component.css @@ -0,0 +1,10 @@ +@import '../../../styles.css'; + +.mdc-menu-surface.mat-mdc-autocomplete-panel { + margin-top: 10px !important; + border-radius: 8px; +} + +:host { + display: block; +} diff --git a/apps/webcomponents/src/app/components/gn-record-view/gn-record-view.component.html b/apps/webcomponents/src/app/components/gn-record-view/gn-record-view.component.html new file mode 100644 index 000000000..e7fc7b22a --- /dev/null +++ b/apps/webcomponents/src/app/components/gn-record-view/gn-record-view.component.html @@ -0,0 +1,68 @@ + + +
+ {{ record.title }} +
+ + + + + + +
+ record.metadata.download + +
+ + + + +
+ record.metadata.links + +
+ + + + +
+ record.metadata.api + +
+ + + +
+
diff --git a/apps/webcomponents/src/app/components/gn-record-view/gn-record-view.component.ts b/apps/webcomponents/src/app/components/gn-record-view/gn-record-view.component.ts new file mode 100644 index 000000000..357636d3a --- /dev/null +++ b/apps/webcomponents/src/app/components/gn-record-view/gn-record-view.component.ts @@ -0,0 +1,53 @@ +import { + ChangeDetectionStrategy, + Component, + Injector, + Input, + OnInit, + ViewEncapsulation, +} from '@angular/core' +import { SearchFacade } from '@geonetwork-ui/feature/search' +import { BaseComponent } from '../base.component' +import { Observable } from 'rxjs' +import { + CatalogRecord, + OnlineResource, +} from '@geonetwork-ui/common/domain/model/record' + +// TODO in this component: +// - Support metadata quality option +// - show data preview + +@Component({ + selector: 'wc-gn-record-view', + templateUrl: './gn-record-view.component.html', + styleUrls: ['./gn-record-view.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.ShadowDom, + providers: [SearchFacade], +}) +export class GnRecordViewComponent extends BaseComponent implements OnInit { + @Input() recordId!: string + + record$: Observable + + constructor(injector: Injector) { + super(injector) + } + + ngOnInit() { + super.ngOnInit() + // todo + this.record$ = this.recordsRepository.getRecord(this.recordId) + } + + getDownloads(onlineResources: OnlineResource[]) { + return onlineResources.filter((d) => d.type === 'download') + } + getAPIs(onlineResources: OnlineResource[]) { + return onlineResources.filter((d) => d.type === 'service') + } + getLinks(onlineResources: OnlineResource[]) { + return onlineResources.filter((d) => d.type === 'link') + } +} diff --git a/apps/webcomponents/src/app/components/gn-record-view/gn-record-view.sample.html b/apps/webcomponents/src/app/components/gn-record-view/gn-record-view.sample.html new file mode 100644 index 000000000..52facab6d --- /dev/null +++ b/apps/webcomponents/src/app/components/gn-record-view/gn-record-view.sample.html @@ -0,0 +1,51 @@ + + + + + Web Component Demo + + + + + + + + +
+
+ + +
+
+
+ + diff --git a/apps/webcomponents/src/app/webcomponents.module.ts b/apps/webcomponents/src/app/webcomponents.module.ts index 1b29facb8..5a0a8b342 100644 --- a/apps/webcomponents/src/app/webcomponents.module.ts +++ b/apps/webcomponents/src/app/webcomponents.module.ts @@ -11,7 +11,18 @@ import { BrowserModule } from '@angular/platform-browser' import { Configuration } from '@geonetwork-ui/data-access/gn4' import { FeatureRecordModule } from '@geonetwork-ui/feature/record' import { FeatureSearchModule } from '@geonetwork-ui/feature/search' -import { UiElementsModule } from '@geonetwork-ui/ui/elements' +import { + ApiCardComponent, + ContentGhostComponent, + DownloadItemComponent, + DownloadsListComponent, + ImageOverlayPreviewComponent, + LinkCardComponent, + MetadataContactComponent, + MetadataInfoComponent, + MetadataQualityComponent, + UiElementsModule, +} from '@geonetwork-ui/ui/elements' import { UiInputsModule } from '@geonetwork-ui/ui/inputs' import { UiSearchModule } from '@geonetwork-ui/ui/search' import { @@ -46,6 +57,12 @@ import { UiDatavizModule } from '@geonetwork-ui/ui/dataviz' import { GnDatasetViewMapComponent } from './components/gn-dataset-view-map/gn-dataset-view-map.component' import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface' import { PlatformServiceInterface } from '@geonetwork-ui/common/domain/platform.service.interface' +import { GnRecordViewComponent } from './components/gn-record-view/gn-record-view.component' +import { LetDirective } from '@ngrx/component' +import { + BlockListComponent, + PreviousNextButtonsComponent, +} from '@geonetwork-ui/ui/layout' const CUSTOM_ELEMENTS: [new (...args) => BaseComponent, string][] = [ [GnFacetsComponent, 'gn-facets'], @@ -57,6 +74,7 @@ const CUSTOM_ELEMENTS: [new (...args) => BaseComponent, string][] = [ [GnMapViewerComponent, 'gn-map-viewer'], [GnFigureDatasetsComponent, 'gn-figure-datasets'], [GnDatasetViewMapComponent, 'gn-dataset-view-map'], + [GnRecordViewComponent, 'gn-record-view'], ] @NgModule({ @@ -73,6 +91,7 @@ const CUSTOM_ELEMENTS: [new (...args) => BaseComponent, string][] = [ GnMapViewerComponent, GnFigureDatasetsComponent, GnDatasetViewMapComponent, + GnRecordViewComponent, ], imports: [ CommonModule, @@ -99,6 +118,18 @@ const CUSTOM_ELEMENTS: [new (...args) => BaseComponent, string][] = [ BrowserAnimationsModule, MapStateContainerComponent, LayersPanelComponent, + MetadataInfoComponent, + ContentGhostComponent, + LetDirective, + ImageOverlayPreviewComponent, + MetadataContactComponent, + MetadataQualityComponent, + DownloadsListComponent, + BlockListComponent, + LinkCardComponent, + ApiCardComponent, + DownloadItemComponent, + PreviousNextButtonsComponent, ], providers: [ provideGn4(), From c5507f3357df6a22bdf55d14db2117a02c2b69ef Mon Sep 17 00:00:00 2001 From: ronitjadhav Date: Wed, 18 Dec 2024 12:38:28 +0100 Subject: [PATCH 12/13] Added Datahub Search and record view --- .github/workflows/deploy.yml | 1 - apps/geoadmin-demo/src/index.html | 141 ++++++++++++++++++++---------- apps/geoadmin-demo/src/styles.css | 140 +++++++++++++++++++++++------ 3 files changed, 208 insertions(+), 74 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c02a59d7c..df94b77a6 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -11,7 +11,6 @@ on: - geocat-gpf jobs: - gh-pages: name: Deploy geoadmin demo runs-on: ubuntu-latest diff --git a/apps/geoadmin-demo/src/index.html b/apps/geoadmin-demo/src/index.html index 7f230b875..203dea46c 100644 --- a/apps/geoadmin-demo/src/index.html +++ b/apps/geoadmin-demo/src/index.html @@ -2,57 +2,108 @@ - Geoadmin + Geocat integration demo - + Geoadmin + Geocat Search - - - - - - - - - - +
+
+ +
    +
    + +
    diff --git a/apps/geoadmin-demo/src/styles.css b/apps/geoadmin-demo/src/styles.css index bcc021bda..2ef289ba1 100644 --- a/apps/geoadmin-demo/src/styles.css +++ b/apps/geoadmin-demo/src/styles.css @@ -1,45 +1,129 @@ -/* You can add global styles to this file, and also import other style files */ -html, -body { +body, +html { + margin: 0; + padding: 0; height: 100%; + font-family: Arial, sans-serif; } -body { - margin: 0; +.map-container { position: relative; -} - -#geoadmin-root { width: 100%; - height: calc(100% - 4px); - border: 0; + height: 100vh; } - -.search-input { +.search-container { position: absolute; + top: 60px; left: 20px; - top: 55px; - width: 40%; + width: 300px; + z-index: 10; +} +#search { + width: 100%; + padding: 10px; + margin-bottom: 0; + border: 1px solid #ddd; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} +#results { background: white; + border: 1px solid #ddd; + border-radius: 8px; + list-style: none; + padding: 0; + margin: 0; + max-height: 400px; + overflow-y: auto; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + width: 100%; } -gn-results-list { - position: absolute; - left: 20px; - top: 125px; - bottom: 20px; - width: 40%; - padding: 8px; - background: white; - overflow: auto; +#results li { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px; + border-bottom: 1px solid #eee; + transition: background-color 0.2s; + gap: 10px; +} + +#results li div { + flex: 1; + overflow-wrap: break-word; +} + +#results li:hover { + background-color: #f0f8ff; + transform: scale(1.02); +} + +#results li:last-child { + border-bottom: none; +} + +.result-title { + font-size: 14px; + font-weight: bold; + color: #333; + margin-bottom: 5px; + line-height: 1.2; +} + +.open-btn { + flex-shrink: 0; + background-color: #0f4395; + color: white; + border: none; + padding: 5px 10px; + border-radius: 3px; + cursor: pointer; + transition: background-color 0.3s; } -gn-record-view { +.open-btn:hover { + background-color: #0e3d84; +} + +#geoadmin-root { + width: 100%; + height: 100vh; + border: none; +} +.gn-record-view-container { position: absolute; - right: 20px; - top: 125px; - bottom: 20px; + right: 55px; + top: 40px; + bottom: 40px; width: 40%; - padding: 8px; background: white; + padding: 20px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + border-radius: 8px; overflow: auto; + z-index: 100; +} + +.close-btn { + position: sticky; + top: 0; + margin-left: auto; + margin-right: 8px; + background: #050505; + color: white; + border: none; + border-radius: 50%; + width: 30px; + height: 30px; + font-size: 16px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + z-index: 10; +} + +.close-btn:hover { + background: #d32f2f; } From 65e85e554ab6ea03ad0cce102a90e0f15a5465e1 Mon Sep 17 00:00:00 2001 From: ronitjadhav Date: Mon, 23 Dec 2024 14:03:21 +0100 Subject: [PATCH 13/13] Updated the gn-record-view webcomponent --- .../gn-record-view.component.html | 104 ++++++++++-------- .../gn-record-view.component.ts | 42 ++++--- 2 files changed, 85 insertions(+), 61 deletions(-) diff --git a/apps/webcomponents/src/app/components/gn-record-view/gn-record-view.component.html b/apps/webcomponents/src/app/components/gn-record-view/gn-record-view.component.html index e7fc7b22a..ba12d94ac 100644 --- a/apps/webcomponents/src/app/components/gn-record-view/gn-record-view.component.html +++ b/apps/webcomponents/src/app/components/gn-record-view/gn-record-view.component.html @@ -4,65 +4,75 @@ [showContent]="!!record?.title" >
    - {{ record.title }} + {{ record?.title || 'No Title Available' }}
    - + + - -
    - record.metadata.download - + + +
    +
    + record.metadata.download + +
    + + +
    - - - -
    - record.metadata.links - +
    +
    + record.metadata.links + +
    + + +
    - - - -
    - record.metadata.api - +
    +
    + record.metadata.api + +
    + + +
    - - - + + +

    No record found for the provided ID.

    +
    diff --git a/apps/webcomponents/src/app/components/gn-record-view/gn-record-view.component.ts b/apps/webcomponents/src/app/components/gn-record-view/gn-record-view.component.ts index 357636d3a..ac5fb466b 100644 --- a/apps/webcomponents/src/app/components/gn-record-view/gn-record-view.component.ts +++ b/apps/webcomponents/src/app/components/gn-record-view/gn-record-view.component.ts @@ -8,7 +8,7 @@ import { } from '@angular/core' import { SearchFacade } from '@geonetwork-ui/feature/search' import { BaseComponent } from '../base.component' -import { Observable } from 'rxjs' +import { Observable, map } from 'rxjs' import { CatalogRecord, OnlineResource, @@ -26,28 +26,42 @@ import { encapsulation: ViewEncapsulation.ShadowDom, providers: [SearchFacade], }) -export class GnRecordViewComponent extends BaseComponent implements OnInit { - @Input() recordId!: string - record$: Observable +export class GnRecordViewComponent extends BaseComponent implements OnInit { + @Input() recordId!: string; + record$: Observable; + downloads$: Observable; + links$: Observable; + apis$: Observable; constructor(injector: Injector) { - super(injector) + super(injector); } ngOnInit() { - super.ngOnInit() - // todo - this.record$ = this.recordsRepository.getRecord(this.recordId) + super.ngOnInit(); + this.record$ = this.recordsRepository.getRecord(this.recordId); + + this.downloads$ = this.record$.pipe( + map((record) => this.getDownloads(record?.onlineResources || [])) + ); + this.links$ = this.record$.pipe( + map((record) => this.getLinks(record?.onlineResources || [])) + ); + this.apis$ = this.record$.pipe( + map((record) => this.getAPIs(record?.onlineResources || [])) + ); } - getDownloads(onlineResources: OnlineResource[]) { - return onlineResources.filter((d) => d.type === 'download') + getDownloads(onlineResources: OnlineResource[]): OnlineResource[] { + return onlineResources.filter((resource) => resource.type === 'download'); } - getAPIs(onlineResources: OnlineResource[]) { - return onlineResources.filter((d) => d.type === 'service') + + getLinks(onlineResources: OnlineResource[]): OnlineResource[] { + return onlineResources.filter((resource) => resource.type === 'link'); } - getLinks(onlineResources: OnlineResource[]) { - return onlineResources.filter((d) => d.type === 'link') + + getAPIs(onlineResources: OnlineResource[]): OnlineResource[] { + return onlineResources.filter((resource) => resource.type === 'service'); } }