diff --git a/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.html b/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.html index 97d6aea100..20ff0dc890 100644 --- a/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.html +++ b/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.html @@ -1,8 +1,10 @@ diff --git a/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.spec.ts b/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.spec.ts index 25b6441795..0be3e0cebe 100644 --- a/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.spec.ts +++ b/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.spec.ts @@ -5,13 +5,27 @@ import { TranslateModule } from '@ngx-translate/core' import { of } from 'rxjs' import { OverviewUploadComponent } from './overview-upload.component' +const imageFileName = 'doge.jpg' +const imageUrl = + 'http://localhost:8080/geonetwork/srv/api/records/8698bf0b-fceb-4f0f-989b-111e7c4af0a4/attachments/doge.jpg' + class RecordsApiServiceMock { getAllResources = jest.fn(() => - of([{ filename: 'filenameGet', url: 'urlGet' }]) + of([ + { + filename: imageFileName, + url: imageUrl, + }, + ]) + ) + putResource = jest.fn(() => + of({ + filename: imageFileName, + url: imageUrl, + }) ) - putResource = jest.fn(() => of({ filename: 'filenamePut', url: 'urlPut' })) putResourceFromURL = jest.fn(() => - of({ filename: 'filenamePutUrl', url: 'urlPutUrl' }) + of({ filename: imageFileName, url: imageUrl }) ) delResource = jest.fn(() => of(void 0)) } @@ -50,9 +64,11 @@ describe('OverviewUploadComponent', () => { }) it('should get all resources corresponding to the metadata UUID on init', () => { + fixture.detectChanges() expect(recordsApiService.getAllResources).toHaveBeenCalledWith(metadataUuid) - expect(component.resourceFileName).toEqual('filenameGet') - expect(component.resourceUrl).toEqual('urlGet') + expect(component.resourceAltText).toEqual(imageFileName) + expect(component.resourceFileName).toEqual(imageFileName) + expect(component.resourceUrl.href).toEqual(imageUrl) }) it('should put the file resource on file change', () => { @@ -63,8 +79,22 @@ describe('OverviewUploadComponent', () => { someFile, 'public' ) - expect(component.resourceFileName).toEqual('filenamePut') - expect(component.resourceUrl).toEqual('urlPut') + expect(component.resourceAltText).toEqual(imageFileName) + expect(component.resourceUrl.href).toEqual(imageUrl) + }) + + it('should put the file resource on alt text change', () => { + const altTextChangeSpy = jest.spyOn(component.overviewChange, 'emit') + + const newAltText = 'newAltText' + const newImageUrl = new URL(imageUrl) + + component.handleAltTextChange(newAltText) + expect(altTextChangeSpy).toHaveBeenCalledWith({ + description: newAltText, + url: newImageUrl, + }) + expect(component.resourceAltText).toEqual('newAltText') }) it('should put the resource from URL on URL change', () => { @@ -74,18 +104,18 @@ describe('OverviewUploadComponent', () => { 'someUrl', 'public' ) - expect(component.resourceFileName).toEqual('filenamePutUrl') - expect(component.resourceUrl).toEqual('urlPutUrl') + expect(component.resourceAltText).toEqual(imageFileName) + expect(component.resourceUrl.href).toEqual(imageUrl) }) it('should delete the resource corresponding to the metadata UUID on delete', () => { - component.resourceFileName = 'filenameDelete' + component.resourceAltText = 'filenameDelete' component.handleDelete() expect(recordsApiService.delResource).toHaveBeenCalledWith( metadataUuid, - 'filenameDelete' + imageFileName ) - expect(component.resourceFileName).toBeNull() + expect(component.resourceAltText).toBeNull() expect(component.resourceUrl).toBeNull() }) }) diff --git a/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.ts b/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.ts index 4e437f3064..3816829642 100644 --- a/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.ts +++ b/libs/feature/editor/src/lib/components/overview-upload/overview-upload.component.ts @@ -2,12 +2,24 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + EventEmitter, Input, + OnChanges, OnInit, + Output, + SimpleChanges, } from '@angular/core' import { CommonModule } from '@angular/common' import { RecordsApiService } from '@geonetwork-ui/data-access/gn4' import { UiInputsModule } from '@geonetwork-ui/ui/inputs' +import { FormControl } from '@angular/forms' +import { GraphicOverview } from '@geonetwork-ui/common/domain/model/record' + +const extractFileNameFromUrl = (url: string): string => { + const pattern = new RegExp(`attachments/([^/?#]+)(?:[/?#]|$)`, 'i') + const match = url.match(pattern) + return match ? match[1] : '' +} @Component({ selector: 'gn-ui-overview-upload', @@ -17,11 +29,15 @@ import { UiInputsModule } from '@geonetwork-ui/ui/inputs' styleUrls: ['./overview-upload.component.css'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class OverviewUploadComponent implements OnInit { +export class OverviewUploadComponent implements OnInit, OnChanges { @Input() metadataUuid: string + @Input() formControl!: FormControl + @Output() overviewChange = new EventEmitter() + @Output() altTextChange: EventEmitter = new EventEmitter() - resourceFileName: string - resourceUrl: string + resourceAltText = '' // = ressourceFileName by default + resourceFileName = '' + resourceUrl: URL constructor( private recordsApiService: RecordsApiService, @@ -29,42 +45,122 @@ export class OverviewUploadComponent implements OnInit { ) {} ngOnInit(): void { - this.recordsApiService - .getAllResources(this.metadataUuid) - .subscribe((resources) => { - this.resourceFileName = resources[0]?.filename - this.resourceUrl = resources[0]?.url + this.recordsApiService.getAllResources(this.metadataUuid).subscribe({ + next: (resources) => { + if (resources && resources.length > 0) { + this.resourceUrl = new URL(resources[0]?.url) + this.resourceAltText = + this.resourceAltText === '' + ? resources[0].filename + : this.resourceAltText + this.resourceFileName = extractFileNameFromUrl(resources[0]?.url) + } else { + this.resourceUrl = null + this.resourceAltText = '' + this.resourceFileName = '' + } + this.cd.markForCheck() - }) + }, + error: this.errorHandle, + }) } handleFileChange(file: File) { this.recordsApiService .putResource(this.metadataUuid, file, 'public') - .subscribe((resource) => { - this.resourceFileName = resource.filename - this.resourceUrl = resource.url - this.cd.markForCheck() + .subscribe({ + next: (resource) => { + this.resourceUrl = new URL(resource.url) + this.resourceAltText = resource.filename + + this.overviewChange.emit({ + url: new URL(resource.url), + description: resource.filename, + }) + + this.cd.markForCheck() + }, + error: this.errorHandle, }) } handleUrlChange(url: string) { this.recordsApiService .putResourceFromURL(this.metadataUuid, url, 'public') - .subscribe((resource) => { - this.resourceFileName = resource.filename - this.resourceUrl = resource.url - this.cd.markForCheck() + .subscribe({ + next: (resource) => { + this.resourceUrl = new URL(resource.url) + this.resourceAltText = resource.filename + + this.overviewChange.emit({ + url: new URL(resource.url), + description: resource.filename, + }) + + this.cd.markForCheck() + }, + error: this.errorHandle, }) } + handleAltTextChange(newAltText: string) { + this.resourceAltText = newAltText + + this.overviewChange.emit({ + url: this.resourceUrl, + description: this.resourceAltText, + }) + this.cd.markForCheck() + } + handleDelete() { this.recordsApiService .delResource(this.metadataUuid, this.resourceFileName) - .subscribe(() => { - this.resourceFileName = null - this.resourceUrl = null - this.cd.markForCheck() + .subscribe({ + next: () => { + this.resourceAltText = null + this.resourceUrl = null + + this.overviewChange.emit(null) + + this.cd.markForCheck() + }, + error: this.errorHandle, }) } + + private errorHandle = (error: never) => { + console.error(error) + + this.resourceUrl = null + this.resourceAltText = '' + this.resourceFileName = '' + + this.overviewChange.emit(null) + + this.cd.markForCheck() + } + + ngOnChanges(changes: SimpleChanges): void { + const overviewChanges = changes['formControl'] + if ( + overviewChanges && + overviewChanges.currentValue !== overviewChanges.previousValue + ) { + let overview: GraphicOverview + if ( + overviewChanges.currentValue.value && + overviewChanges.currentValue.value.length > 0 + ) { + overview = overviewChanges.currentValue.value[0] as GraphicOverview + } else { + return + } + if (overview.description && overview.description !== '') { + this.resourceAltText = overview.description + this.cd.markForCheck() + } + } + } } diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.css b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.html b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.html new file mode 100644 index 0000000000..12ca60d9e2 --- /dev/null +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.html @@ -0,0 +1,5 @@ + diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.spec.ts b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.spec.ts new file mode 100644 index 0000000000..95021ce4f5 --- /dev/null +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.spec.ts @@ -0,0 +1,40 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { FormFieldOverviewsComponent } from './form-field-overviews.component' +import { HttpClientTestingModule } from '@angular/common/http/testing' +import { TranslateModule } from '@ngx-translate/core' +import { FormControl } from '@angular/forms' + +describe('FormFieldOverviewsComponent', () => { + let component: FormFieldOverviewsComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + FormFieldOverviewsComponent, + HttpClientTestingModule, + TranslateModule.forRoot(), + ], + }).compileComponents() + + fixture = TestBed.createComponent(FormFieldOverviewsComponent) + component = fixture.componentInstance + component.metadataUuid = '8505d991-e38f-4704-a47a-e7d335dfbef5' + const control = new FormControl() + control.setValue([ + { + description: 'doge.jpg', + url: new URL( + 'http://localhost:8080/geonetwork/srv/api/0.1/records/8505d991-e38f-4704-a47a-e7d335dfbef5/attachments/doge.jpg' + ), + }, + ]) + component.control = control + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.ts b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.ts new file mode 100644 index 0000000000..5f41978ebb --- /dev/null +++ b/libs/feature/editor/src/lib/components/record-form/form-field/form-field-overviews/form-field-overviews.component.ts @@ -0,0 +1,22 @@ +import { CommonModule } from '@angular/common' +import { ChangeDetectionStrategy, Component, Input } from '@angular/core' +import { OverviewUploadComponent } from '../../../overview-upload/overview-upload.component' +import { FormControl } from '@angular/forms' +import { GraphicOverview } from '@geonetwork-ui/common/domain/model/record' + +@Component({ + selector: 'gn-ui-form-field-overviews', + templateUrl: './form-field-overviews.component.html', + styleUrls: ['./form-field-overviews.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [CommonModule, OverviewUploadComponent], +}) +export class FormFieldOverviewsComponent { + @Input() metadataUuid: string + @Input() control!: FormControl + + handleOverviewChange(overView: GraphicOverview | null) { + this.control.setValue(overView ? [overView] : []) + } +} 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 5ac61e653d..f6fa1f9ee2 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 @@ -76,6 +76,12 @@ + + + { let component: FormFieldComponent @@ -17,7 +27,18 @@ describe('FormFieldComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [FormFieldComponent, TranslateModule.forRoot()], + imports: [ + FormFieldComponent, + TranslateModule.forRoot(), + HttpClientTestingModule, + ], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { + provide: EditorFacade, + useClass: EditorFacadeMock, + }, + ], }).compileComponents() fixture = TestBed.createComponent(FormFieldComponent) @@ -149,4 +170,17 @@ describe('FormFieldComponent', () => { expect(formField).toBeTruthy() }) }) + describe('overviews field', () => { + let formField + beforeEach(() => { + component.model = 'overviews' + fixture.detectChanges() + formField = fixture.debugElement.query( + By.directive(FormFieldOverviewsComponent) + ).componentInstance + }) + it('creates an overview upload form field', () => { + expect(formField).toBeTruthy() + }) + }) }) 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 44621437a5..db576ccb2d 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 @@ -28,6 +28,9 @@ import { FormFieldSpatialExtentComponent } from './form-field-spatial-extent/for 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 { FormFieldOverviewsComponent } from './form-field-overviews/form-field-overviews.component' +import { map, take } from 'rxjs/operators' +import { EditorFacade } from '../../../+state/editor.facade' import { FormFieldConfig } from '../../../models' @Component({ @@ -55,6 +58,7 @@ import { FormFieldConfig } from '../../../models' FormFieldArrayComponent, FormFieldKeywordsComponent, TranslateModule, + FormFieldOverviewsComponent, ], }) export class FormFieldComponent { @@ -70,9 +74,14 @@ export class FormFieldComponent { @ViewChild('titleInput') titleInput: ElementRef + metadataUuid$ = this.facade.record$.pipe( + take(1), + map((record) => record.uniqueIdentifier) + ) + formControl = new FormControl() - constructor() { + constructor(private facade: EditorFacade) { this.valueChange = this.formControl.valueChanges } @@ -101,6 +110,9 @@ export class FormFieldComponent { get isSpatialExtentField() { return this.model === 'spatialExtents' } + get isGraphicOverview() { + return this.model === 'overviews' + } get isSimpleField() { return this.model === 'uniqueIdentifier' || this.model === 'recordUpdated' } 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 54d4e59b87..24048c7656 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 @@ -24,6 +24,7 @@ export class RecordFormComponent { if (!model) { return } + console.log(newValue) this.facade.updateRecordField(model, newValue) } diff --git a/libs/feature/editor/src/lib/fields.config.ts b/libs/feature/editor/src/lib/fields.config.ts index 8c22644b48..1614eb1e05 100644 --- a/libs/feature/editor/src/lib/fields.config.ts +++ b/libs/feature/editor/src/lib/fields.config.ts @@ -83,6 +83,13 @@ export const RECORD_ABSTRACT_FIELD: EditorField = { }, } +export const RECORD_GRAPHICAL_OVERVIEW_FIELD: EditorField = { + model: 'overviews', + formFieldConfig: { + labelKey: marker('editor.record.form.field.overviews'), + }, +} + /************************************************************ *************** SECTIONS ***************** ************************************************************ @@ -90,7 +97,11 @@ export const RECORD_ABSTRACT_FIELD: EditorField = { export const TITLE_SECTION: EditorSection = { hidden: false, - fields: [RECORD_TITLE_FIELD, RECORD_ABSTRACT_FIELD], + fields: [ + RECORD_TITLE_FIELD, + RECORD_ABSTRACT_FIELD, + RECORD_GRAPHICAL_OVERVIEW_FIELD, + ], } export const ABOUT_SECTION: EditorSection = { diff --git a/libs/ui/inputs/src/lib/image-input/image-input.component.html b/libs/ui/inputs/src/lib/image-input/image-input.component.html index ceeb82456a..ec41523dd2 100644 --- a/libs/ui/inputs/src/lib/image-input/image-input.component.html +++ b/libs/ui/inputs/src/lib/image-input/image-input.component.html @@ -51,7 +51,7 @@