From 41b54c01ef943318f86b06eb52f93c90d9982a0f Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Wed, 10 Jul 2024 17:03:12 +0200 Subject: [PATCH] feat(metadata-editor): organize fields into pages and sections. --- .../top-toolbar/top-toolbar.component.html | 2 +- .../src/app/edit/edit-page.component.html | 79 +++++- .../src/app/edit/edit-page.component.ts | 31 ++- .../editor/src/lib/+state/editor.selectors.ts | 18 +- .../form-field/form-field.component.html | 12 +- .../form-field/form-field.component.ts | 2 + .../record-form/record-form.component.html | 48 +++- .../record-form/record-form.component.ts | 17 +- .../editor/src/lib/expressions.spec.ts | 11 +- libs/feature/editor/src/lib/fields.config.ts | 249 +++++++++++++----- .../editor/src/lib/models/fields.model.ts | 30 ++- .../editor/src/lib/services/editor.service.ts | 8 +- 12 files changed, 385 insertions(+), 122 deletions(-) diff --git a/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.html b/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.html index aab1948240..89cd41f9d4 100644 --- a/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.html +++ b/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.html @@ -1,4 +1,4 @@ -
+
side_navigation diff --git a/apps/metadata-editor/src/app/edit/edit-page.component.html b/apps/metadata-editor/src/app/edit/edit-page.component.html index 9fc5cfabfe..0c91dc7b31 100644 --- a/apps/metadata-editor/src/app/edit/edit-page.component.html +++ b/apps/metadata-editor/src/app/edit/edit-page.component.html @@ -1,11 +1,72 @@ -
-
- -
-
-
- + +
+
+ + +
+ +
+
+ +
+ {{ index }} +
+
+ {{ page.label }} +
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+
+ + {{ selectedPage === 0 ? 'Revenir plus tard' : 'Precedent' }} + + Suivant
-
-
+ diff --git a/apps/metadata-editor/src/app/edit/edit-page.component.ts b/apps/metadata-editor/src/app/edit/edit-page.component.ts index 7327286a2c..7f3481e64a 100644 --- a/apps/metadata-editor/src/app/edit/edit-page.component.ts +++ b/apps/metadata-editor/src/app/edit/edit-page.component.ts @@ -15,6 +15,7 @@ import { } from '@geonetwork-ui/feature/notifications' import { TranslateService } from '@ngx-translate/core' import { filter, Subscription, take } from 'rxjs' +import { FilterPipe } from '../pipes/filter.pipe' @Component({ selector: 'md-editor-edit', @@ -29,18 +30,29 @@ import { filter, Subscription, take } from 'rxjs' PublishButtonComponent, TopToolbarComponent, NotificationsContainerComponent, + FilterPipe, ], }) export class EditPageComponent implements OnInit, OnDestroy { subscription = new Subscription() + fields$ = this.facade.recordFields$ + totalPages = 0 + selectedPage = 0 + constructor( private route: ActivatedRoute, private facade: EditorFacade, private notificationsService: NotificationsService, private translateService: TranslateService, private router: Router - ) {} + ) { + this.subscription.add( + this.fields$.subscribe((fields) => { + this.totalPages = fields.pages.length + }) + ) + } ngOnInit(): void { const [currentRecord, currentRecordSource, currentRecordAlreadySaved] = @@ -109,4 +121,21 @@ export class EditPageComponent implements OnInit, OnDestroy { ngOnDestroy() { this.subscription.unsubscribe() } + + previousPageButtonHandler() { + if (this.selectedPage === 0) { + this.router.navigate(['catalog', 'search']) + } else { + this.selectedPage-- + } + } + + nextPageButtonHandler() { + if (this.selectedPage === this.totalPages - 1) return + this.selectedPage++ + } + + pageSectionClickHandler(index: number) { + this.selectedPage = index + } } diff --git a/libs/feature/editor/src/lib/+state/editor.selectors.ts b/libs/feature/editor/src/lib/+state/editor.selectors.ts index e9a601da80..e240f5c5e3 100644 --- a/libs/feature/editor/src/lib/+state/editor.selectors.ts +++ b/libs/feature/editor/src/lib/+state/editor.selectors.ts @@ -41,9 +41,17 @@ export const selectRecordFieldsConfig = createSelector( export const selectRecordFields = createSelector( selectEditorState, - (state: EditorState) => - state.fieldsConfig.map((fieldConfig) => ({ - config: fieldConfig, - value: state.record?.[fieldConfig.model] ?? null, - })) + (state: EditorState) => { + const fieldsConfig = state.fieldsConfig + fieldsConfig.pages.forEach((page) => { + page.sections.forEach((section) => { + section.fields.forEach((field) => { + if (state.record) { + field.value = state.record[field.model] + } + }) + }) + }) + return fieldsConfig + } ) diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html b/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html index 98f8503d27..8d30de79be 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.html @@ -5,7 +5,7 @@ @@ -15,14 +15,14 @@
-

{{ formControl.value }} -

+ help @@ -42,7 +42,7 @@ class="h-[8rem]" [control]="formControl" [label]="config.labelKey | translate" - [hint]="config.hintKey | translate" + [hint]="config.hintKey! | translate" > diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts b/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts index 5077db478e..61af9e011c 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.ts @@ -59,12 +59,14 @@ import { FormFieldKeywordsComponent } from './form-field-keywords/form-field-key }) export class FormFieldComponent { @Input() model: CatalogRecordKeys + @Input() selectedPage = 0 @Input() config: FormFieldConfig @Input() set value(v: unknown) { this.formControl.setValue(v, { emitEvent: false, }) } + @Output() valueChange: Observable @ViewChild('titleInput') titleInput: ElementRef diff --git a/libs/feature/editor/src/lib/components/record-form/record-form.component.html b/libs/feature/editor/src/lib/components/record-form/record-form.component.html index 7b07459330..5b6715abe0 100644 --- a/libs/feature/editor/src/lib/components/record-form/record-form.component.html +++ b/libs/feature/editor/src/lib/components/record-form/record-form.component.html @@ -1,11 +1,37 @@ -
- - - -
+ +
+ + +
+
+
+ {{ section.label }} +
+
+ {{ section.description }} +
+
+ + + + + +
+
+
+
+
diff --git a/libs/feature/editor/src/lib/components/record-form/record-form.component.ts b/libs/feature/editor/src/lib/components/record-form/record-form.component.ts index 310fa77fea..08d6c32db0 100644 --- a/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +++ b/libs/feature/editor/src/lib/components/record-form/record-form.component.ts @@ -1,7 +1,7 @@ import { CommonModule } from '@angular/common' -import { ChangeDetectionStrategy, Component } from '@angular/core' +import { ChangeDetectionStrategy, Component, Input } from '@angular/core' import { EditorFacade } from '../../+state/editor.facade' -import { EditorFieldState, EditorFieldValue } from '../../models/fields.model' +import { EditorFieldPage, EditorFieldValue } from '../../models/fields.model' import { FormFieldComponent } from './form-field' @Component({ @@ -13,18 +13,15 @@ import { FormFieldComponent } from './form-field' imports: [CommonModule, FormFieldComponent], }) export class RecordFormComponent { - fields$ = this.facade.recordFields$ + @Input() selectedPage = 0 + @Input() page: EditorFieldPage constructor(public facade: EditorFacade) {} - handleFieldValueChange(field: EditorFieldState, newValue: EditorFieldValue) { - if (!field.config.model) { + handleFieldValueChange(model: string, newValue: EditorFieldValue) { + if (!model) { return } - this.facade.updateRecordField(field.config.model, newValue) - } - - fieldTracker(index: number, field: EditorFieldState) { - return field.config.model + this.facade.updateRecordField(model, newValue) } } diff --git a/libs/feature/editor/src/lib/expressions.spec.ts b/libs/feature/editor/src/lib/expressions.spec.ts index 6de626a823..e36b9e7fc2 100644 --- a/libs/feature/editor/src/lib/expressions.spec.ts +++ b/libs/feature/editor/src/lib/expressions.spec.ts @@ -1,13 +1,4 @@ import { evaluate, ExpressionEvaluator } from './expressions' -import { EditorFieldConfig } from './models/fields.model' - -const SAMPLE_CONFIG: EditorFieldConfig = { - formFieldConfig: { - labelKey: 'Metadata title', - type: 'text', - }, - model: 'myModel', -} const originalDate = window.Date window.Date = function () { @@ -22,7 +13,7 @@ describe('expressions', () => { evaluator = evaluate('${dateNow()}') }) it('returns the current time at evaluation', () => { - expect(evaluator({ config: SAMPLE_CONFIG, value: 'bla' })).toEqual( + expect(evaluator({ model: 'keywords', value: 'bla' })).toEqual( new Date() ) }) diff --git a/libs/feature/editor/src/lib/fields.config.ts b/libs/feature/editor/src/lib/fields.config.ts index cda10b5196..c641e4f724 100644 --- a/libs/feature/editor/src/lib/fields.config.ts +++ b/libs/feature/editor/src/lib/fields.config.ts @@ -1,71 +1,198 @@ import { marker } from '@biesbjerg/ngx-translate-extract-marker' -import { EditorFieldsConfig } from './models/fields.model' - -export const DEFAULT_FIELDS: EditorFieldsConfig = [ - { - model: 'title', - formFieldConfig: { - labelKey: 'Metadata title', - type: 'text', - }, +import { + EditorField, + EditorFieldsConfig, + EditorFieldSection, +} from './models/fields.model' + +/** + * This file contains the configuration of the fields that will be displayed in the editor. + * To add a new field, you need to create a new EditorField object in the fields part of this file. + * Then add it to the corresponding section in the sections part of this file. + * Finally, add the section to the corresponding page in the pages part of this file. + */ + +/************************************************************ + *************** FIELDS ***************** + ************************************************************ + */ + +export const RECORD_LICENSE_FIELD: EditorField = { + model: 'licenses', + formFieldConfig: { + labelKey: marker('editor.record.form.license'), + type: 'list', }, - { - model: 'abstract', - formFieldConfig: { - labelKey: 'Abstract', - type: 'rich', - }, +} + +export const RECORD_KEYWORDS_FIELD: EditorField = { + model: 'keywords', + formFieldConfig: { + labelKey: marker('editor.record.form.keywords'), + type: 'list', }, - { - model: 'uniqueIdentifier', - formFieldConfig: { - labelKey: 'Unique identifier', - type: 'text', - locked: true, - }, +} + +export const RECORD_UNIQUE_IDENTIFIER_FIELD: EditorField = { + model: 'uniqueIdentifier', + formFieldConfig: { + labelKey: 'Unique identifier', + type: 'text', + locked: true, }, - { - model: 'recordUpdated', - formFieldConfig: { - labelKey: 'Record Updated', - type: 'text', - locked: true, - }, - onSaveProcess: '${dateNow()}', +} + +export const RECORD_RESOURCE_UPDATED_FIELD: EditorField = { + model: 'resourceUpdated', + formFieldConfig: { + labelKey: marker('editor.record.form.resourceUpdated'), + type: 'date', }, - { - model: 'licenses', - formFieldConfig: { - labelKey: marker('editor.record.form.license'), - type: 'list', - }, +} + +export const RECORD_UPDATED_FIELD: EditorField = { + model: 'recordUpdated', + formFieldConfig: { + labelKey: 'Record Updated', + type: 'text', + locked: true, }, - { - model: 'resourceUpdated', - formFieldConfig: { - labelKey: marker('editor.record.form.resourceUpdated'), - type: 'date', - }, + onSaveProcess: '${dateNow()}', +} + +export const RECORD_UPDATE_FREQUENCY_FIELD: EditorField = { + model: 'updateFrequency', + formFieldConfig: { + labelKey: marker('editor.record.form.updateFrequency'), + type: 'text', }, - { - model: 'updateFrequency', - formFieldConfig: { - labelKey: marker('editor.record.form.updateFrequency'), - type: 'text', - }, +} + +export const RECORD_TEMPORAL_EXTENTS_FIELD: EditorField = { + model: 'temporalExtents', + formFieldConfig: { + labelKey: marker('editor.record.form.temporalExtents'), + type: 'list', }, - { - model: 'temporalExtents', - formFieldConfig: { - labelKey: marker('editor.record.form.temporalExtents'), - type: 'list', - }, +} + +export const RECORD_TITLE_FIELD: EditorField = { + model: 'title', + formFieldConfig: { + labelKey: 'Metadata title', + type: 'text', }, - { - model: 'keywords', - formFieldConfig: { - labelKey: marker('editor.record.form.keywords'), - type: 'list', - }, +} + +export const RECORD_ABSTRACT_FIELD: EditorField = { + model: 'abstract', + formFieldConfig: { + labelKey: 'Abstract', + type: 'rich', }, -] +} + +/************************************************************ + *************** SECTIONS ***************** + ************************************************************ + */ + +export const TITLE_SECTION: EditorFieldSection = { + label: '', + description: '', + hidden: false, + fields: [RECORD_TITLE_FIELD, RECORD_ABSTRACT_FIELD], +} + +export const ABOUT_SECTION: EditorFieldSection = { + label: 'A propos de la ressource', + description: 'Ces informations concernent la donnée.', + hidden: false, + fields: [ + RECORD_UNIQUE_IDENTIFIER_FIELD, + RECORD_RESOURCE_UPDATED_FIELD, + RECORD_UPDATED_FIELD, + RECORD_UPDATE_FREQUENCY_FIELD, + RECORD_TEMPORAL_EXTENTS_FIELD, + ], +} + +export const GEOGRAPHICAL_COVER_SECTION: EditorFieldSection = { + label: 'Couverture geographique', + description: '', + hidden: false, + fields: [], +} + +export const ASSOCIATED_RESOURCES_SECTION: EditorFieldSection = { + label: 'Ressources associees', + description: + 'Déposez les jeux de données associées à cette fiche de métadonnée.', + hidden: false, + fields: [], +} + +export const ANNEXES_SECTION: EditorFieldSection = { + label: 'Annexes', + description: '', + hidden: false, + fields: [], +} + +export const CLASSIFICATION_SECTION: EditorFieldSection = { + label: 'Classification', + description: + 'La classification a un impact sur la recherche du jeux de données.', + hidden: false, + fields: [RECORD_KEYWORDS_FIELD], +} + +export const USE_AND_ACCESS_CONDITIONS_SECTION: EditorFieldSection = { + label: "Conditions d'acces et usage", + description: '', + hidden: false, + fields: [RECORD_LICENSE_FIELD], +} + +export const DATA_MANAGERS_SECTION: EditorFieldSection = { + label: 'Responsables de la donnee', + description: 'Cette information concerne la donnée.', + hidden: false, + fields: [], +} + +export const DATA_POINT_OF_CONTACT_SECTION: EditorFieldSection = { + label: 'Point de contact de la metadonee', + description: 'Cette information concerne la fiche de métadonnée.', + hidden: false, + fields: [], +} + +/************************************************************ + *************** PAGES ***************** + ************************************************************ + */ +export const DEFAULT_FIELDS: EditorFieldsConfig = { + pages: [ + { + index: 0, + label: 'Description de la ressource', + sections: [TITLE_SECTION, ABOUT_SECTION, GEOGRAPHICAL_COVER_SECTION], + }, + { + index: 1, + label: 'Ressources', + sections: [ASSOCIATED_RESOURCES_SECTION, ANNEXES_SECTION], + }, + { + index: 2, + label: 'Acces et contact', + sections: [ + CLASSIFICATION_SECTION, + USE_AND_ACCESS_CONDITIONS_SECTION, + DATA_MANAGERS_SECTION, + DATA_POINT_OF_CONTACT_SECTION, + ], + }, + ], +} diff --git a/libs/feature/editor/src/lib/models/fields.model.ts b/libs/feature/editor/src/lib/models/fields.model.ts index 0dae06b007..dec4c9c382 100644 --- a/libs/feature/editor/src/lib/models/fields.model.ts +++ b/libs/feature/editor/src/lib/models/fields.model.ts @@ -1,15 +1,16 @@ import { FormFieldConfig } from '../components/record-form/form-field' +import { CatalogRecordKeys } from '@geonetwork-ui/common/domain/model/record' // Expressions should be enclosed in `${}` to be recognized as such // eg. ${dateNow()} export type EditorFieldExpression = `$\{${string}}` -export interface EditorFieldConfig { +export interface EditorField { // configuration of the form field used as presentation; optional, nothing shown if not defined - formFieldConfig?: FormFieldConfig + formFieldConfig: FormFieldConfig // name of the target field in the record; will not change the record directly if not defined - model?: string + model?: CatalogRecordKeys // a hidden field won't show but can still be used to modify the record // FIXME: currently this is redundant with an absence of formFieldConfig but necessary for clarity @@ -17,13 +18,30 @@ export interface EditorFieldConfig { // the result of this expression will replace the field value on save onSaveProcess?: EditorFieldExpression + + value?: EditorFieldValue +} + +export interface EditorFieldSection { + label: string + description: string + hidden: boolean + fields: EditorField[] } -export type EditorFieldsConfig = EditorFieldConfig[] +export interface EditorFieldPage { + index: number + label: string + sections: EditorFieldSection[] +} + +export interface EditorFieldsConfig { + pages: EditorFieldPage[] +} export type EditorFieldValue = string | number | boolean | unknown export interface EditorFieldState { - config: EditorFieldConfig - value: string | number | boolean | unknown + model: string + value: EditorFieldValue } diff --git a/libs/feature/editor/src/lib/services/editor.service.ts b/libs/feature/editor/src/lib/services/editor.service.ts index 9b7c005438..6859864d44 100644 --- a/libs/feature/editor/src/lib/services/editor.service.ts +++ b/libs/feature/editor/src/lib/services/editor.service.ts @@ -20,12 +20,16 @@ export class EditorService { ): Observable<[CatalogRecord, string]> { const savedRecord = { ...record } + const fields = fieldsConfig.pages + .map((page) => page.sections.map((section) => section.fields)) + .flat(2) + // run onSave processes - for (const field of fieldsConfig) { + for (const field of fields) { if (field.onSaveProcess && field.model) { const evaluator = evaluate(field.onSaveProcess) savedRecord[field.model] = evaluator({ - config: field, + model: field.model, value: record[field.model], }) }