geonetwork-ui 2.2.0-dev.6f0f0ee9 → 2.2.0-dev.70a9dc16

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 (150) hide show
  1. package/esm2022/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.mjs +16 -4
  2. package/esm2022/libs/api/metadata-converter/src/lib/gn4/gn4.metadata.mapper.mjs +3 -1
  3. package/esm2022/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.mjs +1 -1
  4. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/converter.mjs +7 -2
  5. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/read-parts.mjs +4 -1
  6. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/write-parts.mjs +1 -1
  7. package/esm2022/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.mjs +2 -2
  8. package/esm2022/libs/common/domain/src/lib/model/record/metadata.model.mjs +1 -1
  9. package/esm2022/libs/feature/editor/src/index.mjs +6 -1
  10. package/esm2022/libs/feature/editor/src/lib/+state/editor.actions.mjs +8 -0
  11. package/esm2022/libs/feature/editor/src/lib/+state/editor.effects.mjs +26 -0
  12. package/esm2022/libs/feature/editor/src/lib/+state/editor.facade.mjs +30 -0
  13. package/esm2022/libs/feature/editor/src/lib/+state/editor.models.mjs +2 -0
  14. package/esm2022/libs/feature/editor/src/lib/+state/editor.reducer.mjs +42 -0
  15. package/esm2022/libs/feature/editor/src/lib/+state/editor.selectors.mjs +13 -0
  16. package/esm2022/libs/feature/editor/src/lib/expressions.mjs +13 -0
  17. package/esm2022/libs/feature/editor/src/lib/feature-editor.module.mjs +16 -4
  18. package/esm2022/libs/feature/editor/src/lib/fields.config.mjs +34 -0
  19. package/esm2022/libs/feature/editor/src/lib/models/fields.model.mjs +2 -0
  20. package/esm2022/libs/feature/editor/src/lib/record-form/record-form.component.mjs +15 -11
  21. package/esm2022/libs/feature/editor/src/lib/services/editor.service.mjs +22 -44
  22. package/esm2022/libs/feature/record/src/lib/data-view-permalink/data-view-permalink.component.mjs +13 -13
  23. package/esm2022/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.mjs +3 -3
  24. package/esm2022/libs/ui/elements/src/lib/max-lines/max-lines.component.mjs +5 -3
  25. package/esm2022/libs/ui/elements/src/lib/metadata-contact/metadata-contact.component.mjs +3 -3
  26. package/esm2022/libs/ui/elements/src/lib/metadata-info/metadata-info.component.mjs +17 -5
  27. package/esm2022/libs/ui/inputs/src/lib/button/button.component.mjs +9 -9
  28. package/esm2022/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.mjs +3 -3
  29. package/esm2022/libs/ui/inputs/src/lib/dropdown-selector/dropdown-selector.component.mjs +3 -3
  30. package/esm2022/libs/ui/inputs/src/lib/form-field/form-field.component.mjs +4 -2
  31. package/esm2022/libs/ui/inputs/src/lib/form-field/form-field.model.mjs +1 -1
  32. package/esm2022/libs/ui/layout/src/lib/carousel/carousel.component.mjs +3 -3
  33. package/esm2022/libs/util/i18n/src/lib/i18n.constants.mjs +18 -1
  34. package/esm2022/libs/util/shared/src/lib/utils/index.mjs +2 -1
  35. package/esm2022/libs/util/shared/src/lib/utils/temporal-extent-union.mjs +32 -0
  36. package/esm2022/translations/de.json +35 -26
  37. package/esm2022/translations/en.json +27 -18
  38. package/esm2022/translations/es.json +24 -15
  39. package/esm2022/translations/fr.json +45 -36
  40. package/esm2022/translations/it.json +39 -30
  41. package/esm2022/translations/nl.json +25 -16
  42. package/esm2022/translations/pt.json +24 -15
  43. package/fesm2022/geonetwork-ui.mjs +575 -294
  44. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  45. package/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.d.ts.map +1 -1
  46. package/libs/api/metadata-converter/src/lib/gn4/gn4.metadata.mapper.d.ts.map +1 -1
  47. package/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.d.ts +1 -0
  48. package/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.d.ts.map +1 -1
  49. package/libs/api/metadata-converter/src/lib/iso19139/converter.d.ts.map +1 -1
  50. package/libs/api/metadata-converter/src/lib/iso19139/read-parts.d.ts +1 -0
  51. package/libs/api/metadata-converter/src/lib/iso19139/read-parts.d.ts.map +1 -1
  52. package/libs/api/metadata-converter/src/lib/iso19139/write-parts.d.ts.map +1 -1
  53. package/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.d.ts.map +1 -1
  54. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts +3 -0
  55. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts.map +1 -1
  56. package/libs/feature/editor/src/index.d.ts +5 -0
  57. package/libs/feature/editor/src/index.d.ts.map +1 -1
  58. package/libs/feature/editor/src/lib/+state/editor.actions.d.ts +23 -0
  59. package/libs/feature/editor/src/lib/+state/editor.actions.d.ts.map +1 -0
  60. package/libs/feature/editor/src/lib/+state/editor.effects.d.ts +15 -0
  61. package/libs/feature/editor/src/lib/+state/editor.effects.d.ts.map +1 -0
  62. package/libs/feature/editor/src/lib/+state/editor.facade.d.ts +19 -0
  63. package/libs/feature/editor/src/lib/+state/editor.facade.d.ts.map +1 -0
  64. package/libs/feature/editor/src/lib/+state/editor.models.d.ts +2 -0
  65. package/libs/feature/editor/src/lib/+state/editor.models.d.ts.map +1 -0
  66. package/libs/feature/editor/src/lib/+state/editor.reducer.d.ts +18 -0
  67. package/libs/feature/editor/src/lib/+state/editor.reducer.d.ts.map +1 -0
  68. package/libs/feature/editor/src/lib/+state/editor.selectors.d.ts +15 -0
  69. package/libs/feature/editor/src/lib/+state/editor.selectors.d.ts.map +1 -0
  70. package/libs/feature/editor/src/lib/expressions.d.ts +4 -0
  71. package/libs/feature/editor/src/lib/expressions.d.ts.map +1 -0
  72. package/libs/feature/editor/src/lib/feature-editor.module.d.ts +3 -1
  73. package/libs/feature/editor/src/lib/feature-editor.module.d.ts.map +1 -1
  74. package/libs/feature/editor/src/lib/fields.config.d.ts +3 -0
  75. package/libs/feature/editor/src/lib/fields.config.d.ts.map +1 -0
  76. package/libs/feature/editor/src/lib/models/fields.model.d.ts +15 -0
  77. package/libs/feature/editor/src/lib/models/fields.model.d.ts.map +1 -0
  78. package/libs/feature/editor/src/lib/record-form/record-form.component.d.ts +10 -5
  79. package/libs/feature/editor/src/lib/record-form/record-form.component.d.ts.map +1 -1
  80. package/libs/feature/editor/src/lib/services/editor.service.d.ts +3 -13
  81. package/libs/feature/editor/src/lib/services/editor.service.d.ts.map +1 -1
  82. package/libs/ui/elements/src/lib/max-lines/max-lines.component.d.ts.map +1 -1
  83. package/libs/ui/elements/src/lib/metadata-info/metadata-info.component.d.ts +6 -0
  84. package/libs/ui/elements/src/lib/metadata-info/metadata-info.component.d.ts.map +1 -1
  85. package/libs/ui/inputs/src/lib/button/button.component.d.ts.map +1 -1
  86. package/libs/ui/inputs/src/lib/form-field/form-field.component.d.ts.map +1 -1
  87. package/libs/ui/inputs/src/lib/form-field/form-field.model.d.ts +0 -1
  88. package/libs/ui/inputs/src/lib/form-field/form-field.model.d.ts.map +1 -1
  89. package/libs/util/i18n/src/lib/i18n.constants.d.ts +17 -0
  90. package/libs/util/i18n/src/lib/i18n.constants.d.ts.map +1 -1
  91. package/libs/util/shared/src/lib/utils/index.d.ts +1 -0
  92. package/libs/util/shared/src/lib/utils/index.d.ts.map +1 -1
  93. package/libs/util/shared/src/lib/utils/temporal-extent-union.d.ts +5 -0
  94. package/libs/util/shared/src/lib/utils/temporal-extent-union.d.ts.map +1 -0
  95. package/package.json +1 -1
  96. package/src/libs/api/metadata-converter/src/lib/fixtures/generic.records.ts +2 -0
  97. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.ts +2 -0
  98. package/src/libs/api/metadata-converter/src/lib/fixtures/geocat-ch.records.ts +4 -0
  99. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +19 -3
  100. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.metadata.mapper.ts +2 -0
  101. package/src/libs/api/metadata-converter/src/lib/gn4/types/metadata.model.ts +1 -0
  102. package/src/libs/api/metadata-converter/src/lib/iso19139/converter.ts +6 -0
  103. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +5 -1
  104. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +8 -2
  105. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +1 -1
  106. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +4 -0
  107. package/src/libs/common/fixtures/src/lib/elasticsearch/full-response.ts +1 -1
  108. package/src/libs/common/fixtures/src/lib/records.fixtures.ts +2 -0
  109. package/src/libs/feature/editor/src/index.ts +5 -0
  110. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +24 -0
  111. package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +49 -0
  112. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +30 -0
  113. package/src/libs/feature/editor/src/lib/+state/editor.models.ts +1 -0
  114. package/src/libs/feature/editor/src/lib/+state/editor.reducer.ts +68 -0
  115. package/src/libs/feature/editor/src/lib/+state/editor.selectors.ts +39 -0
  116. package/src/libs/feature/editor/src/lib/expressions.ts +16 -0
  117. package/src/libs/feature/editor/src/lib/feature-editor.module.ts +11 -0
  118. package/src/libs/feature/editor/src/lib/fields.config.ts +35 -0
  119. package/src/libs/feature/editor/src/lib/models/fields.model.ts +29 -0
  120. package/src/libs/feature/editor/src/lib/record-form/record-form.component.html +9 -7
  121. package/src/libs/feature/editor/src/lib/record-form/record-form.component.ts +11 -5
  122. package/src/libs/feature/editor/src/lib/services/editor.service.ts +34 -73
  123. package/src/libs/feature/record/src/lib/data-view-permalink/data-view-permalink.component.ts +12 -12
  124. package/src/libs/ui/catalog/src/lib/language-switcher/language-switcher.component.html +1 -1
  125. package/src/libs/ui/elements/src/lib/max-lines/max-lines.component.html +5 -1
  126. package/src/libs/ui/elements/src/lib/max-lines/max-lines.component.ts +1 -0
  127. package/src/libs/ui/elements/src/lib/metadata-contact/metadata-contact.component.html +1 -1
  128. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +107 -19
  129. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +14 -0
  130. package/src/libs/ui/inputs/src/lib/button/button.component.css +0 -5
  131. package/src/libs/ui/inputs/src/lib/button/button.component.html +0 -1
  132. package/src/libs/ui/inputs/src/lib/button/button.component.ts +6 -6
  133. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +1 -1
  134. package/src/libs/ui/inputs/src/lib/dropdown-selector/dropdown-selector.component.html +3 -1
  135. package/src/libs/ui/inputs/src/lib/form-field/form-field.component.ts +3 -1
  136. package/src/libs/ui/inputs/src/lib/form-field/form-field.model.ts +0 -1
  137. package/src/libs/ui/layout/src/lib/carousel/carousel.component.html +1 -0
  138. package/src/libs/util/i18n/src/lib/i18n.constants.ts +18 -0
  139. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  140. package/src/libs/util/shared/src/lib/utils/temporal-extent-union.ts +32 -0
  141. package/tailwind.base.config.js +0 -3
  142. package/tailwind.base.css +43 -14
  143. package/translations/de.json +35 -26
  144. package/translations/en.json +27 -18
  145. package/translations/es.json +24 -15
  146. package/translations/fr.json +45 -36
  147. package/translations/it.json +39 -30
  148. package/translations/nl.json +25 -16
  149. package/translations/pt.json +24 -15
  150. package/translations/sk.json +117 -108
@@ -27,6 +27,7 @@ export const GEOCAT_CH_DATASET_RECORD: DatasetRecord = {
27
27
  ],
28
28
  contactsForResource: [],
29
29
  recordCreated: new Date('2022-02-22T19:40:06'),
30
+ recordPublished: new Date('2022-02-22T19:40:06'),
30
31
  recordUpdated: new Date('2022-02-22T19:40:06'),
31
32
  datasetCreated: new Date('1999-01-01T00:00:00'),
32
33
  datasetUpdated: new Date('2009-01-01'),
@@ -327,6 +328,7 @@ Die Quelle ist zu bezeichnen: „Quelle: Stadt Zürich“.`,
327
328
  temporalExtents: [],
328
329
  status: 'completed',
329
330
  updateFrequency: 'asNeeded',
331
+ languages: [],
330
332
  }
331
333
 
332
334
  export const GEOCAT_CH_SERVICE_RECORD: ServiceRecord = {
@@ -359,6 +361,7 @@ export const GEOCAT_CH_SERVICE_RECORD: ServiceRecord = {
359
361
  },
360
362
  ],
361
363
  recordCreated: new Date('2022-03-07T01:15:51+01:00'),
364
+ recordPublished: new Date('2022-03-07T01:15:51+01:00'),
362
365
  recordUpdated: new Date('2022-03-07T01:15:51+01:00'),
363
366
  title: 'Verkehrsregelungsanlagen (WMS)',
364
367
  abstract: `Diese Karte beinhaltet die Verkehrsregelungsanlagen des Kantons St.Gallen.`,
@@ -420,4 +423,5 @@ export const GEOCAT_CH_SERVICE_RECORD: ServiceRecord = {
420
423
  legalConstraints: [],
421
424
  securityConstraints: [],
422
425
  otherConstraints: [],
426
+ languages: [],
423
427
  }
@@ -28,8 +28,8 @@ import {
28
28
  OnlineLinkResource,
29
29
  } from '../../../../../../libs/common/domain/src/lib/model/record'
30
30
  import { matchProtocol } from '../common/distribution.mapper'
31
- import { LangService } from '../../../../../../libs/util/i18n/src'
32
31
  import { Thesaurus } from './types'
32
+ import { LANG_3_TO_2_MAPPER, LangService } from '../../../../../../libs/util/i18n/src'
33
33
 
34
34
  type ESResponseSource = SourceWithUnknownProps
35
35
 
@@ -132,6 +132,22 @@ export class Gn4FieldMapper {
132
132
  ...output,
133
133
  recordUpdated: toDate(selectField<string>(source, 'changeDate')),
134
134
  }),
135
+ publicationDateForResource: (output, source) => ({
136
+ ...output,
137
+ recordPublished: toDate(
138
+ selectField<string>(source, 'publicationDateForResource')
139
+ ),
140
+ }),
141
+ resourceLanguage: (output, source) => {
142
+ const langList = getAsArray(
143
+ selectField<string>(source, 'resourceLanguage')
144
+ )
145
+ const languages = langList.map((lang) => LANG_3_TO_2_MAPPER[lang])
146
+ return {
147
+ ...output,
148
+ languages,
149
+ }
150
+ },
135
151
  link: (output, source) => {
136
152
  const rawLinks = getAsArray(
137
153
  selectField<SourceWithUnknownProps[]>(source, 'link')
@@ -292,9 +308,9 @@ export class Gn4FieldMapper {
292
308
  ],
293
309
  }
294
310
  },
295
- resourceTemporalDateRange: (output, source) => {
311
+ resourceTemporalExtentDateRange: (output, source) => {
296
312
  const ranges = getAsArray(
297
- selectField(source, 'resourceTemporalDateRange')
313
+ selectField(source, 'resourceTemporalExtentDateRange')
298
314
  )
299
315
  return {
300
316
  ...output,
@@ -24,6 +24,7 @@ export class Gn4MetadataMapper extends MetadataBaseMapper<Gn4Record> {
24
24
  status: null,
25
25
  lineage: null,
26
26
  recordUpdated: null,
27
+ recordPublished: null,
27
28
  ownerOrganization: null,
28
29
  licenses: [],
29
30
  legalConstraints: [],
@@ -36,6 +37,7 @@ export class Gn4MetadataMapper extends MetadataBaseMapper<Gn4Record> {
36
37
  spatialExtents: [],
37
38
  temporalExtents: [],
38
39
  overviews: [],
40
+ languages: [],
39
41
  }
40
42
  const record: CatalogRecord = Object.keys(_source).reduce(
41
43
  (prev, fieldName) =>
@@ -150,6 +150,7 @@ export type MetadataObject = Partial<{
150
150
  pointOfContactOrg: string
151
151
  pointOfContactOrgForResource: string
152
152
  popularity: number
153
+ publicationDateForResource: string
153
154
  rating: string
154
155
  record: string
155
156
  recordGroup: string
@@ -51,6 +51,7 @@ import {
51
51
  readOverviews,
52
52
  readOwnerOrganization,
53
53
  readRecordUpdated,
54
+ readRecordPublished,
54
55
  readSecurityConstraints,
55
56
  readSpatialExtents,
56
57
  readSpatialRepresentation,
@@ -74,6 +75,7 @@ export function toModel(xml: string): CatalogRecord {
74
75
  const contacts = readContacts(rootEl)
75
76
  const recordUpdated = readRecordUpdated(rootEl)
76
77
  const recordCreated = recordUpdated
78
+ const recordPublished = readRecordPublished(rootEl)
77
79
  const keywords = readKeywords(rootEl)
78
80
  const topics = readIsoTopics(rootEl)
79
81
  const legalConstraints = readLegalConstraints(rootEl)
@@ -96,8 +98,10 @@ export function toModel(xml: string): CatalogRecord {
96
98
  return {
97
99
  uniqueIdentifier,
98
100
  kind,
101
+ languages: [],
99
102
  recordCreated,
100
103
  recordUpdated,
104
+ recordPublished,
101
105
  status,
102
106
  title,
103
107
  abstract,
@@ -125,8 +129,10 @@ export function toModel(xml: string): CatalogRecord {
125
129
  return {
126
130
  uniqueIdentifier,
127
131
  kind,
132
+ languages: [],
128
133
  recordCreated,
129
134
  recordUpdated,
135
+ recordPublished,
130
136
  title,
131
137
  abstract,
132
138
  ownerOrganization,
@@ -450,7 +450,7 @@ function findIdentification() {
450
450
  }
451
451
  }
452
452
 
453
- function extractCitationDate(type: 'creation' | 'revision') {
453
+ function extractCitationDate(type: 'creation' | 'revision' | 'publication') {
454
454
  return pipe(
455
455
  findIdentification(),
456
456
  findNestedElements('gmd:citation', 'gmd:CI_Citation', 'gmd:date'),
@@ -517,6 +517,10 @@ export function readRecordUpdated(rootEl: XmlElement): Date {
517
517
  return pipe(findChildElement('gmd:dateStamp'), extractDateTime())(rootEl)
518
518
  }
519
519
 
520
+ export function readRecordPublished(rootEl: XmlElement): Date {
521
+ return pipe(findChildElement('gmd:dateStamp'), extractDateTime())(rootEl)
522
+ }
523
+
520
524
  export function readTitle(rootEl: XmlElement): string {
521
525
  return pipe(
522
526
  findIdentification(),
@@ -291,7 +291,10 @@ function appendResponsibleParty(contact: Individual) {
291
291
  )
292
292
  }
293
293
 
294
- function updateCitationDate(date: Date, type: 'revision' | 'creation') {
294
+ function updateCitationDate(
295
+ date: Date,
296
+ type: 'revision' | 'creation' | 'publication'
297
+ ) {
295
298
  return pipe(
296
299
  findNestedElements('gmd:date', 'gmd:CI_Date'),
297
300
  filterArray(
@@ -308,7 +311,10 @@ function updateCitationDate(date: Date, type: 'revision' | 'creation') {
308
311
  )
309
312
  }
310
313
 
311
- function appendCitationDate(date, type: 'revision' | 'creation') {
314
+ function appendCitationDate(
315
+ date,
316
+ type: 'revision' | 'creation' | 'publication'
317
+ ) {
312
318
  return appendChildren(
313
319
  pipe(
314
320
  createElement('gmd:date'),
@@ -133,7 +133,7 @@ export class ElasticsearchService {
133
133
  title: string,
134
134
  uuid: string,
135
135
  size = 6,
136
- _source = ES_SOURCE_SUMMARY
136
+ _source = [...ES_SOURCE_SUMMARY, 'allKeywords', 'createDate']
137
137
  ): EsSearchParams {
138
138
  return {
139
139
  query: {
@@ -73,6 +73,8 @@ export interface Keyword {
73
73
  type: KeywordType
74
74
  thesaurus?: KeywordThesaurus
75
75
  }
76
+ // languages should be expressed using two-letters ISO 639-1 codes
77
+ export type LanguageCode = string
76
78
 
77
79
  export interface BaseRecord {
78
80
  uniqueIdentifier: Uuid
@@ -82,6 +84,8 @@ export interface BaseRecord {
82
84
  abstract: string
83
85
  recordCreated?: Date
84
86
  recordUpdated: Date
87
+ recordPublished?: Date
88
+ languages: Array<LanguageCode>
85
89
  kind: RecordKind
86
90
  topics: Array<string> // TODO: handle codelists
87
91
  keywords: Array<Keyword>
@@ -131,7 +131,7 @@ export const ES_FIXTURE_FULL_RESPONSE = deepFreeze({
131
131
  creationDateForResource: ['2012-01-01T00:00:00.000Z'],
132
132
  creationYearForResource: '2012',
133
133
  creationMonthForResource: '2012-01',
134
- publicationDateForResource: ['2021-04-01T00:00:00.000Z'],
134
+ publicationDateForResource: '2021-04-01T00:00:00.000Z',
135
135
  publicationYearForResource: '2021',
136
136
  publicationMonthForResource: '2021-04',
137
137
  resourceDate: [
@@ -149,6 +149,7 @@ As such, **it is not very interesting at all.**`,
149
149
  updatedTimes: 3,
150
150
  per: 'month',
151
151
  },
152
+ languages: ['en'],
152
153
  },
153
154
  {
154
155
  uniqueIdentifier: '7d002c4c-92ef-4b9f-a568-d732f740b99e',
@@ -240,5 +241,6 @@ Ce lot de données produit en 2019, a été numérisé à partir du PCI Vecteur
240
241
  temporalExtents: [],
241
242
  status: 'completed',
242
243
  updateFrequency: 'unknown',
244
+ languages: ['fr', 'de'],
243
245
  },
244
246
  ])
@@ -1,3 +1,8 @@
1
+ export * from './lib/+state/editor.facade'
2
+ export * from './lib/+state/editor.models'
3
+ export * from './lib/+state/editor.selectors'
4
+ export * from './lib/+state/editor.reducer'
5
+ export * from './lib/+state/editor.actions'
1
6
  export * from './lib/feature-editor.module'
2
7
  export * from './lib/services/editor.service'
3
8
  export * from './lib/record-form/record-form.component'
@@ -0,0 +1,24 @@
1
+ import { createAction, props } from '@ngrx/store'
2
+ import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
3
+ import { SaveRecordError } from './editor.models'
4
+
5
+ export const openRecord = createAction(
6
+ '[Editor] Open record',
7
+ props<{ record: CatalogRecord }>()
8
+ )
9
+
10
+ export const updateRecordField = createAction(
11
+ '[Editor] Update record field',
12
+ props<{ field: string; value: unknown }>()
13
+ )
14
+
15
+ export const markRecordAsChanged = createAction(
16
+ '[Editor] Mark record as changed'
17
+ )
18
+
19
+ export const saveRecord = createAction('[Editor] Save record')
20
+ export const saveRecordSuccess = createAction('[Editor] Save record success')
21
+ export const saveRecordFailure = createAction(
22
+ '[Editor] Save record failure',
23
+ props<{ error: SaveRecordError }>()
24
+ )
@@ -0,0 +1,49 @@
1
+ import { inject, Injectable } from '@angular/core'
2
+ import { Actions, createEffect, ofType } from '@ngrx/effects'
3
+ import { of, withLatestFrom } from 'rxjs'
4
+ import { catchError, map, switchMap } from 'rxjs/operators'
5
+ import * as EditorActions from './editor.actions'
6
+ import { EditorService } from '../services/editor.service'
7
+ import { Store } from '@ngrx/store'
8
+ import { selectRecord, selectRecordFieldsConfig } from './editor.selectors'
9
+
10
+ @Injectable()
11
+ export class EditorEffects {
12
+ private actions$ = inject(Actions)
13
+ private editorService = inject(EditorService)
14
+ private store = inject(Store)
15
+
16
+ saveRecord$ = createEffect(() =>
17
+ this.actions$.pipe(
18
+ ofType(EditorActions.saveRecord),
19
+ withLatestFrom(
20
+ this.store.select(selectRecord),
21
+ this.store.select(selectRecordFieldsConfig)
22
+ ),
23
+ switchMap(([, record, fieldsConfig]) =>
24
+ this.editorService.saveRecord(record, fieldsConfig).pipe(
25
+ switchMap((newRecord) =>
26
+ of(
27
+ EditorActions.saveRecordSuccess(),
28
+ EditorActions.openRecord({ record: newRecord })
29
+ )
30
+ ),
31
+ catchError((error) =>
32
+ of(
33
+ EditorActions.saveRecordFailure({
34
+ error: error.message,
35
+ })
36
+ )
37
+ )
38
+ )
39
+ )
40
+ )
41
+ )
42
+
43
+ markAsChanged$ = createEffect(() =>
44
+ this.actions$.pipe(
45
+ ofType(EditorActions.updateRecordField),
46
+ map(() => EditorActions.markRecordAsChanged())
47
+ )
48
+ )
49
+ }
@@ -0,0 +1,30 @@
1
+ import { inject, Injectable } from '@angular/core'
2
+ import { select, Store } from '@ngrx/store'
3
+ import * as EditorActions from './editor.actions'
4
+ import * as EditorSelectors from './editor.selectors'
5
+ import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
6
+
7
+ @Injectable()
8
+ export class EditorFacade {
9
+ private readonly store = inject(Store)
10
+
11
+ record$ = this.store.pipe(select(EditorSelectors.selectRecord))
12
+ saving$ = this.store.pipe(select(EditorSelectors.selectRecordSaving))
13
+ saveError$ = this.store.pipe(select(EditorSelectors.selectRecordSaveError))
14
+ changedSinceSave$ = this.store.pipe(
15
+ select(EditorSelectors.selectRecordChangedSinceSave)
16
+ )
17
+ recordFields$ = this.store.pipe(select(EditorSelectors.selectRecordFields))
18
+
19
+ openRecord(record: CatalogRecord) {
20
+ this.store.dispatch(EditorActions.openRecord({ record }))
21
+ }
22
+
23
+ saveRecord() {
24
+ this.store.dispatch(EditorActions.saveRecord())
25
+ }
26
+
27
+ updateRecordField(field: string, value: unknown) {
28
+ this.store.dispatch(EditorActions.updateRecordField({ field, value }))
29
+ }
30
+ }
@@ -0,0 +1 @@
1
+ export type SaveRecordError = string
@@ -0,0 +1,68 @@
1
+ import { Action, createReducer, on } from '@ngrx/store'
2
+ import * as EditorActions from './editor.actions'
3
+ import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
4
+ import { SaveRecordError } from './editor.models'
5
+ import { EditorFieldsConfig } from '../models/fields.model'
6
+ import { DEFAULT_FIELDS } from '../fields.config'
7
+
8
+ export const EDITOR_FEATURE_KEY = 'editor'
9
+
10
+ export interface EditorState {
11
+ record: CatalogRecord | null
12
+ saving: boolean
13
+ saveError: SaveRecordError | null
14
+ changedSinceSave: boolean
15
+ fieldsConfig: EditorFieldsConfig
16
+ }
17
+
18
+ export interface EditorPartialState {
19
+ readonly [EDITOR_FEATURE_KEY]: EditorState
20
+ }
21
+
22
+ export const initialEditorState: EditorState = {
23
+ record: null,
24
+ saving: false,
25
+ saveError: null,
26
+ changedSinceSave: false,
27
+ fieldsConfig: DEFAULT_FIELDS,
28
+ }
29
+
30
+ const reducer = createReducer(
31
+ initialEditorState,
32
+ on(EditorActions.openRecord, (state, { record }) => ({
33
+ ...state,
34
+ changedSinceSave: false,
35
+ record,
36
+ })),
37
+ on(EditorActions.saveRecord, (state) => ({
38
+ ...state,
39
+ saving: true,
40
+ saveError: null,
41
+ })),
42
+ on(EditorActions.saveRecordSuccess, (state) => ({
43
+ ...state,
44
+ saving: false,
45
+ saveError: null,
46
+ changedSinceSave: false,
47
+ })),
48
+ on(EditorActions.saveRecordFailure, (state, { error }) => ({
49
+ ...state,
50
+ saving: false,
51
+ saveError: error,
52
+ })),
53
+ on(EditorActions.updateRecordField, (state, { field, value }) => ({
54
+ ...state,
55
+ record: {
56
+ ...state.record,
57
+ [field]: value,
58
+ },
59
+ })),
60
+ on(EditorActions.markRecordAsChanged, (state) => ({
61
+ ...state,
62
+ changedSinceSave: true,
63
+ }))
64
+ )
65
+
66
+ export function editorReducer(state: EditorState | undefined, action: Action) {
67
+ return reducer(state, action)
68
+ }
@@ -0,0 +1,39 @@
1
+ import { createFeatureSelector, createSelector } from '@ngrx/store'
2
+ import { EDITOR_FEATURE_KEY, EditorState } from './editor.reducer'
3
+
4
+ export const selectEditorState =
5
+ createFeatureSelector<EditorState>(EDITOR_FEATURE_KEY)
6
+
7
+ export const selectRecord = createSelector(
8
+ selectEditorState,
9
+ (state: EditorState) => state.record
10
+ )
11
+
12
+ export const selectRecordSaving = createSelector(
13
+ selectEditorState,
14
+ (state: EditorState) => state.saving
15
+ )
16
+
17
+ export const selectRecordSaveError = createSelector(
18
+ selectEditorState,
19
+ (state: EditorState) => state.saveError
20
+ )
21
+
22
+ export const selectRecordChangedSinceSave = createSelector(
23
+ selectEditorState,
24
+ (state: EditorState) => state.changedSinceSave
25
+ )
26
+
27
+ export const selectRecordFieldsConfig = createSelector(
28
+ selectEditorState,
29
+ (state: EditorState) => state.fieldsConfig
30
+ )
31
+
32
+ export const selectRecordFields = createSelector(
33
+ selectEditorState,
34
+ (state: EditorState) =>
35
+ state.fieldsConfig.map((fieldConfig) => ({
36
+ config: fieldConfig,
37
+ value: state.record?.[fieldConfig.model] || null,
38
+ }))
39
+ )
@@ -0,0 +1,16 @@
1
+ import { EditorFieldState, EditorFieldValue } from './models/fields.model'
2
+
3
+ export type ExpressionEvaluator = (field: EditorFieldState) => EditorFieldValue
4
+
5
+ export function evaluate(expression: string): ExpressionEvaluator {
6
+ if (expression.match(/^\${.*}$/)) {
7
+ return evaluate(expression.slice(2, -1))
8
+ }
9
+ const operator = expression.split('(')[0]
10
+ switch (operator) {
11
+ case 'dateNow':
12
+ return () => new Date()
13
+ default:
14
+ throw new Error(`Unknown operator: ${operator}`)
15
+ }
16
+ }
@@ -10,6 +10,11 @@ import { TranslateModule } from '@ngx-translate/core'
10
10
  import { WizardSummarizeComponent } from './components/wizard-summarize/wizard-summarize.component'
11
11
  import { CommonModule } from '@angular/common'
12
12
  import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http'
13
+ import { StoreModule } from '@ngrx/store'
14
+ import { EffectsModule } from '@ngrx/effects'
15
+ import * as fromEditor from './+state/editor.reducer'
16
+ import { EditorEffects } from './+state/editor.effects'
17
+ import { EditorFacade } from './+state/editor.facade'
13
18
 
14
19
  @NgModule({
15
20
  declarations: [
@@ -27,8 +32,14 @@ import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http'
27
32
  MatFormFieldModule,
28
33
  HttpClientModule,
29
34
  HttpClientXsrfModule,
35
+ StoreModule.forFeature(
36
+ fromEditor.EDITOR_FEATURE_KEY,
37
+ fromEditor.editorReducer
38
+ ),
39
+ EffectsModule.forFeature([EditorEffects]),
30
40
  ],
31
41
  exports: [WizardComponent, WizardSummarizeComponent],
42
+ providers: [EditorFacade],
32
43
  })
33
44
  export class FeatureEditorModule {}
34
45
  export * from './services/wizard.service'
@@ -0,0 +1,35 @@
1
+ import { EditorFieldsConfig } from './models/fields.model'
2
+
3
+ export const DEFAULT_FIELDS: EditorFieldsConfig = [
4
+ {
5
+ model: 'title',
6
+ formFieldConfig: {
7
+ labelKey: 'Metadata title',
8
+ type: 'text',
9
+ },
10
+ },
11
+ {
12
+ model: 'abstract',
13
+ formFieldConfig: {
14
+ labelKey: 'Abstract',
15
+ type: 'rich',
16
+ },
17
+ },
18
+ {
19
+ model: 'uniqueIdentifier',
20
+ formFieldConfig: {
21
+ labelKey: 'Unique identifier',
22
+ type: 'text',
23
+ locked: true,
24
+ },
25
+ },
26
+ {
27
+ model: 'recordUpdated',
28
+ formFieldConfig: {
29
+ labelKey: 'Record Updated',
30
+ type: 'text',
31
+ locked: true,
32
+ },
33
+ onSaveProcess: '${dateNow()}',
34
+ },
35
+ ]
@@ -0,0 +1,29 @@
1
+ import { FormFieldConfig } from '../../../../../../libs/ui/inputs/src'
2
+
3
+ // Expressions should be enclosed in `${}` to be recognized as such
4
+ // eg. ${dateNow()}
5
+ export type EditorFieldExpression = `$\{${string}}`
6
+
7
+ export interface EditorFieldConfig {
8
+ // configuration of the form field used as presentation; optional, nothing shown if not defined
9
+ formFieldConfig?: FormFieldConfig
10
+
11
+ // name of the target field in the record; will not change the record directly if not defined
12
+ model?: string
13
+
14
+ // a hidden field won't show but can still be used to modify the record
15
+ // FIXME: currently this is redundant with an absence of formFieldConfig but necessary for clarity
16
+ hidden?: boolean
17
+
18
+ // the result of this expression will replace the field value on save
19
+ onSaveProcess?: EditorFieldExpression
20
+ }
21
+
22
+ export type EditorFieldsConfig = EditorFieldConfig[]
23
+
24
+ export type EditorFieldValue = string | number | boolean | unknown
25
+
26
+ export interface EditorFieldState {
27
+ config: EditorFieldConfig
28
+ value: string | number | boolean | unknown
29
+ }
@@ -1,8 +1,10 @@
1
- <div class="flex flex-col gap-6">
2
- <gn-ui-form-field
3
- *ngFor="let field of editorService.fields$ | async; trackBy: fieldTracker"
4
- [config]="field.config"
5
- [value]="field.value"
6
- (valueChange)="handleFieldValueChange(field.config.model, $event)"
7
- ></gn-ui-form-field>
1
+ <div class="flex flex-col gap-6 p-6">
2
+ <ng-container *ngFor="let field of fields$ | async; trackBy: fieldTracker">
3
+ <gn-ui-form-field
4
+ *ngIf="field.config.formFieldConfig && !field.config.hidden"
5
+ [config]="field.config.formFieldConfig"
6
+ [value]="field.value"
7
+ (valueChange)="handleFieldValueChange(field, $event)"
8
+ ></gn-ui-form-field>
9
+ </ng-container>
8
10
  </div>
@@ -1,7 +1,8 @@
1
1
  import { CommonModule } from '@angular/common'
2
2
  import { ChangeDetectionStrategy, Component } from '@angular/core'
3
- import { EditorService, FormField } from '../services/editor.service'
4
3
  import { UiInputsModule } from '../../../../../../libs/ui/inputs/src'
4
+ import { EditorFacade } from '../+state/editor.facade'
5
+ import { EditorFieldState, EditorFieldValue } from '../models/fields.model'
5
6
 
6
7
  @Component({
7
8
  selector: 'gn-ui-record-form',
@@ -12,13 +13,18 @@ import { UiInputsModule } from '../../../../../../libs/ui/inputs/src'
12
13
  imports: [CommonModule, UiInputsModule],
13
14
  })
14
15
  export class RecordFormComponent {
15
- constructor(public editorService: EditorService) {}
16
+ fields$ = this.facade.recordFields$
16
17
 
17
- handleFieldValueChange(fieldName: string, value: unknown) {
18
- this.editorService.updateRecordField(fieldName, value)
18
+ constructor(public facade: EditorFacade) {}
19
+
20
+ handleFieldValueChange(field: EditorFieldState, newValue: EditorFieldValue) {
21
+ if (!field.config.model) {
22
+ return
23
+ }
24
+ this.facade.updateRecordField(field.config.model, newValue)
19
25
  }
20
26
 
21
- fieldTracker(index: number, field: FormField) {
27
+ fieldTracker(index: number, field: EditorFieldState) {
22
28
  return field.config.model
23
29
  }
24
30
  }