diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 95b39fdb15..f8aa8eddca 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -159,6 +159,9 @@ "cancel": "Cancel", "success": "The item has been deleted", "error": "An error occured while deleting the item" + }, + "metadata": { + "add-button": "Add new metadata" } } }, diff --git a/src/app/+item-page/edit-item-page/edit-item-page.component.html b/src/app/+item-page/edit-item-page/edit-item-page.component.html index 001b484c2c..46a8126b05 100644 --- a/src/app/+item-page/edit-item-page/edit-item-page.component.html +++ b/src/app/+item-page/edit-item-page/edit-item-page.component.html @@ -16,7 +16,7 @@ - + diff --git a/src/app/+item-page/edit-item-page/edit-item-page.module.ts b/src/app/+item-page/edit-item-page/edit-item-page.module.ts index 0a7b363d6a..adbcf6b0df 100644 --- a/src/app/+item-page/edit-item-page/edit-item-page.module.ts +++ b/src/app/+item-page/edit-item-page/edit-item-page.module.ts @@ -12,6 +12,8 @@ import {AbstractSimpleItemActionComponent} from './simple-item-action/abstract-s import {ItemPrivateComponent} from './item-private/item-private.component'; import {ItemPublicComponent} from './item-public/item-public.component'; import {ItemDeleteComponent} from './item-delete/item-delete.component'; +import { ItemMetadataComponent } from './item-metadata/item-metadata.component'; +import { EditInPlaceComponent } from './item-metadata/edit-in-place-field/edit-in-place-field.component'; /** * Module that contains all components related to the Edit Item page administrator functionality @@ -32,7 +34,9 @@ import {ItemDeleteComponent} from './item-delete/item-delete.component'; ItemPrivateComponent, ItemPublicComponent, ItemDeleteComponent, - ItemStatusComponent + ItemStatusComponent, + ItemMetadataComponent, + EditInPlaceComponent ] }) export class EditItemPageModule { diff --git a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html new file mode 100644 index 0000000000..5a5e16383a --- /dev/null +++ b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html @@ -0,0 +1,42 @@ + + + {{metadata.key}} + + + + + + + + + + + + + + +
+ {{metadata.value}} +
+
+ +
+ + +
+ {{metadata.language}} +
+
+ +
+ + +
+ + +
+
+ + +
+ diff --git a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.scss b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.scss new file mode 100644 index 0000000000..58c24635e6 --- /dev/null +++ b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.scss @@ -0,0 +1,3 @@ +textarea, input, select { + width: 100%; +} \ No newline at end of file diff --git a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts new file mode 100644 index 0000000000..1139415ecc --- /dev/null +++ b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts @@ -0,0 +1,41 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { isNotEmpty } from '../../../../shared/empty.util'; +import { Metadatum } from '../../../../core/shared/metadatum.model'; +import { RegistryService } from '../../../../core/registry/registry.service'; + +@Component({ + selector: 'ds-edit-in-place-field.', + styleUrls: ['./edit-in-place-field.component.scss'], + templateUrl: './edit-in-place-field.component.html', +}) +/** + * Component for displaying an item's status + */ +export class EditInPlaceComponent { + + /** + * The value to display + */ + @Input() metadata: Metadatum; + @Output() mdUpdate: EventEmitter = new EventEmitter(); + @Output() mdRemove: EventEmitter = new EventEmitter(); + editable = false; + + constructor( + private metadataFieldService: RegistryService, + ) { + + } + + isNotEmpty(value) { + return isNotEmpty(value); + } + + update() { + this.mdUpdate.emit(); + } + + remove() { + this.mdRemove.emit() + } +} diff --git a/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.html b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.html new file mode 100644 index 0000000000..bf32358d37 --- /dev/null +++ b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.html @@ -0,0 +1,12 @@ +
+ +
diff --git a/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts new file mode 100644 index 0000000000..cf9420658d --- /dev/null +++ b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.ts @@ -0,0 +1,37 @@ +import { Component, Input } from '@angular/core'; +import { Item } from '../../../core/shared/item.model'; +import { ItemDataService } from '../../../core/data/item-data.service'; +import { Metadatum } from '../../../core/shared/metadatum.model'; + +@Component({ + selector: 'ds-item-metadata', + templateUrl: './item-metadata.component.html', +}) +/** + * Component for displaying an item's status + */ +export class ItemMetadataComponent { + + /** + * The item to display the metadata for + */ + @Input() item: Item; + + constructor(private itemService: ItemDataService) { + + } + + update() { + this.itemService.update(this.item); + } + + removeMetadata(i: number) { + this.item.metadata = this.item.metadata.filter((metadatum: Metadatum, index: number) => index !== i); + this.update(); + } + + addMetadata() { + this.item.metadata = [new Metadatum(), ...this.item.metadata]; + this.update(); + } +} diff --git a/src/app/core/cache/builders/remote-data-build.service.ts b/src/app/core/cache/builders/remote-data-build.service.ts index 62a4992787..96945cea81 100644 --- a/src/app/core/cache/builders/remote-data-build.service.ts +++ b/src/app/core/cache/builders/remote-data-build.service.ts @@ -5,15 +5,7 @@ import { race as observableRace } from 'rxjs'; import { Injectable } from '@angular/core'; -import { - distinctUntilChanged, - first, - flatMap, - map, - startWith, - switchMap, - take -} from 'rxjs/operators'; +import { distinctUntilChanged, flatMap, map, startWith, switchMap } from 'rxjs/operators'; import { hasValue, hasValueOperator, isEmpty, isNotEmpty } from '../../../shared/empty.util'; import { PaginatedList } from '../../data/paginated-list'; import { RemoteData } from '../../data/remote-data'; @@ -29,7 +21,8 @@ import { getMapsTo, getRelationMetadata, getRelationships } from './build-decora import { PageInfo } from '../../shared/page-info.model'; import { filterSuccessfulResponses, - getRequestFromRequestHref, getRequestFromRequestUUID, + getRequestFromRequestHref, + getRequestFromRequestUUID, getResourceLinksFromResponse } from '../../shared/operators'; @@ -51,8 +44,6 @@ export class RemoteDataBuildService { const requestEntry$ = observableRace( href$.pipe(getRequestFromRequestHref(this.requestService)), requestUUID$.pipe(getRequestFromRequestUUID(this.requestService)), - ).pipe( - take(1) ); // always use self link if that is cached, only if it isn't, get it via the response. @@ -94,8 +85,8 @@ export class RemoteDataBuildService { toRemoteDataObservable(requestEntry$: Observable, payload$: Observable) { return observableCombineLatest(requestEntry$, payload$).pipe( map(([reqEntry, payload]) => { - const requestPending = hasValue(reqEntry) && hasValue(reqEntry.requestPending) ? reqEntry.requestPending : true; - const responsePending = hasValue(reqEntry) && hasValue(reqEntry.responsePending) ? reqEntry.responsePending : false; + const requestPending = hasValue(reqEntry.requestPending) ? reqEntry.requestPending : true; + const responsePending = hasValue(reqEntry.responsePending) ? reqEntry.responsePending : false; let isSuccessful: boolean; let error: RemoteDataError; if (hasValue(reqEntry) && hasValue(reqEntry.response)) { diff --git a/src/app/core/registry/registry.service.ts b/src/app/core/registry/registry.service.ts index 6cd3a7fa28..4bd0dabb70 100644 --- a/src/app/core/registry/registry.service.ts +++ b/src/app/core/registry/registry.service.ts @@ -28,7 +28,7 @@ import { import { HALEndpointService } from '../shared/hal-endpoint.service'; import { RegistryMetadatafieldsResponseParsingService } from '../data/registry-metadatafields-response-parsing.service'; import { RegistryMetadatafieldsResponse } from './registry-metadatafields-response.model'; -import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; +import { hasValue, hasNoValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; import { URLCombiner } from '../url-combiner/url-combiner'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { RegistryBitstreamformatsResponseParsingService } from '../data/registry-bitstreamformats-response-parsing.service'; @@ -166,6 +166,41 @@ export class RegistryService { return this.rdb.toRemoteDataObservable(requestEntryObs, payloadObs); } + public getAllMetadataFields(pagination?: PaginationComponentOptions): Observable>> { + if (hasNoValue(pagination)) { + pagination = { currentPage: 1, pageSize: Number.MAX_VALUE } as any; + } + const requestObs = this.getMetadataFieldsRequestObs(pagination); + + const requestEntryObs = requestObs.pipe( + flatMap((request: RestRequest) => this.requestService.getByHref(request.href)) + ); + + const rmrObs: Observable = requestEntryObs.pipe( + getResponseFromEntry(), + map((response: RegistryMetadatafieldsSuccessResponse) => response.metadatafieldsResponse) + ); + + const metadatafieldsObs: Observable = rmrObs.pipe( + map((rmr: RegistryMetadatafieldsResponse) => rmr.metadatafields), + map((metadataFields: MetadataField[]) => metadataFields) + ); + + const pageInfoObs: Observable = requestEntryObs.pipe( + getResponseFromEntry(), + + map((response: RegistryMetadatafieldsSuccessResponse) => response.pageInfo) + ); + + const payloadObs = observableCombineLatest(metadatafieldsObs, pageInfoObs).pipe( + map(([metadatafields, pageInfo]) => { + return new PaginatedList(pageInfo, metadatafields); + }) + ); + + return this.rdb.toRemoteDataObservable(requestEntryObs, payloadObs); + } + public getBitstreamFormats(pagination: PaginationComponentOptions): Observable>> { const requestObs = this.getBitstreamFormatsRequestObs(pagination); @@ -238,6 +273,26 @@ export class RegistryService { ); } + private getMetadataFieldsRequestObs(pagination: PaginationComponentOptions): Observable { + return this.halService.getEndpoint(this.metadataFieldsPath).pipe( + map((url: string) => { + const args: string[] = []; + args.push(`size=${pagination.pageSize}`); + args.push(`page=${pagination.currentPage - 1}`); + if (isNotEmpty(args)) { + url = new URLCombiner(url, `?${args.join('&')}`).toString(); + } + const request = new GetRequest(this.requestService.generateRequestId(), url); + return Object.assign(request, { + getResponseParser(): GenericConstructor { + return RegistryMetadatafieldsResponseParsingService; + } + }); + }), + tap((request: RestRequest) => this.requestService.configure(request)), + ); + } + private getBitstreamFormatsRequestObs(pagination: PaginationComponentOptions): Observable { return this.halService.getEndpoint(this.bitstreamFormatsPath).pipe( map((url: string) => { diff --git a/src/app/core/shared/metadatum.model.ts b/src/app/core/shared/metadatum.model.ts index a3c5830608..ca1f3d8591 100644 --- a/src/app/core/shared/metadatum.model.ts +++ b/src/app/core/shared/metadatum.model.ts @@ -19,5 +19,4 @@ export class Metadatum { */ @autoserialize value: string; - } diff --git a/src/app/core/shared/operators.ts b/src/app/core/shared/operators.ts index 6fe510c1aa..4540bed0c4 100644 --- a/src/app/core/shared/operators.ts +++ b/src/app/core/shared/operators.ts @@ -60,7 +60,7 @@ export const getRemoteDataPayload = () => export const getSucceededRemoteData = () => (source: Observable>): Observable> => - source.pipe(find((rd: RemoteData) => rd.hasSucceeded), hasValueOperator()); + source.pipe(find((rd: RemoteData) => rd.hasSucceeded)); export const getAllSucceededRemoteData = () => (source: Observable>): Observable> =>