mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
59334: basic edit metadata implementation
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@@ -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}}">
|
||||
|
@@ -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 {
|
||||
|
@@ -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-->
|
||||
<!--></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>
|
@@ -0,0 +1,3 @@
|
||||
textarea, input, select {
|
||||
width: 100%;
|
||||
}
|
@@ -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()
|
||||
}
|
||||
}
|
@@ -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>
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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)) {
|
||||
|
@@ -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) => {
|
||||
|
@@ -19,5 +19,4 @@ export class Metadatum {
|
||||
*/
|
||||
@autoserialize
|
||||
value: string;
|
||||
|
||||
}
|
||||
|
@@ -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>> =>
|
||||
|
Reference in New Issue
Block a user