geonetwork-ui 2.11.0-dev.96d0c6ce2 → 2.11.0-dev.a5c259d9e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/fesm2022/geonetwork-ui.mjs +971 -585
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +165 -59
  4. package/index.d.ts.map +1 -1
  5. package/package.json +11 -11
  6. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +43 -13
  7. package/src/libs/api/metadata-converter/src/lib/gn4/types/elasticsearch.model.ts +13 -1
  8. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +23 -3
  9. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +40 -16
  10. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +12 -0
  11. package/src/libs/common/domain/src/lib/model/search/sort-by.model.ts +1 -5
  12. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +2 -2
  13. package/src/libs/feature/dataviz/src/lib/chart-view/chart-view.component.ts +3 -2
  14. package/src/libs/feature/dataviz/src/lib/geo-table-view/geo-table-view.component.ts +2 -1
  15. package/src/libs/feature/dataviz/src/lib/service/data.service.ts +22 -22
  16. package/src/libs/feature/map/src/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
  17. package/src/libs/feature/notifications/src/lib/notifications.service.ts +6 -4
  18. package/src/libs/feature/record/src/lib/state/mdview.actions.ts +4 -8
  19. package/src/libs/feature/record/src/lib/state/mdview.effects.ts +7 -16
  20. package/src/libs/feature/record/src/lib/state/mdview.facade.ts +1 -3
  21. package/src/libs/feature/record/src/lib/state/mdview.reducer.ts +4 -9
  22. package/src/libs/feature/record/src/lib/state/mdview.selectors.ts +2 -7
  23. package/src/libs/feature/router/src/lib/default/router.service.ts +1 -1
  24. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +7 -0
  25. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +39 -0
  26. package/src/libs/feature/search/src/lib/sort-by/sort-by.component.ts +1 -1
  27. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +3 -1
  28. package/src/libs/feature/search/src/lib/utils/service/fields.ts +16 -1
  29. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.html +6 -7
  30. package/src/libs/ui/dataviz/src/lib/data-table/data-table.component.ts +31 -19
  31. package/src/libs/ui/dataviz/src/lib/data-table/data-table.fixtures.ts +2 -2
  32. package/src/libs/ui/inputs/src/index.ts +1 -0
  33. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
  34. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
  35. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
  36. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.css +0 -0
  37. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +117 -0
  38. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +191 -0
  39. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +4 -5
  40. package/src/libs/util/app-config/src/lib/app-config.ts +3 -0
  41. package/src/libs/util/app-config/src/lib/model.ts +1 -0
  42. package/src/libs/util/data-fetcher/src/lib/data-fetcher.ts +19 -22
  43. package/src/libs/util/data-fetcher/src/lib/engine/duckdb.ts +185 -0
  44. package/src/libs/util/data-fetcher/src/lib/engine/results.ts +63 -0
  45. package/src/libs/util/data-fetcher/src/lib/{sql-utils.ts → engine/sql-utils.ts} +28 -13
  46. package/src/libs/util/data-fetcher/src/lib/model.ts +2 -1
  47. package/src/libs/util/data-fetcher/src/lib/readers/base-file.ts +53 -38
  48. package/src/libs/util/data-fetcher/src/lib/readers/base.ts +11 -0
  49. package/src/libs/util/data-fetcher/src/lib/readers/csv.ts +9 -47
  50. package/src/libs/util/data-fetcher/src/lib/readers/excel.ts +27 -27
  51. package/src/libs/util/data-fetcher/src/lib/readers/geojson.ts +5 -24
  52. package/src/libs/util/data-fetcher/src/lib/readers/gml.ts +5 -49
  53. package/src/libs/util/data-fetcher/src/lib/readers/json.ts +5 -23
  54. package/src/libs/util/data-fetcher/src/lib/readers/wfs.ts +184 -128
  55. package/src/libs/util/data-fetcher/src/lib/utils.ts +0 -143
  56. package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
  57. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  58. package/tailwind.base.css +5 -0
  59. package/translations/de.json +11 -1
  60. package/translations/en.json +11 -1
  61. package/translations/es.json +10 -0
  62. package/translations/fr.json +11 -1
  63. package/translations/it.json +11 -1
  64. package/translations/nl.json +10 -0
  65. package/translations/pt.json +10 -0
  66. package/translations/sk.json +11 -1
  67. package/src/libs/util/data-fetcher/src/lib/readers/base-cache.ts +0 -14
@@ -6,33 +6,32 @@
6
6
  matSort
7
7
  (matSortChange)="setSort($event)"
8
8
  [matSortDisableClear]="true"
9
- *ngrxLet="properties$ as properties"
10
9
  >
11
- @for (attr of _featureAttributes; track attr) {
12
- <ng-container [matColumnDef]="attr.value">
10
+ @for (column of columns; track column) {
11
+ <ng-container [matColumnDef]="column.name">
13
12
  <th
14
13
  mat-header-cell
15
14
  *matHeaderCellDef
16
15
  mat-sort-header
17
16
  class="text-sm text-black bg-white"
18
17
  >
19
- {{ attr.label }}
18
+ {{ column.label }}
20
19
  </th>
21
20
  <td
22
21
  mat-cell
23
22
  *matCellDef="let element"
24
23
  class="whitespace-nowrap pr-1 truncate"
25
24
  >
26
- {{ element[attr.value] }}
25
+ {{ element[column.name] }}
27
26
  </td>
28
27
  </ng-container>
29
28
  }
30
29
 
31
- <tr mat-header-row *matHeaderRowDef="properties; sticky: true"></tr>
30
+ <tr mat-header-row *matHeaderRowDef="columnNames; sticky: true"></tr>
32
31
  <tr
33
32
  [id]="getRowEltId(row.id)"
34
33
  mat-row
35
- *matRowDef="let row; columns: properties"
34
+ *matRowDef="let row; columns: columnNames"
36
35
  (click)="selected.emit(row)"
37
36
  [class.active]="
38
37
  activeId !== undefined && activeId !== null && row.id === activeId
@@ -6,14 +6,14 @@ import {
6
6
  Component,
7
7
  ElementRef,
8
8
  EventEmitter,
9
+ inject,
9
10
  Input,
10
11
  OnChanges,
11
12
  OnInit,
12
13
  Output,
13
14
  ViewChild,
14
- inject,
15
15
  } from '@angular/core'
16
- import { MatSort, MatSortModule } from '@angular/material/sort'
16
+ import { MatSort, MatSortModule, Sort } from '@angular/material/sort'
17
17
  import { MatTableModule } from '@angular/material/table'
18
18
  import {
19
19
  TranslateDirective,
@@ -29,12 +29,11 @@ import {
29
29
  } from '@angular/material/paginator'
30
30
  import { CustomMatPaginatorIntl } from './custom.mat.paginator.intl'
31
31
  import { CommonModule } from '@angular/common'
32
- import { BehaviorSubject, filter, firstValueFrom } from 'rxjs'
32
+ import { BehaviorSubject } from 'rxjs'
33
33
  import {
34
34
  LoadingMaskComponent,
35
35
  PopupAlertComponent,
36
36
  } from '../../../../../../libs/ui/widgets/src'
37
- import { LetDirective } from '@ngrx/component'
38
37
 
39
38
  const rowIdPrefix = 'table-item-'
40
39
 
@@ -46,6 +45,11 @@ export interface TableItemModel {
46
45
  [key: string]: TableItemType
47
46
  }
48
47
 
48
+ interface TableColumn {
49
+ name: string
50
+ label: string
51
+ }
52
+
49
53
  @Component({
50
54
  standalone: true,
51
55
  imports: [
@@ -56,7 +60,6 @@ export interface TableItemModel {
56
60
  CommonModule,
57
61
  LoadingMaskComponent,
58
62
  PopupAlertComponent,
59
- LetDirective,
60
63
  TranslatePipe,
61
64
  TranslateDirective,
62
65
  ],
@@ -71,15 +74,21 @@ export class DataTableComponent implements OnInit, AfterViewInit, OnChanges {
71
74
  private cdr = inject(ChangeDetectorRef)
72
75
  private translateService = inject(TranslateService)
73
76
 
74
- _featureAttributes = []
77
+ columnsFromFeatureCatalog: TableColumn[] = null
75
78
  @Input() set featureAttributes(value: { value: string; label: string }[]) {
76
- this._featureAttributes = value
77
- this.properties$.next(value.map((attr) => attr.value))
79
+ this.columnsFromFeatureCatalog = value.map((attrs) => ({
80
+ name: attrs.value,
81
+ label: attrs.label,
82
+ }))
78
83
  }
84
+ columnsFromDataset: TableColumn[] = []
79
85
  @Input() set dataset(value: BaseReader) {
80
86
  this.dataset_ = value
81
87
  this.dataset_.load()
82
- this.dataset_.info.then((info) => (this.count = info.itemsCount))
88
+ this.dataset_.info.then((info) => {
89
+ this.count = info.itemsCount
90
+ this.cdr.detectChanges()
91
+ })
83
92
  }
84
93
  @Input() activeId: TableItemId
85
94
  @Output() selected = new EventEmitter<any>()
@@ -88,13 +97,19 @@ export class DataTableComponent implements OnInit, AfterViewInit, OnChanges {
88
97
  @ViewChild(MatPaginator) paginator: MatPaginator
89
98
 
90
99
  dataset_: BaseReader
91
- properties$ = new BehaviorSubject<string[]>(null)
92
100
  dataSource: DataTableDataSource
93
101
  headerHeight: number
94
102
  count: number
95
103
  loading$ = new BehaviorSubject<boolean>(false)
96
104
  error = null
97
105
 
106
+ get columns() {
107
+ return this.columnsFromFeatureCatalog ?? this.columnsFromDataset
108
+ }
109
+ get columnNames() {
110
+ return this.columns.map((c) => c.name)
111
+ }
112
+
98
113
  ngOnInit() {
99
114
  this.dataSource = new DataTableDataSource()
100
115
  }
@@ -110,7 +125,7 @@ export class DataTableComponent implements OnInit, AfterViewInit, OnChanges {
110
125
  this.setPagination()
111
126
  }
112
127
 
113
- setSort(sort: MatSort) {
128
+ setSort(sort: Sort) {
114
129
  if (!this.dataset_) return
115
130
  if (!sort.active) {
116
131
  this.dataset_.orderBy()
@@ -132,15 +147,12 @@ export class DataTableComponent implements OnInit, AfterViewInit, OnChanges {
132
147
 
133
148
  async readData() {
134
149
  this.loading$.next(true)
135
- // wait for properties to be read
136
- const properties = await firstValueFrom(
137
- this.properties$.pipe(filter((p) => !!p))
138
- )
139
- const propsWithoutGeom = properties.filter(
140
- (p) => !p.toLowerCase().startsWith('geom')
141
- )
142
- this.dataset_.select(...propsWithoutGeom)
143
150
  try {
151
+ // wait for properties to be read
152
+ if (!this.columnsFromFeatureCatalog) {
153
+ this.columnsFromDataset = await this.dataset_.properties
154
+ }
155
+ this.dataset_.select(...this.columnNames)
144
156
  await this.dataSource.showData(this.dataset_.read())
145
157
  this.error = null
146
158
  } catch (error) {
@@ -25,8 +25,8 @@ export const tableItemsFixture = {
25
25
  geometry: null,
26
26
  properties: {
27
27
  id: '0003',
28
- firstName: 'Claude',
29
- lastName: 'François',
28
+ firstName: 'Lady',
29
+ lastName: 'Gaga',
30
30
  },
31
31
  },
32
32
  ] as DataItem[],
@@ -25,3 +25,4 @@ export * from './lib/url-input/url-input.component'
25
25
  export * from './lib/viewport-intersector/viewport-intersector.component'
26
26
  export * from './lib/search-feature-catalog/search-feature-catalog.component'
27
27
  export * from './lib/date-range-inputs/date-range-inputs.component'
28
+ export * from './lib/spatial-extent-dropdown/spatial-extent-dropdown.component'
@@ -1,4 +1,5 @@
1
1
  ngx-dropzone {
2
2
  height: auto;
3
3
  border: none;
4
+ overflow: hidden;
4
5
  }
@@ -1,18 +1,31 @@
1
- <div class="flex h-full">
1
+ <div
2
+ class="flex h-full items-center"
3
+ [class]="extraClass"
4
+ (click)="openFileSelector()"
5
+ >
2
6
  <ngx-dropzone
3
- class="flex-1"
7
+ class="flex-1 overflow-hidden"
4
8
  [multiple]="false"
5
- (change)="selectFile($event)"
9
+ [disableClick]="true"
6
10
  [accept]="accept"
11
+ [maxFileSize]="maxFileSizeBytes"
12
+ [style.background-color]="dropzoneBackgroundColor"
13
+ (change)="selectFile($event)"
7
14
  >
8
- @if (!fileName) {
9
- <div class="text-gray-900 pl-2 py-2" translate="">
15
+ @if (!fileName || !showFileName) {
16
+ <div [class]="textClass">
10
17
  {{ placeholder }}
11
18
  </div>
12
19
  }
13
20
 
14
- @if (fileName) {
15
- <div class="text-gray-900 pl-2 py-2">{{ fileName }}</div>
21
+ @if (fileName && showFileName) {
22
+ <div [class]="textClass">
23
+ {{ fileName }}
24
+ </div>
16
25
  }
17
26
  </ngx-dropzone>
27
+
28
+ @if (icon) {
29
+ <ng-icon [name]="icon" class="shrink-0 text-gray-950"></ng-icon>
30
+ }
18
31
  </div>
@@ -1,30 +1,71 @@
1
- import { Component, EventEmitter, Input, Output } from '@angular/core'
1
+ import {
2
+ Component,
3
+ EventEmitter,
4
+ Input,
5
+ Output,
6
+ ViewChild,
7
+ } from '@angular/core'
2
8
  import { marker } from '@biesbjerg/ngx-translate-extract-marker'
3
- import { NgxDropzoneModule } from 'ngx-dropzone'
9
+ import { NgxDropzoneComponent, NgxDropzoneModule } from 'ngx-dropzone'
10
+ import { NgIcon } from '@ng-icons/core'
11
+ import { megabytesToBytes } from '../../../../../../libs/util/shared/src'
4
12
 
5
13
  export const placeholder = 'dropFile'
6
14
  marker('dropFile')
7
15
 
16
+ export type DragAndDropFileInputError = 'invalid-extension' | 'file-too-large'
17
+
8
18
  @Component({
9
19
  selector: 'gn-ui-drag-and-drop-file-input',
10
20
  templateUrl: './drag-and-drop-file-input.component.html',
11
21
  styleUrls: ['./drag-and-drop-file-input.component.css'],
12
22
  standalone: true,
13
- imports: [NgxDropzoneModule],
23
+ imports: [NgxDropzoneModule, NgIcon],
14
24
  })
15
25
  export class DragAndDropFileInputComponent {
16
26
  @Input() placeholder = placeholder
17
27
  @Input() accept = '*'
18
- @Output() fileChange = new EventEmitter<any>()
28
+ @Input() maxFileSizeMb: number | null = null
29
+ @Input() icon: string | null = null
30
+ @Input() dropzoneBackgroundColor: string | null = null
31
+ @Input() textClass = ''
32
+ @Input() extraClass = ''
33
+ @Input() showFileName = true
34
+
35
+ @Output() fileChange = new EventEmitter<File>()
36
+ @Output() errorChange = new EventEmitter<DragAndDropFileInputError>()
37
+
19
38
  selectedFile: File = null
20
39
 
40
+ @ViewChild(NgxDropzoneComponent) private dropzone: NgxDropzoneComponent
41
+
21
42
  get fileName(): string | null {
22
43
  return this.selectedFile && this.selectedFile.name
23
44
  }
24
45
 
46
+ get maxFileSizeBytes(): number | null {
47
+ return typeof this.maxFileSizeMb === 'number'
48
+ ? megabytesToBytes(this.maxFileSizeMb)
49
+ : null
50
+ }
51
+
25
52
  selectFile(event) {
53
+ if (event.rejectedFiles?.length) {
54
+ const reason = event.rejectedFiles[0].reason
55
+ this.errorChange.emit(
56
+ reason === 'size' ? 'file-too-large' : 'invalid-extension'
57
+ )
58
+ return
59
+ }
26
60
  this.selectedFile = event.addedFiles[0]
27
-
28
61
  this.fileChange.emit(this.selectedFile)
29
62
  }
63
+
64
+ openFileSelector() {
65
+ this.dropzone.showFileSelector()
66
+ }
67
+
68
+ clear() {
69
+ this.selectedFile = null
70
+ }
30
71
  }
@@ -0,0 +1,117 @@
1
+ <gn-ui-button
2
+ type="outline"
3
+ extraClass="bg-background w-full !p-[8px] !pl-[16px]"
4
+ [title]="title"
5
+ (buttonClick)="toggleOverlay()"
6
+ cdkOverlayOrigin
7
+ #overlayOrigin="cdkOverlayOrigin"
8
+ >
9
+ <div class="grow flex items-center mr-2 gap-2 overflow-hidden">
10
+ <div class="text-left font-medium truncate py-1">
11
+ {{ title }}
12
+ </div>
13
+ @if (hasSelection) {
14
+ <div
15
+ class="bg-primary-lighter text-white shrink-0 rounded-full font-bold text-[12px] w-[22px] h-[22px] flex items-center justify-center"
16
+ >
17
+ 1
18
+ </div>
19
+ }
20
+ </div>
21
+ <button class="h-6 w-6 flex items-center" data-cy="clearSelection">
22
+ @if (hasSelection && !overlayOpen) {
23
+ <ng-icon
24
+ class="shrink-0 opacity-40 mr-1.5 hover:opacity-80 transition-colors clear-btn"
25
+ (click)="removeSelection($event)"
26
+ name="matClose"
27
+ ></ng-icon>
28
+ }
29
+ </button>
30
+ <ng-icon
31
+ [name]="overlayOpen ? 'matExpandLess' : 'matExpandMore'"
32
+ class="shrink-0 opacity-40"
33
+ >
34
+ </ng-icon>
35
+ </gn-ui-button>
36
+
37
+ <ng-template
38
+ cdkConnectedOverlay
39
+ cdkConnectedOverlayHasBackdrop
40
+ cdkConnectedOverlayBackdropClass="cdk-overlay-transparent-backdrop"
41
+ [cdkConnectedOverlayOrigin]="overlayOrigin"
42
+ [cdkConnectedOverlayOpen]="overlayOpen"
43
+ [cdkConnectedOverlayPositions]="overlayPositions"
44
+ [cdkConnectedOverlayScrollStrategy]="scrollStrategy"
45
+ [cdkConnectedOverlayFlexibleDimensions]="true"
46
+ (overlayOutsideClick)="closeOverlay()"
47
+ (detach)="closeOverlay()"
48
+ >
49
+ <div
50
+ class="bg-white border border-gray-400 rounded shadow-lg pt-[12px] px-[16px] overlay-container w-[280px] flex flex-col gap-2"
51
+ [style.min-width]="overlayMinWidth"
52
+ >
53
+ <div class="text-xs text-black">
54
+ {{ 'search.filters.spatialExtent.helpText' | translate }}
55
+ </div>
56
+
57
+ <div
58
+ [class]="
59
+ (hasSelection
60
+ ? 'border-b-[1px] border-gray-400 pb-[20px]'
61
+ : 'pb-[8px]') + ' text-black'
62
+ "
63
+ >
64
+ <gn-ui-drag-and-drop-file-input
65
+ accept=".json,.geojson"
66
+ [placeholder]="'search.filters.spatialExtent.import' | translate"
67
+ [maxFileSizeMb]="maxFileSizeMb"
68
+ [showFileName]="false"
69
+ textClass="text-black truncate"
70
+ dropzoneBackgroundColor="transparent"
71
+ icon="iconoirImport"
72
+ extraClass="gn-ui-gray-outline-input w-full !h-[32px] px-[8px] py-[4px] justify-between"
73
+ (fileChange)="handleFileSelected($event)"
74
+ (errorChange)="handleFileError($event)"
75
+ data-cy="importGeojson"
76
+ ></gn-ui-drag-and-drop-file-input>
77
+ </div>
78
+
79
+ @if (hasSelection) {
80
+ <gn-ui-button
81
+ [type]="'primary-light'"
82
+ [title]="
83
+ ('search.filters.spatialExtent.bboxDelete' | translate) +
84
+ ' ' +
85
+ fileName
86
+ "
87
+ (buttonClick)="removeSelection($event)"
88
+ extraClass="group gap-x-2 w-full h-[28px] px-[8px] py-[4px] mb-[12px] bg-primary-lighter text-white"
89
+ data-test="spatial-extent-selected-item"
90
+ >
91
+ <ng-icon
92
+ name="iconoirSquareDashed"
93
+ class="shrink-0"
94
+ size="15px"
95
+ ></ng-icon>
96
+ <span class="group-hover:hidden truncate text-sm m-auto">
97
+ {{ 'search.filters.spatialExtent.bboxPrefix' | translate }}
98
+ {{ fileName }}
99
+ </span>
100
+ <span class="hidden group-hover:block truncate text-sm m-auto">
101
+ {{ 'search.filters.spatialExtent.bboxDelete' | translate }}
102
+ {{ fileName }}
103
+ </span>
104
+ <ng-icon
105
+ name="iconoirCheckCircle"
106
+ class="group-hover:hidden shrink-0"
107
+ size="14px"
108
+ ></ng-icon>
109
+ <ng-icon
110
+ name="iconoirTrash"
111
+ class="!hidden group-hover:!block shrink-0"
112
+ size="15px"
113
+ ></ng-icon>
114
+ </gn-ui-button>
115
+ }
116
+ </div>
117
+ </ng-template>
@@ -0,0 +1,191 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ ChangeDetectorRef,
4
+ Component,
5
+ EventEmitter,
6
+ Input,
7
+ Output,
8
+ ViewChild,
9
+ inject,
10
+ } from '@angular/core'
11
+ import {
12
+ CdkConnectedOverlay,
13
+ CdkOverlayOrigin,
14
+ ConnectedPosition,
15
+ OverlayModule,
16
+ ScrollStrategyOptions,
17
+ } from '@angular/cdk/overlay'
18
+
19
+ import { ButtonComponent } from '../button/button.component'
20
+ import { NgIcon, provideIcons } from '@ng-icons/core'
21
+ import {
22
+ iconoirCheckCircle,
23
+ iconoirImport,
24
+ iconoirSquareDashed,
25
+ iconoirTrash,
26
+ } from '@ng-icons/iconoir'
27
+ import {
28
+ matClose,
29
+ matExpandLess,
30
+ matExpandMore,
31
+ } from '@ng-icons/material-icons/baseline'
32
+ import { TranslatePipe } from '@ngx-translate/core'
33
+ import { marker } from '@biesbjerg/ngx-translate-extract-marker'
34
+ import {
35
+ BoundingBox,
36
+ getGeometryBoundingBox,
37
+ getGeometryFromGeoJSON,
38
+ propagateToDocumentOnly,
39
+ readFileAsText,
40
+ } from '../../../../../../libs/util/shared/src'
41
+ import {
42
+ DragAndDropFileInputComponent,
43
+ DragAndDropFileInputError,
44
+ } from '../drag-and-drop-file-input/drag-and-drop-file-input.component'
45
+
46
+ marker('search.filters.spatialExtent.import')
47
+ marker('search.filters.spatialExtent.helpText')
48
+ marker('search.filters.spatialExtent.error.title')
49
+ marker('search.filters.spatialExtent.bboxPrefix')
50
+
51
+ export interface SpatialExtentDropdownError {
52
+ key: string
53
+ params?: Record<string, string | number>
54
+ }
55
+ marker('search.filters.spatialExtent.bboxDelete')
56
+
57
+ @Component({
58
+ selector: 'gn-ui-spatial-extent-dropdown',
59
+ standalone: true,
60
+ imports: [
61
+ ButtonComponent,
62
+ NgIcon,
63
+ OverlayModule,
64
+ TranslatePipe,
65
+ DragAndDropFileInputComponent,
66
+ ],
67
+ providers: [
68
+ provideIcons({
69
+ iconoirCheckCircle,
70
+ iconoirImport,
71
+ iconoirSquareDashed,
72
+ iconoirTrash,
73
+ matClose,
74
+ matExpandLess,
75
+ matExpandMore,
76
+ }),
77
+ ],
78
+ templateUrl: './spatial-extent-dropdown.component.html',
79
+ styleUrls: ['./spatial-extent-dropdown.component.css'],
80
+ changeDetection: ChangeDetectionStrategy.OnPush,
81
+ })
82
+ export class SpatialExtentDropdownComponent {
83
+ private cd = inject(ChangeDetectorRef)
84
+ private scrollStrategies = inject(ScrollStrategyOptions)
85
+
86
+ @Input() title: string
87
+ @Input() maxFileSizeMb: number | null = null
88
+
89
+ @Output() bboxChange = new EventEmitter<BoundingBox | null>()
90
+ @Output() errorChange = new EventEmitter<SpatialExtentDropdownError>()
91
+
92
+ bbox: BoundingBox | null = null
93
+ fileName = ''
94
+
95
+ @ViewChild('overlayOrigin') overlayOrigin: CdkOverlayOrigin
96
+ @ViewChild(CdkConnectedOverlay) overlay: CdkConnectedOverlay
97
+ @ViewChild(DragAndDropFileInputComponent)
98
+ fileInput: DragAndDropFileInputComponent
99
+
100
+ overlayPositions: ConnectedPosition[] = [
101
+ {
102
+ originX: 'start',
103
+ originY: 'bottom',
104
+ overlayX: 'start',
105
+ overlayY: 'top',
106
+ offsetY: 8,
107
+ },
108
+ {
109
+ originX: 'start',
110
+ originY: 'top',
111
+ overlayX: 'start',
112
+ overlayY: 'bottom',
113
+ offsetY: -8,
114
+ },
115
+ ]
116
+ scrollStrategy = this.scrollStrategies.reposition()
117
+ overlayOpen = false
118
+ overlayMinWidth = 'none'
119
+
120
+ errorKey: string | null = null
121
+
122
+ get hasSelection() {
123
+ return !!this.bbox
124
+ }
125
+
126
+ openOverlay() {
127
+ this.overlayMinWidth =
128
+ this.overlayOrigin.elementRef.nativeElement.getBoundingClientRect()
129
+ .width + 'px'
130
+ this.overlayOpen = true
131
+ }
132
+
133
+ closeOverlay() {
134
+ this.overlayOpen = false
135
+ }
136
+
137
+ toggleOverlay() {
138
+ if (this.overlayOpen) {
139
+ this.closeOverlay()
140
+ } else {
141
+ this.openOverlay()
142
+ }
143
+ }
144
+
145
+ async handleFileSelected(file: File) {
146
+ this.errorKey = null
147
+ let content: string
148
+ try {
149
+ content = await readFileAsText(file)
150
+ const parsed = JSON.parse(content)
151
+ const geometry = getGeometryFromGeoJSON(parsed)
152
+ if (!geometry) {
153
+ this.setError(marker('search.filters.spatialExtent.error.noGeometry'))
154
+ return
155
+ }
156
+ const bbox = getGeometryBoundingBox(geometry)
157
+ this.bbox = bbox
158
+ this.fileName = file.name
159
+ this.bboxChange.emit(bbox)
160
+ this.cd.markForCheck()
161
+ } catch {
162
+ this.setError(marker('search.filters.spatialExtent.error.invalidFormat'))
163
+ return
164
+ }
165
+ }
166
+
167
+ handleFileError(error: DragAndDropFileInputError) {
168
+ if (error === 'file-too-large') {
169
+ this.setError(marker('search.filters.spatialExtent.error.fileTooLarge'), {
170
+ maxSize: this.maxFileSizeMb,
171
+ })
172
+ } else {
173
+ this.setError(marker('search.filters.spatialExtent.error.invalidFormat'))
174
+ }
175
+ }
176
+
177
+ private setError(errorKey: string, params?: Record<string, string | number>) {
178
+ this.errorKey = errorKey
179
+ this.errorChange.emit({ key: errorKey, params })
180
+ this.cd.markForCheck()
181
+ }
182
+
183
+ removeSelection(event: Event) {
184
+ this.bbox = null
185
+ this.fileName = ''
186
+ this.errorKey = null
187
+ this.fileInput?.clear()
188
+ this.bboxChange.emit(null)
189
+ propagateToDocumentOnly(event)
190
+ }
191
+ }
@@ -29,11 +29,11 @@ import {
29
29
  MapExtentChangeEvent,
30
30
  MapExtentChangeEventType,
31
31
  MapContext,
32
- MapContextLayerXyz,
33
32
  MapContextView,
34
33
  MapEventsByType,
35
34
  SourceLoadErrorEvent,
36
35
  SourceLoadErrorType,
36
+ MapContextLayerMapLibreStyle,
37
37
  } from '@geospatial-sdk/core'
38
38
  import {
39
39
  applyContextDiffToMap,
@@ -55,10 +55,9 @@ import {
55
55
  import { matSwipeOutline } from '@ng-icons/material-icons/outline'
56
56
  import { transformExtent } from 'ol/proj.js'
57
57
 
58
- const DEFAULT_BASEMAP_LAYER: MapContextLayerXyz = {
59
- type: 'xyz',
60
- url: `https://{a-c}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png`,
61
- attributions: `<span>© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <a href="https://carto.com/">Carto</a></span>`,
58
+ export const DEFAULT_BASEMAP_LAYER: MapContextLayerMapLibreStyle = {
59
+ type: 'maplibre-style',
60
+ styleUrl: `https://basemaps.cartocdn.com/gl/positron-gl-style/style.json`,
62
61
  }
63
62
 
64
63
  const DEFAULT_VIEW: MapContextView = {
@@ -252,6 +252,7 @@ export function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
252
252
  'search_preset',
253
253
  'advanced_filters',
254
254
  'limit',
255
+ 'spatial_extent_max_file_size',
255
256
  ],
256
257
  warnings,
257
258
  errors
@@ -281,6 +282,8 @@ export function loadAppConfig(configUrl = 'assets/configuration/default.toml') {
281
282
  })),
282
283
  ADVANCED_FILTERS: parsedSearchSection.advanced_filters,
283
284
  LIMIT: parsedSearchSection.limit,
285
+ SPATIAL_EXTENT_MAX_FILE_SIZE:
286
+ parsedSearchSection.spatial_extent_max_file_size,
284
287
  } as SearchConfig)
285
288
 
286
289
  const parsedMetadataQualitySection = parseConfigSection(
@@ -63,6 +63,7 @@ export interface SearchConfig {
63
63
  SEARCH_PRESET?: SearchPreset[]
64
64
  ADVANCED_FILTERS?: []
65
65
  LIMIT?: number
66
+ SPATIAL_EXTENT_MAX_FILE_SIZE?: number
66
67
  }
67
68
 
68
69
  export interface MetadataQualityConfig {