geonetwork-ui 2.3.0 → 2.4.0-dev.cd525aa1

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 (79) hide show
  1. package/esm2022/libs/api/repository/src/lib/gn4/gn4-repository.mjs +57 -9
  2. package/esm2022/libs/common/domain/src/lib/repository/records-repository.interface.mjs +1 -1
  3. package/esm2022/libs/data-access/gn4/src/openapi/api/records.api.service.mjs +2 -2
  4. package/esm2022/libs/feature/editor/src/lib/+state/editor.actions.mjs +1 -1
  5. package/esm2022/libs/feature/editor/src/lib/+state/editor.effects.mjs +11 -3
  6. package/esm2022/libs/feature/editor/src/lib/+state/editor.facade.mjs +5 -3
  7. package/esm2022/libs/feature/editor/src/lib/+state/editor.reducer.mjs +6 -2
  8. package/esm2022/libs/feature/editor/src/lib/+state/editor.selectors.mjs +3 -1
  9. package/esm2022/libs/feature/editor/src/lib/services/editor.service.mjs +20 -40
  10. package/esm2022/libs/feature/record/src/lib/state/mdview.effects.mjs +2 -2
  11. package/esm2022/libs/feature/search/src/lib/results-table/results-table.component.mjs +18 -10
  12. package/esm2022/libs/ui/elements/src/lib/metadata-info/metadata-info.component.mjs +7 -7
  13. package/esm2022/libs/ui/elements/src/lib/ui-elements.module.mjs +7 -4
  14. package/esm2022/libs/ui/widgets/src/lib/badge/badge.component.mjs +4 -3
  15. package/esm2022/libs/ui/widgets/src/lib/ui-widgets.module.mjs +1 -6
  16. package/esm2022/translations/de.json +5 -1
  17. package/esm2022/translations/en.json +5 -1
  18. package/esm2022/translations/es.json +5 -1
  19. package/esm2022/translations/fr.json +5 -1
  20. package/esm2022/translations/it.json +5 -1
  21. package/esm2022/translations/nl.json +5 -1
  22. package/esm2022/translations/pt.json +5 -1
  23. package/fesm2022/geonetwork-ui.mjs +156 -77
  24. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  25. package/libs/api/repository/src/lib/gn4/gn4-repository.d.ts +15 -3
  26. package/libs/api/repository/src/lib/gn4/gn4-repository.d.ts.map +1 -1
  27. package/libs/common/domain/src/lib/repository/records-repository.interface.d.ts +24 -1
  28. package/libs/common/domain/src/lib/repository/records-repository.interface.d.ts.map +1 -1
  29. package/libs/data-access/gn4/src/openapi/api/records.api.service.d.ts +6 -6
  30. package/libs/data-access/gn4/src/openapi/api/records.api.service.d.ts.map +1 -1
  31. package/libs/feature/editor/src/lib/+state/editor.actions.d.ts +4 -0
  32. package/libs/feature/editor/src/lib/+state/editor.actions.d.ts.map +1 -1
  33. package/libs/feature/editor/src/lib/+state/editor.effects.d.ts +5 -0
  34. package/libs/feature/editor/src/lib/+state/editor.effects.d.ts.map +1 -1
  35. package/libs/feature/editor/src/lib/+state/editor.facade.d.ts +9 -8
  36. package/libs/feature/editor/src/lib/+state/editor.facade.d.ts.map +1 -1
  37. package/libs/feature/editor/src/lib/+state/editor.reducer.d.ts +10 -0
  38. package/libs/feature/editor/src/lib/+state/editor.reducer.d.ts.map +1 -1
  39. package/libs/feature/editor/src/lib/+state/editor.selectors.d.ts +2 -0
  40. package/libs/feature/editor/src/lib/+state/editor.selectors.d.ts.map +1 -1
  41. package/libs/feature/editor/src/lib/services/editor.service.d.ts +6 -9
  42. package/libs/feature/editor/src/lib/services/editor.service.d.ts.map +1 -1
  43. package/libs/feature/record/src/lib/state/mdview.effects.d.ts.map +1 -1
  44. package/libs/feature/search/src/lib/results-table/results-table.component.d.ts +4 -1
  45. package/libs/feature/search/src/lib/results-table/results-table.component.d.ts.map +1 -1
  46. package/libs/ui/elements/src/lib/ui-elements.module.d.ts +2 -1
  47. package/libs/ui/elements/src/lib/ui-elements.module.d.ts.map +1 -1
  48. package/libs/ui/widgets/src/lib/badge/badge.component.d.ts +2 -2
  49. package/libs/ui/widgets/src/lib/badge/badge.component.d.ts.map +1 -1
  50. package/libs/ui/widgets/src/lib/ui-widgets.module.d.ts +10 -11
  51. package/libs/ui/widgets/src/lib/ui-widgets.module.d.ts.map +1 -1
  52. package/package.json +1 -1
  53. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +145 -7
  54. package/src/libs/common/domain/src/lib/repository/records-repository.interface.ts +36 -3
  55. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +203 -0
  56. package/src/libs/data-access/gn4/src/openapi/api/records.api.service.ts +9 -9
  57. package/src/libs/data-access/gn4/src/spec.yaml +4 -5
  58. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +5 -1
  59. package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +31 -3
  60. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +13 -3
  61. package/src/libs/feature/editor/src/lib/+state/editor.reducer.ts +22 -5
  62. package/src/libs/feature/editor/src/lib/+state/editor.selectors.ts +10 -0
  63. package/src/libs/feature/editor/src/lib/services/editor.service.ts +17 -52
  64. package/src/libs/feature/record/src/lib/state/mdview.effects.ts +1 -3
  65. package/src/libs/feature/search/src/lib/results-table/results-table.component.html +12 -1
  66. package/src/libs/feature/search/src/lib/results-table/results-table.component.ts +9 -1
  67. package/src/libs/ui/elements/src/lib/ui-elements.module.ts +2 -1
  68. package/src/libs/ui/widgets/src/lib/badge/badge.component.html +1 -1
  69. package/src/libs/ui/widgets/src/lib/badge/badge.component.ts +4 -1
  70. package/src/libs/ui/widgets/src/lib/ui-widgets.module.ts +0 -3
  71. package/tailwind.base.css +21 -1
  72. package/translations/de.json +5 -1
  73. package/translations/en.json +5 -1
  74. package/translations/es.json +5 -1
  75. package/translations/fr.json +5 -1
  76. package/translations/it.json +5 -1
  77. package/translations/nl.json +5 -1
  78. package/translations/pt.json +5 -1
  79. package/translations/sk.json +5 -1
@@ -252,3 +252,206 @@ Ce lot de données produit en 2019, a été numérisé à partir du PCI Vecteur
252
252
  languages: ['fr', 'de'],
253
253
  },
254
254
  ])
255
+
256
+ export const DATASET_RECORD_SIMPLE: DatasetRecord = {
257
+ uniqueIdentifier: 'my-dataset-001',
258
+ kind: 'dataset',
259
+ languages: [],
260
+ recordUpdated: new Date('2022-02-01T14:12:00.000Z'),
261
+ resourceCreated: new Date('2022-09-01T12:18:19.000Z'),
262
+ resourceUpdated: new Date('2022-12-04T14:12:00.000Z'),
263
+ status: 'ongoing',
264
+ title: 'A very interesting dataset (un jeu de données très intéressant)',
265
+ abstract: `This dataset has been established for testing purposes.`,
266
+ ownerOrganization: { name: 'MyOrganization' },
267
+ contacts: [
268
+ {
269
+ email: 'bob@org.net',
270
+ position: 'developer',
271
+ organization: { name: 'MyOrganization' },
272
+ role: 'point_of_contact',
273
+ firstName: 'Bob',
274
+ lastName: 'TheGreat',
275
+ },
276
+ ],
277
+ contactsForResource: [],
278
+ keywords: [],
279
+ topics: ['testData'],
280
+ licenses: [],
281
+ legalConstraints: [],
282
+ securityConstraints: [],
283
+ otherConstraints: [],
284
+ lineage: 'This record was edited manually to test the conversion processes',
285
+ spatialRepresentation: 'grid',
286
+ overviews: [],
287
+ spatialExtents: [],
288
+ temporalExtents: [],
289
+ distributions: [
290
+ {
291
+ type: 'download',
292
+ url: new URL('http://my-org.net/download/1.zip'),
293
+ name: 'Direct download',
294
+ description: 'Dataset downloaded as a shapefile',
295
+ mimeType: 'x-gis/x-shapefile',
296
+ },
297
+ ],
298
+ updateFrequency: { per: 'month', updatedTimes: 3 },
299
+ }
300
+
301
+ export const DATASET_RECORD_SIMPLE_AS_XML = `<?xml version="1.0" encoding="UTF-8"?>
302
+ <mdb:MD_Metadata xmlns:mdb="http://standards.iso.org/iso/19115/-3/mdb/2.0" xmlns:mcc="http://standards.iso.org/iso/19115/-3/mcc/1.0" xmlns:gco="http://standards.iso.org/iso/19115/-3/gco/1.0" xmlns:cit="http://standards.iso.org/iso/19115/-3/cit/2.0" xmlns:mri="http://standards.iso.org/iso/19115/-3/mri/1.0" xmlns:mco="http://standards.iso.org/iso/19115/-3/mco/1.0" xmlns:gcx="http://standards.iso.org/iso/19115/-3/gcx/1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:mmi="http://standards.iso.org/iso/19115/-3/mmi/1.0" xmlns:mrd="http://standards.iso.org/iso/19115/-3/mrd/1.0" xmlns:mrl="http://standards.iso.org/iso/19115/-3/mrl/2.0">
303
+ <mdb:metadataIdentifier>
304
+ <mcc:MD_Identifier>
305
+ <mcc:code>
306
+ <gco:CharacterString>my-dataset-001</gco:CharacterString>
307
+ </mcc:code>
308
+ </mcc:MD_Identifier>
309
+ </mdb:metadataIdentifier>
310
+ <mdb:metadataScope>
311
+ <mdb:MD_MetadataScope>
312
+ <mdb:resourceScope>
313
+ <mcc:MD_ScopeCode codeList="https://standards.iso.org/iso/19115/resources/Codelists/cat/codelists.xml#MD_ScopeCode" codeListValue="dataset">dataset</mcc:MD_ScopeCode>
314
+ </mdb:resourceScope>
315
+ </mdb:MD_MetadataScope>
316
+ </mdb:metadataScope>
317
+ <mdb:contact>
318
+ <cit:CI_Responsibility>
319
+ <cit:role>
320
+ <cit:CI_RoleCode codeList="https://standards.iso.org/iso/19115/resources/Codelists/cat/codelists.xml#CI_RoleCode" codeListValue="pointOfContact">pointOfContact</cit:CI_RoleCode>
321
+ </cit:role>
322
+ <cit:party>
323
+ <cit:CI_Organisation>
324
+ <cit:name>
325
+ <gco:CharacterString>MyOrganization</gco:CharacterString>
326
+ </cit:name>
327
+ <cit:contactInfo>
328
+ <cit:CI_Contact>
329
+ <cit:address>
330
+ <cit:CI_Address>
331
+ <cit:electronicMailAddress>
332
+ <gco:CharacterString>bob@org.net</gco:CharacterString>
333
+ </cit:electronicMailAddress>
334
+ </cit:CI_Address>
335
+ </cit:address>
336
+ </cit:CI_Contact>
337
+ </cit:contactInfo>
338
+ <cit:individual>
339
+ <cit:CI_Individual>
340
+ <cit:name>
341
+ <gco:CharacterString>Bob TheGreat</gco:CharacterString>
342
+ </cit:name>
343
+ <cit:positionName>
344
+ <gco:CharacterString>developer</gco:CharacterString>
345
+ </cit:positionName>
346
+ </cit:CI_Individual>
347
+ </cit:individual>
348
+ </cit:CI_Organisation>
349
+ </cit:party>
350
+ </cit:CI_Responsibility>
351
+ </mdb:contact>
352
+ <mdb:dateInfo>
353
+ <cit:CI_Date>
354
+ <cit:date>
355
+ <gco:DateTime>2022-02-01T15:12:00</gco:DateTime>
356
+ </cit:date>
357
+ <cit:dateType>
358
+ <cit:CI_DateTypeCode codeList="https://standards.iso.org/iso/19115/resources/Codelists/cat/codelists.xml#CI_DateTypeCode" codeListValue="revision">revision</cit:CI_DateTypeCode>
359
+ </cit:dateType>
360
+ </cit:CI_Date>
361
+ </mdb:dateInfo>
362
+ <mdb:identificationInfo>
363
+ <mri:MD_DataIdentification>
364
+ <mri:citation>
365
+ <cit:CI_Citation>
366
+ <cit:title>
367
+ <gco:CharacterString>A very interesting dataset (un jeu de données très intéressant)</gco:CharacterString>
368
+ </cit:title>
369
+ <cit:date>
370
+ <cit:CI_Date>
371
+ <cit:date>
372
+ <gco:DateTime>2022-09-01T14:18:19</gco:DateTime>
373
+ </cit:date>
374
+ <cit:dateType>
375
+ <cit:CI_DateTypeCode codeList="https://standards.iso.org/iso/19115/resources/Codelists/cat/codelists.xml#CI_DateTypeCode" codeListValue="creation">creation</cit:CI_DateTypeCode>
376
+ </cit:dateType>
377
+ </cit:CI_Date>
378
+ </cit:date>
379
+ <cit:date>
380
+ <cit:CI_Date>
381
+ <cit:date>
382
+ <gco:DateTime>2022-12-04T15:12:00</gco:DateTime>
383
+ </cit:date>
384
+ <cit:dateType>
385
+ <cit:CI_DateTypeCode codeList="https://standards.iso.org/iso/19115/resources/Codelists/cat/codelists.xml#CI_DateTypeCode" codeListValue="revision">revision</cit:CI_DateTypeCode>
386
+ </cit:dateType>
387
+ </cit:CI_Date>
388
+ </cit:date>
389
+ </cit:CI_Citation>
390
+ </mri:citation>
391
+ <mri:abstract>
392
+ <gco:CharacterString>This dataset has been established for testing purposes.</gco:CharacterString>
393
+ </mri:abstract>
394
+ <mri:topicCategory>
395
+ <mri:MD_TopicCategoryCode>testData</mri:MD_TopicCategoryCode>
396
+ </mri:topicCategory>
397
+ <mri:status>
398
+ <mcc:MD_ProgressCode codeList="https://standards.iso.org/iso/19115/resources/Codelists/cat/codelists.xml#MD_ProgressCode" codeListValue="onGoing">onGoing</mcc:MD_ProgressCode>
399
+ </mri:status>
400
+ <mri:resourceMaintenance>
401
+ <mmi:MD_MaintenanceInformation>
402
+ <mmi:userDefinedMaintenanceFrequency>
403
+ <gco:TM_PeriodDuration>P0Y0M10D</gco:TM_PeriodDuration>
404
+ </mmi:userDefinedMaintenanceFrequency>
405
+ </mmi:MD_MaintenanceInformation>
406
+ </mri:resourceMaintenance>
407
+ <mri:spatialRepresentationType>
408
+ <mcc:MD_SpatialRepresentationTypeCode codeList="https://standards.iso.org/iso/19115/resources/Codelists/cat/codelists.xml#MD_SpatialRepresentationTypeCode" codeListValue="grid">grid</mcc:MD_SpatialRepresentationTypeCode>
409
+ </mri:spatialRepresentationType>
410
+ </mri:MD_DataIdentification>
411
+ </mdb:identificationInfo>
412
+ <mdb:distributionInfo>
413
+ <mrd:MD_Distribution>
414
+ <mrd:distributionFormat>
415
+ <mrd:MD_Format>
416
+ <mrd:formatSpecificationCitation>
417
+ <cit:CI_Citation>
418
+ <cit:title>
419
+ <gco:CharacterString>x-gis/x-shapefile</gco:CharacterString>
420
+ </cit:title>
421
+ </cit:CI_Citation>
422
+ </mrd:formatSpecificationCitation>
423
+ </mrd:MD_Format>
424
+ </mrd:distributionFormat>
425
+ <mrd:transferOptions>
426
+ <mrd:MD_DigitalTransferOptions>
427
+ <mrd:onLine>
428
+ <cit:CI_OnlineResource>
429
+ <cit:linkage>
430
+ <gco:CharacterString>http://my-org.net/download/1.zip</gco:CharacterString>
431
+ </cit:linkage>
432
+ <cit:description>
433
+ <gco:CharacterString>Dataset downloaded as a shapefile</gco:CharacterString>
434
+ </cit:description>
435
+ <cit:name>
436
+ <gco:CharacterString>Direct download</gco:CharacterString>
437
+ </cit:name>
438
+ <cit:protocol>
439
+ <gco:CharacterString>WWW:DOWNLOAD</gco:CharacterString>
440
+ </cit:protocol>
441
+ <cit:function>
442
+ <cit:CI_OnLineFunctionCode codeList="http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#CI_OnLineFunctionCode" codeListValue="download"/>
443
+ </cit:function>
444
+ </cit:CI_OnlineResource>
445
+ </mrd:onLine>
446
+ </mrd:MD_DigitalTransferOptions>
447
+ </mrd:transferOptions>
448
+ </mrd:MD_Distribution>
449
+ </mdb:distributionInfo>
450
+ <mdb:resourceLineage>
451
+ <mrl:LI_Lineage>
452
+ <mrl:statement>
453
+ <gco:CharacterString>This record was edited manually to test the conversion processes</gco:CharacterString>
454
+ </mrl:statement>
455
+ </mrl:LI_Lineage>
456
+ </mdb:resourceLineage>
457
+ </mdb:MD_Metadata>`
@@ -4586,8 +4586,8 @@ export class RecordsApiService {
4586
4586
  accept?: string,
4587
4587
  observe?: 'body',
4588
4588
  reportProgress?: boolean,
4589
- options?: { httpHeaderAccept?: 'application/json' | 'application/xml' }
4590
- ): Observable<object>
4589
+ options?: { httpHeaderAccept?: 'application/xml' | 'application/json' }
4590
+ ): Observable<string>
4591
4591
  public getRecordAs(
4592
4592
  metadataUuid: string,
4593
4593
  addSchemaLocation?: boolean,
@@ -4598,8 +4598,8 @@ export class RecordsApiService {
4598
4598
  accept?: string,
4599
4599
  observe?: 'response',
4600
4600
  reportProgress?: boolean,
4601
- options?: { httpHeaderAccept?: 'application/json' | 'application/xml' }
4602
- ): Observable<HttpResponse<object>>
4601
+ options?: { httpHeaderAccept?: 'application/xml' | 'application/json' }
4602
+ ): Observable<HttpResponse<string>>
4603
4603
  public getRecordAs(
4604
4604
  metadataUuid: string,
4605
4605
  addSchemaLocation?: boolean,
@@ -4610,8 +4610,8 @@ export class RecordsApiService {
4610
4610
  accept?: string,
4611
4611
  observe?: 'events',
4612
4612
  reportProgress?: boolean,
4613
- options?: { httpHeaderAccept?: 'application/json' | 'application/xml' }
4614
- ): Observable<HttpEvent<object>>
4613
+ options?: { httpHeaderAccept?: 'application/xml' | 'application/json' }
4614
+ ): Observable<HttpEvent<string>>
4615
4615
  public getRecordAs(
4616
4616
  metadataUuid: string,
4617
4617
  addSchemaLocation?: boolean,
@@ -4622,7 +4622,7 @@ export class RecordsApiService {
4622
4622
  accept?: string,
4623
4623
  observe: any = 'body',
4624
4624
  reportProgress: boolean = false,
4625
- options?: { httpHeaderAccept?: 'application/json' | 'application/xml' }
4625
+ options?: { httpHeaderAccept?: 'application/xml' | 'application/json' }
4626
4626
  ): Observable<any> {
4627
4627
  if (metadataUuid === null || metadataUuid === undefined) {
4628
4628
  throw new Error(
@@ -4677,8 +4677,8 @@ export class RecordsApiService {
4677
4677
  if (httpHeaderAcceptSelected === undefined) {
4678
4678
  // to determine the Accept header
4679
4679
  const httpHeaderAccepts: string[] = [
4680
- 'application/json',
4681
4680
  'application/xml',
4681
+ 'application/json',
4682
4682
  ]
4683
4683
  httpHeaderAcceptSelected =
4684
4684
  this.configuration.selectHeaderAccept(httpHeaderAccepts)
@@ -4695,7 +4695,7 @@ export class RecordsApiService {
4695
4695
  responseType_ = 'text'
4696
4696
  }
4697
4697
 
4698
- return this.httpClient.get<object>(
4698
+ return this.httpClient.get<string>(
4699
4699
  `${this.configuration.basePath}/records/${encodeURIComponent(
4700
4700
  String(metadataUuid)
4701
4701
  )}/formatters/xml`,
@@ -11114,16 +11114,15 @@ paths:
11114
11114
  default:
11115
11115
  description: default response
11116
11116
  content:
11117
- application/json: {}
11117
+ application/xml:
11118
+ schema:
11119
+ type: string
11118
11120
  "200":
11119
11121
  description: Return the record.
11120
11122
  content:
11121
11123
  application/xml:
11122
11124
  schema:
11123
- type: object
11124
- application/json:
11125
- schema:
11126
- type: object
11125
+ type: string
11127
11126
  "403":
11128
11127
  description: Operation not allowed. User needs to be able to view the resource.
11129
11128
  content:
@@ -4,7 +4,11 @@ import { SaveRecordError } from './editor.models'
4
4
 
5
5
  export const openRecord = createAction(
6
6
  '[Editor] Open record',
7
- props<{ record: CatalogRecord }>()
7
+ props<{
8
+ record: CatalogRecord
9
+ alreadySavedOnce: boolean
10
+ recordSource?: string | null
11
+ }>()
8
12
  )
9
13
 
10
14
  export const updateRecordField = createAction(
@@ -1,16 +1,18 @@
1
1
  import { inject, Injectable } from '@angular/core'
2
2
  import { Actions, createEffect, ofType } from '@ngrx/effects'
3
- import { of, withLatestFrom } from 'rxjs'
3
+ import { debounceTime, filter, of, withLatestFrom } from 'rxjs'
4
4
  import { catchError, map, switchMap } from 'rxjs/operators'
5
5
  import * as EditorActions from './editor.actions'
6
6
  import { EditorService } from '../services/editor.service'
7
7
  import { Store } from '@ngrx/store'
8
8
  import { selectRecord, selectRecordFieldsConfig } from './editor.selectors'
9
+ import { RecordsRepositoryInterface } from '../../../../../../libs/common/domain/src/lib/repository/records-repository.interface'
9
10
 
10
11
  @Injectable()
11
12
  export class EditorEffects {
12
13
  private actions$ = inject(Actions)
13
14
  private editorService = inject(EditorService)
15
+ private recordsRepository = inject(RecordsRepositoryInterface)
14
16
  private store = inject(Store)
15
17
 
16
18
  saveRecord$ = createEffect(() =>
@@ -22,10 +24,14 @@ export class EditorEffects {
22
24
  ),
23
25
  switchMap(([, record, fieldsConfig]) =>
24
26
  this.editorService.saveRecord(record, fieldsConfig).pipe(
25
- switchMap((newRecord) =>
27
+ switchMap(([record, recordSource]) =>
26
28
  of(
27
29
  EditorActions.saveRecordSuccess(),
28
- EditorActions.openRecord({ record: newRecord })
30
+ EditorActions.openRecord({
31
+ record,
32
+ alreadySavedOnce: true,
33
+ recordSource,
34
+ })
29
35
  )
30
36
  ),
31
37
  catchError((error) =>
@@ -46,4 +52,26 @@ export class EditorEffects {
46
52
  map(() => EditorActions.markRecordAsChanged())
47
53
  )
48
54
  )
55
+
56
+ saveRecordDraft$ = createEffect(
57
+ () =>
58
+ this.actions$.pipe(
59
+ ofType(EditorActions.updateRecordField),
60
+ debounceTime(1000),
61
+ withLatestFrom(this.store.select(selectRecord)),
62
+ switchMap(([, record]) => this.editorService.saveRecordAsDraft(record))
63
+ ),
64
+ { dispatch: false }
65
+ )
66
+
67
+ checkHasChangesOnOpen$ = createEffect(() =>
68
+ this.actions$.pipe(
69
+ ofType(EditorActions.openRecord),
70
+ map(({ record }) =>
71
+ this.recordsRepository.recordHasDraft(record.uniqueIdentifier)
72
+ ),
73
+ filter((hasDraft) => hasDraft),
74
+ map(() => EditorActions.markRecordAsChanged())
75
+ )
76
+ )
49
77
  }
@@ -3,7 +3,7 @@ import { select, Store } from '@ngrx/store'
3
3
  import * as EditorActions from './editor.actions'
4
4
  import * as EditorSelectors from './editor.selectors'
5
5
  import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
6
- import { filter, Observable } from 'rxjs'
6
+ import { filter } from 'rxjs'
7
7
  import { Actions, ofType } from '@ngrx/effects'
8
8
 
9
9
  @Injectable()
@@ -12,6 +12,10 @@ export class EditorFacade {
12
12
  private actions$ = inject(Actions)
13
13
 
14
14
  record$ = this.store.pipe(select(EditorSelectors.selectRecord))
15
+ recordSource$ = this.store.pipe(select(EditorSelectors.selectRecordSource))
16
+ alreadySavedOnce$ = this.store.pipe(
17
+ select(EditorSelectors.selectRecordAlreadySavedOnce)
18
+ )
15
19
  saving$ = this.store.pipe(select(EditorSelectors.selectRecordSaving))
16
20
  saveError$ = this.store.pipe(
17
21
  select(EditorSelectors.selectRecordSaveError),
@@ -23,8 +27,14 @@ export class EditorFacade {
23
27
  )
24
28
  recordFields$ = this.store.pipe(select(EditorSelectors.selectRecordFields))
25
29
 
26
- openRecord(record: CatalogRecord) {
27
- this.store.dispatch(EditorActions.openRecord({ record }))
30
+ openRecord(
31
+ record: CatalogRecord,
32
+ recordSource: string,
33
+ alreadySavedOnce: boolean
34
+ ) {
35
+ this.store.dispatch(
36
+ EditorActions.openRecord({ record, recordSource, alreadySavedOnce })
37
+ )
28
38
  }
29
39
 
30
40
  saveRecord() {
@@ -7,8 +7,18 @@ import { DEFAULT_FIELDS } from '../fields.config'
7
7
 
8
8
  export const EDITOR_FEATURE_KEY = 'editor'
9
9
 
10
+ /**
11
+ * @property record The record being edited
12
+ * @property recordSource Original representation of the record as text, used as a reference; null means the record hasn't be serialized yet
13
+ * @property saving
14
+ * @property saveError
15
+ * @property changedSinceSave
16
+ * @property fieldsConfig Configuration for the fields in the editor
17
+ */
10
18
  export interface EditorState {
11
19
  record: CatalogRecord | null
20
+ recordSource: string | null
21
+ alreadySavedOnce: boolean
12
22
  saving: boolean
13
23
  saveError: SaveRecordError | null
14
24
  changedSinceSave: boolean
@@ -21,6 +31,8 @@ export interface EditorPartialState {
21
31
 
22
32
  export const initialEditorState: EditorState = {
23
33
  record: null,
34
+ recordSource: null,
35
+ alreadySavedOnce: false,
24
36
  saving: false,
25
37
  saveError: null,
26
38
  changedSinceSave: false,
@@ -29,11 +41,16 @@ export const initialEditorState: EditorState = {
29
41
 
30
42
  const reducer = createReducer(
31
43
  initialEditorState,
32
- on(EditorActions.openRecord, (state, { record }) => ({
33
- ...state,
34
- changedSinceSave: false,
35
- record,
36
- })),
44
+ on(
45
+ EditorActions.openRecord,
46
+ (state, { record, recordSource, alreadySavedOnce }) => ({
47
+ ...state,
48
+ changedSinceSave: false,
49
+ recordSource: recordSource ?? null,
50
+ alreadySavedOnce,
51
+ record,
52
+ })
53
+ ),
37
54
  on(EditorActions.saveRecord, (state) => ({
38
55
  ...state,
39
56
  saving: true,
@@ -9,6 +9,11 @@ export const selectRecord = createSelector(
9
9
  (state: EditorState) => state.record
10
10
  )
11
11
 
12
+ export const selectRecordSource = createSelector(
13
+ selectEditorState,
14
+ (state: EditorState) => state.recordSource
15
+ )
16
+
12
17
  export const selectRecordSaving = createSelector(
13
18
  selectEditorState,
14
19
  (state: EditorState) => state.saving
@@ -24,6 +29,11 @@ export const selectRecordChangedSinceSave = createSelector(
24
29
  (state: EditorState) => state.changedSinceSave
25
30
  )
26
31
 
32
+ export const selectRecordAlreadySavedOnce = createSelector(
33
+ selectEditorState,
34
+ (state: EditorState) => state.alreadySavedOnce
35
+ )
36
+
27
37
  export const selectRecordFieldsConfig = createSelector(
28
38
  selectEditorState,
29
39
  (state: EditorState) => state.fieldsConfig
@@ -1,50 +1,22 @@
1
- import { Inject, Injectable, Optional } from '@angular/core'
2
- import {
3
- findConverterForDocument,
4
- Iso19139Converter,
5
- } from '../../../../../../libs/api/metadata-converter/src'
6
- import { Configuration } from '../../../../../../libs/data-access/gn4/src'
7
- import { from, Observable } from 'rxjs'
8
- import { map, switchMap } from 'rxjs/operators'
9
- import { HttpClient } from '@angular/common/http'
1
+ import { Injectable } from '@angular/core'
2
+ import { Observable } from 'rxjs'
3
+ import { map } from 'rxjs/operators'
10
4
  import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
11
5
  import { EditorFieldsConfig } from '../models/fields.model'
12
6
  import { evaluate } from '../expressions'
7
+ import { RecordsRepositoryInterface } from '../../../../../../libs/common/domain/src/lib/repository/records-repository.interface'
13
8
 
14
9
  @Injectable({
15
10
  providedIn: 'root',
16
11
  })
17
12
  export class EditorService {
18
- private apiUrl = `${this.apiConfiguration?.basePath || '/geonetwork/srv/api'}`
13
+ constructor(private recordsRepository: RecordsRepositoryInterface) {}
19
14
 
20
- constructor(
21
- private http: HttpClient,
22
- @Optional()
23
- @Inject(Configuration)
24
- private apiConfiguration: Configuration
25
- ) {}
26
-
27
- // TODO: use the catalog repository instead
28
- loadRecordByUuid(uuid: string): Observable<CatalogRecord> {
29
- return this.http
30
- .get(`${this.apiUrl}/records/${uuid}/formatters/xml`, {
31
- responseType: 'text',
32
- headers: {
33
- Accept: 'application/xml',
34
- },
35
- })
36
- .pipe(
37
- switchMap((response) =>
38
- findConverterForDocument(response).readRecord(response.toString())
39
- )
40
- )
41
- }
42
-
43
- // returns the record as it was when saved
15
+ // returns the record as it was when saved, alongside its source
44
16
  saveRecord(
45
17
  record: CatalogRecord,
46
18
  fieldsConfig: EditorFieldsConfig
47
- ): Observable<CatalogRecord> {
19
+ ): Observable<[CatalogRecord, string]> {
48
20
  const savedRecord = { ...record }
49
21
 
50
22
  // run onSave processes
@@ -57,23 +29,16 @@ export class EditorService {
57
29
  })
58
30
  }
59
31
  }
32
+ return this.recordsRepository
33
+ .saveRecord(savedRecord)
34
+ .pipe(map((recordSource) => [savedRecord, recordSource]))
35
+ }
60
36
 
61
- // TODO: use the catalog repository instead
62
- // TODO: use converter based on the format of the record before change
63
- return from(new Iso19139Converter().writeRecord(savedRecord)).pipe(
64
- switchMap((recordXml) =>
65
- this.http.put(
66
- `${this.apiUrl}/records?metadataType=METADATA&uuidProcessing=OVERWRITE&transformWith=_none_&publishToAll=on`,
67
- recordXml,
68
- {
69
- headers: {
70
- 'Content-Type': 'application/xml',
71
- },
72
- withCredentials: true,
73
- }
74
- )
75
- ),
76
- map(() => savedRecord)
77
- )
37
+ // emits and completes once saving is done
38
+ // note: onSave processes are not run for drafts
39
+ saveRecordAsDraft(record: CatalogRecord): Observable<void> {
40
+ return this.recordsRepository
41
+ .saveRecordAsDraft(record)
42
+ .pipe(map(() => undefined))
78
43
  }
79
44
  }
@@ -20,9 +20,7 @@ export class MdViewEffects {
20
20
  loadFullMetadata$ = createEffect(() =>
21
21
  this.actions$.pipe(
22
22
  ofType(MdViewActions.loadFullMetadata),
23
- switchMap(({ uuid }) =>
24
- this.recordsRepository.getByUniqueIdentifier(uuid)
25
- ),
23
+ switchMap(({ uuid }) => this.recordsRepository.getRecord(uuid)),
26
24
  map((record) => {
27
25
  if (record === null) {
28
26
  return MdViewActions.loadFullMetadataFailure({ notFound: true })
@@ -34,7 +34,18 @@
34
34
  <span translate>record.metadata.title</span>
35
35
  </ng-template>
36
36
  <ng-template #cell let-item>
37
- {{ item.title }}
37
+ <div class="flex flex-row items-center gap-2 max-w-full">
38
+ <span class="overflow-hidden text-ellipsis">{{ item.title }}</span>
39
+ <gn-ui-badge
40
+ *ngIf="hasDraft(item)"
41
+ [style.--gn-ui-badge-padding]="'0.4em 0.6em'"
42
+ [style.--gn-ui-badge-text-color]="'#3d2006'"
43
+ [style.--gn-ui-badge-background-color]="'#ffbc7b'"
44
+ [style.--gn-ui-badge-rounded]="'4px'"
45
+ >
46
+ <span translate>dashboard.records.hasDraft</span>
47
+ </gn-ui-badge>
48
+ </div>
38
49
  </ng-template>
39
50
  </gn-ui-interactive-table-column>
40
51
 
@@ -20,6 +20,8 @@ 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
+ import { RecordsRepositoryInterface } from '../../../../../../libs/common/domain/src/lib/repository/records-repository.interface'
23
25
 
24
26
  @Component({
25
27
  selector: 'gn-ui-results-table',
@@ -33,6 +35,7 @@ import { SearchService } from '../utils/service/search.service'
33
35
  InteractiveTableColumnComponent,
34
36
  MatIconModule,
35
37
  TranslateModule,
38
+ BadgeComponent,
36
39
  ],
37
40
  })
38
41
  export class ResultsTableComponent {
@@ -44,7 +47,8 @@ export class ResultsTableComponent {
44
47
  constructor(
45
48
  private searchFacade: SearchFacade,
46
49
  private searchService: SearchService,
47
- private selectionService: SelectionService
50
+ private selectionService: SelectionService,
51
+ private recordsRepository: RecordsRepositoryInterface
48
52
  ) {}
49
53
 
50
54
  dateToString(date: Date): string {
@@ -161,4 +165,8 @@ export class ResultsTableComponent {
161
165
  })
162
166
  )
163
167
  }
168
+
169
+ hasDraft(record: CatalogRecord): boolean {
170
+ return this.recordsRepository.recordHasDraft(record.uniqueIdentifier)
171
+ }
164
172
  }
@@ -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 { UiWidgetsModule } from '../../../../../libs/ui/widgets/src'
12
+ import { BadgeComponent, 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'
@@ -49,6 +49,7 @@ import { TimeSincePipe } from './user-feedback-item/time-since.pipe'
49
49
  MarkdownParserComponent,
50
50
  ThumbnailComponent,
51
51
  TimeSincePipe,
52
+ BadgeComponent,
52
53
  ],
53
54
  declarations: [
54
55
  MetadataInfoComponent,
@@ -1,5 +1,5 @@
1
1
  <div
2
- class="inline-block bg-black opacity-70 py-1.5 px-3 rounded font-medium text-gray-50 text-sm leading-none"
2
+ class="gn-ui-badge"
3
3
  [ngClass]="
4
4
  clickable
5
5
  ? 'hover:bg-primary cursor-pointer transition-colors duration-100'