59334: basic edit metadata implementation

This commit is contained in:
lotte
2019-01-25 13:27:18 +01:00
parent f08fda9bce
commit b1a2571858
12 changed files with 206 additions and 19 deletions

View File

@@ -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"
}
}
},

View File

@@ -16,7 +16,7 @@
</ngb-tab>
<ngb-tab title="{{'item.edit.tabs.metadata.head' | translate}}">
<ng-template ngbTabContent>
<ds-item-metadata [item]="(itemRD$ | async)?.payload"></ds-item-metadata>
</ng-template>
</ngb-tab>
<ngb-tab title="{{'item.edit.tabs.view.head' | translate}}">

View File

@@ -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 {

View File

@@ -0,0 +1,42 @@
<td class="col-3">
<!--<div *ngIf="!editable">-->
<span>{{metadata.key}}</span>
<!--</div>-->
<!--<div *ngIf="editable" class="field-container">-->
<!--<ds-input-suggestions [suggestions]="(filterSearchResults | async)"-->
<!--[action]="getCurrentUrl()"-->
<!--[name]="filterConfig.paramName"-->
<!--[(ngModel)]="metadata.key"-->
<!--(submitSuggestion)="updateField($event)"-->
<!--(clickSuggestion)="updateField($event)"-->
<!--(findSuggestions)="findSuggestions($event)"-->
<!--ngDefaultControl-->
<!--&gt;</ds-input-suggestions>-->
<!--</div>-->
</td>
<td class="col-7">
<div *ngIf="!editable">
<span>{{metadata.value}}</span>
</div>
<div *ngIf="editable" class="field-container">
<textarea type="textarea" [ngModel]="metadata.value" [dsDebounce] (onDebounce)="update()"></textarea>
</div>
</td>
<td class="col-1">
<div *ngIf="!editable">
<span>{{metadata.language}}</span>
</div>
<div *ngIf="editable" class="field-container">
<input type="text" [ngModel]="metadata.language" [dsDebounce] (onDebounce)="update()"/>
</div>
</td>
<td class="col-1">
<div *ngIf="!editable">
<i class="fas fa-edit fa-fw" (click)="editable = !editable"></i>
<i class="fas fa-trash fa-fw" (click)="remove()"></i>
</div>
<div *ngIf="editable">
<i class="fas fa-times fa-fw" (click)="editable = !editable"></i>
<i class="fas fa-trash fa-fw" (click)="remove()"></i>
</div>
</td>

View File

@@ -0,0 +1,3 @@
textarea, input, select {
width: 100%;
}

View File

@@ -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<any> = new EventEmitter();
@Output() mdRemove: EventEmitter<any> = new EventEmitter();
editable = false;
constructor(
private metadataFieldService: RegistryService,
) {
}
isNotEmpty(value) {
return isNotEmpty(value);
}
update() {
this.mdUpdate.emit();
}
remove() {
this.mdRemove.emit()
}
}

View File

@@ -0,0 +1,12 @@
<div class="container">
<div class="item-metadata">
<button class="btn btn-primary w-100 my-2" (click)="addMetadata()">{{"item.edit.metadata.add-button" | translate}}</button>
<table class="table table-responsive table-striped">
<tbody>
<tr *ngFor="let metadatum of item.metadata; let i=index">
<ds-edit-in-place-field [metadata]="metadatum" class="d-flex" (mdUpdate)="update()" (mdRemove)="removeMetadata(i)"></ds-edit-in-place-field>
</tr>
</tbody>
</table>
</div>
</div>

View File

@@ -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();
}
}

View File

@@ -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<T>(requestEntry$: Observable<RequestEntry>, payload$: Observable<T>) {
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)) {

View File

@@ -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<RemoteData<PaginatedList<MetadataField>>> {
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<RegistryMetadatafieldsResponse> = requestEntryObs.pipe(
getResponseFromEntry(),
map((response: RegistryMetadatafieldsSuccessResponse) => response.metadatafieldsResponse)
);
const metadatafieldsObs: Observable<MetadataField[]> = rmrObs.pipe(
map((rmr: RegistryMetadatafieldsResponse) => rmr.metadatafields),
map((metadataFields: MetadataField[]) => metadataFields)
);
const pageInfoObs: Observable<PageInfo> = 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<RemoteData<PaginatedList<BitstreamFormat>>> {
const requestObs = this.getBitstreamFormatsRequestObs(pagination);
@@ -238,6 +273,26 @@ export class RegistryService {
);
}
private getMetadataFieldsRequestObs(pagination: PaginationComponentOptions): Observable<RestRequest> {
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<ResponseParsingService> {
return RegistryMetadatafieldsResponseParsingService;
}
});
}),
tap((request: RestRequest) => this.requestService.configure(request)),
);
}
private getBitstreamFormatsRequestObs(pagination: PaginationComponentOptions): Observable<RestRequest> {
return this.halService.getEndpoint(this.bitstreamFormatsPath).pipe(
map((url: string) => {

View File

@@ -19,5 +19,4 @@ export class Metadatum {
*/
@autoserialize
value: string;
}

View File

@@ -60,7 +60,7 @@ export const getRemoteDataPayload = () =>
export const getSucceededRemoteData = () =>
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
source.pipe(find((rd: RemoteData<T>) => rd.hasSucceeded), hasValueOperator());
source.pipe(find((rd: RemoteData<T>) => rd.hasSucceeded));
export const getAllSucceededRemoteData = () =>
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>