geonetwork-ui 2.4.0-dev.573bccd7 → 2.4.0-dev.656babc7

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 (94) hide show
  1. package/esm2022/libs/api/metadata-converter/src/lib/gn4/types/index.mjs +2 -1
  2. package/esm2022/libs/api/metadata-converter/src/lib/gn4/types/keywords.model.mjs +2 -0
  3. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/read-parts.mjs +1 -1
  4. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/utils/keyword.mapper.mjs +1 -1
  5. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/write-parts.mjs +1 -1
  6. package/esm2022/libs/api/repository/src/lib/gn4/platform/gn4-platform.mapper.mjs +11 -3
  7. package/esm2022/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.mjs +34 -14
  8. package/esm2022/libs/common/domain/src/lib/model/record/metadata.model.mjs +1 -1
  9. package/esm2022/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.mjs +1 -1
  10. package/esm2022/libs/common/domain/src/lib/platform.service.interface.mjs +1 -1
  11. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.mjs +87 -0
  12. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.mjs +8 -3
  13. package/esm2022/libs/feature/editor/src/lib/components/record-form/form-field/index.mjs +2 -1
  14. package/esm2022/libs/feature/editor/src/lib/feature-editor.module.mjs +4 -3
  15. package/esm2022/libs/feature/editor/src/lib/fields.config.mjs +8 -1
  16. package/esm2022/libs/feature/search/src/lib/results-table/results-table.component.mjs +6 -6
  17. package/esm2022/libs/ui/elements/src/lib/metadata-info/metadata-info.component.mjs +4 -4
  18. package/esm2022/libs/ui/elements/src/lib/ui-elements.module.mjs +3 -3
  19. package/esm2022/libs/ui/inputs/src/index.mjs +2 -1
  20. package/esm2022/libs/ui/inputs/src/lib/badge/badge.component.mjs +28 -0
  21. package/esm2022/libs/ui/inputs/src/lib/ui-inputs.module.mjs +10 -4
  22. package/esm2022/libs/ui/widgets/src/index.mjs +1 -2
  23. package/fesm2022/geonetwork-ui.mjs +163 -37
  24. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  25. package/libs/api/metadata-converter/src/lib/gn4/types/index.d.ts +1 -0
  26. package/libs/api/metadata-converter/src/lib/gn4/types/index.d.ts.map +1 -1
  27. package/libs/api/metadata-converter/src/lib/gn4/types/keywords.model.d.ts +31 -0
  28. package/libs/api/metadata-converter/src/lib/gn4/types/keywords.model.d.ts.map +1 -0
  29. package/libs/api/metadata-converter/src/lib/iso19139/read-parts.d.ts +3 -2
  30. package/libs/api/metadata-converter/src/lib/iso19139/read-parts.d.ts.map +1 -1
  31. package/libs/api/metadata-converter/src/lib/iso19139/utils/keyword.mapper.d.ts +1 -1
  32. package/libs/api/metadata-converter/src/lib/iso19139/utils/keyword.mapper.d.ts.map +1 -1
  33. package/libs/api/metadata-converter/src/lib/iso19139/write-parts.d.ts +3 -2
  34. package/libs/api/metadata-converter/src/lib/iso19139/write-parts.d.ts.map +1 -1
  35. package/libs/api/repository/src/lib/gn4/platform/gn4-platform.mapper.d.ts +3 -3
  36. package/libs/api/repository/src/lib/gn4/platform/gn4-platform.mapper.d.ts.map +1 -1
  37. package/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.d.ts +8 -5
  38. package/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.d.ts.map +1 -1
  39. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts +4 -7
  40. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts.map +1 -1
  41. package/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.d.ts +12 -5
  42. package/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.d.ts.map +1 -1
  43. package/libs/common/domain/src/lib/platform.service.interface.d.ts +3 -3
  44. package/libs/common/domain/src/lib/platform.service.interface.d.ts.map +1 -1
  45. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.d.ts +35 -0
  46. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.d.ts.map +1 -0
  47. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.d.ts +1 -0
  48. package/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.d.ts.map +1 -1
  49. package/libs/feature/editor/src/lib/components/record-form/form-field/index.d.ts +1 -0
  50. package/libs/feature/editor/src/lib/components/record-form/form-field/index.d.ts.map +1 -1
  51. package/libs/feature/editor/src/lib/feature-editor.module.d.ts.map +1 -1
  52. package/libs/feature/editor/src/lib/fields.config.d.ts.map +1 -1
  53. package/libs/feature/search/src/lib/results-table/results-table.component.d.ts.map +1 -1
  54. package/libs/ui/elements/src/lib/ui-elements.module.d.ts +1 -1
  55. package/libs/ui/inputs/src/index.d.ts +1 -0
  56. package/libs/ui/inputs/src/index.d.ts.map +1 -1
  57. package/libs/ui/inputs/src/lib/badge/badge.component.d.ts +11 -0
  58. package/libs/ui/inputs/src/lib/badge/badge.component.d.ts.map +1 -0
  59. package/libs/ui/inputs/src/lib/ui-inputs.module.d.ts +2 -1
  60. package/libs/ui/inputs/src/lib/ui-inputs.module.d.ts.map +1 -1
  61. package/libs/ui/widgets/src/index.d.ts +0 -1
  62. package/libs/ui/widgets/src/index.d.ts.map +1 -1
  63. package/package.json +1 -1
  64. package/src/libs/api/metadata-converter/src/lib/gn4/types/index.ts +1 -0
  65. package/src/libs/api/metadata-converter/src/lib/gn4/types/keywords.model.ts +31 -0
  66. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +2 -2
  67. package/src/libs/api/metadata-converter/src/lib/iso19139/utils/keyword.mapper.ts +1 -1
  68. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +2 -2
  69. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.mapper.ts +24 -3
  70. package/src/libs/api/repository/src/lib/gn4/platform/gn4-platform.service.ts +70 -21
  71. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +4 -9
  72. package/src/libs/common/domain/src/lib/model/thesaurus/thesaurus.model.ts +13 -6
  73. package/src/libs/common/domain/src/lib/platform.service.interface.ts +3 -3
  74. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.html +20 -0
  75. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.ts +119 -0
  76. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +5 -0
  77. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +5 -0
  78. package/src/libs/feature/editor/src/lib/components/record-form/form-field/index.ts +1 -1
  79. package/src/libs/feature/editor/src/lib/feature-editor.module.ts +2 -1
  80. package/src/libs/feature/editor/src/lib/fields.config.ts +7 -0
  81. package/src/libs/feature/search/src/lib/results-table/results-table.component.ts +1 -2
  82. package/src/libs/ui/elements/src/lib/ui-elements.module.ts +2 -2
  83. package/src/libs/ui/inputs/src/index.ts +1 -0
  84. package/src/libs/ui/inputs/src/lib/badge/badge.component.css +0 -0
  85. package/src/libs/ui/inputs/src/lib/badge/badge.component.html +18 -0
  86. package/src/libs/ui/inputs/src/lib/badge/badge.component.ts +27 -0
  87. package/src/libs/ui/inputs/src/lib/ui-inputs.module.ts +3 -0
  88. package/src/libs/ui/widgets/src/index.ts +0 -1
  89. package/esm2022/libs/ui/widgets/src/lib/badge/badge.component.mjs +0 -18
  90. package/libs/ui/widgets/src/lib/badge/badge.component.d.ts +0 -7
  91. package/libs/ui/widgets/src/lib/badge/badge.component.d.ts.map +0 -1
  92. package/src/libs/ui/widgets/src/lib/badge/badge.component.html +0 -10
  93. package/src/libs/ui/widgets/src/lib/badge/badge.component.ts +0 -14
  94. /package/src/libs/{ui/widgets/src/lib/badge/badge.component.css → feature/editor/src/lib/components/record-form/form-field/form-field-keywords/form-field-keywords.component.css} +0 -0
@@ -1,4 +1,4 @@
1
- import { KeywordType } from '../../../../../../../libs/common/domain/src/lib/model/record'
1
+ import { KeywordType } from '../../../../../../../libs/common/domain/src/lib/model/thesaurus'
2
2
 
3
3
  export function getKeywordTypeFromKeywordTypeCode(
4
4
  typeCode: string
@@ -6,7 +6,6 @@ import {
6
6
  DatasetServiceDistribution,
7
7
  Individual,
8
8
  Keyword,
9
- KeywordThesaurus,
10
9
  RecordStatus,
11
10
  Role,
12
11
  ServiceEndpoint,
@@ -46,6 +45,7 @@ import {
46
45
  } from '../xml-utils'
47
46
  import { readKind } from './read-parts'
48
47
  import { namePartsToFull } from './utils/individual-name'
48
+ import { ThesaurusModel } from '../../../../../../libs/common/domain/src/lib/model/thesaurus'
49
49
 
50
50
  export function writeCharacterString(
51
51
  text: string
@@ -367,7 +367,7 @@ export function removeKeywords() {
367
367
  }
368
368
 
369
369
  // returns a <gmd:thesaurusName> element
370
- export function createThesaurus(thesaurus: KeywordThesaurus) {
370
+ export function createThesaurus(thesaurus: ThesaurusModel) {
371
371
  return pipe(
372
372
  createElement('gmd:thesaurusName'),
373
373
  createChild('gmd:CI_Citation'),
@@ -8,11 +8,16 @@ import { Injectable } from '@angular/core'
8
8
  import { AvatarServiceInterface } from '../auth'
9
9
  import { map } from 'rxjs/operators'
10
10
  import { Observable, of } from 'rxjs'
11
- import { ThesaurusModel } from '../../../../../../../libs/common/domain/src/lib/model/thesaurus/thesaurus.model'
12
11
  import {
12
+ Keyword,
13
13
  UserFeedback,
14
14
  UserFeedbackViewModel,
15
15
  } from '../../../../../../../libs/common/domain/src/lib/model/record'
16
+ import {
17
+ KeywordApiResponse,
18
+ ThesaurusApiResponse,
19
+ } from '../../../../../../../libs/api/metadata-converter/src'
20
+ import { KeywordType } from '../../../../../../../libs/common/domain/src/lib/model/thesaurus'
16
21
 
17
22
  @Injectable()
18
23
  export class Gn4PlatformMapper {
@@ -52,8 +57,12 @@ export class Gn4PlatformMapper {
52
57
  return { ...apiUser, id: id.toString() } as UserModel
53
58
  }
54
59
 
55
- thesaurusFromApi(thesaurus: any[], lang3?: string): ThesaurusModel {
56
- return thesaurus.map((keyword) => {
60
+ keywordsFromApi(
61
+ keywords: KeywordApiResponse[],
62
+ thesaurus: ThesaurusApiResponse[],
63
+ lang3?: string
64
+ ): Keyword[] {
65
+ return keywords.map((keyword): Keyword => {
57
66
  let key = keyword.uri
58
67
  // sometines GN can prefix an URI with an "all thesaurus" URI; only keep the last one
59
68
  if (key.indexOf('@@@') > -1) {
@@ -65,10 +74,22 @@ export class Gn4PlatformMapper {
65
74
  lang3 && lang3 in keyword.definitions
66
75
  ? keyword.definitions[lang3]
67
76
  : keyword.definition
77
+
78
+ const matchedThesaurus = thesaurus.find(
79
+ (thes) => keyword.thesaurusKey === thes.key
80
+ )
81
+
68
82
  return {
69
83
  key,
70
84
  label,
71
85
  description,
86
+ type: matchedThesaurus?.dname as KeywordType,
87
+ thesaurus: {
88
+ id: matchedThesaurus?.key,
89
+ name: matchedThesaurus?.title,
90
+ url: new URL(matchedThesaurus?.url),
91
+ type: matchedThesaurus?.dname as KeywordType,
92
+ },
72
93
  }
73
94
  })
74
95
  }
@@ -1,10 +1,11 @@
1
1
  import { Injectable } from '@angular/core'
2
- import { Observable, of, switchMap } from 'rxjs'
2
+ import { Observable, combineLatest, of, switchMap } from 'rxjs'
3
3
  import { catchError, map, shareReplay, tap } from 'rxjs/operators'
4
4
  import {
5
5
  MeApiService,
6
6
  RegistriesApiService,
7
7
  SiteApiService,
8
+ ThesaurusInfoApiModel,
8
9
  ToolsApiService,
9
10
  UserfeedbackApiService,
10
11
  UsersApiService,
@@ -12,13 +13,18 @@ import {
12
13
  import { PlatformServiceInterface } from '../../../../../../../libs/common/domain/src/lib/platform.service.interface'
13
14
  import { UserModel } from '../../../../../../../libs/common/domain/src/lib/model/user/user.model'
14
15
  import {
16
+ Keyword,
15
17
  Organization,
16
18
  UserFeedback,
17
19
  } from '../../../../../../../libs/common/domain/src/lib/model/record'
18
20
  import { Gn4PlatformMapper } from './gn4-platform.mapper'
19
21
  import { ltr } from 'semver'
20
- import { ThesaurusModel } from '../../../../../../../libs/common/domain/src/lib/model/thesaurus/thesaurus.model'
21
22
  import { LangService } from '../../../../../../../libs/util/i18n/src'
23
+ import { HttpClient } from '@angular/common/http'
24
+ import {
25
+ KeywordApiResponse,
26
+ ThesaurusApiResponse,
27
+ } from '../../../../../../../libs/api/metadata-converter/src'
22
28
 
23
29
  const minApiVersion = '4.2.2'
24
30
 
@@ -60,7 +66,7 @@ export class Gn4PlatformService implements PlatformServiceInterface {
60
66
  * A map of already loaded thesauri (groups of keywords); the key is a URI
61
67
  * @private
62
68
  */
63
- private thesauri: Record<string, Observable<ThesaurusModel>> = {}
69
+ private keywordsByThesauri: Record<string, Observable<Keyword[]>> = {}
64
70
 
65
71
  constructor(
66
72
  private siteApiService: SiteApiService,
@@ -70,7 +76,8 @@ export class Gn4PlatformService implements PlatformServiceInterface {
70
76
  private toolsApiService: ToolsApiService,
71
77
  private registriesApiService: RegistriesApiService,
72
78
  private langService: LangService,
73
- private userfeedbackApiService: UserfeedbackApiService
79
+ private userfeedbackApiService: UserfeedbackApiService,
80
+ private httpClient: HttpClient
74
81
  ) {
75
82
  this.me$ = this.meApi.getMe().pipe(
76
83
  switchMap((apiUser) => this.mapper.userFromMeApi(apiUser)),
@@ -121,7 +128,7 @@ export class Gn4PlatformService implements PlatformServiceInterface {
121
128
  // the thesaurus URI is inferred by removing a part of the keyword URI
122
129
  // this is not exact science but it's OK, we'll still end up loading a bunch of keywords at once anyway
123
130
  const thesaurusUri = key.replace(/\/([^/]+)$/, '/')
124
- return this.getThesaurusByUri(thesaurusUri).pipe(
131
+ return this.getKeywordsByUri(thesaurusUri).pipe(
125
132
  map((thesaurus) => {
126
133
  for (const item of thesaurus) {
127
134
  if (item.key === key) return item.label
@@ -133,12 +140,49 @@ export class Gn4PlatformService implements PlatformServiceInterface {
133
140
  return this.keyTranslations$.pipe(map((translations) => translations[key]))
134
141
  }
135
142
 
136
- getThesaurusByUri(uri: string): Observable<ThesaurusModel> {
137
- if (this.thesauri[uri]) {
138
- return this.thesauri[uri]
143
+ private allThesaurus$ = this.httpClient
144
+ .get(
145
+ `${this.registriesApiService.configuration.basePath}/thesaurus?_content_type=json`
146
+ )
147
+ .pipe(
148
+ map((thesaurus) => {
149
+ // FIXME: find a better way to exclude place keywords
150
+ // thesaurus[0].filter((thes) => thes.dname !== 'place')
151
+ return thesaurus[0] as ThesaurusApiResponse[]
152
+ }),
153
+ shareReplay(1)
154
+ )
155
+
156
+ searchKeywords(query: string): Observable<Keyword[]> {
157
+ const keywords$: Observable<KeywordApiResponse[]> =
158
+ this.registriesApiService.searchKeywords(
159
+ query,
160
+ this.langService.iso3,
161
+ 10,
162
+ 0,
163
+ null,
164
+ null,
165
+ null,
166
+ `*${query}*`
167
+ ) as Observable<KeywordApiResponse[]>
168
+
169
+ return combineLatest([keywords$, this.allThesaurus$]).pipe(
170
+ map(([keywords, thesaurus]) => {
171
+ return this.mapper.keywordsFromApi(
172
+ keywords,
173
+ thesaurus,
174
+ this.langService.iso3
175
+ )
176
+ })
177
+ )
178
+ }
179
+
180
+ getKeywordsByUri(uri: string): Observable<Keyword[]> {
181
+ if (this.keywordsByThesauri[uri]) {
182
+ return this.keywordsByThesauri[uri]
139
183
  }
140
- this.thesauri[uri] = this.registriesApiService
141
- .searchKeywords(
184
+ const keywords$: Observable<KeywordApiResponse[]> =
185
+ this.registriesApiService.searchKeywords(
142
186
  null,
143
187
  this.langService.iso3,
144
188
  1000,
@@ -147,18 +191,23 @@ export class Gn4PlatformService implements PlatformServiceInterface {
147
191
  null,
148
192
  null,
149
193
  `${uri}*`
150
- )
151
- .pipe(
152
- map((thesaurus) =>
153
- this.mapper.thesaurusFromApi(
154
- thesaurus as any[],
155
- this.langService.iso3
156
- )
157
- ),
158
- shareReplay(1)
159
- )
194
+ ) as Observable<KeywordApiResponse[]>
195
+
196
+ this.keywordsByThesauri[uri] = combineLatest([
197
+ keywords$,
198
+ this.allThesaurus$,
199
+ ]).pipe(
200
+ map(([keywords, thesaurus]) => {
201
+ return this.mapper.keywordsFromApi(
202
+ keywords,
203
+ thesaurus,
204
+ this.langService.iso3
205
+ )
206
+ }),
207
+ shareReplay(1)
208
+ )
160
209
 
161
- return this.thesauri[uri]
210
+ return this.keywordsByThesauri[uri]
162
211
  }
163
212
 
164
213
  getUserFeedbacks(uuid: string): Observable<UserFeedback[]> {
@@ -2,6 +2,7 @@ import { marker } from '@biesbjerg/ngx-translate-extract-marker'
2
2
  import type { Individual } from './contact.model'
3
3
  import type { Organization } from './organization.model'
4
4
  import type { Geometry } from 'geojson'
5
+ import { KeywordType, ThesaurusModel } from '../thesaurus'
5
6
 
6
7
  type Uuid = string
7
8
 
@@ -60,18 +61,12 @@ export type SpatialRepresentationType =
60
61
  | 'table'
61
62
  | 'point'
62
63
 
63
- export type KeywordType = 'place' | 'temporal' | 'theme' | 'other'
64
-
65
- export type KeywordThesaurus = {
66
- id: string
67
- name?: string
68
- url?: URL
69
- }
70
-
71
64
  export interface Keyword {
65
+ key?: string
72
66
  label: string
67
+ description?: string
73
68
  type: KeywordType
74
- thesaurus?: KeywordThesaurus
69
+ thesaurus?: ThesaurusModel
75
70
  }
76
71
  // languages should be expressed using two-letters ISO 639-1 codes
77
72
  export type LanguageCode = string
@@ -1,7 +1,14 @@
1
- export interface ThesaurusItemModel {
2
- key: string
3
- label: string
4
- description?: string
5
- }
1
+ export type KeywordType = 'place' | 'temporal' | 'theme' | 'other'
6
2
 
7
- export type ThesaurusModel = ThesaurusItemModel[]
3
+ export interface ThesaurusModel {
4
+ id: string
5
+ name?: string
6
+ url?: URL
7
+ thesaurusKey?: string
8
+ definition?: string
9
+ definitions?: object
10
+ uri?: string
11
+ value?: string
12
+ values?: object
13
+ type?: KeywordType
14
+ }
@@ -1,8 +1,7 @@
1
1
  import type { Observable } from 'rxjs'
2
2
  import type { UserModel } from './model/user/user.model'
3
3
  import type { Organization } from './model/record/organization.model'
4
- import type { ThesaurusModel } from './model/thesaurus/'
5
- import { UserFeedback } from './model/record'
4
+ import { Keyword, UserFeedback } from './model/record'
6
5
 
7
6
  export abstract class PlatformServiceInterface {
8
7
  abstract getType(): string
@@ -16,7 +15,8 @@ export abstract class PlatformServiceInterface {
16
15
  ): Observable<UserModel[]>
17
16
  abstract getOrganizations(): Observable<Organization[]>
18
17
  abstract translateKey(key: string): Observable<string>
19
- abstract getThesaurusByUri(uri: string): Observable<ThesaurusModel>
18
+ abstract searchKeywords(query: string): Observable<Keyword[]>
19
+ abstract getKeywordsByUri(uri: string): Observable<Keyword[]>
20
20
  abstract getUserFeedbacks(recordUuid: string): Observable<UserFeedback[]>
21
21
  abstract postUserFeedbacks(recordUuid: UserFeedback): Observable<void>
22
22
  }
@@ -0,0 +1,20 @@
1
+ <div class="flex flex-col gap-3">
2
+ <gn-ui-autocomplete
3
+ [placeholder]="'Search for keywords in all thesaurus'"
4
+ [displayWithFn]="displayWithFn"
5
+ [action]="autoCompleteAction"
6
+ (itemSelected)="handleItemSelection($event)"
7
+ (inputSubmitted)="handleInputSubmission($event)"
8
+ (inputCleared)="handleInputCleared()"
9
+ [value]="searchInputValue$ | async"
10
+ [clearOnSelection]="true"
11
+ ></gn-ui-autocomplete>
12
+ <div class="flex gap-2 flex-wrap">
13
+ <gn-ui-badge
14
+ *ngFor="let keyword of control.value; let index = index"
15
+ [removable]="true"
16
+ (badgeRemoveClicked)="removeKeyword(index)"
17
+ >{{ keyword.label }}</gn-ui-badge
18
+ >
19
+ </div>
20
+ </div>
@@ -0,0 +1,119 @@
1
+ import { CommonModule } from '@angular/common'
2
+ import {
3
+ ChangeDetectionStrategy,
4
+ Component,
5
+ EventEmitter,
6
+ Input,
7
+ OnInit,
8
+ Output,
9
+ } from '@angular/core'
10
+ import { FormControl } from '@angular/forms'
11
+ import {
12
+ KeywordType,
13
+ ThesaurusModel,
14
+ } from '../../../../../../../../../libs/common/domain/src/lib/model/thesaurus'
15
+ import { PlatformServiceInterface } from '../../../../../../../../../libs/common/domain/src/lib/platform.service.interface'
16
+ import {
17
+ DropdownSelectorComponent,
18
+ UiInputsModule,
19
+ } from '../../../../../../../../../libs/ui/inputs/src'
20
+ import { UiWidgetsModule } from '../../../../../../../../../libs/ui/widgets/src'
21
+ import { Observable, map } from 'rxjs'
22
+
23
+ @Component({
24
+ selector: 'gn-ui-form-field-keywords',
25
+ templateUrl: './form-field-keywords.component.html',
26
+ styleUrls: ['./form-field-keywords.component.css'],
27
+ changeDetection: ChangeDetectionStrategy.OnPush,
28
+ standalone: true,
29
+ imports: [
30
+ DropdownSelectorComponent,
31
+ UiInputsModule,
32
+ CommonModule,
33
+ UiWidgetsModule,
34
+ ],
35
+ })
36
+ export class FormFieldKeywordsComponent implements OnInit {
37
+ @Input() control: FormControl<any>
38
+ @Output() itemSelected = new EventEmitter<string>()
39
+ @Output() inputSubmitted = new EventEmitter<string>()
40
+ searchInputValue$: Observable<void | { title: string }>
41
+ allThesaurus$: Observable<any[]>
42
+
43
+ displayWithFn = (item) => {
44
+ if (item) {
45
+ return `${item?.title} (${item?.value?.name})`
46
+ }
47
+ return null
48
+ }
49
+
50
+ autoCompleteAction = (query: string) => {
51
+ const keywords$ = this.platformService.searchKeywords(query).pipe(
52
+ map((thesaurus) =>
53
+ thesaurus.map((thes) => {
54
+ return { title: thes.label, value: thes.thesaurus }
55
+ })
56
+ )
57
+ )
58
+
59
+ return keywords$
60
+ }
61
+
62
+ constructor(private platformService: PlatformServiceInterface) {}
63
+
64
+ ngOnInit(): void {
65
+ this.searchInputValue$ = this.autoCompleteAction('')[0]
66
+ }
67
+
68
+ // type: { title: string; value: ThesaurusModel }
69
+ handleItemSelection(item) {
70
+ this.addKeyword({
71
+ label: item.title,
72
+ thesaurus: item.value,
73
+ type: item.value.type,
74
+ })
75
+ }
76
+
77
+ handleInputSubmission(any: string) {
78
+ // Should there be an input submission?
79
+
80
+ if (this.inputSubmitted.observers.length > 0) {
81
+ this.inputSubmitted.emit(any)
82
+ } else {
83
+ // this.searchService.updateFilters({ any })
84
+ }
85
+ }
86
+
87
+ async handleInputCleared() {
88
+ this.autoCompleteAction('')
89
+ }
90
+
91
+ addKeyword(item: {
92
+ label: string
93
+ thesaurus: ThesaurusModel
94
+ type: KeywordType
95
+ }) {
96
+ const addedKeywords = [...this.control.value, item]
97
+
98
+ // remove duplicates from keyword
99
+ const filteredKeywords = addedKeywords.filter((value, index, self) => {
100
+ return (
101
+ index ===
102
+ self.findIndex(
103
+ (t) =>
104
+ t?.label === value?.label &&
105
+ t?.thesaurus?.id === value?.thesaurus?.id &&
106
+ t?.type === value?.type
107
+ )
108
+ )
109
+ })
110
+
111
+ this.control.setValue(filteredKeywords)
112
+ }
113
+
114
+ removeKeyword(index: number) {
115
+ const removeKeywords = this.control.value.filter((_, i) => i !== index)
116
+
117
+ this.control.setValue(removeKeywords)
118
+ }
119
+ }
@@ -90,6 +90,11 @@
90
90
  <ng-container *ngIf="isSpatialExtentField">
91
91
  <gn-ui-form-field-spatial-extent></gn-ui-form-field-spatial-extent>
92
92
  </ng-container>
93
+ <ng-container *ngIf="isKeywords">
94
+ <gn-ui-form-field-keywords
95
+ [control]="formControl"
96
+ ></gn-ui-form-field-keywords>
97
+ </ng-container>
93
98
  <div
94
99
  *ngIf="isFieldInvalid && config.invalidHintKey"
95
100
  class="mt-2 text-pink-500 text-sm field-invalid-hint"
@@ -27,6 +27,7 @@ import { FormFieldSimpleComponent } from './form-field-simple/form-field-simple.
27
27
  import { FormFieldSpatialExtentComponent } from './form-field-spatial-extent/form-field-spatial-extent.component'
28
28
  import { FormFieldConfig } from './form-field.model'
29
29
  import { FormFieldUpdateFrequencyComponent } from './form-field-update-frequency/form-field-update-frequency.component'
30
+ import { FormFieldKeywordsComponent } from './form-field-keywords/form-field-keywords.component'
30
31
 
31
32
  @Component({
32
33
  selector: 'gn-ui-form-field',
@@ -51,6 +52,7 @@ import { FormFieldUpdateFrequencyComponent } from './form-field-update-frequency
51
52
  FormFieldSpatialExtentComponent,
52
53
  FormFieldFileComponent,
53
54
  FormFieldArrayComponent,
55
+ FormFieldKeywordsComponent,
54
56
  TranslateModule,
55
57
  ],
56
58
  })
@@ -137,6 +139,9 @@ export class FormFieldComponent {
137
139
  get isTemporalExtents() {
138
140
  return this.model === 'temporalExtents'
139
141
  }
142
+ get isKeywords() {
143
+ return this.model === 'keywords'
144
+ }
140
145
 
141
146
  get withoutWrapper() {
142
147
  return this.model === 'title' || this.model === 'abstract'
@@ -1,7 +1,7 @@
1
+ export * from './form-field-keywords/form-field-keywords.component'
1
2
  export * from './form-field-license/form-field-license.component'
2
3
  export * from './form-field-resource-updated/form-field-resource-updated.component'
3
4
  export * from './form-field-temporal-extents/form-field-temporal-extents.component'
4
-
5
5
  export * from './form-field-simple/form-field-simple.component'
6
6
  export * from './form-field-file/form-field-file.component'
7
7
  export * from './form-field-rich/form-field-rich.component'
@@ -15,6 +15,7 @@ import * as fromEditor from './+state/editor.reducer'
15
15
  import { WizardFieldComponent } from './components/wizard-field/wizard-field.component'
16
16
  import { WizardSummarizeComponent } from './components/wizard-summarize/wizard-summarize.component'
17
17
  import { WizardComponent } from './components/wizard/wizard.component'
18
+ import { Gn4PlatformService } from '../../../../../libs/api/repository/src'
18
19
 
19
20
  @NgModule({
20
21
  declarations: [
@@ -39,7 +40,7 @@ import { WizardComponent } from './components/wizard/wizard.component'
39
40
  EffectsModule.forFeature([EditorEffects]),
40
41
  ],
41
42
  exports: [WizardComponent, WizardSummarizeComponent],
42
- providers: [EditorFacade],
43
+ providers: [EditorFacade, Gn4PlatformService],
43
44
  })
44
45
  export class FeatureEditorModule {}
45
46
  export * from './models/index'
@@ -61,4 +61,11 @@ export const DEFAULT_FIELDS: EditorFieldsConfig = [
61
61
  type: 'list',
62
62
  },
63
63
  },
64
+ {
65
+ model: 'keywords',
66
+ formFieldConfig: {
67
+ labelKey: marker('editor.record.form.keywords'),
68
+ type: 'list',
69
+ },
70
+ },
64
71
  ]
@@ -6,7 +6,7 @@ import {
6
6
  getFileFormat,
7
7
  getFormatPriority,
8
8
  } from '../../../../../../libs/util/shared/src'
9
- import { UiInputsModule } from '../../../../../../libs/ui/inputs/src'
9
+ import { BadgeComponent, UiInputsModule } from '../../../../../../libs/ui/inputs/src'
10
10
  import {
11
11
  InteractiveTableColumnComponent,
12
12
  InteractiveTableComponent,
@@ -20,7 +20,6 @@ import { CommonModule } from '@angular/common'
20
20
  import { map, take } from 'rxjs/operators'
21
21
  import { FieldSort } from '../../../../../../libs/common/domain/src/lib/model/search'
22
22
  import { SearchService } from '../utils/service/search.service'
23
- import { BadgeComponent } from '../../../../../../libs/ui/widgets/src'
24
23
  import { RecordsRepositoryInterface } from '../../../../../../libs/common/domain/src/lib/repository/records-repository.interface'
25
24
 
26
25
  @Component({
@@ -9,7 +9,7 @@ import { ContentGhostComponent } from './content-ghost/content-ghost.component'
9
9
  import { DownloadItemComponent } from './download-item/download-item.component'
10
10
  import { DownloadsListComponent } from './downloads-list/downloads-list.component'
11
11
  import { ApiCardComponent } from './api-card/api-card.component'
12
- import { BadgeComponent, UiWidgetsModule } from '../../../../../libs/ui/widgets/src'
12
+ import { UiWidgetsModule } from '../../../../../libs/ui/widgets/src'
13
13
  import { UiLayoutModule } from '../../../../../libs/ui/layout/src'
14
14
  import { TranslateModule } from '@ngx-translate/core'
15
15
  import { RelatedRecordCardComponent } from './related-record-card/related-record-card.component'
@@ -20,7 +20,7 @@ import { MetadataQualityItemComponent } from './metadata-quality-item/metadata-q
20
20
  import { ErrorComponent } from './error/error.component'
21
21
  import { PaginationComponent } from './pagination/pagination.component'
22
22
  import { ThumbnailComponent } from './thumbnail/thumbnail.component'
23
- import { UiInputsModule } from '../../../../../libs/ui/inputs/src'
23
+ import { BadgeComponent, UiInputsModule } from '../../../../../libs/ui/inputs/src'
24
24
  import { FormsModule } from '@angular/forms'
25
25
  import { AvatarComponent } from './avatar/avatar.component'
26
26
  import { UserPreviewComponent } from './user-preview/user-preview.component'
@@ -1,4 +1,5 @@
1
1
  export * from './lib/autocomplete/autocomplete.component'
2
+ export * from './lib/badge/badge.component'
2
3
  export * from './lib/button/button.component'
3
4
  export * from './lib/check-toggle/check-toggle.component'
4
5
  export * from './lib/checkbox/checkbox.component'
@@ -0,0 +1,18 @@
1
+ <div
2
+ class="gn-ui-badge flex items-center gap-1.5"
3
+ [ngClass]="
4
+ clickable
5
+ ? 'hover:bg-primary cursor-pointer transition-colors duration-100'
6
+ : ''
7
+ "
8
+ >
9
+ <span><ng-content></ng-content></span>
10
+ <gn-ui-button
11
+ class="material-symbols-outlined cursor-pointer text-xs"
12
+ *ngIf="removable"
13
+ (buttonClick)="removeBadge()"
14
+ extraClass="p-1 bg-transparent border-0 border-transparent "
15
+ >
16
+ close
17
+ </gn-ui-button>
18
+ </div>
@@ -0,0 +1,27 @@
1
+ import { CommonModule } from '@angular/common'
2
+ import {
3
+ ChangeDetectionStrategy,
4
+ Component,
5
+ EventEmitter,
6
+ Input,
7
+ Output,
8
+ } from '@angular/core'
9
+ import { ButtonComponent } from '../button/button.component'
10
+
11
+ @Component({
12
+ selector: 'gn-ui-badge',
13
+ templateUrl: './badge.component.html',
14
+ styleUrls: ['./badge.component.css'],
15
+ changeDetection: ChangeDetectionStrategy.OnPush,
16
+ standalone: true,
17
+ imports: [CommonModule, ButtonComponent],
18
+ })
19
+ export class BadgeComponent {
20
+ @Input() clickable? = false
21
+ @Input() removable? = false
22
+ @Output() badgeRemoveClicked = new EventEmitter<void>()
23
+
24
+ removeBadge() {
25
+ this.badgeRemoveClicked.emit()
26
+ }
27
+ }
@@ -6,6 +6,7 @@ import { TagInputModule } from 'ngx-chips'
6
6
  import { NgxDropzoneModule } from 'ngx-dropzone'
7
7
  import { AutocompleteComponent } from './autocomplete/autocomplete.component'
8
8
  import { ButtonComponent } from './button/button.component'
9
+ import { BadgeComponent } from './badge/badge.component'
9
10
  import { ChipsInputComponent } from './chips-input/chips-input.component'
10
11
  import { DragAndDropFileInputComponent } from './drag-and-drop-file-input/drag-and-drop-file-input.component'
11
12
  import { DropdownSelectorComponent } from './dropdown-selector/dropdown-selector.component'
@@ -73,6 +74,7 @@ import { ImageInputComponent } from './image-input/image-input.component'
73
74
  DropdownSelectorComponent,
74
75
  DateRangePickerComponent,
75
76
  CheckToggleComponent,
77
+ BadgeComponent,
76
78
  ],
77
79
  exports: [
78
80
  DropdownSelectorComponent,
@@ -93,6 +95,7 @@ import { ImageInputComponent } from './image-input/image-input.component'
93
95
  DateRangePickerComponent,
94
96
  EditableLabelDirective,
95
97
  ImageInputComponent,
98
+ BadgeComponent,
96
99
  ],
97
100
  })
98
101
  export class UiInputsModule {}
@@ -1,7 +1,6 @@
1
1
  export * from './lib/ui-widgets.module'
2
2
  export * from './lib/progress-bar/progress-bar.component'
3
3
  export * from './lib/loading-mask/loading-mask.component'
4
- export * from './lib/badge/badge.component'
5
4
  export * from './lib/color-scale/color-scale.component'
6
5
  export * from './lib/popup-alert/popup-alert.component'
7
6
  export * from './lib/spinning-loader/spinning-loader.component'