Skip to content

Commit 50f7211

Browse files
93746: Edit metadata redesign - Virtual metadata
1 parent 454bfd2 commit 50f7211

8 files changed

Lines changed: 130 additions & 57 deletions

File tree

src/app/core/data/relationship.service.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { HttpClient, HttpHeaders } from '@angular/common/http';
22
import { Inject, Injectable } from '@angular/core';
33
import { MemoizedSelector, select, Store } from '@ngrx/store';
4-
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
4+
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
55
import { distinctUntilChanged, filter, map, mergeMap, startWith, switchMap, take, tap } from 'rxjs/operators';
66
import {
77
compareArraysUsingIds, PAGINATED_RELATIONS_TO_ITEMS_OPERATOR,
@@ -44,6 +44,11 @@ import { DeleteRequest, FindListOptions, PostRequest, RestRequest } from './requ
4444
import { RequestService } from './request.service';
4545
import { RequestEntryState } from './request.reducer';
4646
import { NoContent } from '../shared/NoContent.model';
47+
import { MetadataValue } from '../shared/metadata.models';
48+
import { MetadataRepresentation } from '../shared/metadata-representation/metadata-representation.model';
49+
import { MetadatumRepresentation } from '../shared/metadata-representation/metadatum/metadatum-representation.model';
50+
import { ItemMetadataRepresentation } from '../shared/metadata-representation/item/item-metadata-representation.model';
51+
import { DSpaceObject } from '../shared/dspace-object.model';
4752

4853
const relationshipListsStateSelector = (state: AppState) => state.relationshipLists;
4954

@@ -523,4 +528,34 @@ export class RelationshipService extends DataService<Relationship> {
523528
}) as Observable<RemoteData<PaginatedList<Relationship>>>;
524529

525530
}
531+
532+
/**
533+
* Resolve a {@link MetadataValue} into a {@link MetadataRepresentation} of the correct type
534+
* @param metadatum {@link MetadataValue} to resolve
535+
* @param parentItem Parent dspace object the metadata value belongs to
536+
* @param itemType The type of item this metadata value represents (will only be used when no related item can be found, as a fallback)
537+
*/
538+
resolveMetadataRepresentation(metadatum: MetadataValue, parentItem: DSpaceObject, itemType: string): Observable<MetadataRepresentation> {
539+
if (metadatum.isVirtual) {
540+
return this.findById(metadatum.virtualValue, true, false, followLink('leftItem'), followLink('rightItem')).pipe(
541+
getFirstSucceededRemoteData(),
542+
switchMap((relRD: RemoteData<Relationship>) =>
543+
observableCombineLatest(relRD.payload.leftItem, relRD.payload.rightItem).pipe(
544+
filter(([leftItem, rightItem]) => leftItem.hasCompleted && rightItem.hasCompleted),
545+
map(([leftItem, rightItem]) => {
546+
if (!leftItem.hasSucceeded || !rightItem.hasSucceeded) {
547+
return observableOf(Object.assign(new MetadatumRepresentation(itemType), metadatum));
548+
} else if (rightItem.hasSucceeded && leftItem.payload.id === parentItem.id) {
549+
return rightItem.payload;
550+
} else if (rightItem.payload.id === parentItem.id) {
551+
return leftItem.payload;
552+
}
553+
}),
554+
map((item: Item) => Object.assign(new ItemMetadataRepresentation(metadatum), item))
555+
)
556+
));
557+
} else {
558+
return observableOf(Object.assign(new MetadatumRepresentation(itemType), metadatum));
559+
}
560+
}
526561
}

src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-field-values/dso-edit-metadata-field-values.component.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
<div class="flex-grow-1 ds-drop-list">
1+
<div class="flex-grow-1 ds-drop-list h-100">
22
<ds-dso-edit-metadata-value *ngFor="let mdValue of form.fields[mdField]; let idx = index"
3+
[dso]="dso"
34
[mdValue]="mdValue"
45
[dsoType]="dsoType"
56
[saving$]="saving$"

src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-field-values/dso-edit-metadata-field-values.component.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Component, EventEmitter, Input, Output } from '@angular/core';
22
import { DsoEditMetadataChangeType, DsoEditMetadataForm } from '../dso-edit-metadata-form';
33
import { Observable } from 'rxjs/internal/Observable';
4+
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
45

56
@Component({
67
selector: 'ds-dso-edit-metadata-field-values',
@@ -11,6 +12,11 @@ import { Observable } from 'rxjs/internal/Observable';
1112
* Component displaying table rows for each value for a certain metadata field within a form
1213
*/
1314
export class DsoEditMetadataFieldValuesComponent {
15+
/**
16+
* The parent {@link DSpaceObject} to display a metadata form for
17+
* Also used to determine metadata-representations in case of virtual metadata
18+
*/
19+
@Input() dso: DSpaceObject;
1420
/**
1521
* A dynamic form object containing all information about the metadata and the changes made to them, see {@link DsoEditMetadataForm}
1622
*/

src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.html

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
<div class="d-flex flex-row ds-value-row"
1+
<div class="d-flex flex-row ds-value-row" *ngVar="mdValue.newValue.isVirtual as isVirtual"
22
[ngClass]="{ 'ds-warning': mdValue.change === DsoEditMetadataChangeTypeEnum.UPDATE, 'ds-danger': mdValue.change === DsoEditMetadataChangeTypeEnum.REMOVE, 'ds-success': mdValue.change === DsoEditMetadataChangeTypeEnum.ADD, 'h-100': isOnlyValue }">
3-
<div class="flex-grow-1 ds-flex-cell ds-value-cell d-flex align-items-center">
4-
<div class="dont-break-out preserve-line-breaks" *ngIf="!mdValue.editing">{{ mdValue.newValue.value }}</div>
5-
<textarea class="form-control" rows="2" *ngIf="mdValue.editing" [(ngModel)]="mdValue.newValue.value"
3+
<div class="flex-grow-1 ds-flex-cell ds-value-cell d-flex align-items-center" *ngVar="(mdRepresentation$ | async) as mdRepresentation">
4+
<div class="dont-break-out preserve-line-breaks" *ngIf="!mdValue.editing && !mdRepresentation">{{ mdValue.newValue.value }}</div>
5+
<textarea class="form-control" rows="2" *ngIf="mdValue.editing && !mdRepresentation" [(ngModel)]="mdValue.newValue.value"
66
[dsDebounce]="300" (onDebounce)="confirm.emit(false)"></textarea>
7+
<div class="d-flex" *ngIf="mdRepresentation">
8+
<a class="mr-2" target="_blank" [routerLink]="mdRepresentationItemRoute$ | async">{{ mdRepresentationName$ | async }}</a>
9+
<ds-type-badge [object]="mdRepresentation"></ds-type-badge>
10+
</div>
711
</div>
812
<div class="ds-flex-cell ds-lang-cell">
913
<div class="dont-break-out preserve-line-breaks" *ngIf="!mdValue.editing">{{ mdValue.newValue.language }}</div>
@@ -12,22 +16,24 @@
1216
</div>
1317
<div class="text-center ds-flex-cell ds-edit-cell">
1418
<div class="btn-group edit-field">
15-
<button class="btn btn-outline-primary btn-sm ng-star-inserted" ngbTooltip="{{ dsoType + '.edit.metadata.edit.buttons.edit' | translate }}" *ngIf="!mdValue.editing"
16-
[disabled]="mdValue.change === DsoEditMetadataChangeTypeEnum.REMOVE || (saving$ | async)" (click)="edit.emit()">
17-
<i class="fas fa-edit fa-fw"></i>
18-
</button>
19-
<button class="btn btn-outline-success btn-sm ng-star-inserted" ngbTooltip="{{ dsoType + '.edit.metadata.edit.buttons.confirm' | translate }}" *ngIf="mdValue.editing"
20-
[disabled]="(saving$ | async)" (click)="confirm.emit(true)">
21-
<i class="fas fa-check fa-fw"></i>
22-
</button>
23-
<button class="btn btn-outline-danger btn-sm" ngbTooltip="{{ dsoType + '.edit.metadata.edit.buttons.remove' | translate }}"
24-
[disabled]="mdValue.change || mdValue.editing || (saving$ | async)" (click)="remove.emit()">
25-
<i class="fas fa-trash-alt fa-fw"></i>
26-
</button>
27-
<button class="btn btn-outline-warning btn-sm" ngbTooltip="{{ dsoType + '.edit.metadata.edit.buttons.undo' | translate }}"
28-
[disabled]="(!mdValue.change && !mdValue.editing) || (saving$ | async)" (click)="undo.emit()">
29-
<i class="fas fa-undo-alt fa-fw"></i>
30-
</button>
19+
<div class="btn-group" [ngbTooltip]="isVirtual ? (dsoType + '.edit.metadata.edit.buttons.virtual' | translate) : null">
20+
<button class="btn btn-outline-primary btn-sm ng-star-inserted" ngbTooltip="{{ dsoType + '.edit.metadata.edit.buttons.edit' | translate }}" *ngIf="!mdValue.editing"
21+
[disabled]="isVirtual || mdValue.change === DsoEditMetadataChangeTypeEnum.REMOVE || (saving$ | async)" (click)="edit.emit()">
22+
<i class="fas fa-edit fa-fw"></i>
23+
</button>
24+
<button class="btn btn-outline-success btn-sm ng-star-inserted" ngbTooltip="{{ dsoType + '.edit.metadata.edit.buttons.confirm' | translate }}" *ngIf="mdValue.editing"
25+
[disabled]="isVirtual || (saving$ | async)" (click)="confirm.emit(true)">
26+
<i class="fas fa-check fa-fw"></i>
27+
</button>
28+
<button class="btn btn-outline-danger btn-sm" ngbTooltip="{{ dsoType + '.edit.metadata.edit.buttons.remove' | translate }}"
29+
[disabled]="isVirtual || mdValue.change || mdValue.editing || (saving$ | async)" (click)="remove.emit()">
30+
<i class="fas fa-trash-alt fa-fw"></i>
31+
</button>
32+
<button class="btn btn-outline-warning btn-sm" ngbTooltip="{{ dsoType + '.edit.metadata.edit.buttons.undo' | translate }}"
33+
[disabled]="isVirtual || (!mdValue.change && !mdValue.editing) || (saving$ | async)" (click)="undo.emit()">
34+
<i class="fas fa-undo-alt fa-fw"></i>
35+
</button>
36+
</div>
3137
<!-- TODO: Enable drag -->
3238
<button class="btn btn-outline-secondary ds-drag-handle btn-sm"
3339
[ngClass]="{'disabled': isOnlyValue || (saving$ | async)}" ngbTooltip="{{ dsoType + '.edit.metadata.edit.buttons.drag' | translate }}" [disabled]="isOnlyValue || (saving$ | async)">

src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-value/dso-edit-metadata-value.component.ts

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1-
import { Component, EventEmitter, Input, Output } from '@angular/core';
1+
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
22
import { DsoEditMetadataChangeType, DsoEditMetadataValue } from '../dso-edit-metadata-form';
33
import { Observable } from 'rxjs/internal/Observable';
4+
import { MetadataRepresentationType } from '../../../core/shared/metadata-representation/metadata-representation.model';
5+
import { RelationshipService } from '../../../core/data/relationship.service';
6+
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
7+
import { of } from 'rxjs/internal/observable/of';
8+
import { ItemMetadataRepresentation } from '../../../core/shared/metadata-representation/item/item-metadata-representation.model';
9+
import { map } from 'rxjs/operators';
10+
import { getItemPageRoute } from '../../../item-page/item-page-routing-paths';
11+
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
412

513
@Component({
614
selector: 'ds-dso-edit-metadata-value',
@@ -10,7 +18,13 @@ import { Observable } from 'rxjs/internal/Observable';
1018
/**
1119
* Component displaying a single editable row for a metadata value
1220
*/
13-
export class DsoEditMetadataValueComponent {
21+
export class DsoEditMetadataValueComponent implements OnInit {
22+
/**
23+
* The parent {@link DSpaceObject} to display a metadata form for
24+
* Also used to determine metadata-representations in case of virtual metadata
25+
*/
26+
@Input() dso: DSpaceObject;
27+
1428
/**
1529
* Editable metadata value to show
1630
*/
@@ -59,4 +73,42 @@ export class DsoEditMetadataValueComponent {
5973
* @type {DsoEditMetadataChangeType}
6074
*/
6175
public DsoEditMetadataChangeTypeEnum = DsoEditMetadataChangeType;
76+
77+
/**
78+
* The item this metadata value represents in case it's virtual (if any, otherwise null)
79+
*/
80+
mdRepresentation$: Observable<ItemMetadataRepresentation | null>;
81+
82+
/**
83+
* The route to the item represented by this virtual metadata value (otherwise null)
84+
*/
85+
mdRepresentationItemRoute$: Observable<string | null>;
86+
87+
/**
88+
* The name of the item represented by this virtual metadata value (otherwise null)
89+
*/
90+
mdRepresentationName$: Observable<string | null>;
91+
92+
constructor(protected relationshipService: RelationshipService,
93+
protected dsoNameService: DSONameService) {
94+
}
95+
96+
ngOnInit(): void {
97+
this.initVirtualProperties();
98+
}
99+
100+
/**
101+
* Initialise potential properties of a virtual metadata value
102+
*/
103+
initVirtualProperties() {
104+
this.mdRepresentation$ = this.mdValue.newValue.isVirtual ?
105+
this.relationshipService.resolveMetadataRepresentation(this.mdValue.newValue, this.dso, 'Item')
106+
.pipe(map((mdRepresentation) => mdRepresentation.representationType === MetadataRepresentationType.Item ? mdRepresentation : null)) : of(null);
107+
this.mdRepresentationItemRoute$ = this.mdRepresentation$.pipe(
108+
map((mdRepresentation) => mdRepresentation ? getItemPageRoute(mdRepresentation) : null),
109+
);
110+
this.mdRepresentationName$ = this.mdRepresentation$.pipe(
111+
map((mdRepresentation) => mdRepresentation ? this.dsoNameService.getName(mdRepresentation) : null),
112+
);
113+
}
62114
}

src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
</ds-metadata-field-selector>
2929
</div>
3030
<div class="flex-grow-1 ds-drop-list">
31-
<ds-dso-edit-metadata-value [mdValue]="form.newValue"
31+
<ds-dso-edit-metadata-value [dso]="dso"
32+
[mdValue]="form.newValue"
3233
[dsoType]="dsoType"
3334
[saving$]="savingOrLoadingFieldValidation$"
3435
[isOnlyValue]="true"
@@ -43,6 +44,7 @@
4344
<span class="dont-break-out preserve-line-breaks">{{ mdField }}</span>
4445
</div>
4546
<ds-dso-edit-metadata-field-values class="flex-grow-1"
47+
[dso]="dso"
4648
[form]="form"
4749
[dsoType]="dsoType"
4850
[saving$]="saving$"

src/app/item-page/simple/metadata-representation-list/metadata-representation-list.component.ts

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,12 @@
11
import { Component, Input } from '@angular/core';
22
import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model';
33
import {
4-
combineLatest as observableCombineLatest,
54
Observable,
6-
of as observableOf,
75
zip as observableZip
86
} from 'rxjs';
97
import { RelationshipService } from '../../../core/data/relationship.service';
108
import { MetadataValue } from '../../../core/shared/metadata.models';
11-
import { getFirstSucceededRemoteData } from '../../../core/shared/operators';
12-
import { filter, map, switchMap } from 'rxjs/operators';
13-
import { RemoteData } from '../../../core/data/remote-data';
14-
import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
159
import { Item } from '../../../core/shared/item.model';
16-
import { MetadatumRepresentation } from '../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
17-
import { ItemMetadataRepresentation } from '../../../core/shared/metadata-representation/item/item-metadata-representation.model';
18-
import { followLink } from '../../../shared/utils/follow-link-config.model';
1910
import { AbstractIncrementalListComponent } from '../abstract-incremental-list/abstract-incremental-list.component';
2011

2112
@Component({
@@ -85,29 +76,7 @@ export class MetadataRepresentationListComponent extends AbstractIncrementalList
8576
...metadata
8677
.slice((this.objects.length * this.incrementBy), (this.objects.length * this.incrementBy) + this.incrementBy)
8778
.map((metadatum: any) => Object.assign(new MetadataValue(), metadatum))
88-
.map((metadatum: MetadataValue) => {
89-
if (metadatum.isVirtual) {
90-
return this.relationshipService.findById(metadatum.virtualValue, true, false, followLink('leftItem'), followLink('rightItem')).pipe(
91-
getFirstSucceededRemoteData(),
92-
switchMap((relRD: RemoteData<Relationship>) =>
93-
observableCombineLatest(relRD.payload.leftItem, relRD.payload.rightItem).pipe(
94-
filter(([leftItem, rightItem]) => leftItem.hasCompleted && rightItem.hasCompleted),
95-
map(([leftItem, rightItem]) => {
96-
if (!leftItem.hasSucceeded || !rightItem.hasSucceeded) {
97-
return observableOf(Object.assign(new MetadatumRepresentation(this.itemType), metadatum));
98-
} else if (rightItem.hasSucceeded && leftItem.payload.id === this.parentItem.id) {
99-
return rightItem.payload;
100-
} else if (rightItem.payload.id === this.parentItem.id) {
101-
return leftItem.payload;
102-
}
103-
}),
104-
map((item: Item) => Object.assign(new ItemMetadataRepresentation(metadatum), item))
105-
)
106-
));
107-
} else {
108-
return observableOf(Object.assign(new MetadatumRepresentation(this.itemType), metadatum));
109-
}
110-
})
79+
.map((metadatum: MetadataValue) => this.relationshipService.resolveMetadataRepresentation(metadatum, this.parentItem, this.itemType)),
11180
);
11281
}
11382
}

src/assets/i18n/en.json5

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1803,6 +1803,8 @@
18031803

18041804
"item.edit.metadata.edit.buttons.unedit": "Stop editing",
18051805

1806+
"item.edit.metadata.edit.buttons.virtual": "This is a virtual metadata value, i.e. a value inherited from a related entity. It can’t be modified directly. Add or remove the corresponding relationship in the \"Relationships\" tab",
1807+
18061808
"item.edit.metadata.empty": "The item currently doesn't contain any metadata. Click Add to start adding a metadata value.",
18071809

18081810
"item.edit.metadata.headers.edit": "Edit",

0 commit comments

Comments
 (0)