Skip to content

Commit

Permalink
Merge pull request #812 from geonetwork/ME/add-image-input-component
Browse files Browse the repository at this point in the history
Metadata Editor: add image input component
  • Loading branch information
LHBruneton-C2C committed Apr 18, 2024
2 parents 3d65a13 + 2bd7075 commit c372298
Show file tree
Hide file tree
Showing 32 changed files with 1,086 additions and 45 deletions.
1 change: 0 additions & 1 deletion libs/data-access/gn4/src/openapi/.openapi-generator/FILES
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ model/iProcessingReport.api.model.ts
model/iSODate.api.model.ts
model/infoReport.api.model.ts
model/inlineObject1.api.model.ts
model/inlineObject3.api.model.ts
model/inlineObject4.api.model.ts
model/isoLanguage.api.model.ts
model/jSONObject.api.model.ts
Expand Down
55 changes: 43 additions & 12 deletions libs/data-access/gn4/src/openapi/api/records.api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import {
import { ExtentDtoApiModel } from '../model/models'
import { FeatureResponseApiModel } from '../model/models'
import { IProcessingReportApiModel } from '../model/models'
import { InlineObject3ApiModel } from '../model/models'
import { MetadataBatchApproveParameterApiModel } from '../model/models'
import { MetadataBatchSubmitParameterApiModel } from '../model/models'
import { MetadataCategoryApiModel } from '../model/models'
Expand Down Expand Up @@ -75,6 +74,20 @@ export class RecordsApiService {
this.encoder = this.configuration.encoder || new CustomHttpParameterCodec()
}

/**
* @param consumes string[] mime-types
* @return true: consumes contains 'multipart/form-data', false: otherwise
*/
private canConsumeForm(consumes: string[]): boolean {
const form = 'multipart/form-data'
for (const consume of consumes) {
if (form === consume) {
return true
}
}
return false
}

private addToHttpParams(
httpParams: HttpParams,
value: any,
Expand Down Expand Up @@ -8290,44 +8303,44 @@ export class RecordsApiService {
/**
* Create a new resource for a given metadata
* @param metadataUuid The metadata UUID
* @param file The file to upload
* @param visibility The sharing policy
* @param approved Use approved version or not
* @param inlineObject3ApiModel
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public putResource(
metadataUuid: string,
file: Blob,
visibility?: 'public' | 'private',
approved?: boolean,
inlineObject3ApiModel?: InlineObject3ApiModel,
observe?: 'body',
reportProgress?: boolean,
options?: { httpHeaderAccept?: 'application/json' }
): Observable<MetadataResourceApiModel>
public putResource(
metadataUuid: string,
file: Blob,
visibility?: 'public' | 'private',
approved?: boolean,
inlineObject3ApiModel?: InlineObject3ApiModel,
observe?: 'response',
reportProgress?: boolean,
options?: { httpHeaderAccept?: 'application/json' }
): Observable<HttpResponse<MetadataResourceApiModel>>
public putResource(
metadataUuid: string,
file: Blob,
visibility?: 'public' | 'private',
approved?: boolean,
inlineObject3ApiModel?: InlineObject3ApiModel,
observe?: 'events',
reportProgress?: boolean,
options?: { httpHeaderAccept?: 'application/json' }
): Observable<HttpEvent<MetadataResourceApiModel>>
public putResource(
metadataUuid: string,
file: Blob,
visibility?: 'public' | 'private',
approved?: boolean,
inlineObject3ApiModel?: InlineObject3ApiModel,
observe: any = 'body',
reportProgress: boolean = false,
options?: { httpHeaderAccept?: 'application/json' }
Expand All @@ -8337,6 +8350,11 @@ export class RecordsApiService {
'Required parameter metadataUuid was null or undefined when calling putResource.'
)
}
if (file === null || file === undefined) {
throw new Error(
'Required parameter file was null or undefined when calling putResource.'
)
}

let queryParameters = new HttpParams({ encoder: this.encoder })
if (visibility !== undefined && visibility !== null) {
Expand Down Expand Up @@ -8369,11 +8387,24 @@ export class RecordsApiService {
}

// to determine the Content-Type header
const consumes: string[] = ['application/json']
const httpContentTypeSelected: string | undefined =
this.configuration.selectHeaderContentType(consumes)
if (httpContentTypeSelected !== undefined) {
headers = headers.set('Content-Type', httpContentTypeSelected)
const consumes: string[] = ['multipart/form-data']

const canConsumeForm = this.canConsumeForm(consumes)

let formParams: { append(param: string, value: any): any }
let useForm = false
let convertFormParamsToString = false
// use FormData to transmit files using content-type "multipart/form-data"
// see https://stackoverflow.com/questions/4007969/application-x-www-form-urlencoded-or-multipart-form-data
useForm = canConsumeForm
if (useForm) {
formParams = new FormData()
} else {
formParams = new HttpParams({ encoder: this.encoder })
}

if (file !== undefined) {
formParams = (formParams.append('file', <any>file) as any) || formParams
}

let responseType_: 'text' | 'json' = 'json'
Expand All @@ -8388,7 +8419,7 @@ export class RecordsApiService {
`${this.configuration.basePath}/records/${encodeURIComponent(
String(metadataUuid)
)}/attachments`,
inlineObject3ApiModel,
convertFormParamsToString ? formParams.toString() : formParams,
{
params: queryParameters,
responseType: <any>responseType_,
Expand Down
18 changes: 0 additions & 18 deletions libs/data-access/gn4/src/openapi/model/inlineObject3.api.model.ts

This file was deleted.

1 change: 0 additions & 1 deletion libs/data-access/gn4/src/openapi/model/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export * from './iProcessingReport.api.model'
export * from './iSODate.api.model'
export * from './infoReport.api.model'
export * from './inlineObject1.api.model'
export * from './inlineObject3.api.model'
export * from './inlineObject4.api.model'
export * from './isoLanguage.api.model'
export * from './jSONObject.api.model'
Expand Down
2 changes: 1 addition & 1 deletion libs/data-access/gn4/src/spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7390,7 +7390,7 @@ paths:
example: true
requestBody:
content:
application/json:
multipart/form-data:
schema:
required:
- file
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<gn-ui-image-input
[maxSizeMB]="5"
[previewUrl]="resourceUrl"
[altText]="resourceFileName"
(fileChange)="handleFileChange($event)"
(urlChange)="handleUrlChange($event)"
(delete)="handleDelete()"
></gn-ui-image-input>
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { RecordsApiService } from '@geonetwork-ui/data-access/gn4'
import { TranslateModule } from '@ngx-translate/core'
import { of } from 'rxjs'
import { OverviewUploadComponent } from './overview-upload.component'

class RecordsApiServiceMock {
getAllResources = jest.fn(() =>
of([{ filename: 'filenameGet', url: 'urlGet' }])
)
putResource = jest.fn(() => of({ filename: 'filenamePut', url: 'urlPut' }))
putResourceFromURL = jest.fn(() =>
of({ filename: 'filenamePutUrl', url: 'urlPutUrl' })
)
delResource = jest.fn(() => of(void 0))
}

const metadataUuid = '8505d991-e38f-4704-a47a-e7d335dfbef5'

describe('OverviewUploadComponent', () => {
let component: OverviewUploadComponent
let fixture: ComponentFixture<OverviewUploadComponent>
let recordsApiService: RecordsApiService

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
OverviewUploadComponent,
HttpClientTestingModule,
TranslateModule.forRoot(),
],
providers: [
{
provide: RecordsApiService,
useClass: RecordsApiServiceMock,
},
],
}).compileComponents()
recordsApiService = TestBed.inject(RecordsApiService)

fixture = TestBed.createComponent(OverviewUploadComponent)
component = fixture.componentInstance
component.metadataUuid = metadataUuid
fixture.detectChanges()
})

it('should create', () => {
expect(component).toBeTruthy()
})

it('should get all resources corresponding to the metadata UUID on init', () => {
expect(recordsApiService.getAllResources).toHaveBeenCalledWith(metadataUuid)
expect(component.resourceFileName).toEqual('filenameGet')
expect(component.resourceUrl).toEqual('urlGet')
})

it('should put the file resource on file change', () => {
const someFile = new File([], 'someFile')
component.handleFileChange(someFile)
expect(recordsApiService.putResource).toHaveBeenCalledWith(
metadataUuid,
someFile,
'public'
)
expect(component.resourceFileName).toEqual('filenamePut')
expect(component.resourceUrl).toEqual('urlPut')
})

it('should put the resource from URL on URL change', () => {
component.handleUrlChange('someUrl')
expect(recordsApiService.putResourceFromURL).toHaveBeenCalledWith(
metadataUuid,
'someUrl',
'public'
)
expect(component.resourceFileName).toEqual('filenamePutUrl')
expect(component.resourceUrl).toEqual('urlPutUrl')
})

it('should delete the resource corresponding to the metadata UUID on delete', () => {
component.resourceFileName = 'filenameDelete'
component.handleDelete()
expect(recordsApiService.delResource).toHaveBeenCalledWith(
metadataUuid,
'filenameDelete'
)
expect(component.resourceFileName).toBeNull()
expect(component.resourceUrl).toBeNull()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Input,
OnInit,
} from '@angular/core'
import { CommonModule } from '@angular/common'
import { RecordsApiService } from '@geonetwork-ui/data-access/gn4'
import { UiInputsModule } from '@geonetwork-ui/ui/inputs'

@Component({
selector: 'gn-ui-overview-upload',
standalone: true,
imports: [CommonModule, UiInputsModule],
templateUrl: './overview-upload.component.html',
styleUrls: ['./overview-upload.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class OverviewUploadComponent implements OnInit {
@Input() metadataUuid: string

resourceFileName: string
resourceUrl: string

constructor(
private recordsApiService: RecordsApiService,
private cd: ChangeDetectorRef
) {}

ngOnInit(): void {
this.recordsApiService
.getAllResources(this.metadataUuid)
.subscribe((resources) => {
this.resourceFileName = resources[0]?.filename
this.resourceUrl = resources[0]?.url
this.cd.markForCheck()
})
}

handleFileChange(file: File) {
this.recordsApiService
.putResource(this.metadataUuid, file, 'public')
.subscribe((resource) => {
this.resourceFileName = resource.filename
this.resourceUrl = resource.url
this.cd.markForCheck()
})
}

handleUrlChange(url: string) {
this.recordsApiService
.putResourceFromURL(this.metadataUuid, url, 'public')
.subscribe((resource) => {
this.resourceFileName = resource.filename
this.resourceUrl = resource.url
this.cd.markForCheck()
})
}

handleDelete() {
this.recordsApiService
.delResource(this.metadataUuid, this.resourceFileName)
.subscribe(() => {
this.resourceFileName = null
this.resourceUrl = null
this.cd.markForCheck()
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ describe('ThumbnailComponent', () => {

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UtilSharedModule],
declarations: [ThumbnailComponent],
imports: [ThumbnailComponent, UtilSharedModule],
})
.overrideComponent(ThumbnailComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default },
Expand Down
22 changes: 22 additions & 0 deletions libs/ui/elements/src/lib/thumbnail/thumbnail.component.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {
componentWrapperDecorator,
Meta,
moduleMetadata,
StoryObj,
} from '@storybook/angular'
import { ThumbnailComponent } from './thumbnail.component'

export default {
title: 'Elements/ThumbnailComponent',
component: ThumbnailComponent,
decorators: [
moduleMetadata({
imports: [ThumbnailComponent],
}),
componentWrapperDecorator(
(story) => `<div style="max-width: 800px">${story}</div>`
),
],
} as Meta<ThumbnailComponent>

export const Primary: StoryObj<ThumbnailComponent> = {}
Loading

0 comments on commit c372298

Please sign in to comment.