-
+
+
+
+
+ editor.record.form.field.onlineResource.edit.identifier.error
+
+
-
+
+
- editor.record.form.field.onlineResource.edit.identifier.submit
+ editor.record.form.field.onlineResource.edit.identifier.submit
diff --git a/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.ts b/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.ts
index f32acb6d3b..4dfdb67ac4 100644
--- a/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.ts
+++ b/libs/feature/editor/src/lib/components/online-service-resource-input/online-service-resource-input.component.ts
@@ -1,10 +1,12 @@
import { CommonModule } from '@angular/common'
import {
ChangeDetectionStrategy,
+ ChangeDetectorRef,
Component,
EventEmitter,
Input,
OnChanges,
+ OnInit,
Output,
} from '@angular/core'
import { FormsModule } from '@angular/forms'
@@ -17,6 +19,8 @@ import {
} from '@geonetwork-ui/common/domain/model/record'
import {
ButtonComponent,
+ DropdownChoice,
+ DropdownSelectorComponent,
TextInputComponent,
UrlInputComponent,
} from '@geonetwork-ui/ui/inputs'
@@ -27,6 +31,7 @@ import {
provideNgIconsConfig,
} from '@ng-icons/core'
import { iconoirCloudUpload } from '@ng-icons/iconoir'
+import { getLayers } from '@geonetwork-ui/util/shared'
@Component({
selector: 'gn-ui-online-service-resource-input',
@@ -35,6 +40,7 @@ import { iconoirCloudUpload } from '@ng-icons/iconoir'
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
+ DropdownSelectorComponent,
ButtonComponent,
CommonModule,
FormsModule,
@@ -52,18 +58,21 @@ import { iconoirCloudUpload } from '@ng-icons/iconoir'
}),
],
})
-export class OnlineServiceResourceInputComponent implements OnChanges {
- @Input() service: Omit
+export class OnlineServiceResourceInputComponent implements OnChanges, OnInit {
+ @Input() service: DatasetServiceDistribution
@Input() protocolHint?: string
@Input() disabled? = false
+ @Input() modifyMode? = false
@Output() urlChange: EventEmitter = new EventEmitter()
@Output() identifierSubmit: EventEmitter<{
url: string
identifier: string
}> = new EventEmitter()
+ errorMessage = false
selectedProtocol: ServiceProtocol
url: string
+ layers: DropdownChoice[] | undefined = undefined
protocolOptions: {
label: string
@@ -99,6 +108,14 @@ export class OnlineServiceResourceInputComponent implements OnChanges {
},
]
+ constructor(private cdr: ChangeDetectorRef) {}
+
+ get activeLayerSuggestion() {
+ return !['wps', 'GPFDL', 'esriRest', 'other'].includes(
+ this.service.accessServiceProtocol
+ )
+ }
+
ngOnChanges() {
this.selectedProtocol =
this.protocolOptions.find(
@@ -106,8 +123,50 @@ export class OnlineServiceResourceInputComponent implements OnChanges {
)?.value ?? 'other'
}
- handleUrlChange(url: string) {
+ ngOnInit() {
+ if (this.service.url) {
+ this.url = this.service.url.toString()
+ }
+ }
+
+ handleUrlValueChange(url: string) {
+ this.url = url
+ this.service.url = new URL(url)
+ this.resetLayersSuggestion()
+ this.urlChange.emit(this.url)
+ }
+
+ async handleUploadClick(url: string) {
this.url = url
+
+ try {
+ const layers = await getLayers(url, this.service.accessServiceProtocol)
+ this.layers = layers.map((l) => {
+ return {
+ label: l.title ? `${l.title} ${l.name ? `(${l.name})` : ''}` : l.name,
+ value: l.name || l.title,
+ }
+ })
+
+ if (this.layers.length === 0) {
+ throw new Error('No layers found')
+ }
+ } catch (e) {
+ this.errorMessage = true
+ this.layers = undefined
+ }
+
+ this.cdr.detectChanges()
+ }
+
+ handleSelectValue(val: string) {
+ this.service.identifierInService = val
+ }
+
+ resetLayersSuggestion() {
+ this.errorMessage = false
+ this.layers = undefined
+ this.service.identifierInService = null
}
submitIdentifier(identifier: string) {
diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-resources/form-field-online-resources.component.html b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-resources/form-field-online-resources.component.html
index 6598d5dd0d..6424b9b8e5 100644
--- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-resources/form-field-online-resources.component.html
+++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-resources/form-field-online-resources.component.html
@@ -21,7 +21,6 @@
@@ -55,19 +54,22 @@
[(value)]="onlineResource.description"
>
-
+
-
-
+
+
+
+
diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-resources/form-field-online-resources.component.ts b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-resources/form-field-online-resources.component.ts
index a33209e5e5..7b3307f9b9 100644
--- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-resources/form-field-online-resources.component.ts
+++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-online-resources/form-field-online-resources.component.ts
@@ -92,11 +92,12 @@ export class FormFieldOnlineResourcesComponent {
notLinkResources: OnlineNotLinkResource[] = []
uploadProgress = undefined
uploadSubscription: Subscription = null
- newService = {
+ newService = {
type: 'service',
accessServiceProtocol: 'ogcFeatures',
identifierInService: '',
- } as Omit
+ url: undefined,
+ }
protected MAX_UPLOAD_SIZE_MB = MAX_UPLOAD_SIZE_MB
diff --git a/libs/ui/inputs/src/lib/url-input/url-input.component.stories.ts b/libs/ui/inputs/src/lib/url-input/url-input.component.stories.ts
index 1a676d4f9b..5a14245b14 100644
--- a/libs/ui/inputs/src/lib/url-input/url-input.component.stories.ts
+++ b/libs/ui/inputs/src/lib/url-input/url-input.component.stories.ts
@@ -1,10 +1,35 @@
-import { Meta, StoryObj } from '@storybook/angular'
+import {
+ applicationConfig,
+ Meta,
+ moduleMetadata,
+ StoryObj,
+} from '@storybook/angular'
+import {
+ NgIconComponent,
+ provideIcons,
+ provideNgIconsConfig,
+} from '@ng-icons/core'
+import { matStar } from '@ng-icons/material-icons/baseline'
import { UrlInputComponent } from './url-input.component'
export default {
title: 'Inputs/UrlInputComponent',
component: UrlInputComponent,
- decorators: [],
+ decorators: [
+ moduleMetadata({
+ imports: [NgIconComponent],
+ }),
+ applicationConfig({
+ providers: [
+ provideIcons({
+ matStar,
+ }),
+ provideNgIconsConfig({
+ size: '0.9em',
+ }),
+ ],
+ }),
+ ],
argTypes: {
valueChange: {
action: 'valueChange',
@@ -56,3 +81,20 @@ export const WithoutUploadButton: StoryObj = {
`,
}),
}
+
+export const WithCustomValidateButton: StoryObj = {
+ args: {
+ value: null,
+ disabled: false,
+ placeholder: 'https://mysite.org/file',
+ showValidateButton: true,
+ },
+ render: (args) => ({
+ props: args,
+ template: `
+
+
+ `,
+ }),
+}
diff --git a/libs/util/shared/src/lib/links/link-utils.spec.ts b/libs/util/shared/src/lib/links/link-utils.spec.ts
index 6a276cef8b..b7ada94c93 100644
--- a/libs/util/shared/src/lib/links/link-utils.spec.ts
+++ b/libs/util/shared/src/lib/links/link-utils.spec.ts
@@ -5,12 +5,102 @@ import {
getBadgeColor,
getFileFormat,
getFileFormatFromServiceOutput,
+ getLayers,
getLinkLabel,
getLinkPriority,
mimeTypeToFormat,
} from './link-utils'
import { DatasetDownloadDistribution } from '@geonetwork-ui/common/domain/model/record'
+jest.mock('@camptocamp/ogc-client', () => ({
+ WfsEndpoint: class {
+ constructor(private url) {}
+ isReady() {
+ return Promise.resolve(this)
+ }
+ getFeatureTypes() {
+ return [
+ {
+ name: 'ft1',
+ title: 'Feature Type 1',
+ },
+ {
+ name: 'ft2',
+ title: 'Feature Type 2',
+ },
+ {
+ name: 'ft3',
+ title: 'Feature Type 3',
+ },
+ ]
+ }
+ },
+ OgcApiEndpoint: class {
+ constructor(private url) {}
+ get allCollections() {
+ return [
+ {
+ name: 'ogc-collection-1',
+ title: 'Ogc Collection 1',
+ },
+ {
+ name: 'ogc-collection-2',
+ title: 'Ogc Collection 2',
+ },
+ ]
+ }
+ },
+ WmsEndpoint: class {
+ constructor(private url) {}
+ isReady() {
+ return Promise.resolve(this)
+ }
+ getLayers() {
+ return [
+ {
+ name: 'wms-layer-1',
+ title: 'WMS layer 1',
+ abstract: 'WMS layer 1',
+ children: [
+ {
+ name: 'wms-layer-1-1',
+ title: 'WMS layer 1 - 1',
+ abstract: 'WMS layer 1 - 1',
+ },
+ ],
+ },
+ {
+ name: 'wms-layer-2',
+ title: 'WMS layer 2',
+ abstract: 'WMS layer 2',
+ },
+ ]
+ }
+ },
+ WmtsEndpoint: class {
+ constructor(private url) {}
+ isReady() {
+ return Promise.resolve(this)
+ }
+ getLayers() {
+ return [
+ {
+ name: 'wmts-layer-1',
+ title: 'WMTS layer 1',
+ },
+ {
+ name: 'wmts-layer-2',
+ title: 'WMTS layer 2',
+ },
+ {
+ name: 'wmts-layer-3',
+ title: 'WMTS layer 3',
+ },
+ ]
+ }
+ },
+}))
+
describe('link utils', () => {
describe('#getFileFormat', () => {
describe('for a csv FILE link', () => {
@@ -335,4 +425,86 @@ describe('link utils', () => {
).toEqual('Cities (geojson)')
})
})
+
+ describe('#getLayers', () => {
+ beforeEach(() => {
+ jest.clearAllMocks()
+ })
+
+ it('should return OGC Features layers', async () => {
+ const layers = await getLayers('https://example.com', 'ogcFeatures')
+ expect(layers).toEqual([
+ {
+ name: 'ogc-collection-1',
+ title: 'Ogc Collection 1',
+ },
+ {
+ name: 'ogc-collection-2',
+ title: 'Ogc Collection 2',
+ },
+ ])
+ })
+
+ it('should return WFS feature types', async () => {
+ const layers = await getLayers('https://example.com', 'wfs')
+ expect(layers).toEqual([
+ {
+ name: 'ft1',
+ title: 'Feature Type 1',
+ },
+ {
+ name: 'ft2',
+ title: 'Feature Type 2',
+ },
+ {
+ name: 'ft3',
+ title: 'Feature Type 3',
+ },
+ ])
+ })
+
+ it('should return flattened WMS layers (filtered)', async () => {
+ const layers = await getLayers('https://example.com', 'wms')
+ expect(layers).toEqual([
+ {
+ name: 'wms-layer-1',
+ title: 'WMS layer 1',
+ abstract: 'WMS layer 1',
+ },
+ {
+ name: 'wms-layer-1-1',
+ title: 'WMS layer 1 - 1',
+ abstract: 'WMS layer 1 - 1',
+ },
+ {
+ name: 'wms-layer-2',
+ title: 'WMS layer 2',
+ abstract: 'WMS layer 2',
+ },
+ ])
+ })
+
+ it('should return WMTS layers', async () => {
+ const layers = await getLayers('https://example.com', 'wmts')
+ expect(layers).toEqual([
+ {
+ name: 'wmts-layer-1',
+ title: 'WMTS layer 1',
+ },
+ {
+ name: 'wmts-layer-2',
+ title: 'WMTS layer 2',
+ },
+ {
+ name: 'wmts-layer-3',
+ title: 'WMTS layer 3',
+ },
+ ])
+ })
+
+ it('should return undefined for an unknown serviceProtocol', async () => {
+ const layers = await getLayers('https://example.com', 'unknown' as any)
+ expect(layers).toBeUndefined()
+ })
+ })
})
diff --git a/libs/util/shared/src/lib/links/link-utils.ts b/libs/util/shared/src/lib/links/link-utils.ts
index 329e40b882..75bdd312a5 100644
--- a/libs/util/shared/src/lib/links/link-utils.ts
+++ b/libs/util/shared/src/lib/links/link-utils.ts
@@ -1,5 +1,14 @@
import { marker } from '@biesbjerg/ngx-translate-extract-marker'
-import { DatasetOnlineResource } from '@geonetwork-ui/common/domain/model/record'
+import {
+ DatasetOnlineResource,
+ ServiceProtocol,
+} from '@geonetwork-ui/common/domain/model/record'
+import {
+ OgcApiEndpoint,
+ WfsEndpoint,
+ WmsEndpoint,
+ WmtsEndpoint,
+} from '@camptocamp/ogc-client'
marker('downloads.wfs.featuretype.not.found')
@@ -242,6 +251,47 @@ export function getLinkLabel(link: DatasetOnlineResource): string {
return format ? `${label} (${format})` : label
}
+export async function getLayers(url: string, serviceProtocol: ServiceProtocol) {
+ switch (serviceProtocol) {
+ case 'ogcFeatures': {
+ const layers = await new OgcApiEndpoint(url).allCollections
+ return layers
+ }
+ case 'wfs': {
+ const endpointWfs = new WfsEndpoint(url)
+ await endpointWfs.isReady()
+ return endpointWfs.getFeatureTypes()
+ }
+ case 'wms': {
+ const endpointWms = new WmsEndpoint(url)
+ await endpointWms.isReady()
+ return endpointWms
+ .getLayers()
+ .flatMap(wmsLayerFlatten)
+ .filter((l) => l.name)
+ }
+ case 'wmts': {
+ const endpointWmts = new WmtsEndpoint(url)
+ await endpointWmts.isReady()
+ return endpointWmts.getLayers()
+ }
+ default:
+ return undefined
+ }
+}
+
+function wmsLayerFlatten(layerFull) {
+ const layer = {
+ title: layerFull.title,
+ name: layerFull.name,
+ abstract: layerFull.abstract,
+ }
+
+ return 'children' in layerFull && Array.isArray(layerFull.children)
+ ? [layer, ...layerFull.children.flatMap(wmsLayerFlatten)]
+ : [layer]
+}
+
export function getMimeTypeForFormat(format: FileFormat): string | null {
return format in FORMATS ? FORMATS[format.toLowerCase()].mimeTypes[0] : null
}
diff --git a/translations/de.json b/translations/de.json
index 850c3ab37b..fc63b61ca2 100644
--- a/translations/de.json
+++ b/translations/de.json
@@ -235,8 +235,10 @@
"editor.record.form.field.onlineResource.dialogTitle": "",
"editor.record.form.field.onlineResource.edit.description": "",
"editor.record.form.field.onlineResource.edit.protocol": "",
+ "editor.record.form.field.onlineResource.edit.identifier.error": "Schichten konnten nicht vom Protokoll abgerufen werden",
"editor.record.form.field.onlineResource.edit.identifier.placeholder": "Ebenenname",
"editor.record.form.field.onlineResource.edit.identifier.placeholder.wps": "Prozessname",
+ "editor.record.form.field.onlineResource.edit.identifier.select.label": "Wählen Sie eine Ebene",
"editor.record.form.field.onlineResource.edit.identifier.submit": "Link zum Dienst",
"editor.record.form.field.onlineResource.edit.title": "",
"editor.record.form.field.onlineResource.fileSize": "",
diff --git a/translations/en.json b/translations/en.json
index 1767f8a995..9236932a6c 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -235,8 +235,10 @@
"editor.record.form.field.onlineResource.dialogTitle": "Modify the dataset preview",
"editor.record.form.field.onlineResource.edit.description": "Description",
"editor.record.form.field.onlineResource.edit.protocol": "Protocol",
+ "editor.record.form.field.onlineResource.edit.identifier.error": "Unable to retrieve layers from protocol",
"editor.record.form.field.onlineResource.edit.identifier.placeholder": "Layer name",
"editor.record.form.field.onlineResource.edit.identifier.placeholder.wps": "Process name",
+ "editor.record.form.field.onlineResource.edit.identifier.select.label": "Select a layer",
"editor.record.form.field.onlineResource.edit.identifier.submit": "Link to the service",
"editor.record.form.field.onlineResource.edit.title": "Title",
"editor.record.form.field.onlineResource.fileSize": "{sizeMB}MB",
diff --git a/translations/es.json b/translations/es.json
index c794b247a2..48807a14a5 100644
--- a/translations/es.json
+++ b/translations/es.json
@@ -235,8 +235,10 @@
"editor.record.form.field.onlineResource.dialogTitle": "",
"editor.record.form.field.onlineResource.edit.description": "",
"editor.record.form.field.onlineResource.edit.protocol": "",
+ "editor.record.form.field.onlineResource.edit.identifier.error": "No se pueden recuperar las capas del protocolo",
"editor.record.form.field.onlineResource.edit.identifier.placeholder": "Nombre de la capa",
"editor.record.form.field.onlineResource.edit.identifier.placeholder.wps": "Nombre del proceso",
+ "editor.record.form.field.onlineResource.edit.identifier.select.label": "Selecciona una capa",
"editor.record.form.field.onlineResource.edit.identifier.submit": "Enlace al servicio",
"editor.record.form.field.onlineResource.edit.title": "",
"editor.record.form.field.onlineResource.fileSize": "",
diff --git a/translations/fr.json b/translations/fr.json
index 75c5c6b72d..f3a0400234 100644
--- a/translations/fr.json
+++ b/translations/fr.json
@@ -235,8 +235,10 @@
"editor.record.form.field.onlineResource.dialogTitle": "Modifier l'aperçu du jeu de données",
"editor.record.form.field.onlineResource.edit.description": "Description",
"editor.record.form.field.onlineResource.edit.protocol": "Protocole",
+ "editor.record.form.field.onlineResource.edit.identifier.error": "Impossible de récupérer les couches depuis le protocole",
"editor.record.form.field.onlineResource.edit.identifier.placeholder": "Nom de la couche",
"editor.record.form.field.onlineResource.edit.identifier.placeholder.wps": "Nom du processus",
+ "editor.record.form.field.onlineResource.edit.identifier.select.label": "Sélectionner une couche",
"editor.record.form.field.onlineResource.edit.identifier.submit": "Lier le service",
"editor.record.form.field.onlineResource.edit.title": "Titre",
"editor.record.form.field.onlineResource.fileSize": "{sizeMB} Mo",
diff --git a/translations/it.json b/translations/it.json
index 45b1925cb4..9ac12ef466 100644
--- a/translations/it.json
+++ b/translations/it.json
@@ -229,8 +229,10 @@
"editor.record.form.field.keywords": "Parole chiave",
"editor.record.form.field.legalConstraints": "Vincolo legale",
"editor.record.form.field.license": "Licenza",
+ "editor.record.form.field.onlineResource.edit.identifier.error": "Impossibile recuperare i livelli dal protocollo",
"editor.record.form.field.onlineResource.edit.identifier.placeholder": "Nome del livello",
"editor.record.form.field.onlineResource.edit.identifier.placeholder.wps": "Nome del processo",
+ "editor.record.form.field.onlineResource.edit.identifier.select.label": "Seleziona un livello",
"editor.record.form.field.onlineResource.edit.identifier.submit": "Collegare il servizio",
"editor.record.form.field.title.placeholder": "Inserisci un titolo",
"editor.record.form.field.onlineLinkResources": "Risorse allegate",
diff --git a/translations/nl.json b/translations/nl.json
index 177194f459..9f939209d5 100644
--- a/translations/nl.json
+++ b/translations/nl.json
@@ -235,8 +235,10 @@
"editor.record.form.field.onlineResource.dialogTitle": "",
"editor.record.form.field.onlineResource.edit.description": "",
"editor.record.form.field.onlineResource.edit.protocol": "",
+ "editor.record.form.field.onlineResource.edit.identifier.error": "Kan lagen niet ophalen van het protocol",
"editor.record.form.field.onlineResource.edit.identifier.placeholder": "Laagnaam",
"editor.record.form.field.onlineResource.edit.identifier.placeholder.wps": "Procesnaam",
+ "editor.record.form.field.onlineResource.edit.identifier.select.label": "Selecteer een laag",
"editor.record.form.field.onlineResource.edit.identifier.submit": "Link naar de dienst",
"editor.record.form.field.onlineResource.edit.title": "",
"editor.record.form.field.onlineResource.fileSize": "",
diff --git a/translations/pt.json b/translations/pt.json
index 4f029e84c5..18a074e814 100644
--- a/translations/pt.json
+++ b/translations/pt.json
@@ -235,8 +235,10 @@
"editor.record.form.field.onlineResource.dialogTitle": "",
"editor.record.form.field.onlineResource.edit.description": "",
"editor.record.form.field.onlineResource.edit.protocol": "",
+ "editor.record.form.field.onlineResource.edit.identifier.error": "Não é possível recuperar camadas do protocolo",
"editor.record.form.field.onlineResource.edit.identifier.placeholder": "Nome da camada",
"editor.record.form.field.onlineResource.edit.identifier.placeholder.wps": "Nome do processo",
+ "editor.record.form.field.onlineResource.edit.identifier.select.label": "Selecione uma camada",
"editor.record.form.field.onlineResource.edit.identifier.submit": "Link para o serviço",
"editor.record.form.field.onlineResource.edit.title": "",
"editor.record.form.field.onlineResource.fileSize": "",
diff --git a/translations/sk.json b/translations/sk.json
index 6d5ce62093..ff7443ac9f 100644
--- a/translations/sk.json
+++ b/translations/sk.json
@@ -235,8 +235,10 @@
"editor.record.form.field.onlineResource.dialogTitle": "",
"editor.record.form.field.onlineResource.edit.description": "",
"editor.record.form.field.onlineResource.edit.protocol": "",
+ "editor.record.form.field.onlineResource.edit.identifier.error": "Nie je možné načítať vrstvy z protokolu",
"editor.record.form.field.onlineResource.edit.identifier.placeholder": "Názov vrstvy",
"editor.record.form.field.onlineResource.edit.identifier.placeholder.wps": "Názov procesu",
+ "editor.record.form.field.onlineResource.edit.identifier.select.label": "Vyberte vrstvu",
"editor.record.form.field.onlineResource.edit.identifier.submit": "Odkaz na službu",
"editor.record.form.field.onlineResource.edit.title": "",
"editor.record.form.field.onlineResource.fileSize": "",