From 233e51ecf7353808afa051444031d4f404f94345 Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Wed, 10 Jul 2024 16:49:25 +0200 Subject: [PATCH 1/7] feature(datahub): add Petrona font to the project./ --- assets-common/css/default-fonts.css | 8 ++++++++ assets-common/fonts/Petrona-Regular.woff2 | Bin 0 -> 21296 bytes tailwind.base.config.js | 1 + 3 files changed, 9 insertions(+) create mode 100644 assets-common/fonts/Petrona-Regular.woff2 diff --git a/assets-common/css/default-fonts.css b/assets-common/css/default-fonts.css index fc8d76c604..34941cbd8c 100644 --- a/assets-common/css/default-fonts.css +++ b/assets-common/css/default-fonts.css @@ -1,3 +1,11 @@ +/* Petrona */ +@font-face { + font-family: 'Petrona'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(../fonts/Petrona-Regular.woff2) format('woff2'); +} /* arabic */ @font-face { font-family: 'Readex Pro'; diff --git a/assets-common/fonts/Petrona-Regular.woff2 b/assets-common/fonts/Petrona-Regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..bac09590b6045853dd96334768991328183a94b7 GIT binary patch literal 21296 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 NtxT>8=|*}0005{qHc Date: Wed, 10 Jul 2024 16:50:09 +0200 Subject: [PATCH 2/7] feature(datahub): add custom filter pipe. --- apps/metadata-editor/src/app/pipes/filter.pipe.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 apps/metadata-editor/src/app/pipes/filter.pipe.ts diff --git a/apps/metadata-editor/src/app/pipes/filter.pipe.ts b/apps/metadata-editor/src/app/pipes/filter.pipe.ts new file mode 100644 index 0000000000..4cbff21914 --- /dev/null +++ b/apps/metadata-editor/src/app/pipes/filter.pipe.ts @@ -0,0 +1,11 @@ +import { Pipe, PipeTransform } from '@angular/core' + +@Pipe({ + name: 'filter', + standalone: true, +}) +export class FilterPipe implements PipeTransform { + transform(array: any[], index: number): any { + return array.find((item) => item.index === index) + } +} From 93b253875d32a99f310ed09751a86e7baf7a60db Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Wed, 10 Jul 2024 16:52:41 +0200 Subject: [PATCH 3/7] chore(datahub/metadata-editor): update TypeScript config to target ES2020 - Updated tsconfig.json to set target and lib to ES2020 - Ensured compatibility with modern JavaScript features like Array.prototype.flat --- apps/metadata-editor/tsconfig.json | 8 +++++++- libs/feature/editor/tsconfig.json | 8 +++++++- libs/feature/editor/tsconfig.lib.json | 11 +++++++++-- libs/util/shared/tsconfig.lib.json | 6 +++++- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/apps/metadata-editor/tsconfig.json b/apps/metadata-editor/tsconfig.json index 310da87429..d2c6daf3e1 100644 --- a/apps/metadata-editor/tsconfig.json +++ b/apps/metadata-editor/tsconfig.json @@ -18,7 +18,13 @@ "strict": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, - "target": "es2020" + "target": "es2020", + "lib": [ + "dom", + "es2020", + "dom.iterable" + ], + "downlevelIteration": true }, "angularCompilerOptions": { "strictInjectionParameters": true, diff --git a/libs/feature/editor/tsconfig.json b/libs/feature/editor/tsconfig.json index aeb1c9ace3..cdea0bdb38 100644 --- a/libs/feature/editor/tsconfig.json +++ b/libs/feature/editor/tsconfig.json @@ -15,7 +15,13 @@ "strict": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, - "target": "es2020" + "target": "es2020", + "lib": [ + "dom", + "es2020", + "dom.iterable" + ], + "downlevelIteration": true }, "angularCompilerOptions": { "strictInjectionParameters": true, diff --git a/libs/feature/editor/tsconfig.lib.json b/libs/feature/editor/tsconfig.lib.json index 1f4078133c..7ca4377996 100644 --- a/libs/feature/editor/tsconfig.lib.json +++ b/libs/feature/editor/tsconfig.lib.json @@ -7,7 +7,12 @@ "declarationMap": true, "inlineSources": true, "types": [], - "lib": ["dom", "es2018"] + "lib": [ + "dom", + "es2020", + "dom.iterable" + ], + "downlevelIteration": true }, "exclude": [ "src/test-setup.ts", @@ -15,5 +20,7 @@ "**/*.test.ts", "jest.config.ts" ], - "include": ["**/*.ts"] + "include": [ + "**/*.ts" + ] } diff --git a/libs/util/shared/tsconfig.lib.json b/libs/util/shared/tsconfig.lib.json index 1f4078133c..9bb502669e 100644 --- a/libs/util/shared/tsconfig.lib.json +++ b/libs/util/shared/tsconfig.lib.json @@ -7,7 +7,11 @@ "declarationMap": true, "inlineSources": true, "types": [], - "lib": ["dom", "es2018"] + "lib": [ + "dom", + "es2020", + "dom.iterable" + ] }, "exclude": [ "src/test-setup.ts", From fac18eddd84e207ffd23e0e528415709dc797f5c Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Wed, 10 Jul 2024 17:03:12 +0200 Subject: [PATCH 4/7] feat(metadata-editor): organize fields into pages and sections. --- .../breadcrumbs/page-selector.component.css | 0 .../breadcrumbs/page-selector.component.html | 39 +++ .../page-selector.component.spec.ts | 26 ++ .../breadcrumbs/page-selector.component.ts | 30 +++ .../top-toolbar/top-toolbar.component.html | 2 +- .../src/app/edit/edit-page.component.html | 45 +++- .../src/app/edit/edit-page.component.spec.ts | 12 +- .../src/app/edit/edit-page.component.ts | 44 +++- .../src/app/pipes/filter.pipe.ts | 11 - apps/metadata-editor/tsconfig.json | 6 +- libs/feature/editor/src/index.ts | 1 + .../editor/src/lib/+state/editor.reducer.ts | 4 +- .../src/lib/+state/editor.selectors.spec.ts | 78 +++--- .../editor/src/lib/+state/editor.selectors.ts | 19 +- .../form-field/form-field.component.html | 18 +- .../form-field/form-field.component.spec.ts | 1 - .../form-field/form-field.component.ts | 3 +- .../form-field/form-field.model.ts | 43 ---- .../record-form/form-field/index.ts | 1 - .../record-form/record-form.component.html | 54 +++- .../record-form/record-form.component.spec.ts | 5 +- .../record-form/record-form.component.ts | 28 ++- .../editor/src/lib/expressions.spec.ts | 11 +- libs/feature/editor/src/lib/expressions.ts | 2 +- libs/feature/editor/src/lib/fields.config.ts | 236 +++++++++++++----- .../src/lib/fixtures/editor.fixtures.ts | 158 ++++++++++++ libs/feature/editor/src/lib/fixtures/index.ts | 1 + .../src/lib/models/editor-config.model.ts | 55 ++++ .../editor/src/lib/models/fields.model.ts | 29 --- libs/feature/editor/src/lib/models/index.ts | 1 + .../editor/src/lib/services/editor.service.ts | 12 +- libs/feature/editor/tsconfig.json | 6 +- libs/feature/editor/tsconfig.lib.json | 10 +- libs/util/shared/tsconfig.lib.json | 6 +- translations/de.json | 28 +++ translations/en.json | 28 +++ translations/es.json | 28 +++ translations/fr.json | 28 +++ translations/it.json | 28 +++ translations/nl.json | 28 +++ translations/pt.json | 28 +++ translations/sk.json | 28 +++ 42 files changed, 936 insertions(+), 285 deletions(-) create mode 100644 apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.css create mode 100644 apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.html create mode 100644 apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.spec.ts create mode 100644 apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.ts delete mode 100644 apps/metadata-editor/src/app/pipes/filter.pipe.ts delete mode 100644 libs/feature/editor/src/lib/components/record-form/form-field/form-field.model.ts create mode 100644 libs/feature/editor/src/lib/fixtures/editor.fixtures.ts create mode 100644 libs/feature/editor/src/lib/fixtures/index.ts create mode 100644 libs/feature/editor/src/lib/models/editor-config.model.ts delete mode 100644 libs/feature/editor/src/lib/models/fields.model.ts diff --git a/apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.css b/apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.html b/apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.html new file mode 100644 index 0000000000..b38938f323 --- /dev/null +++ b/apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.html @@ -0,0 +1,39 @@ +
+ +
+
+ +
+ {{ index }} +
+
+ {{ page.labelKey }} +
+
+
+
+
+
+
+
+
diff --git a/apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.spec.ts b/apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.spec.ts new file mode 100644 index 0000000000..0e6f756aed --- /dev/null +++ b/apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.spec.ts @@ -0,0 +1,26 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { TranslateModule } from '@ngx-translate/core' +import { PageSelectorComponent } from './page-selector.component' +import { EDITOR_CONFIG } from '@geonetwork-ui/feature/editor' + +describe('BreadcrumbsComponent', () => { + let component: PageSelectorComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], + providers: [], + }).compileComponents() + + fixture = TestBed.createComponent(PageSelectorComponent) + component = fixture.componentInstance + component.pages = EDITOR_CONFIG().pages + component.selectedPage = 0 + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.ts b/apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.ts new file mode 100644 index 0000000000..7b3c3df71c --- /dev/null +++ b/apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.ts @@ -0,0 +1,30 @@ +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + Output, +} from '@angular/core' +import { CommonModule } from '@angular/common' +import { ButtonComponent } from '@geonetwork-ui/ui/inputs' +import { TranslateModule } from '@ngx-translate/core' +import { EditorFieldPage } from '@geonetwork-ui/feature/editor' + +@Component({ + selector: 'md-editor-page-selector', + standalone: true, + imports: [CommonModule, ButtonComponent, TranslateModule], + templateUrl: './page-selector.component.html', + styleUrls: ['./page-selector.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class PageSelectorComponent { + @Input() selectedPage = 0 + @Input() pages: EditorFieldPage[] + + @Output() selectedPageChange = new EventEmitter() + + pageSectionClickHandler(index: number) { + this.selectedPageChange.emit(index) + } +} 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..d93d4aab53 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,38 @@ -
-
- -
-
-
- + +
+
+ + +
+
+
+ +
+ +
+
+ + {{ + selectedPage === 0 + ? ('editor.record.form.bottomButtons.comeBackLater' | translate) + : ('editor.record.form.bottomButtons.previous' | translate) + }} + + editor.record.form.bottomButtons.next
-
-
+ diff --git a/apps/metadata-editor/src/app/edit/edit-page.component.spec.ts b/apps/metadata-editor/src/app/edit/edit-page.component.spec.ts index 401d9541ce..36fc290d2d 100644 --- a/apps/metadata-editor/src/app/edit/edit-page.component.spec.ts +++ b/apps/metadata-editor/src/app/edit/edit-page.component.spec.ts @@ -1,12 +1,14 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' import { EditPageComponent } from './edit-page.component' import { ActivatedRoute, Router } from '@angular/router' -import { EditorFacade } from '@geonetwork-ui/feature/editor' +import { EDITOR_CONFIG, EditorFacade } from '@geonetwork-ui/feature/editor' import { NO_ERRORS_SCHEMA } from '@angular/core' import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures' import { BehaviorSubject, Subject } from 'rxjs' import { NotificationsService } from '@geonetwork-ui/feature/notifications' import { TranslateModule } from '@ngx-translate/core' +import { FindPipe } from '../pipes/filter.pipe' +import { PageSelectorComponent } from './components/breadcrumbs/page-selector.component' const getRoute = () => ({ snapshot: { @@ -25,6 +27,7 @@ class RouterMock { class EditorFacadeMock { record$ = new BehaviorSubject(DATASET_RECORDS[0]) + recordFields$ = new BehaviorSubject(EDITOR_CONFIG()) openRecord = jest.fn() saveError$ = new Subject() saveSuccess$ = new Subject() @@ -42,7 +45,12 @@ describe('EditPageComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [EditPageComponent, TranslateModule.forRoot()], + imports: [ + EditPageComponent, + TranslateModule.forRoot(), + FindPipe, + PageSelectorComponent, + ], schemas: [NO_ERRORS_SCHEMA], providers: [ { 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..8586f05362 100644 --- a/apps/metadata-editor/src/app/edit/edit-page.component.ts +++ b/apps/metadata-editor/src/app/edit/edit-page.component.ts @@ -3,6 +3,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' import { EditorFacade, + EditorFieldPage, RecordFormComponent, } from '@geonetwork-ui/feature/editor' import { ButtonComponent } from '@geonetwork-ui/ui/inputs' @@ -13,8 +14,14 @@ import { NotificationsContainerComponent, NotificationsService, } from '@geonetwork-ui/feature/notifications' -import { TranslateService } from '@ngx-translate/core' +import { TranslateModule, TranslateService } from '@ngx-translate/core' import { filter, Subscription, take } from 'rxjs' +import { PageSelectorComponent } from './components/breadcrumbs/page-selector.component' +import { marker } from '@biesbjerg/ngx-translate-extract-marker' + +marker('editor.record.form.bottomButtons.comeBackLater') +marker('editor.record.form.bottomButtons.previous') +marker('editor.record.form.bottomButtons.next') @Component({ selector: 'md-editor-edit', @@ -29,18 +36,30 @@ import { filter, Subscription, take } from 'rxjs' PublishButtonComponent, TopToolbarComponent, NotificationsContainerComponent, + PageSelectorComponent, + TranslateModule, ], }) 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 +128,25 @@ export class EditPageComponent implements OnInit, OnDestroy { ngOnDestroy() { this.subscription.unsubscribe() } + + getSelectedPageFields(pages: EditorFieldPage[]) { + return pages[this.selectedPage] + } + + previousPageButtonHandler() { + if (this.selectedPage === 0) { + this.router.navigate(['catalog', 'search']) + } else { + this.selectedPage-- + } + } + + nextPageButtonHandler() { + if (this.selectedPage === this.totalPages - 1) return + this.selectedPage++ + } + + selectedPageChange(index: number) { + this.selectedPage = index + } } diff --git a/apps/metadata-editor/src/app/pipes/filter.pipe.ts b/apps/metadata-editor/src/app/pipes/filter.pipe.ts deleted file mode 100644 index 4cbff21914..0000000000 --- a/apps/metadata-editor/src/app/pipes/filter.pipe.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' - -@Pipe({ - name: 'filter', - standalone: true, -}) -export class FilterPipe implements PipeTransform { - transform(array: any[], index: number): any { - return array.find((item) => item.index === index) - } -} diff --git a/apps/metadata-editor/tsconfig.json b/apps/metadata-editor/tsconfig.json index d2c6daf3e1..54f2b40f0e 100644 --- a/apps/metadata-editor/tsconfig.json +++ b/apps/metadata-editor/tsconfig.json @@ -19,11 +19,7 @@ "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "target": "es2020", - "lib": [ - "dom", - "es2020", - "dom.iterable" - ], + "lib": ["dom", "es2020", "dom.iterable"], "downlevelIteration": true }, "angularCompilerOptions": { diff --git a/libs/feature/editor/src/index.ts b/libs/feature/editor/src/index.ts index 5e6dd85211..c95afdefd4 100644 --- a/libs/feature/editor/src/index.ts +++ b/libs/feature/editor/src/index.ts @@ -9,3 +9,4 @@ export * from './lib/components/record-form/record-form.component' export * from './lib/components/wizard/wizard.component' export * from './lib/components/wizard-field/wizard-field.component' export * from './lib/components/wizard-summarize/wizard-summarize.component' +export * from './lib/fixtures' diff --git a/libs/feature/editor/src/lib/+state/editor.reducer.ts b/libs/feature/editor/src/lib/+state/editor.reducer.ts index 425e3d0c41..506f3436fb 100644 --- a/libs/feature/editor/src/lib/+state/editor.reducer.ts +++ b/libs/feature/editor/src/lib/+state/editor.reducer.ts @@ -2,7 +2,7 @@ import { Action, createReducer, on } from '@ngrx/store' import * as EditorActions from './editor.actions' import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record' import { SaveRecordError } from './editor.models' -import { EditorFieldsConfig } from '../models/fields.model' +import { EditorConfig } from '../models' import { DEFAULT_FIELDS } from '../fields.config' export const EDITOR_FEATURE_KEY = 'editor' @@ -22,7 +22,7 @@ export interface EditorState { saving: boolean saveError: SaveRecordError | null changedSinceSave: boolean - fieldsConfig: EditorFieldsConfig + fieldsConfig: EditorConfig } export interface EditorPartialState { diff --git a/libs/feature/editor/src/lib/+state/editor.selectors.spec.ts b/libs/feature/editor/src/lib/+state/editor.selectors.spec.ts index 0d61773f97..e3ee07bfd9 100644 --- a/libs/feature/editor/src/lib/+state/editor.selectors.spec.ts +++ b/libs/feature/editor/src/lib/+state/editor.selectors.spec.ts @@ -58,45 +58,26 @@ describe('Editor Selectors', () => { describe('selectRecordFields', () => { it('should return the config and value for each field', () => { const result = EditorSelectors.selectRecordFields(state) - expect(result).toEqual([ - { - config: DEFAULT_FIELDS[0], - value: DATASET_RECORDS[0].title, - }, - { - config: DEFAULT_FIELDS[1], - value: DATASET_RECORDS[0].abstract, - }, - { - config: DEFAULT_FIELDS[2], - value: DATASET_RECORDS[0].uniqueIdentifier, - }, - { - config: DEFAULT_FIELDS[3], - value: DATASET_RECORDS[0].recordUpdated, - }, - { - config: DEFAULT_FIELDS[4], - value: DATASET_RECORDS[0].licenses, - }, - { - config: DEFAULT_FIELDS[5], - value: DATASET_RECORDS[0].resourceUpdated, - }, - { - config: DEFAULT_FIELDS[6], - value: DATASET_RECORDS[0].updateFrequency, - }, - { - config: DEFAULT_FIELDS[7], - value: DATASET_RECORDS[0].temporalExtents, - }, - { - config: DEFAULT_FIELDS[8], - value: DATASET_RECORDS[0].keywords, - }, - ]) + + const actualSections = result.pages.map((page) => page.sections).flat() + + const expectedSections = DEFAULT_FIELDS.pages + .map((page) => page.sections) + .flat() + + expect(actualSections).toEqual(expectedSections) + + const actualFields = actualSections + .map((section) => section.fields) + .flat() + + const expectedFields = expectedSections + .map((section) => section.fields) + .flat() + + expect(actualFields).toEqual(expectedFields) }) + it('should not coerce falsy values to null', () => { const result = EditorSelectors.selectRecordFields({ ...state, @@ -109,14 +90,19 @@ describe('Editor Selectors', () => { }, }, }) - expect(result).toContainEqual({ - config: DEFAULT_FIELDS[0], - value: '', - }) - expect(result).toContainEqual({ - config: DEFAULT_FIELDS[1], - value: '', - }) + + const resultFields = result.pages + .flatMap((page) => page.sections) + .flatMap((section) => section.fields) + + const abstractField = resultFields.find( + (field) => field.model === 'abstract' + ) + + const titleField = resultFields.find((field) => field.model === 'title') + + expect(abstractField.value).toEqual('') + expect(titleField.value).toEqual('') }) }) }) diff --git a/libs/feature/editor/src/lib/+state/editor.selectors.ts b/libs/feature/editor/src/lib/+state/editor.selectors.ts index e9a601da80..3661ace5f1 100644 --- a/libs/feature/editor/src/lib/+state/editor.selectors.ts +++ b/libs/feature/editor/src/lib/+state/editor.selectors.ts @@ -41,9 +41,18 @@ 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..ec1b774a7b 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 @@ -4,8 +4,8 @@ @@ -15,14 +15,14 @@
-

{{ formControl.value }} -

+ help @@ -41,14 +41,14 @@ diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.spec.ts b/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.spec.ts index aa81387dba..6337d1d9b3 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.spec.ts +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field.component.spec.ts @@ -23,7 +23,6 @@ describe('FormFieldComponent', () => { fixture = TestBed.createComponent(FormFieldComponent) component = fixture.componentInstance component.config = { - type: 'text', labelKey: 'my.label', } }) 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..44621437a5 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 @@ -25,10 +25,10 @@ import { FormFieldObjectComponent } from './form-field-object/form-field-object. import { FormFieldRichComponent } from './form-field-rich/form-field-rich.component' import { FormFieldSimpleComponent } from './form-field-simple/form-field-simple.component' import { FormFieldSpatialExtentComponent } from './form-field-spatial-extent/form-field-spatial-extent.component' -import { FormFieldConfig } from './form-field.model' import { FormFieldUpdateFrequencyComponent } from './form-field-update-frequency/form-field-update-frequency.component' import { CatalogRecordKeys } from '@geonetwork-ui/common/domain/model/record' import { FormFieldKeywordsComponent } from './form-field-keywords/form-field-keywords.component' +import { FormFieldConfig } from '../../../models' @Component({ selector: 'gn-ui-form-field', @@ -65,6 +65,7 @@ export class FormFieldComponent { emitEvent: false, }) } + @Output() valueChange: Observable @ViewChild('titleInput') titleInput: ElementRef diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field.model.ts b/libs/feature/editor/src/lib/components/record-form/form-field/form-field.model.ts deleted file mode 100644 index cbc9d543b3..0000000000 --- a/libs/feature/editor/src/lib/components/record-form/form-field/form-field.model.ts +++ /dev/null @@ -1,43 +0,0 @@ -type BaseFormFieldType = - | 'text' - | 'number' - | 'rich' - | 'date' - | 'list' - | 'spatial_extent' - | 'temporal_extent' - | 'url' - | 'file' - | 'toggle' - -type AllFormFieldType = BaseFormFieldType | 'object' | 'array' - -interface FormFieldConfigBase { - type: AllFormFieldType - labelKey: string - hintKey?: string - tooltipKey?: string - required?: boolean - locked?: boolean - invalid?: boolean - invalidHintKey?: string -} - -export interface FormFieldConfigSimple extends FormFieldConfigBase { - type: BaseFormFieldType -} - -export interface FormFieldConfigArray extends FormFieldConfigBase { - type: 'array' - items: FormFieldConfig -} - -export interface FormFieldConfigObject extends FormFieldConfigBase { - type: 'object' - fields: Array -} - -export type FormFieldConfig = - | FormFieldConfigSimple - | FormFieldConfigArray - | FormFieldConfigObject diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/index.ts b/libs/feature/editor/src/lib/components/record-form/form-field/index.ts index 053dede8e5..8555c39727 100644 --- a/libs/feature/editor/src/lib/components/record-form/form-field/index.ts +++ b/libs/feature/editor/src/lib/components/record-form/form-field/index.ts @@ -9,4 +9,3 @@ export * from './form-field-object/form-field-object.component' export * from './form-field-array/form-field-array.component' export * from './form-field-spatial-extent/form-field-spatial-extent.component' export * from './form-field.component' -export * from './form-field.model' 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..ed64b72b3a 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,43 @@ -
- - - -
+ +
+ + +
+
+
+ {{ section.labelKey }} +
+
+ {{ section.descriptionKey }} +
+
+ + + + + +
+
+
+
+
diff --git a/libs/feature/editor/src/lib/components/record-form/record-form.component.spec.ts b/libs/feature/editor/src/lib/components/record-form/record-form.component.spec.ts index ca1d821f0a..2042e4c857 100644 --- a/libs/feature/editor/src/lib/components/record-form/record-form.component.spec.ts +++ b/libs/feature/editor/src/lib/components/record-form/record-form.component.spec.ts @@ -32,10 +32,7 @@ describe('RecordFormComponent', () => { describe('handleFieldValueChange', () => { it('should call facade.updateRecordField', () => { - component.handleFieldValueChange( - { config: { model: 'title' }, value: 'old title' }, - 'new title' - ) + component.handleFieldValueChange('title', 'new title') expect(component.facade.updateRecordField).toHaveBeenCalledWith( 'title', 'new title' 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..06863d04b7 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,8 +1,14 @@ 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 { + EditorField, + EditorFieldPage, + EditorFieldValue, + EditorSection, +} from '../../models' import { FormFieldComponent } from './form-field' +import { TranslateModule } from '@ngx-translate/core' @Component({ selector: 'gn-ui-record-form', @@ -10,21 +16,25 @@ import { FormFieldComponent } from './form-field' styleUrls: ['./record-form.component.css'], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [CommonModule, FormFieldComponent], + imports: [CommonModule, FormFieldComponent, TranslateModule], }) export class RecordFormComponent { - fields$ = this.facade.recordFields$ + @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) + this.facade.updateRecordField(model, newValue) } - fieldTracker(index: number, field: EditorFieldState) { - return field.config.model + fieldTracker(index: number, field: EditorField): any { + return field.model + } + + sectionTracker(index: number, section: EditorSection): any { + return section.labelKey } } 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/expressions.ts b/libs/feature/editor/src/lib/expressions.ts index 59aabd6885..253f52a2fa 100644 --- a/libs/feature/editor/src/lib/expressions.ts +++ b/libs/feature/editor/src/lib/expressions.ts @@ -1,4 +1,4 @@ -import { EditorFieldState, EditorFieldValue } from './models/fields.model' +import { EditorFieldState, EditorFieldValue } from './models/' export type ExpressionEvaluator = (field: EditorFieldState) => EditorFieldValue diff --git a/libs/feature/editor/src/lib/fields.config.ts b/libs/feature/editor/src/lib/fields.config.ts index 6591a3090c..62a7d83bbe 100644 --- a/libs/feature/editor/src/lib/fields.config.ts +++ b/libs/feature/editor/src/lib/fields.config.ts @@ -1,71 +1,185 @@ import { marker } from '@biesbjerg/ngx-translate-extract-marker' -import { EditorFieldsConfig } from './models/fields.model' - -export const DEFAULT_FIELDS: EditorFieldsConfig = [ - { - model: 'title', - formFieldConfig: { - labelKey: marker('editor.record.form.metadata.title'), - type: 'text', - }, +import { + EditorConfig, + EditorField, + EditorSection, +} from './models/editor-config.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.field.license'), }, - { - model: 'abstract', - formFieldConfig: { - labelKey: marker('editor.record.form.abstract'), - type: 'rich', - }, +} + +export const RECORD_KEYWORDS_FIELD: EditorField = { + model: 'keywords', + formFieldConfig: { + labelKey: marker('editor.record.form.field.keywords'), }, - { - model: 'uniqueIdentifier', - formFieldConfig: { - labelKey: marker('editor.record.form.unique.identifier'), - type: 'text', - locked: true, - }, +} + +export const RECORD_UNIQUE_IDENTIFIER_FIELD: EditorField = { + model: 'uniqueIdentifier', + formFieldConfig: { + labelKey: marker('editor.record.form.field.uniqueIdentifier'), + locked: true, }, - { - model: 'recordUpdated', - formFieldConfig: { - labelKey: marker('editor.record.form.record.updated'), - type: 'text', - locked: true, - }, - onSaveProcess: '${dateNow()}', +} + +export const RECORD_RESOURCE_UPDATED_FIELD: EditorField = { + model: 'resourceUpdated', + formFieldConfig: { + labelKey: marker('editor.record.form.field.resourceUpdated'), }, - { - model: 'licenses', - formFieldConfig: { - labelKey: marker('editor.record.form.license'), - type: 'list', - }, +} + +export const RECORD_UPDATED_FIELD: EditorField = { + model: 'recordUpdated', + formFieldConfig: { + labelKey: marker('editor.record.form.field.recordUpdated'), + 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.field.updateFrequency'), }, - { - 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.field.temporalExtents'), }, - { - model: 'temporalExtents', - formFieldConfig: { - labelKey: marker('editor.record.form.temporalExtents'), - type: 'list', - }, +} + +export const RECORD_TITLE_FIELD: EditorField = { + model: 'title', + formFieldConfig: { + labelKey: marker('editor.record.form.field.title'), }, - { - model: 'keywords', - formFieldConfig: { - labelKey: marker('editor.record.form.keywords'), - type: 'list', - }, +} + +export const RECORD_ABSTRACT_FIELD: EditorField = { + model: 'abstract', + formFieldConfig: { + labelKey: marker('editor.record.form.field.abstract'), }, -] +} + +/************************************************************ + *************** SECTIONS ***************** + ************************************************************ + */ + +export const TITLE_SECTION: EditorSection = { + hidden: false, + fields: [RECORD_TITLE_FIELD, RECORD_ABSTRACT_FIELD], +} + +export const ABOUT_SECTION: EditorSection = { + labelKey: marker('editor.record.form.section.about.label'), + descriptionKey: marker('editor.record.form.section.about.description'), + 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_COVERAGE_SECTION: EditorSection = { + labelKey: marker('editor.record.form.section.geographicalCoverage.label'), + hidden: false, + fields: [], +} + +export const ASSOCIATED_RESOURCES_SECTION: EditorSection = { + labelKey: marker('editor.record.form.section.associatedResources.label'), + descriptionKey: marker( + 'editor.record.form.section.associatedResources.description' + ), + hidden: false, + fields: [], +} + +export const ANNEXES_SECTION: EditorSection = { + labelKey: marker('editor.record.form.section.annexes.label'), + hidden: false, + fields: [], +} + +export const CLASSIFICATION_SECTION: EditorSection = { + labelKey: marker('editor.record.form.section.classification.label'), + descriptionKey: marker( + 'editor.record.form.section.classification.description' + ), + hidden: false, + fields: [RECORD_KEYWORDS_FIELD], +} + +export const USE_AND_ACCESS_CONDITIONS_SECTION: EditorSection = { + labelKey: marker('editor.record.form.section.useAndAccessConditions.label'), + hidden: false, + fields: [RECORD_LICENSE_FIELD], +} + +export const DATA_MANAGERS_SECTION: EditorSection = { + labelKey: marker('editor.record.form.section.dataManagers.label'), + descriptionKey: marker('editor.record.form.section.dataManagers.description'), + hidden: false, + fields: [], +} + +export const DATA_POINT_OF_CONTACT_SECTION: EditorSection = { + labelKey: marker('editor.record.form.section.dataPointOfContact.label'), + descriptionKey: marker( + 'editor.record.form.section.dataPointOfContact.description' + ), + hidden: false, + fields: [], +} + +/************************************************************ + *************** PAGES ***************** + ************************************************************ + */ +export const DEFAULT_FIELDS: EditorConfig = { + pages: [ + { + labelKey: marker('editor.record.form.page.description'), + sections: [TITLE_SECTION, ABOUT_SECTION, GEOGRAPHICAL_COVERAGE_SECTION], + }, + { + labelKey: marker('editor.record.form.page.ressources'), + sections: [ASSOCIATED_RESOURCES_SECTION, ANNEXES_SECTION], + }, + { + labelKey: marker('editor.record.form.page.accessAndContact'), + sections: [ + CLASSIFICATION_SECTION, + USE_AND_ACCESS_CONDITIONS_SECTION, + DATA_MANAGERS_SECTION, + DATA_POINT_OF_CONTACT_SECTION, + ], + }, + ], +} diff --git a/libs/feature/editor/src/lib/fixtures/editor.fixtures.ts b/libs/feature/editor/src/lib/fixtures/editor.fixtures.ts new file mode 100644 index 0000000000..d220a192ef --- /dev/null +++ b/libs/feature/editor/src/lib/fixtures/editor.fixtures.ts @@ -0,0 +1,158 @@ +import { EditorConfig, EditorField, EditorSection } from '../models' + +export const EDITOR_CONFIG = (): EditorConfig => ({ + pages: [ + { + labelKey: 'Resource description', + sections: [EDITOR_SECTION_ABOUT()], + }, + { + labelKey: 'Resources', + sections: [EDITOR_SECTION_CLASSIFICATION()], + }, + { + labelKey: 'Access and contact', + sections: [ + EDITOR_SECTION_USE_AND_ACCESS_CONDITIONS(), + EDITOR_SECTION_DATA_MANAGER(), + ], + }, + ], +}) + +export const EDITOR_SECTION_ABOUT = (): EditorSection => ({ + labelKey: 'About the resource', + descriptionKey: 'This section describes the resource.', + hidden: false, + fields: [ + EDITOR_FIELD_TITLE(), + EDITOR_FIELD_ABSTRACT(), + EDITOR_FIELD_RESOURCE_UPDATED(), + EDITOR_FIELD_RECORD_UPDATED(), + EDITOR_FIELD_UPDATE_FREQUENCY(), + EDITOR_FIELD_TEMPORAL_EXTENTS(), + ], +}) + +export const EDITOR_SECTION_DATA_MANAGER = (): EditorSection => ({ + labelKey: 'Data manager', + descriptionKey: '', + hidden: false, + fields: [], +}) + +export const EDITOR_SECTION_USE_AND_ACCESS_CONDITIONS = (): EditorSection => ({ + labelKey: 'Data manager', + descriptionKey: '', + hidden: false, + fields: [EDITOR_FIELD_LICENSE()], +}) + +export const EDITOR_SECTION_CLASSIFICATION = (): EditorSection => ({ + labelKey: 'Classification', + descriptionKey: 'The classification has an impact on the access to the data.', + hidden: false, + fields: [EDITOR_FIELD_KEYWORDS(), EDITOR_FIELD_UNIQUE_IDENTIFIER()], +}) + +export const EDITOR_FIELD_TITLE = (): EditorField => ({ + model: 'title', + hidden: false, + value: 'Accroches vĂ©los MEL', + formFieldConfig: { + labelKey: 'editor.record.form.field.title', + }, +}) + +export const EDITOR_FIELD_ABSTRACT = (): EditorField => ({ + model: 'abstract', + hidden: false, + value: 'Abstract', + formFieldConfig: { + labelKey: 'editor.record.form.field.abstract', + }, +}) + +export const EDITOR_FIELD_RESOURCE_UPDATED = (): EditorField => ({ + model: 'resourceUpdated', + hidden: false, + formFieldConfig: { + labelKey: 'editor.record.form.field.resourceUpdated', + }, +}) + +export const EDITOR_FIELD_RECORD_UPDATED = (): EditorField => ({ + model: 'recordUpdated', + hidden: false, + formFieldConfig: { + labelKey: 'editor.record.form.field.recordUpdated', + locked: true, + }, + value: '2024-07-16T05:18:53.000Z', + onSaveProcess: '${dateNow()}', +}) + +export const EDITOR_FIELD_UPDATE_FREQUENCY = (): EditorField => ({ + model: 'updateFrequency', + hidden: false, + formFieldConfig: { + labelKey: 'editor.record.form.field.updateFrequency', + }, + value: 'unknown', +}) + +export const EDITOR_FIELD_TEMPORAL_EXTENTS = (): EditorField => ({ + model: 'temporalExtents', + hidden: false, + formFieldConfig: { + labelKey: 'editor.record.form.field.temporalExtents', + }, + value: [], +}) + +export const EDITOR_FIELD_SPATIAL_EXTENTS = (): EditorField => ({ + model: 'spatialExtents', + hidden: false, + formFieldConfig: { + labelKey: 'editor.record.form.field.spatialExtents', + }, +}) + +export const EDITOR_FIELD_KEYWORDS = (): EditorField => ({ + model: 'keywords', + hidden: false, + formFieldConfig: { + labelKey: 'editor.record.form.field.keywords', + }, +}) + +export const EDITOR_FIELD_UNIQUE_IDENTIFIER = (): EditorField => ({ + model: 'uniqueIdentifier', + hidden: false, + formFieldConfig: { + labelKey: 'editor.record.form.field.uniqueIdentifier', + locked: true, + }, + value: 'accroche_velos', +}) + +export const EDITOR_FIELD_LICENSE = (): EditorField => ({ + model: 'licenses', + hidden: false, + formFieldConfig: { + labelKey: 'editor.record.form.field.license', + locked: true, + }, +}) + +export const EDITOR_FIELDS = (): EditorField[] => [ + EDITOR_FIELD_TITLE(), + EDITOR_FIELD_ABSTRACT(), + EDITOR_FIELD_RESOURCE_UPDATED(), + EDITOR_FIELD_RECORD_UPDATED(), + EDITOR_FIELD_UPDATE_FREQUENCY(), + EDITOR_FIELD_TEMPORAL_EXTENTS(), + EDITOR_FIELD_SPATIAL_EXTENTS(), + EDITOR_FIELD_KEYWORDS(), + EDITOR_FIELD_UNIQUE_IDENTIFIER(), +] diff --git a/libs/feature/editor/src/lib/fixtures/index.ts b/libs/feature/editor/src/lib/fixtures/index.ts new file mode 100644 index 0000000000..ed9fbc8aeb --- /dev/null +++ b/libs/feature/editor/src/lib/fixtures/index.ts @@ -0,0 +1 @@ +export * from './editor.fixtures' diff --git a/libs/feature/editor/src/lib/models/editor-config.model.ts b/libs/feature/editor/src/lib/models/editor-config.model.ts new file mode 100644 index 0000000000..8530049354 --- /dev/null +++ b/libs/feature/editor/src/lib/models/editor-config.model.ts @@ -0,0 +1,55 @@ +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 type EditorFieldValue = string | number | boolean | unknown + +export interface FormFieldConfig { + labelKey?: string + hintKey?: string + tooltipKey?: string + required?: boolean + locked?: boolean + invalid?: boolean + invalidHintKey?: string +} + +export interface EditorField { + // configuration of the form field used as presentation + formFieldConfig: FormFieldConfig + + // name of the target field in the record; will not change the record directly if not defined + 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 + hidden?: boolean + + // the result of this expression will replace the field value on save + onSaveProcess?: EditorFieldExpression + + value?: EditorFieldValue +} + +export interface EditorSection { + labelKey?: string + descriptionKey?: string + hidden: boolean + fields: EditorField[] +} + +export interface EditorFieldPage { + labelKey?: string + sections: EditorSection[] +} + +export interface EditorConfig { + pages: EditorFieldPage[] +} + +export interface EditorFieldState { + model: string + value: EditorFieldValue +} diff --git a/libs/feature/editor/src/lib/models/fields.model.ts b/libs/feature/editor/src/lib/models/fields.model.ts deleted file mode 100644 index 0dae06b007..0000000000 --- a/libs/feature/editor/src/lib/models/fields.model.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { FormFieldConfig } from '../components/record-form/form-field' - -// Expressions should be enclosed in `${}` to be recognized as such -// eg. ${dateNow()} -export type EditorFieldExpression = `$\{${string}}` - -export interface EditorFieldConfig { - // configuration of the form field used as presentation; optional, nothing shown if not defined - formFieldConfig?: FormFieldConfig - - // name of the target field in the record; will not change the record directly if not defined - model?: string - - // 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 - hidden?: boolean - - // the result of this expression will replace the field value on save - onSaveProcess?: EditorFieldExpression -} - -export type EditorFieldsConfig = EditorFieldConfig[] - -export type EditorFieldValue = string | number | boolean | unknown - -export interface EditorFieldState { - config: EditorFieldConfig - value: string | number | boolean | unknown -} diff --git a/libs/feature/editor/src/lib/models/index.ts b/libs/feature/editor/src/lib/models/index.ts index 65975121b2..1adb10500d 100644 --- a/libs/feature/editor/src/lib/models/index.ts +++ b/libs/feature/editor/src/lib/models/index.ts @@ -1,2 +1,3 @@ export * from './wizard-field.model' export * from './wizard-field.type' +export * from './editor-config.model' diff --git a/libs/feature/editor/src/lib/services/editor.service.ts b/libs/feature/editor/src/lib/services/editor.service.ts index 9b7c005438..1cc1b67791 100644 --- a/libs/feature/editor/src/lib/services/editor.service.ts +++ b/libs/feature/editor/src/lib/services/editor.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core' import { Observable, switchMap } from 'rxjs' import { map, tap } from 'rxjs/operators' import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record' -import { EditorFieldsConfig } from '../models/fields.model' +import { EditorConfig } from '../models/' import { evaluate } from '../expressions' import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface' @@ -15,17 +15,21 @@ export class EditorService { // returns the record as it was when saved, alongside its source saveRecord( record: CatalogRecord, - fieldsConfig: EditorFieldsConfig, + fieldsConfig: EditorConfig, generateNewUniqueIdentifier = false ): Observable<[CatalogRecord, string]> { const savedRecord = { ...record } + const fields = fieldsConfig.pages.flatMap((page) => + page.sections.flatMap((section) => section.fields) + ) + // 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], }) } diff --git a/libs/feature/editor/tsconfig.json b/libs/feature/editor/tsconfig.json index cdea0bdb38..9e29358f76 100644 --- a/libs/feature/editor/tsconfig.json +++ b/libs/feature/editor/tsconfig.json @@ -16,11 +16,7 @@ "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "target": "es2020", - "lib": [ - "dom", - "es2020", - "dom.iterable" - ], + "lib": ["dom", "es2020", "dom.iterable"], "downlevelIteration": true }, "angularCompilerOptions": { diff --git a/libs/feature/editor/tsconfig.lib.json b/libs/feature/editor/tsconfig.lib.json index 7ca4377996..2a4c9a5e36 100644 --- a/libs/feature/editor/tsconfig.lib.json +++ b/libs/feature/editor/tsconfig.lib.json @@ -7,11 +7,7 @@ "declarationMap": true, "inlineSources": true, "types": [], - "lib": [ - "dom", - "es2020", - "dom.iterable" - ], + "lib": ["dom", "es2020", "dom.iterable"], "downlevelIteration": true }, "exclude": [ @@ -20,7 +16,5 @@ "**/*.test.ts", "jest.config.ts" ], - "include": [ - "**/*.ts" - ] + "include": ["**/*.ts"] } diff --git a/libs/util/shared/tsconfig.lib.json b/libs/util/shared/tsconfig.lib.json index 9bb502669e..02d129fa3b 100644 --- a/libs/util/shared/tsconfig.lib.json +++ b/libs/util/shared/tsconfig.lib.json @@ -7,11 +7,7 @@ "declarationMap": true, "inlineSources": true, "types": [], - "lib": [ - "dom", - "es2020", - "dom.iterable" - ] + "lib": ["dom", "es2020", "dom.iterable"] }, "exclude": [ "src/test-setup.ts", diff --git a/translations/de.json b/translations/de.json index 25539c677e..12b58965fd 100644 --- a/translations/de.json +++ b/translations/de.json @@ -165,6 +165,18 @@ "downloads.format.unknown": "unbekannt", "downloads.wfs.featuretype.not.found": "Der Layer wurde nicht gefunden", "dropFile": "Datei ablegen", + "editor.record.form.bottomButtons.comeBackLater": "", + "editor.record.form.bottomButtons.next": "", + "editor.record.form.bottomButtons.previous": "", + "editor.record.form.field.abstract": "", + "editor.record.form.field.keywords": "Schlagwörter", + "editor.record.form.field.license": "Lizenz", + "editor.record.form.field.recordUpdated": "", + "editor.record.form.field.resourceUpdated": "", + "editor.record.form.field.temporalExtents": "", + "editor.record.form.field.title": "", + "editor.record.form.field.uniqueIdentifier": "", + "editor.record.form.field.updateFrequency": "", "editor.record.form.abstract": "Kurzbeschreibung", "editor.record.form.keywords": "SchlĂ¼sselwörter", "editor.record.form.license": "Lizenz", @@ -177,6 +189,22 @@ "editor.record.form.license.odc-by": "Open Data Commons ODC-By", "editor.record.form.license.pddl": "Open Data Commons PDDL", "editor.record.form.license.unknown": "Unbekannt oder nicht vorhanden", + "editor.record.form.page.accessAndContact": "", + "editor.record.form.page.description": "", + "editor.record.form.page.ressources": "", + "editor.record.form.section.about.description": "", + "editor.record.form.section.about.label": "", + "editor.record.form.section.annexes.label": "", + "editor.record.form.section.associatedResources.description": "", + "editor.record.form.section.associatedResources.label": "", + "editor.record.form.section.classification.description": "", + "editor.record.form.section.classification.label": "", + "editor.record.form.section.dataManagers.description": "", + "editor.record.form.section.dataManagers.label": "", + "editor.record.form.section.dataPointOfContact.description": "", + "editor.record.form.section.dataPointOfContact.label": "", + "editor.record.form.section.geographicalCoverage.label": "", + "editor.record.form.section.useAndAccessConditions.label": "", "editor.record.form.metadata.title": "Metadaten-Titel", "editor.record.form.record.updated": "Datensatz zuletzt aktualisiert", "editor.record.form.resourceUpdated": "Letztes Aktualisierungsdatum", diff --git a/translations/en.json b/translations/en.json index c3f80d04d0..4055d950f6 100644 --- a/translations/en.json +++ b/translations/en.json @@ -165,6 +165,18 @@ "downloads.format.unknown": "unknown", "downloads.wfs.featuretype.not.found": "The layer was not found", "dropFile": "drop file", + "editor.record.form.bottomButtons.comeBackLater": "Come back later", + "editor.record.form.bottomButtons.next": "Next", + "editor.record.form.bottomButtons.previous": "Previous", + "editor.record.form.field.abstract": "Abstract", + "editor.record.form.field.keywords": "Keywords", + "editor.record.form.field.license": "License", + "editor.record.form.field.recordUpdated": "Record Updated", + "editor.record.form.field.resourceUpdated": "Resource Updated", + "editor.record.form.field.temporalExtents": "Temporal extents", + "editor.record.form.field.title": "Metadata title", + "editor.record.form.field.uniqueIdentifier": "Unique identifier", + "editor.record.form.field.updateFrequency": "Update frequency", "editor.record.form.abstract": "Abstract", "editor.record.form.keywords": "Keywords", "editor.record.form.license": "License", @@ -177,6 +189,22 @@ "editor.record.form.license.odc-by": "Open Data Commons ODC-By", "editor.record.form.license.pddl": "Open Data Commons PDDL", "editor.record.form.license.unknown": "Unknown or absent", + "editor.record.form.page.accessAndContact": "Access and contact", + "editor.record.form.page.description": "Resource description", + "editor.record.form.page.ressources": "Resources", + "editor.record.form.section.about.description": "This section describes the resource.", + "editor.record.form.section.about.label": "About the resource", + "editor.record.form.section.annexes.label": "Annexes", + "editor.record.form.section.associatedResources.description": "Drop files here to associate them with the resource.", + "editor.record.form.section.associatedResources.label": "Associated resources", + "editor.record.form.section.classification.description": "The classification has an impact on the access to the data.", + "editor.record.form.section.classification.label": "Classification", + "editor.record.form.section.dataManagers.description": "The data managers are responsible for the data.", + "editor.record.form.section.dataManagers.label": "Data managers", + "editor.record.form.section.dataPointOfContact.description": "This information concerns the metadata.", + "editor.record.form.section.dataPointOfContact.label": "Data point of contact", + "editor.record.form.section.geographicalCoverage.label": "Geographical coverage", + "editor.record.form.section.useAndAccessConditions.label": "Use and access conditions", "editor.record.form.metadata.title": "Metadata title", "editor.record.form.record.updated": "Record updated", "editor.record.form.resourceUpdated": "Last update date", diff --git a/translations/es.json b/translations/es.json index e74e4256e4..54dfd827fb 100644 --- a/translations/es.json +++ b/translations/es.json @@ -165,6 +165,18 @@ "downloads.format.unknown": "", "downloads.wfs.featuretype.not.found": "", "dropFile": "", + "editor.record.form.bottomButtons.comeBackLater": "", + "editor.record.form.bottomButtons.next": "", + "editor.record.form.bottomButtons.previous": "", + "editor.record.form.field.abstract": "", + "editor.record.form.field.keywords": "", + "editor.record.form.field.license": "", + "editor.record.form.field.recordUpdated": "", + "editor.record.form.field.resourceUpdated": "", + "editor.record.form.field.temporalExtents": "", + "editor.record.form.field.title": "", + "editor.record.form.field.uniqueIdentifier": "", + "editor.record.form.field.updateFrequency": "", "editor.record.form.abstract": "", "editor.record.form.keywords": "", "editor.record.form.license": "", @@ -177,6 +189,22 @@ "editor.record.form.license.odc-by": "", "editor.record.form.license.pddl": "", "editor.record.form.license.unknown": "", + "editor.record.form.page.accessAndContact": "", + "editor.record.form.page.description": "", + "editor.record.form.page.ressources": "", + "editor.record.form.section.about.description": "", + "editor.record.form.section.about.label": "", + "editor.record.form.section.annexes.label": "", + "editor.record.form.section.associatedResources.description": "", + "editor.record.form.section.associatedResources.label": "", + "editor.record.form.section.classification.description": "", + "editor.record.form.section.classification.label": "", + "editor.record.form.section.dataManagers.description": "", + "editor.record.form.section.dataManagers.label": "", + "editor.record.form.section.dataPointOfContact.description": "", + "editor.record.form.section.dataPointOfContact.label": "", + "editor.record.form.section.geographicalCoverage.label": "", + "editor.record.form.section.useAndAccessConditions.label": "", "editor.record.form.metadata.title": "", "editor.record.form.record.updated": "", "editor.record.form.resourceUpdated": "", diff --git a/translations/fr.json b/translations/fr.json index c3124afef3..29374cb89a 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -165,6 +165,18 @@ "downloads.format.unknown": "inconnu", "downloads.wfs.featuretype.not.found": "La couche n'a pas Ă©tĂ© retrouvĂ©e", "dropFile": "Faites glisser votre fichier", + "editor.record.form.bottomButtons.comeBackLater": "Revenir plus tard", + "editor.record.form.bottomButtons.next": "Suivant", + "editor.record.form.bottomButtons.previous": "PrĂ©cĂ©dent", + "editor.record.form.field.abstract": "RĂ©sumĂ©", + "editor.record.form.field.keywords": "Mots-clĂ©s", + "editor.record.form.field.license": "Licence", + "editor.record.form.field.recordUpdated": "Date de dernière rĂ©vision", + "editor.record.form.field.resourceUpdated": "Date de dernière rĂ©vision", + "editor.record.form.field.temporalExtents": "Étendue temporelle", + "editor.record.form.field.title": "Titre", + "editor.record.form.field.uniqueIdentifier": "Identifiant unique", + "editor.record.form.field.updateFrequency": "FrĂ©quence de mise Ă  jour", "editor.record.form.abstract": "", "editor.record.form.keywords": "", "editor.record.form.license": "Licence", @@ -177,6 +189,22 @@ "editor.record.form.license.odc-by": "", "editor.record.form.license.pddl": "", "editor.record.form.license.unknown": "Non-reconnue ou absente", + "editor.record.form.page.accessAndContact": "Acces et contact", + "editor.record.form.page.description": "Description de la ressource", + "editor.record.form.page.ressources": "Ressources", + "editor.record.form.section.about.description": "Ces informations concernent la donnĂ©e.", + "editor.record.form.section.about.label": "A propos de la ressource", + "editor.record.form.section.annexes.label": "Annexes", + "editor.record.form.section.associatedResources.description": "DĂ©posez les jeux de donnĂ©es associĂ©es Ă  cette fiche de mĂ©tadonnĂ©e.", + "editor.record.form.section.associatedResources.label": "Ressources associees", + "editor.record.form.section.classification.description": "La classification a un impact sur la recherche du jeux de donnĂ©es.", + "editor.record.form.section.classification.label": "Classification", + "editor.record.form.section.dataManagers.description": "Cette information concerne la donnĂ©e.", + "editor.record.form.section.dataManagers.label": "Responsables de la donnee", + "editor.record.form.section.dataPointOfContact.description": "Cette information concerne la fiche de mĂ©tadonnĂ©e.", + "editor.record.form.section.dataPointOfContact.label": "Point de contact de la metadonee", + "editor.record.form.section.geographicalCoverage.label": "Couverture geographique", + "editor.record.form.section.useAndAccessConditions.label": "Conditions d'acces et usage", "editor.record.form.metadata.title": "", "editor.record.form.record.updated": "", "editor.record.form.resourceUpdated": "Date de dernière rĂ©vision", diff --git a/translations/it.json b/translations/it.json index fd42ba00df..b945330f98 100644 --- a/translations/it.json +++ b/translations/it.json @@ -165,6 +165,18 @@ "downloads.format.unknown": "sconosciuto", "downloads.wfs.featuretype.not.found": "Il layer non è stato trovato", "dropFile": "Trascina il suo file", + "editor.record.form.bottomButtons.comeBackLater": "", + "editor.record.form.bottomButtons.next": "", + "editor.record.form.bottomButtons.previous": "", + "editor.record.form.field.abstract": "", + "editor.record.form.field.keywords": "", + "editor.record.form.field.license": "Licenza", + "editor.record.form.field.recordUpdated": "", + "editor.record.form.field.resourceUpdated": "", + "editor.record.form.field.temporalExtents": "", + "editor.record.form.field.title": "", + "editor.record.form.field.uniqueIdentifier": "", + "editor.record.form.field.updateFrequency": "", "editor.record.form.abstract": "", "editor.record.form.keywords": "", "editor.record.form.license": "Licenza", @@ -177,6 +189,22 @@ "editor.record.form.license.odc-by": "", "editor.record.form.license.pddl": "", "editor.record.form.license.unknown": "Non riconosciuta o assente", + "editor.record.form.page.accessAndContact": "", + "editor.record.form.page.description": "", + "editor.record.form.page.ressources": "", + "editor.record.form.section.about.description": "", + "editor.record.form.section.about.label": "", + "editor.record.form.section.annexes.label": "", + "editor.record.form.section.associatedResources.description": "", + "editor.record.form.section.associatedResources.label": "", + "editor.record.form.section.classification.description": "", + "editor.record.form.section.classification.label": "", + "editor.record.form.section.dataManagers.description": "", + "editor.record.form.section.dataManagers.label": "", + "editor.record.form.section.dataPointOfContact.description": "", + "editor.record.form.section.dataPointOfContact.label": "", + "editor.record.form.section.geographicalCoverage.label": "", + "editor.record.form.section.useAndAccessConditions.label": "", "editor.record.form.metadata.title": "", "editor.record.form.record.updated": "", "editor.record.form.resourceUpdated": "", diff --git a/translations/nl.json b/translations/nl.json index f714c53ec2..4fcfa583f1 100644 --- a/translations/nl.json +++ b/translations/nl.json @@ -165,6 +165,18 @@ "downloads.format.unknown": "", "downloads.wfs.featuretype.not.found": "", "dropFile": "", + "editor.record.form.bottomButtons.comeBackLater": "", + "editor.record.form.bottomButtons.next": "", + "editor.record.form.bottomButtons.previous": "", + "editor.record.form.field.abstract": "", + "editor.record.form.field.keywords": "", + "editor.record.form.field.license": "", + "editor.record.form.field.recordUpdated": "", + "editor.record.form.field.resourceUpdated": "", + "editor.record.form.field.temporalExtents": "", + "editor.record.form.field.title": "", + "editor.record.form.field.uniqueIdentifier": "", + "editor.record.form.field.updateFrequency": "", "editor.record.form.abstract": "", "editor.record.form.keywords": "", "editor.record.form.license": "", @@ -177,6 +189,22 @@ "editor.record.form.license.odc-by": "", "editor.record.form.license.pddl": "", "editor.record.form.license.unknown": "", + "editor.record.form.page.accessAndContact": "", + "editor.record.form.page.description": "", + "editor.record.form.page.ressources": "", + "editor.record.form.section.about.description": "", + "editor.record.form.section.about.label": "", + "editor.record.form.section.annexes.label": "", + "editor.record.form.section.associatedResources.description": "", + "editor.record.form.section.associatedResources.label": "", + "editor.record.form.section.classification.description": "", + "editor.record.form.section.classification.label": "", + "editor.record.form.section.dataManagers.description": "", + "editor.record.form.section.dataManagers.label": "", + "editor.record.form.section.dataPointOfContact.description": "", + "editor.record.form.section.dataPointOfContact.label": "", + "editor.record.form.section.geographicalCoverage.label": "", + "editor.record.form.section.useAndAccessConditions.label": "", "editor.record.form.metadata.title": "", "editor.record.form.record.updated": "", "editor.record.form.resourceUpdated": "", diff --git a/translations/pt.json b/translations/pt.json index a702a9c851..f6782e7344 100644 --- a/translations/pt.json +++ b/translations/pt.json @@ -165,6 +165,18 @@ "downloads.format.unknown": "", "downloads.wfs.featuretype.not.found": "", "dropFile": "", + "editor.record.form.bottomButtons.comeBackLater": "", + "editor.record.form.bottomButtons.next": "", + "editor.record.form.bottomButtons.previous": "", + "editor.record.form.field.abstract": "", + "editor.record.form.field.keywords": "", + "editor.record.form.field.license": "", + "editor.record.form.field.recordUpdated": "", + "editor.record.form.field.resourceUpdated": "", + "editor.record.form.field.temporalExtents": "", + "editor.record.form.field.title": "", + "editor.record.form.field.uniqueIdentifier": "", + "editor.record.form.field.updateFrequency": "", "editor.record.form.abstract": "", "editor.record.form.keywords": "", "editor.record.form.license": "", @@ -177,6 +189,22 @@ "editor.record.form.license.odc-by": "", "editor.record.form.license.pddl": "", "editor.record.form.license.unknown": "", + "editor.record.form.page.accessAndContact": "", + "editor.record.form.page.description": "", + "editor.record.form.page.ressources": "", + "editor.record.form.section.about.description": "", + "editor.record.form.section.about.label": "", + "editor.record.form.section.annexes.label": "", + "editor.record.form.section.associatedResources.description": "", + "editor.record.form.section.associatedResources.label": "", + "editor.record.form.section.classification.description": "", + "editor.record.form.section.classification.label": "", + "editor.record.form.section.dataManagers.description": "", + "editor.record.form.section.dataManagers.label": "", + "editor.record.form.section.dataPointOfContact.description": "", + "editor.record.form.section.dataPointOfContact.label": "", + "editor.record.form.section.geographicalCoverage.label": "", + "editor.record.form.section.useAndAccessConditions.label": "", "editor.record.form.metadata.title": "", "editor.record.form.record.updated": "", "editor.record.form.resourceUpdated": "", diff --git a/translations/sk.json b/translations/sk.json index f3dd2a423b..2df4e8b133 100644 --- a/translations/sk.json +++ b/translations/sk.json @@ -165,6 +165,18 @@ "downloads.format.unknown": "neznĂ¡my", "downloads.wfs.featuretype.not.found": "Vrstva nebola nĂ¡jdenĂ¡", "dropFile": "nahraÅ¥ sĂºbor", + "editor.record.form.bottomButtons.comeBackLater": "", + "editor.record.form.bottomButtons.next": "", + "editor.record.form.bottomButtons.previous": "", + "editor.record.form.field.abstract": "", + "editor.record.form.field.keywords": "", + "editor.record.form.field.license": "Licencia", + "editor.record.form.field.recordUpdated": "", + "editor.record.form.field.resourceUpdated": "", + "editor.record.form.field.temporalExtents": "", + "editor.record.form.field.title": "", + "editor.record.form.field.uniqueIdentifier": "", + "editor.record.form.field.updateFrequency": "", "editor.record.form.abstract": "", "editor.record.form.keywords": "", "editor.record.form.license": "Licencia", @@ -177,6 +189,22 @@ "editor.record.form.license.odc-by": "", "editor.record.form.license.pddl": "", "editor.record.form.license.unknown": "NeznĂ¡me alebo chĂ½bajĂºce", + "editor.record.form.page.accessAndContact": "", + "editor.record.form.page.description": "", + "editor.record.form.page.ressources": "", + "editor.record.form.section.about.description": "", + "editor.record.form.section.about.label": "", + "editor.record.form.section.annexes.label": "", + "editor.record.form.section.associatedResources.description": "", + "editor.record.form.section.associatedResources.label": "", + "editor.record.form.section.classification.description": "", + "editor.record.form.section.classification.label": "", + "editor.record.form.section.dataManagers.description": "", + "editor.record.form.section.dataManagers.label": "", + "editor.record.form.section.dataPointOfContact.description": "", + "editor.record.form.section.dataPointOfContact.label": "", + "editor.record.form.section.geographicalCoverage.label": "", + "editor.record.form.section.useAndAccessConditions.label": "", "editor.record.form.metadata.title": "", "editor.record.form.record.updated": "", "editor.record.form.resourceUpdated": "", From 5eb47b23ffe620f1751dd6e4583ea19b80271a18 Mon Sep 17 00:00:00 2001 From: Olivia Guyot Date: Thu, 18 Jul 2024 10:10:06 +0200 Subject: [PATCH 5/7] wip store current page in state --- .../breadcrumbs/page-selector.component.html | 12 +++-- .../breadcrumbs/page-selector.component.ts | 18 +++----- .../src/app/edit/edit-page.component.html | 12 ++--- .../src/app/edit/edit-page.component.ts | 44 ++++++++----------- .../editor/src/lib/+state/editor.actions.ts | 5 +++ .../editor/src/lib/+state/editor.effects.ts | 4 +- .../editor/src/lib/+state/editor.facade.ts | 10 ++++- .../editor/src/lib/+state/editor.models.ts | 11 +++++ .../editor/src/lib/+state/editor.reducer.ts | 12 +++-- .../src/lib/+state/editor.selectors.spec.ts | 6 +-- .../editor/src/lib/+state/editor.selectors.ts | 35 ++++++++------- .../record-form/record-form.component.html | 22 +++++++--- .../record-form/record-form.component.ts | 21 ++++----- .../src/lib/models/editor-config.model.ts | 2 +- 14 files changed, 119 insertions(+), 95 deletions(-) diff --git a/apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.html b/apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.html index b38938f323..3085adc2f5 100644 --- a/apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.html +++ b/apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.html @@ -1,19 +1,23 @@
{{ index }} @@ -21,7 +25,7 @@
config.pages)) - @Output() selectedPageChange = new EventEmitter() + constructor(public facade: EditorFacade) {} pageSectionClickHandler(index: number) { - this.selectedPageChange.emit(index) + this.facade.setCurrentPage(index) // TODO } } 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 d93d4aab53..a71413574e 100644 --- a/apps/metadata-editor/src/app/edit/edit-page.component.html +++ b/apps/metadata-editor/src/app/edit/edit-page.component.html @@ -2,19 +2,13 @@
- +
- +
{{ - selectedPage === 0 + (currentPage$ | async) === 0 ? ('editor.record.form.bottomButtons.comeBackLater' | translate) : ('editor.record.form.bottomButtons.previous' | translate) }} 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 8586f05362..16d2ecc7d3 100644 --- a/apps/metadata-editor/src/app/edit/edit-page.component.ts +++ b/apps/metadata-editor/src/app/edit/edit-page.component.ts @@ -3,7 +3,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' import { EditorFacade, - EditorFieldPage, RecordFormComponent, } from '@geonetwork-ui/feature/editor' import { ButtonComponent } from '@geonetwork-ui/ui/inputs' @@ -15,9 +14,10 @@ import { NotificationsService, } from '@geonetwork-ui/feature/notifications' import { TranslateModule, TranslateService } from '@ngx-translate/core' -import { filter, Subscription, take } from 'rxjs' +import { filter, firstValueFrom, Subscription, take } from 'rxjs' import { PageSelectorComponent } from './components/breadcrumbs/page-selector.component' import { marker } from '@biesbjerg/ngx-translate-extract-marker' +import { map } from 'rxjs/operators' marker('editor.record.form.bottomButtons.comeBackLater') marker('editor.record.form.bottomButtons.previous') @@ -43,9 +43,11 @@ marker('editor.record.form.bottomButtons.next') export class EditPageComponent implements OnInit, OnDestroy { subscription = new Subscription() - fields$ = this.facade.recordFields$ - totalPages = 0 - selectedPage = 0 + fields$ = this.facade.currentSections$ + currentPage$ = this.facade.currentPage$ + pagesLength$ = this.facade.editorConfig$.pipe( + map((config) => config.pages.length) + ) constructor( private route: ActivatedRoute, @@ -53,13 +55,7 @@ export class EditPageComponent implements OnInit, OnDestroy { 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] = @@ -129,24 +125,20 @@ export class EditPageComponent implements OnInit, OnDestroy { this.subscription.unsubscribe() } - getSelectedPageFields(pages: EditorFieldPage[]) { - return pages[this.selectedPage] - } - - previousPageButtonHandler() { - if (this.selectedPage === 0) { + async previousPageButtonHandler() { + const currentPage = await firstValueFrom(this.currentPage$) + if (currentPage === 0) { this.router.navigate(['catalog', 'search']) } else { - this.selectedPage-- + this.facade.setCurrentPage(currentPage - 1) // TODO } } - nextPageButtonHandler() { - if (this.selectedPage === this.totalPages - 1) return - this.selectedPage++ - } - - selectedPageChange(index: number) { - this.selectedPage = index + async nextPageButtonHandler() { + const currentPage = await firstValueFrom(this.currentPage$) + const pagesCount = await firstValueFrom(this.pagesLength$) + if (currentPage < pagesCount - 1) { + this.facade.setCurrentPage(currentPage + 1) + } } } diff --git a/libs/feature/editor/src/lib/+state/editor.actions.ts b/libs/feature/editor/src/lib/+state/editor.actions.ts index f2c6b3f93d..47152c22e6 100644 --- a/libs/feature/editor/src/lib/+state/editor.actions.ts +++ b/libs/feature/editor/src/lib/+state/editor.actions.ts @@ -28,3 +28,8 @@ export const saveRecordFailure = createAction( ) export const draftSaveSuccess = createAction('[Editor] Draft save success') + +export const setCurrentPage = createAction( + '[Editor] Set current page', + props<{ page: number }>() +) diff --git a/libs/feature/editor/src/lib/+state/editor.effects.ts b/libs/feature/editor/src/lib/+state/editor.effects.ts index 7c28a2890f..4b07925dac 100644 --- a/libs/feature/editor/src/lib/+state/editor.effects.ts +++ b/libs/feature/editor/src/lib/+state/editor.effects.ts @@ -6,9 +6,9 @@ import * as EditorActions from './editor.actions' import { EditorService } from '../services/editor.service' import { Store } from '@ngrx/store' import { + selectEditorConfig, selectRecord, selectRecordAlreadySavedOnce, - selectRecordFieldsConfig, } from './editor.selectors' import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface' @@ -24,7 +24,7 @@ export class EditorEffects { ofType(EditorActions.saveRecord), withLatestFrom( this.store.select(selectRecord), - this.store.select(selectRecordFieldsConfig), + this.store.select(selectEditorConfig), this.store.select(selectRecordAlreadySavedOnce) ), switchMap(([, record, fieldsConfig, alreadySavedOnce]) => diff --git a/libs/feature/editor/src/lib/+state/editor.facade.ts b/libs/feature/editor/src/lib/+state/editor.facade.ts index d6d0992df9..861cb03f1f 100644 --- a/libs/feature/editor/src/lib/+state/editor.facade.ts +++ b/libs/feature/editor/src/lib/+state/editor.facade.ts @@ -25,8 +25,12 @@ export class EditorFacade { changedSinceSave$ = this.store.pipe( select(EditorSelectors.selectRecordChangedSinceSave) ) - recordFields$ = this.store.pipe(select(EditorSelectors.selectRecordFields)) + currentSections$ = this.store.pipe( + select(EditorSelectors.selectRecordSections) + ) draftSaveSuccess$ = this.actions$.pipe(ofType(EditorActions.draftSaveSuccess)) + currentPage$ = this.store.pipe(select(EditorSelectors.selectCurrentPage)) + editorConfig$ = this.store.pipe(select(EditorSelectors.selectEditorConfig)) openRecord( record: CatalogRecord, @@ -45,4 +49,8 @@ export class EditorFacade { updateRecordField(field: string, value: unknown) { this.store.dispatch(EditorActions.updateRecordField({ field, value })) } + + setCurrentPage(page: number) { + this.store.dispatch(EditorActions.setCurrentPage({ page })) + } } diff --git a/libs/feature/editor/src/lib/+state/editor.models.ts b/libs/feature/editor/src/lib/+state/editor.models.ts index 903988428c..e59136767b 100644 --- a/libs/feature/editor/src/lib/+state/editor.models.ts +++ b/libs/feature/editor/src/lib/+state/editor.models.ts @@ -1 +1,12 @@ +import { EditorField, EditorFieldValue, EditorSection } from '../models' + export type SaveRecordError = string + +export interface EditorFieldWithValue { + config: EditorField + value: EditorFieldValue +} + +export type EditorSectionWithValues = EditorSection & { + fieldsWithValues: EditorFieldWithValue[] +} diff --git a/libs/feature/editor/src/lib/+state/editor.reducer.ts b/libs/feature/editor/src/lib/+state/editor.reducer.ts index 506f3436fb..0006741a46 100644 --- a/libs/feature/editor/src/lib/+state/editor.reducer.ts +++ b/libs/feature/editor/src/lib/+state/editor.reducer.ts @@ -13,7 +13,7 @@ export const EDITOR_FEATURE_KEY = 'editor' * @property saving * @property saveError * @property changedSinceSave - * @property fieldsConfig Configuration for the fields in the editor + * @property editorConfig Configuration for the fields in the editor */ export interface EditorState { record: CatalogRecord | null @@ -22,7 +22,8 @@ export interface EditorState { saving: boolean saveError: SaveRecordError | null changedSinceSave: boolean - fieldsConfig: EditorConfig + editorConfig: EditorConfig + currentPage: number } export interface EditorPartialState { @@ -36,7 +37,8 @@ export const initialEditorState: EditorState = { saving: false, saveError: null, changedSinceSave: false, - fieldsConfig: DEFAULT_FIELDS, + editorConfig: DEFAULT_FIELDS, + currentPage: 0, } const reducer = createReducer( @@ -77,6 +79,10 @@ const reducer = createReducer( on(EditorActions.markRecordAsChanged, (state) => ({ ...state, changedSinceSave: true, + })), + on(EditorActions.setCurrentPage, (state, { page }) => ({ + ...state, + currentPage: page, })) ) diff --git a/libs/feature/editor/src/lib/+state/editor.selectors.spec.ts b/libs/feature/editor/src/lib/+state/editor.selectors.spec.ts index e3ee07bfd9..1b94e59ca0 100644 --- a/libs/feature/editor/src/lib/+state/editor.selectors.spec.ts +++ b/libs/feature/editor/src/lib/+state/editor.selectors.spec.ts @@ -51,13 +51,13 @@ describe('Editor Selectors', () => { }) it('selectRecordFieldsConfig() should return the current "fieldsConfig" state', () => { - const result = EditorSelectors.selectRecordFieldsConfig(state) + const result = EditorSelectors.selectEditorConfig(state) expect(result).toEqual(DEFAULT_FIELDS) }) describe('selectRecordFields', () => { it('should return the config and value for each field', () => { - const result = EditorSelectors.selectRecordFields(state) + const result = EditorSelectors.selectRecordSections(state) const actualSections = result.pages.map((page) => page.sections).flat() @@ -79,7 +79,7 @@ describe('Editor Selectors', () => { }) it('should not coerce falsy values to null', () => { - const result = EditorSelectors.selectRecordFields({ + const result = EditorSelectors.selectRecordSections({ ...state, editor: { ...state.editor, diff --git a/libs/feature/editor/src/lib/+state/editor.selectors.ts b/libs/feature/editor/src/lib/+state/editor.selectors.ts index 3661ace5f1..886daa909a 100644 --- a/libs/feature/editor/src/lib/+state/editor.selectors.ts +++ b/libs/feature/editor/src/lib/+state/editor.selectors.ts @@ -1,5 +1,6 @@ import { createFeatureSelector, createSelector } from '@ngrx/store' import { EDITOR_FEATURE_KEY, EditorState } from './editor.reducer' +import { EditorSectionWithValues } from './editor.models' export const selectEditorState = createFeatureSelector(EDITOR_FEATURE_KEY) @@ -34,25 +35,29 @@ export const selectRecordAlreadySavedOnce = createSelector( (state: EditorState) => state.alreadySavedOnce ) -export const selectRecordFieldsConfig = createSelector( +export const selectEditorConfig = createSelector( selectEditorState, - (state: EditorState) => state.fieldsConfig + (state: EditorState) => state.editorConfig ) -export const selectRecordFields = createSelector( +export const selectCurrentPage = createSelector( + selectEditorState, + (state: EditorState) => state.currentPage +) + +export const selectRecordSections = createSelector( selectEditorState, (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 + const currentPage = state.editorConfig.pages[state.currentPage] + if (!currentPage) { + return [] as EditorSectionWithValues[] + } + return currentPage.sections.map((section) => ({ + ...section, + fieldsWithValues: section.fields.map((fieldConfig) => ({ + config: fieldConfig, + value: state.record?.[fieldConfig.model] ?? null, + })), + })) as EditorSectionWithValues[] } ) 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 ed64b72b3a..277b79d049 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,7 +1,10 @@ - +
@@ -25,14 +28,19 @@
- + 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 06863d04b7..54d4e59b87 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,14 +1,13 @@ import { CommonModule } from '@angular/common' -import { ChangeDetectionStrategy, Component, Input } from '@angular/core' +import { ChangeDetectionStrategy, Component } from '@angular/core' import { EditorFacade } from '../../+state/editor.facade' -import { - EditorField, - EditorFieldPage, - EditorFieldValue, - EditorSection, -} from '../../models' +import { EditorFieldValue } from '../../models' import { FormFieldComponent } from './form-field' import { TranslateModule } from '@ngx-translate/core' +import { + EditorFieldWithValue, + EditorSectionWithValues, +} from '../../+state/editor.models' @Component({ selector: 'gn-ui-record-form', @@ -19,8 +18,6 @@ import { TranslateModule } from '@ngx-translate/core' imports: [CommonModule, FormFieldComponent, TranslateModule], }) export class RecordFormComponent { - @Input() page: EditorFieldPage - constructor(public facade: EditorFacade) {} handleFieldValueChange(model: string, newValue: EditorFieldValue) { @@ -30,11 +27,11 @@ export class RecordFormComponent { this.facade.updateRecordField(model, newValue) } - fieldTracker(index: number, field: EditorField): any { - return field.model + fieldTracker(index: number, field: EditorFieldWithValue): any { + return field.config.model } - sectionTracker(index: number, section: EditorSection): any { + sectionTracker(index: number, section: EditorSectionWithValues): any { return section.labelKey } } diff --git a/libs/feature/editor/src/lib/models/editor-config.model.ts b/libs/feature/editor/src/lib/models/editor-config.model.ts index 8530049354..1380ebe9d0 100644 --- a/libs/feature/editor/src/lib/models/editor-config.model.ts +++ b/libs/feature/editor/src/lib/models/editor-config.model.ts @@ -30,7 +30,7 @@ export interface EditorField { // the result of this expression will replace the field value on save onSaveProcess?: EditorFieldExpression - value?: EditorFieldValue + // value?: EditorFieldValue } export interface EditorSection { From 7ae1be76d460ea92159665558b81553dbd337e1c Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Tue, 23 Jul 2024 09:47:58 +0200 Subject: [PATCH 6/7] code review fix --- .../page-selector.component.css | 0 .../page-selector.component.html | 2 +- .../page-selector.component.spec.ts | 20 +++++-- .../page-selector.component.ts | 2 +- .../src/app/edit/edit-page.component.spec.ts | 10 ++-- .../src/app/edit/edit-page.component.ts | 4 +- apps/metadata-editor/tsconfig.json | 3 +- assets-common/css/default-fonts.css | 8 --- assets-common/fonts/Petrona-Regular.woff2 | Bin 21296 -> 0 bytes libs/common/fixtures/src/index.ts | 2 + .../src/lib/editor}/editor.fixtures.ts | 34 ++++++------ .../fixtures/src/lib/editor}/index.ts | 0 libs/feature/editor/src/index.ts | 1 - .../src/lib/+state/editor.effects.spec.ts | 3 +- .../src/lib/+state/editor.selectors.spec.ts | 50 +++++++++++------- libs/feature/editor/tsconfig.json | 3 +- libs/feature/editor/tsconfig.lib.json | 3 +- tailwind.base.config.js | 1 - 18 files changed, 77 insertions(+), 69 deletions(-) rename apps/metadata-editor/src/app/edit/components/{breadcrumbs => page-selector}/page-selector.component.css (100%) rename apps/metadata-editor/src/app/edit/components/{breadcrumbs => page-selector}/page-selector.component.html (97%) rename apps/metadata-editor/src/app/edit/components/{breadcrumbs => page-selector}/page-selector.component.spec.ts (59%) rename apps/metadata-editor/src/app/edit/components/{breadcrumbs => page-selector}/page-selector.component.ts (94%) delete mode 100644 assets-common/fonts/Petrona-Regular.woff2 rename libs/{feature/editor/src/lib/fixtures => common/fixtures/src/lib/editor}/editor.fixtures.ts (72%) rename libs/{feature/editor/src/lib/fixtures => common/fixtures/src/lib/editor}/index.ts (100%) diff --git a/apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.css b/apps/metadata-editor/src/app/edit/components/page-selector/page-selector.component.css similarity index 100% rename from apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.css rename to apps/metadata-editor/src/app/edit/components/page-selector/page-selector.component.css diff --git a/apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.html b/apps/metadata-editor/src/app/edit/components/page-selector/page-selector.component.html similarity index 97% rename from apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.html rename to apps/metadata-editor/src/app/edit/components/page-selector/page-selector.component.html index 3085adc2f5..279daf25d9 100644 --- a/apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.html +++ b/apps/metadata-editor/src/app/edit/components/page-selector/page-selector.component.html @@ -20,7 +20,7 @@ : 'bg-gray-200' " > - {{ index }} + {{ index + 1 }}
{ +class EditorFacadeMock { + editorConfig$ = new BehaviorSubject(EDITOR_CONFIG()) + setCurrentPage = jest.fn() +} + +describe('PageSelectorComponent', () => { let component: PageSelectorComponent let fixture: ComponentFixture beforeEach(async () => { await TestBed.configureTestingModule({ imports: [TranslateModule.forRoot()], - providers: [], + providers: [ + { + provide: EditorFacade, + useClass: EditorFacadeMock, + }, + ], }).compileComponents() fixture = TestBed.createComponent(PageSelectorComponent) component = fixture.componentInstance - component.pages = EDITOR_CONFIG().pages - component.selectedPage = 0 fixture.detectChanges() }) diff --git a/apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.ts b/apps/metadata-editor/src/app/edit/components/page-selector/page-selector.component.ts similarity index 94% rename from apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.ts rename to apps/metadata-editor/src/app/edit/components/page-selector/page-selector.component.ts index 0cb4d03769..0170944011 100644 --- a/apps/metadata-editor/src/app/edit/components/breadcrumbs/page-selector.component.ts +++ b/apps/metadata-editor/src/app/edit/components/page-selector/page-selector.component.ts @@ -19,6 +19,6 @@ export class PageSelectorComponent { constructor(public facade: EditorFacade) {} pageSectionClickHandler(index: number) { - this.facade.setCurrentPage(index) // TODO + this.facade.setCurrentPage(index) } } diff --git a/apps/metadata-editor/src/app/edit/edit-page.component.spec.ts b/apps/metadata-editor/src/app/edit/edit-page.component.spec.ts index 36fc290d2d..4889c0822c 100644 --- a/apps/metadata-editor/src/app/edit/edit-page.component.spec.ts +++ b/apps/metadata-editor/src/app/edit/edit-page.component.spec.ts @@ -1,14 +1,13 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' import { EditPageComponent } from './edit-page.component' import { ActivatedRoute, Router } from '@angular/router' -import { EDITOR_CONFIG, EditorFacade } from '@geonetwork-ui/feature/editor' import { NO_ERRORS_SCHEMA } from '@angular/core' -import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures' +import { DATASET_RECORDS, EDITOR_CONFIG } from '@geonetwork-ui/common/fixtures' import { BehaviorSubject, Subject } from 'rxjs' import { NotificationsService } from '@geonetwork-ui/feature/notifications' import { TranslateModule } from '@ngx-translate/core' -import { FindPipe } from '../pipes/filter.pipe' -import { PageSelectorComponent } from './components/breadcrumbs/page-selector.component' +import { PageSelectorComponent } from './components/page-selector/page-selector.component' +import { EditorFacade } from '@geonetwork-ui/feature/editor' const getRoute = () => ({ snapshot: { @@ -27,11 +26,11 @@ class RouterMock { class EditorFacadeMock { record$ = new BehaviorSubject(DATASET_RECORDS[0]) - recordFields$ = new BehaviorSubject(EDITOR_CONFIG()) openRecord = jest.fn() saveError$ = new Subject() saveSuccess$ = new Subject() draftSaveSuccess$ = new Subject() + editorConfig$ = new BehaviorSubject(EDITOR_CONFIG()) } class NotificationsServiceMock { showNotification = jest.fn() @@ -48,7 +47,6 @@ describe('EditPageComponent', () => { imports: [ EditPageComponent, TranslateModule.forRoot(), - FindPipe, PageSelectorComponent, ], schemas: [NO_ERRORS_SCHEMA], 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 16d2ecc7d3..5c7a54a539 100644 --- a/apps/metadata-editor/src/app/edit/edit-page.component.ts +++ b/apps/metadata-editor/src/app/edit/edit-page.component.ts @@ -15,7 +15,7 @@ import { } from '@geonetwork-ui/feature/notifications' import { TranslateModule, TranslateService } from '@ngx-translate/core' import { filter, firstValueFrom, Subscription, take } from 'rxjs' -import { PageSelectorComponent } from './components/breadcrumbs/page-selector.component' +import { PageSelectorComponent } from './components/page-selector/page-selector.component' import { marker } from '@biesbjerg/ngx-translate-extract-marker' import { map } from 'rxjs/operators' @@ -130,7 +130,7 @@ export class EditPageComponent implements OnInit, OnDestroy { if (currentPage === 0) { this.router.navigate(['catalog', 'search']) } else { - this.facade.setCurrentPage(currentPage - 1) // TODO + this.facade.setCurrentPage(currentPage - 1) } } diff --git a/apps/metadata-editor/tsconfig.json b/apps/metadata-editor/tsconfig.json index 54f2b40f0e..099e2430b3 100644 --- a/apps/metadata-editor/tsconfig.json +++ b/apps/metadata-editor/tsconfig.json @@ -19,8 +19,7 @@ "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "target": "es2020", - "lib": ["dom", "es2020", "dom.iterable"], - "downlevelIteration": true + "lib": ["dom", "es2020", "dom.iterable"] }, "angularCompilerOptions": { "strictInjectionParameters": true, diff --git a/assets-common/css/default-fonts.css b/assets-common/css/default-fonts.css index 34941cbd8c..fc8d76c604 100644 --- a/assets-common/css/default-fonts.css +++ b/assets-common/css/default-fonts.css @@ -1,11 +1,3 @@ -/* Petrona */ -@font-face { - font-family: 'Petrona'; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url(../fonts/Petrona-Regular.woff2) format('woff2'); -} /* arabic */ @font-face { font-family: 'Readex Pro'; diff --git a/assets-common/fonts/Petrona-Regular.woff2 b/assets-common/fonts/Petrona-Regular.woff2 deleted file mode 100644 index bac09590b6045853dd96334768991328183a94b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21296 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 NtxT>8=|*}0005{qHc ({ +export const EDITOR_CONFIG = () => ({ pages: [ { labelKey: 'Resource description', @@ -20,7 +18,7 @@ export const EDITOR_CONFIG = (): EditorConfig => ({ ], }) -export const EDITOR_SECTION_ABOUT = (): EditorSection => ({ +export const EDITOR_SECTION_ABOUT = () => ({ labelKey: 'About the resource', descriptionKey: 'This section describes the resource.', hidden: false, @@ -34,28 +32,28 @@ export const EDITOR_SECTION_ABOUT = (): EditorSection => ({ ], }) -export const EDITOR_SECTION_DATA_MANAGER = (): EditorSection => ({ +export const EDITOR_SECTION_DATA_MANAGER = () => ({ labelKey: 'Data manager', descriptionKey: '', hidden: false, fields: [], }) -export const EDITOR_SECTION_USE_AND_ACCESS_CONDITIONS = (): EditorSection => ({ +export const EDITOR_SECTION_USE_AND_ACCESS_CONDITIONS = () => ({ labelKey: 'Data manager', descriptionKey: '', hidden: false, fields: [EDITOR_FIELD_LICENSE()], }) -export const EDITOR_SECTION_CLASSIFICATION = (): EditorSection => ({ +export const EDITOR_SECTION_CLASSIFICATION = () => ({ labelKey: 'Classification', descriptionKey: 'The classification has an impact on the access to the data.', hidden: false, fields: [EDITOR_FIELD_KEYWORDS(), EDITOR_FIELD_UNIQUE_IDENTIFIER()], }) -export const EDITOR_FIELD_TITLE = (): EditorField => ({ +export const EDITOR_FIELD_TITLE = () => ({ model: 'title', hidden: false, value: 'Accroches vélos MEL', @@ -64,7 +62,7 @@ export const EDITOR_FIELD_TITLE = (): EditorField => ({ }, }) -export const EDITOR_FIELD_ABSTRACT = (): EditorField => ({ +export const EDITOR_FIELD_ABSTRACT = () => ({ model: 'abstract', hidden: false, value: 'Abstract', @@ -73,7 +71,7 @@ export const EDITOR_FIELD_ABSTRACT = (): EditorField => ({ }, }) -export const EDITOR_FIELD_RESOURCE_UPDATED = (): EditorField => ({ +export const EDITOR_FIELD_RESOURCE_UPDATED = () => ({ model: 'resourceUpdated', hidden: false, formFieldConfig: { @@ -81,7 +79,7 @@ export const EDITOR_FIELD_RESOURCE_UPDATED = (): EditorField => ({ }, }) -export const EDITOR_FIELD_RECORD_UPDATED = (): EditorField => ({ +export const EDITOR_FIELD_RECORD_UPDATED = () => ({ model: 'recordUpdated', hidden: false, formFieldConfig: { @@ -92,7 +90,7 @@ export const EDITOR_FIELD_RECORD_UPDATED = (): EditorField => ({ onSaveProcess: '${dateNow()}', }) -export const EDITOR_FIELD_UPDATE_FREQUENCY = (): EditorField => ({ +export const EDITOR_FIELD_UPDATE_FREQUENCY = () => ({ model: 'updateFrequency', hidden: false, formFieldConfig: { @@ -101,7 +99,7 @@ export const EDITOR_FIELD_UPDATE_FREQUENCY = (): EditorField => ({ value: 'unknown', }) -export const EDITOR_FIELD_TEMPORAL_EXTENTS = (): EditorField => ({ +export const EDITOR_FIELD_TEMPORAL_EXTENTS = () => ({ model: 'temporalExtents', hidden: false, formFieldConfig: { @@ -110,7 +108,7 @@ export const EDITOR_FIELD_TEMPORAL_EXTENTS = (): EditorField => ({ value: [], }) -export const EDITOR_FIELD_SPATIAL_EXTENTS = (): EditorField => ({ +export const EDITOR_FIELD_SPATIAL_EXTENTS = () => ({ model: 'spatialExtents', hidden: false, formFieldConfig: { @@ -118,7 +116,7 @@ export const EDITOR_FIELD_SPATIAL_EXTENTS = (): EditorField => ({ }, }) -export const EDITOR_FIELD_KEYWORDS = (): EditorField => ({ +export const EDITOR_FIELD_KEYWORDS = () => ({ model: 'keywords', hidden: false, formFieldConfig: { @@ -126,7 +124,7 @@ export const EDITOR_FIELD_KEYWORDS = (): EditorField => ({ }, }) -export const EDITOR_FIELD_UNIQUE_IDENTIFIER = (): EditorField => ({ +export const EDITOR_FIELD_UNIQUE_IDENTIFIER = () => ({ model: 'uniqueIdentifier', hidden: false, formFieldConfig: { @@ -136,7 +134,7 @@ export const EDITOR_FIELD_UNIQUE_IDENTIFIER = (): EditorField => ({ value: 'accroche_velos', }) -export const EDITOR_FIELD_LICENSE = (): EditorField => ({ +export const EDITOR_FIELD_LICENSE = () => ({ model: 'licenses', hidden: false, formFieldConfig: { @@ -145,7 +143,7 @@ export const EDITOR_FIELD_LICENSE = (): EditorField => ({ }, }) -export const EDITOR_FIELDS = (): EditorField[] => [ +export const EDITOR_FIELDS = () => [ EDITOR_FIELD_TITLE(), EDITOR_FIELD_ABSTRACT(), EDITOR_FIELD_RESOURCE_UPDATED(), diff --git a/libs/feature/editor/src/lib/fixtures/index.ts b/libs/common/fixtures/src/lib/editor/index.ts similarity index 100% rename from libs/feature/editor/src/lib/fixtures/index.ts rename to libs/common/fixtures/src/lib/editor/index.ts diff --git a/libs/feature/editor/src/index.ts b/libs/feature/editor/src/index.ts index c95afdefd4..5e6dd85211 100644 --- a/libs/feature/editor/src/index.ts +++ b/libs/feature/editor/src/index.ts @@ -9,4 +9,3 @@ export * from './lib/components/record-form/record-form.component' export * from './lib/components/wizard/wizard.component' export * from './lib/components/wizard-field/wizard-field.component' export * from './lib/components/wizard-summarize/wizard-summarize.component' -export * from './lib/fixtures' diff --git a/libs/feature/editor/src/lib/+state/editor.effects.spec.ts b/libs/feature/editor/src/lib/+state/editor.effects.spec.ts index 7ec73663fe..49bdb73887 100644 --- a/libs/feature/editor/src/lib/+state/editor.effects.spec.ts +++ b/libs/feature/editor/src/lib/+state/editor.effects.spec.ts @@ -26,7 +26,8 @@ const initialEditorState = { saveError: null, changedSinceSave: false, alreadySavedOnce: true, - fieldsConfig: [], + editorConfig: [], + currentPage: 0, } describe('EditorEffects', () => { diff --git a/libs/feature/editor/src/lib/+state/editor.selectors.spec.ts b/libs/feature/editor/src/lib/+state/editor.selectors.spec.ts index 1b94e59ca0..58053095e6 100644 --- a/libs/feature/editor/src/lib/+state/editor.selectors.spec.ts +++ b/libs/feature/editor/src/lib/+state/editor.selectors.spec.ts @@ -1,7 +1,12 @@ -import { EditorPartialState, initialEditorState } from './editor.reducer' +import { + EDITOR_FEATURE_KEY, + EditorPartialState, + initialEditorState, +} from './editor.reducer' import * as EditorSelectors from './editor.selectors' import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures' import { DEFAULT_FIELDS } from '../fields.config' +import { EditorSectionWithValues } from './editor.models' describe('Editor Selectors', () => { let state: EditorPartialState @@ -56,22 +61,27 @@ describe('Editor Selectors', () => { }) describe('selectRecordFields', () => { - it('should return the config and value for each field', () => { - const result = EditorSelectors.selectRecordSections(state) - - const actualSections = result.pages.map((page) => page.sections).flat() - - const expectedSections = DEFAULT_FIELDS.pages - .map((page) => page.sections) - .flat() - - expect(actualSections).toEqual(expectedSections) - - const actualFields = actualSections + it('should return the config and value for specified page', () => { + const recordSections = EditorSelectors.selectRecordSections(state) + + const expectedResult = DEFAULT_FIELDS.pages[0].sections.map( + (section) => ({ + ...section, + fieldsWithValues: section.fields.map((fieldConfig) => ({ + config: fieldConfig, + value: + state[EDITOR_FEATURE_KEY].record?.[fieldConfig.model] ?? null, + })), + }) + ) as EditorSectionWithValues[] + + expect(recordSections).toEqual(expectedResult) + + const actualFields = recordSections .map((section) => section.fields) .flat() - const expectedFields = expectedSections + const expectedFields = expectedResult .map((section) => section.fields) .flat() @@ -91,15 +101,17 @@ describe('Editor Selectors', () => { }, }) - const resultFields = result.pages - .flatMap((page) => page.sections) - .flatMap((section) => section.fields) + const resultFields = result.flatMap( + (section) => section.fieldsWithValues + ) const abstractField = resultFields.find( - (field) => field.model === 'abstract' + (field) => field.config.model === 'abstract' ) - const titleField = resultFields.find((field) => field.model === 'title') + const titleField = resultFields.find( + (field) => field.config.model === 'title' + ) expect(abstractField.value).toEqual('') expect(titleField.value).toEqual('') diff --git a/libs/feature/editor/tsconfig.json b/libs/feature/editor/tsconfig.json index 9e29358f76..5879bfb75c 100644 --- a/libs/feature/editor/tsconfig.json +++ b/libs/feature/editor/tsconfig.json @@ -16,8 +16,7 @@ "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "target": "es2020", - "lib": ["dom", "es2020", "dom.iterable"], - "downlevelIteration": true + "lib": ["dom", "es2020", "dom.iterable"] }, "angularCompilerOptions": { "strictInjectionParameters": true, diff --git a/libs/feature/editor/tsconfig.lib.json b/libs/feature/editor/tsconfig.lib.json index 2a4c9a5e36..02d129fa3b 100644 --- a/libs/feature/editor/tsconfig.lib.json +++ b/libs/feature/editor/tsconfig.lib.json @@ -7,8 +7,7 @@ "declarationMap": true, "inlineSources": true, "types": [], - "lib": ["dom", "es2020", "dom.iterable"], - "downlevelIteration": true + "lib": ["dom", "es2020", "dom.iterable"] }, "exclude": [ "src/test-setup.ts", diff --git a/tailwind.base.config.js b/tailwind.base.config.js index 6464667647..faea732f64 100644 --- a/tailwind.base.config.js +++ b/tailwind.base.config.js @@ -51,7 +51,6 @@ module.exports = { title: 'var(--font-family-title, ui-serif, Georgia, Cambria, "Times New Roman", Times, serif)', // alias for serif mono: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace', - petrona: ['Petrona', 'serif'], }, fontSize: { 13: '13px', From f2ae2cfea2f2129208c93ceb333639a089cfaabc Mon Sep 17 00:00:00 2001 From: Romuald Caplier Date: Tue, 23 Jul 2024 10:52:22 +0200 Subject: [PATCH 7/7] added test for page selector component --- .../page-selector.component.spec.ts | 33 ++++++++++- .../src/app/edit/edit-page.component.html | 58 +++++++++---------- .../src/app/edit/edit-page.component.ts | 1 - .../editor/src/lib/+state/editor.reducer.ts | 4 +- .../src/lib/+state/editor.selectors.spec.ts | 6 +- .../form-field/form-field.component.html | 2 +- libs/feature/editor/src/lib/fields.config.ts | 2 +- .../src/lib/models/editor-config.model.ts | 2 - .../src/lib/services/editor.service.spec.ts | 8 ++- 9 files changed, 72 insertions(+), 44 deletions(-) diff --git a/apps/metadata-editor/src/app/edit/components/page-selector/page-selector.component.spec.ts b/apps/metadata-editor/src/app/edit/components/page-selector/page-selector.component.spec.ts index 5b18f3550a..14f024b9bc 100644 --- a/apps/metadata-editor/src/app/edit/components/page-selector/page-selector.component.spec.ts +++ b/apps/metadata-editor/src/app/edit/components/page-selector/page-selector.component.spec.ts @@ -4,19 +4,22 @@ import { PageSelectorComponent } from './page-selector.component' import { EDITOR_CONFIG } from '@geonetwork-ui/common/fixtures' import { BehaviorSubject } from 'rxjs' import { EditorFacade } from '@geonetwork-ui/feature/editor' +import { By } from '@angular/platform-browser' class EditorFacadeMock { editorConfig$ = new BehaviorSubject(EDITOR_CONFIG()) + currentPage$ = new BehaviorSubject(0) setCurrentPage = jest.fn() } describe('PageSelectorComponent', () => { let component: PageSelectorComponent let fixture: ComponentFixture + let facade: EditorFacadeMock beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot()], + imports: [TranslateModule.forRoot(), PageSelectorComponent], providers: [ { provide: EditorFacade, @@ -27,10 +30,38 @@ describe('PageSelectorComponent', () => { fixture = TestBed.createComponent(PageSelectorComponent) component = fixture.componentInstance + facade = TestBed.inject(EditorFacade) as unknown as EditorFacadeMock fixture.detectChanges() }) it('should create', () => { expect(component).toBeTruthy() }) + + it('should render the correct number of pages', () => { + const pages = fixture.debugElement.queryAll(By.css('.w-10.h-10')) + expect(pages.length).toBe(EDITOR_CONFIG().pages.length) + }) + + it('should highlight the current page', () => { + const currentPageIndex = facade.currentPage$.getValue() + const currentPageElement = fixture.debugElement.queryAll( + By.css('.w-10.h-10') + )[currentPageIndex] + expect(currentPageElement.nativeElement.classList).toContain('bg-primary') + }) + + it('should call pageSectionClickHandler with the correct index', () => { + jest.spyOn(component, 'pageSectionClickHandler') + const button = fixture.debugElement.queryAll(By.css('gn-ui-button'))[1] + button.triggerEventHandler('buttonClick', null) + fixture.detectChanges() + expect(component.pageSectionClickHandler).toHaveBeenCalledWith(1) + }) + + it('should call facade.setCurrentPage with the correct index', () => { + const index = 1 + component.pageSectionClickHandler(index) + expect(facade.setCurrentPage).toHaveBeenCalledWith(index) + }) }) 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 a71413574e..5400a6648f 100644 --- a/apps/metadata-editor/src/app/edit/edit-page.component.html +++ b/apps/metadata-editor/src/app/edit/edit-page.component.html @@ -1,32 +1,30 @@ - -
-
- - -
-
-
- -
- -
-
- - {{ - (currentPage$ | async) === 0 - ? ('editor.record.form.bottomButtons.comeBackLater' | translate) - : ('editor.record.form.bottomButtons.previous' | translate) - }} - - editor.record.form.bottomButtons.next +
+
+ + +
+
+
+
+ +
+
+ + {{ + (currentPage$ | async) === 0 + ? ('editor.record.form.bottomButtons.comeBackLater' | translate) + : ('editor.record.form.bottomButtons.previous' | translate) + }} + + editor.record.form.bottomButtons.next
- +
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 5c7a54a539..13813bb548 100644 --- a/apps/metadata-editor/src/app/edit/edit-page.component.ts +++ b/apps/metadata-editor/src/app/edit/edit-page.component.ts @@ -43,7 +43,6 @@ marker('editor.record.form.bottomButtons.next') export class EditPageComponent implements OnInit, OnDestroy { subscription = new Subscription() - fields$ = this.facade.currentSections$ currentPage$ = this.facade.currentPage$ pagesLength$ = this.facade.editorConfig$.pipe( map((config) => config.pages.length) diff --git a/libs/feature/editor/src/lib/+state/editor.reducer.ts b/libs/feature/editor/src/lib/+state/editor.reducer.ts index 0006741a46..d2b4e96a5c 100644 --- a/libs/feature/editor/src/lib/+state/editor.reducer.ts +++ b/libs/feature/editor/src/lib/+state/editor.reducer.ts @@ -3,7 +3,7 @@ import * as EditorActions from './editor.actions' import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record' import { SaveRecordError } from './editor.models' import { EditorConfig } from '../models' -import { DEFAULT_FIELDS } from '../fields.config' +import { DEFAULT_CONFIGURATION } from '../fields.config' export const EDITOR_FEATURE_KEY = 'editor' @@ -37,7 +37,7 @@ export const initialEditorState: EditorState = { saving: false, saveError: null, changedSinceSave: false, - editorConfig: DEFAULT_FIELDS, + editorConfig: DEFAULT_CONFIGURATION, currentPage: 0, } diff --git a/libs/feature/editor/src/lib/+state/editor.selectors.spec.ts b/libs/feature/editor/src/lib/+state/editor.selectors.spec.ts index 58053095e6..6241cc02f4 100644 --- a/libs/feature/editor/src/lib/+state/editor.selectors.spec.ts +++ b/libs/feature/editor/src/lib/+state/editor.selectors.spec.ts @@ -5,7 +5,7 @@ import { } from './editor.reducer' import * as EditorSelectors from './editor.selectors' import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures' -import { DEFAULT_FIELDS } from '../fields.config' +import { DEFAULT_CONFIGURATION } from '../fields.config' import { EditorSectionWithValues } from './editor.models' describe('Editor Selectors', () => { @@ -57,14 +57,14 @@ describe('Editor Selectors', () => { it('selectRecordFieldsConfig() should return the current "fieldsConfig" state', () => { const result = EditorSelectors.selectEditorConfig(state) - expect(result).toEqual(DEFAULT_FIELDS) + expect(result).toEqual(DEFAULT_CONFIGURATION) }) describe('selectRecordFields', () => { it('should return the config and value for specified page', () => { const recordSections = EditorSelectors.selectRecordSections(state) - const expectedResult = DEFAULT_FIELDS.pages[0].sections.map( + const expectedResult = DEFAULT_CONFIGURATION.pages[0].sections.map( (section) => ({ ...section, fieldsWithValues: section.fields.map((fieldConfig) => ({ 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 ec1b774a7b..5ac61e653d 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 @@ -17,7 +17,7 @@
diff --git a/libs/feature/editor/src/lib/fields.config.ts b/libs/feature/editor/src/lib/fields.config.ts index 62a7d83bbe..8c22644b48 100644 --- a/libs/feature/editor/src/lib/fields.config.ts +++ b/libs/feature/editor/src/lib/fields.config.ts @@ -162,7 +162,7 @@ export const DATA_POINT_OF_CONTACT_SECTION: EditorSection = { *************** PAGES ***************** ************************************************************ */ -export const DEFAULT_FIELDS: EditorConfig = { +export const DEFAULT_CONFIGURATION: EditorConfig = { pages: [ { labelKey: marker('editor.record.form.page.description'), diff --git a/libs/feature/editor/src/lib/models/editor-config.model.ts b/libs/feature/editor/src/lib/models/editor-config.model.ts index 1380ebe9d0..5e420e1893 100644 --- a/libs/feature/editor/src/lib/models/editor-config.model.ts +++ b/libs/feature/editor/src/lib/models/editor-config.model.ts @@ -29,8 +29,6 @@ export interface EditorField { // the result of this expression will replace the field value on save onSaveProcess?: EditorFieldExpression - - // value?: EditorFieldValue } export interface EditorSection { diff --git a/libs/feature/editor/src/lib/services/editor.service.spec.ts b/libs/feature/editor/src/lib/services/editor.service.spec.ts index c58845c36b..425b844750 100644 --- a/libs/feature/editor/src/lib/services/editor.service.spec.ts +++ b/libs/feature/editor/src/lib/services/editor.service.spec.ts @@ -5,7 +5,7 @@ import { HttpTestingController, } from '@angular/common/http/testing' import { CatalogRecord } from '@geonetwork-ui/common/domain/model/record' -import { DEFAULT_FIELDS } from '../fields.config' +import { DEFAULT_CONFIGURATION } from '../fields.config' import { DATASET_RECORDS } from '@geonetwork-ui/common/fixtures' import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface' import { firstValueFrom, of } from 'rxjs' @@ -57,7 +57,7 @@ describe('EditorService', () => { let savedRecord: [CatalogRecord, string] beforeEach(async () => { savedRecord = await firstValueFrom( - service.saveRecord(SAMPLE_RECORD, DEFAULT_FIELDS) + service.saveRecord(SAMPLE_RECORD, DEFAULT_CONFIGURATION) ) }) it('calls repository.saveRecord and repository.clearRecordDraft', () => { @@ -77,7 +77,9 @@ describe('EditorService', () => { }) describe('if a new one has to be generated', () => { beforeEach(() => { - service.saveRecord(SAMPLE_RECORD, DEFAULT_FIELDS, true).subscribe() + service + .saveRecord(SAMPLE_RECORD, DEFAULT_CONFIGURATION, true) + .subscribe() }) it('clears the unique identifier of the record', () => { const expected = {