mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
59334: edit metadata finetuning
This commit is contained in:
@@ -7,13 +7,16 @@
|
|||||||
<li *ngFor="let page of pages" class="nav-item">
|
<li *ngFor="let page of pages" class="nav-item">
|
||||||
<a class="nav-link"
|
<a class="nav-link"
|
||||||
[ngClass]="{'active' : page === currentPage}"
|
[ngClass]="{'active' : page === currentPage}"
|
||||||
[routerLink]="'/items/' + (itemRD$ | async)?.payload.uuid + '/edit/' + page">
|
[routerLink]="['./' + page]">
|
||||||
{{'item.edit.tabs.' + page + '.head' | translate}}
|
{{'item.edit.tabs.' + page + '.head' | translate}}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-pane active">
|
<div class="tab-pane active">
|
||||||
<router-outlet></router-outlet>
|
<div class="mb-4">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
||||||
|
<a [routerLink]="getItemPage((itemRD$ | async)?.payload)" class="btn btn-outline-secondary">Cancel</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -0,0 +1,5 @@
|
|||||||
|
@import '../../../styles/variables.scss';
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
min-width: $edit-item-button-min-width;
|
||||||
|
}
|
@@ -6,6 +6,7 @@ import { Item } from '../../core/shared/item.model';
|
|||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { isNotEmpty } from '../../shared/empty.util';
|
import { isNotEmpty } from '../../shared/empty.util';
|
||||||
|
import { getItemPageRoute } from '../item-page-routing.module';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-edit-item-page',
|
selector: 'ds-edit-item-page',
|
||||||
@@ -48,4 +49,8 @@ export class EditItemPageComponent implements OnInit {
|
|||||||
.filter((path: string) => isNotEmpty(path)); // ignore reroutes
|
.filter((path: string) => isNotEmpty(path)); // ignore reroutes
|
||||||
this.itemRD$ = this.route.data.pipe(map((data) => data.item));
|
this.itemRD$ = this.route.data.pipe(map((data) => data.item));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getItemPage(item: Item): string {
|
||||||
|
return getItemPageRoute(item.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,7 @@ import { ItemPublicComponent } from './item-public/item-public.component';
|
|||||||
import { ItemDeleteComponent } from './item-delete/item-delete.component';
|
import { ItemDeleteComponent } from './item-delete/item-delete.component';
|
||||||
import { ItemMetadataComponent } from './item-metadata/item-metadata.component';
|
import { ItemMetadataComponent } from './item-metadata/item-metadata.component';
|
||||||
import { EditInPlaceFieldComponent } from './item-metadata/edit-in-place-field/edit-in-place-field.component';
|
import { EditInPlaceFieldComponent } from './item-metadata/edit-in-place-field/edit-in-place-field.component';
|
||||||
|
import { ItemBitstreamsComponent } from './item-bitstreams/item-bitstreams.component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module that contains all components related to the Edit Item page administrator functionality
|
* Module that contains all components related to the Edit Item page administrator functionality
|
||||||
@@ -36,6 +37,7 @@ import { EditInPlaceFieldComponent } from './item-metadata/edit-in-place-field/e
|
|||||||
ItemDeleteComponent,
|
ItemDeleteComponent,
|
||||||
ItemStatusComponent,
|
ItemStatusComponent,
|
||||||
ItemMetadataComponent,
|
ItemMetadataComponent,
|
||||||
|
ItemBitstreamsComponent,
|
||||||
EditInPlaceFieldComponent
|
EditInPlaceFieldComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@@ -9,6 +9,7 @@ import { ItemPublicComponent } from './item-public/item-public.component';
|
|||||||
import { ItemDeleteComponent } from './item-delete/item-delete.component';
|
import { ItemDeleteComponent } from './item-delete/item-delete.component';
|
||||||
import { ItemStatusComponent } from './item-status/item-status.component';
|
import { ItemStatusComponent } from './item-status/item-status.component';
|
||||||
import { ItemMetadataComponent } from './item-metadata/item-metadata.component';
|
import { ItemMetadataComponent } from './item-metadata/item-metadata.component';
|
||||||
|
import { ItemBitstreamsComponent } from './item-bitstreams/item-bitstreams.component';
|
||||||
|
|
||||||
const ITEM_EDIT_WITHDRAW_PATH = 'withdraw';
|
const ITEM_EDIT_WITHDRAW_PATH = 'withdraw';
|
||||||
const ITEM_EDIT_REINSTATE_PATH = 'reinstate';
|
const ITEM_EDIT_REINSTATE_PATH = 'reinstate';
|
||||||
@@ -39,8 +40,7 @@ const ITEM_EDIT_DELETE_PATH = 'delete';
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'bitstreams',
|
path: 'bitstreams',
|
||||||
/* TODO - change when bitstreams page exists */
|
component: ItemBitstreamsComponent
|
||||||
component: ItemStatusComponent
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'metadata',
|
path: 'metadata',
|
||||||
@@ -49,12 +49,12 @@ const ITEM_EDIT_DELETE_PATH = 'delete';
|
|||||||
{
|
{
|
||||||
path: 'view',
|
path: 'view',
|
||||||
/* TODO - change when view page exists */
|
/* TODO - change when view page exists */
|
||||||
component: ItemStatusComponent
|
component: ItemBitstreamsComponent
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'curate',
|
path: 'curate',
|
||||||
/* TODO - change when curate page exists */
|
/* TODO - change when curate page exists */
|
||||||
component: ItemStatusComponent
|
component: ItemBitstreamsComponent
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@@ -0,0 +1,3 @@
|
|||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
@@ -0,0 +1 @@
|
|||||||
|
@import '../../../../styles/variables.scss';
|
@@ -0,0 +1,13 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-item-bitstreams',
|
||||||
|
styleUrls: ['./item-bitstreams.component.scss'],
|
||||||
|
templateUrl: './item-bitstreams.component.html',
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component for displaying an item's bitstreams edit page
|
||||||
|
*/
|
||||||
|
export class ItemBitstreamsComponent {
|
||||||
|
/* TODO implement */
|
||||||
|
}
|
@@ -1,11 +1,6 @@
|
|||||||
<script src="edit-in-place-field.component.ts"></script>
|
<!--{{metadata?.uuid}}-->
|
||||||
<div [ngClass]="{
|
<td>
|
||||||
'table-warning': fieldUpdate.changeType === 0,
|
<div class="metadata-field">
|
||||||
'table-danger': fieldUpdate.changeType === 2,
|
|
||||||
'table-success': fieldUpdate.changeType === 1
|
|
||||||
}" class="d-flex">
|
|
||||||
<!--{{metadata?.uuid}}-->
|
|
||||||
<td class="col-3">
|
|
||||||
<div *ngIf="!(editable | async)">
|
<div *ngIf="!(editable | async)">
|
||||||
<span>{{metadata?.key?.split('.').join('.​')}}</span>
|
<span>{{metadata?.key?.split('.').join('.​')}}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -25,39 +20,47 @@
|
|||||||
</div>
|
</div>
|
||||||
<small class="text-danger"
|
<small class="text-danger"
|
||||||
*ngIf="!(valid | async)">{{"item.edit.metadata.metadatafield.invalid" | translate}}</small>
|
*ngIf="!(valid | async)">{{"item.edit.metadata.metadatafield.invalid" | translate}}</small>
|
||||||
</td>
|
</div>
|
||||||
<td class="col-6">
|
</td>
|
||||||
<div *ngIf="!(editable | async)">
|
<td class="w-100">
|
||||||
<span>{{metadata?.value}}</span>
|
<div *ngIf="!(editable | async)">
|
||||||
</div>
|
<span>{{metadata?.value}}</span>
|
||||||
<div *ngIf="(editable | async)" class="field-container">
|
</div>
|
||||||
|
<div *ngIf="(editable | async)" class="field-container">
|
||||||
<textarea class="form-control" type="textarea" [(ngModel)]="metadata.value" [dsDebounce]
|
<textarea class="form-control" type="textarea" [(ngModel)]="metadata.value" [dsDebounce]
|
||||||
(onDebounce)="update()"></textarea>
|
(onDebounce)="update()"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="col-1 text-center">
|
<td class="text-center">
|
||||||
<div *ngIf="!(editable | async)">
|
<div *ngIf="!(editable | async)">
|
||||||
<span>{{metadata?.language}}</span>
|
<span>{{metadata?.language}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="(editable | async)" class="field-container">
|
<div *ngIf="(editable | async)" class="field-container">
|
||||||
<input class="form-control" type="text" [(ngModel)]="metadata.language" [dsDebounce]
|
<input class="form-control" type="text" [(ngModel)]="metadata.language" [dsDebounce]
|
||||||
(onDebounce)="update()"/>
|
(onDebounce)="update()"/>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="col-2 text-center">
|
<td class="text-center">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button [disabled]="!(canSetEditable() | async)" *ngIf="!(editable | async)" (click)="setEditable(true)" class="btn btn-primary btn-sm" title="{{'item.edit.metadata.edit.buttons.edit' | translate}}">
|
<button [disabled]="!(canSetEditable() | async)" *ngIf="!(editable | async)"
|
||||||
<i class="fas fa-edit fa-fw"></i>
|
(click)="setEditable(true)" class="btn btn-outline-primary btn-sm"
|
||||||
</button>
|
title="{{'item.edit.metadata.edit.buttons.edit' | translate}}">
|
||||||
<button [disabled]="!(canSetUneditable() | async)" *ngIf="(editable | async)" (click)="setEditable(false)" class="btn btn-success btn-sm" title="{{'item.edit.metadata.edit.buttons.unedit' | translate}}">
|
<i class="fas fa-edit fa-fw"></i>
|
||||||
<i class="fas fa-check fa-fw"></i>
|
</button>
|
||||||
</button>
|
<button [disabled]="!(canSetUneditable() | async)" *ngIf="(editable | async)"
|
||||||
<button [disabled]="!(canRemove() | async)" (click)="remove()" class="btn btn-danger btn-sm" title="{{'item.edit.metadata.edit.buttons.remove' | translate}}">
|
(click)="setEditable(false)" class="btn btn-outline-success btn-sm"
|
||||||
<i class="fas fa-trash-alt fa-fw"></i>
|
title="{{'item.edit.metadata.edit.buttons.unedit' | translate}}">
|
||||||
</button>
|
<i class="fas fa-check fa-fw"></i>
|
||||||
<button [disabled]="!(canUndo() | async)" (click)="removeChangesFromField()" class="btn btn-warning btn-sm" title="{{'item.edit.metadata.edit.buttons.undo' | translate}}">
|
</button>
|
||||||
<i class="fas fa-undo-alt fa-fw"></i>
|
<button [disabled]="!(canRemove() | async)" (click)="remove()"
|
||||||
</button>
|
class="btn btn-outline-danger btn-sm"
|
||||||
</div>
|
title="{{'item.edit.metadata.edit.buttons.remove' | translate}}">
|
||||||
</td>
|
<i class="fas fa-trash-alt fa-fw"></i>
|
||||||
</div>
|
</button>
|
||||||
|
<button [disabled]="!(canUndo() | async)" (click)="removeChangesFromField()"
|
||||||
|
class="btn btn-outline-warning btn-sm"
|
||||||
|
title="{{'item.edit.metadata.edit.buttons.undo' | translate}}">
|
||||||
|
<i class="fas fa-undo-alt fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
@@ -1 +1,10 @@
|
|||||||
@import '../../../../../styles/variables.scss';
|
@import '../../../../../styles/variables.scss';
|
||||||
|
.btn[disabled] {
|
||||||
|
color: $gray-600;
|
||||||
|
border-color: $gray-600;
|
||||||
|
z-index: 0; // prevent border colors jumping on hover
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadata-field {
|
||||||
|
width: $edit-item-metadata-field-width;
|
||||||
|
}
|
@@ -400,66 +400,64 @@ describe('EditInPlaceFieldComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('canRemove', () => {
|
describe('canRemove', () => {
|
||||||
describe('when editable is currently true', () => {
|
describe('when the fieldUpdate\'s changeType is currently not REMOVE or ADD', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
comp.editable = observableOf(true);
|
comp.fieldUpdate.changeType = FieldChangeType.UPDATE;
|
||||||
fixture.detectChanges();
|
});
|
||||||
|
it('canRemove should return an observable emitting true', () => {
|
||||||
|
const expected = '(a|)';
|
||||||
|
scheduler.expectObservable(comp.canRemove()).toBe(expected, { a: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the fieldUpdate\'s changeType is currently ADD', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.fieldUpdate.changeType = FieldChangeType.ADD;
|
||||||
});
|
});
|
||||||
it('canRemove should return an observable emitting false', () => {
|
it('canRemove should return an observable emitting false', () => {
|
||||||
const expected = '(a|)';
|
const expected = '(a|)';
|
||||||
scheduler.expectObservable(comp.canRemove()).toBe(expected, { a: false });
|
scheduler.expectObservable(comp.canRemove()).toBe(expected, { a: false });
|
||||||
});
|
});
|
||||||
});
|
})
|
||||||
|
|
||||||
describe('when editable is currently false', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
comp.editable = observableOf(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when the fieldUpdate\'s changeType is currently not REMOVE or ADD', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
comp.fieldUpdate.changeType = FieldChangeType.UPDATE;
|
|
||||||
});
|
|
||||||
it('canRemove should return an observable emitting true', () => {
|
|
||||||
const expected = '(a|)';
|
|
||||||
scheduler.expectObservable(comp.canRemove()).toBe(expected, { a: true });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when the fieldUpdate\'s changeType is currently ADD', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
comp.fieldUpdate.changeType = FieldChangeType.ADD;
|
|
||||||
});
|
|
||||||
it('canRemove should return an observable emitting false', () => {
|
|
||||||
const expected = '(a|)';
|
|
||||||
scheduler.expectObservable(comp.canRemove()).toBe(expected, { a: false });
|
|
||||||
});
|
|
||||||
})
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('canUndo', () => {
|
describe('canUndo', () => {
|
||||||
|
|
||||||
describe('when the fieldUpdate\'s changeType is currently ADD, UPDATE or REMOVE', () => {
|
describe('when editable is currently true', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
comp.fieldUpdate.changeType = FieldChangeType.ADD;
|
comp.editable = observableOf(true);
|
||||||
|
comp.fieldUpdate.changeType = undefined;
|
||||||
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('canUndo should return an observable emitting true', () => {
|
it('canUndo should return an observable emitting true', () => {
|
||||||
const expected = '(a|)';
|
const expected = '(a|)';
|
||||||
scheduler.expectObservable(comp.canUndo()).toBe(expected, { a: true });
|
scheduler.expectObservable(comp.canUndo()).toBe(expected, { a: true });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when the fieldUpdate\'s changeType is currently undefined', () => {
|
describe('when editable is currently false', () => {
|
||||||
beforeEach(() => {
|
describe('when the fieldUpdate\'s changeType is currently ADD, UPDATE or REMOVE', () => {
|
||||||
comp.fieldUpdate.changeType = undefined;
|
beforeEach(() => {
|
||||||
|
comp.fieldUpdate.changeType = FieldChangeType.ADD;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('canUndo should return an observable emitting true', () => {
|
||||||
|
const expected = '(a|)';
|
||||||
|
scheduler.expectObservable(comp.canUndo()).toBe(expected, { a: true });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('canUndo should return an observable emitting false', () => {
|
describe('when the fieldUpdate\'s changeType is currently undefined', () => {
|
||||||
const expected = '(a|)';
|
beforeEach(() => {
|
||||||
scheduler.expectObservable(comp.canUndo()).toBe(expected, { a: false });
|
comp.fieldUpdate.changeType = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('canUndo should return an observable emitting false', () => {
|
||||||
|
const expected = '(a|)';
|
||||||
|
scheduler.expectObservable(comp.canUndo()).toBe(expected, { a: false });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -14,7 +14,8 @@ import { getSucceededRemoteData } from '../../../../core/shared/operators';
|
|||||||
import { FormControl } from '@angular/forms';
|
import { FormControl } from '@angular/forms';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-edit-in-place-field',
|
// tslint:disable-next-line:component-selector
|
||||||
|
selector: '[ds-edit-in-place-field]',
|
||||||
styleUrls: ['./edit-in-place-field.component.scss'],
|
styleUrls: ['./edit-in-place-field.component.scss'],
|
||||||
templateUrl: './edit-in-place-field.component.html',
|
templateUrl: './edit-in-place-field.component.html',
|
||||||
})
|
})
|
||||||
@@ -131,7 +132,7 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges {
|
|||||||
(fields: MetadataField[]) => this.metadataFieldSuggestions.next(
|
(fields: MetadataField[]) => this.metadataFieldSuggestions.next(
|
||||||
fields.map((field: MetadataField) => {
|
fields.map((field: MetadataField) => {
|
||||||
return {
|
return {
|
||||||
displayValue: field.toString(),
|
displayValue: field.toString().split('.').join('.​'),
|
||||||
value: field.toString()
|
value: field.toString()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@@ -1,56 +1,63 @@
|
|||||||
<div class="container">
|
<div class="item-metadata">
|
||||||
<div class="item-metadata">
|
<div class="button-row top d-flex">
|
||||||
<div class="button-row d-flex justify-content-between">
|
<button class="mr-auto btn btn-success"
|
||||||
<button class="btn btn-success my-2"
|
(click)="add()"><i
|
||||||
(click)="add()"><i
|
class="fas fa-plus"></i>
|
||||||
class="fas fa-plus"></i> {{"item.edit.metadata.add-button" | translate}}
|
<span class="d-none d-sm-inline"> {{"item.edit.metadata.add-button" | translate}}</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-danger" *ngIf="!(isReinstatable() | async)"
|
||||||
|
[disabled]="!(hasChanges() | async)"
|
||||||
|
(click)="discard()"><i
|
||||||
|
class="fas fa-times"></i>
|
||||||
|
<span class="d-none d-sm-inline"> {{"item.edit.metadata.discard-button" | translate}}</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-warning" *ngIf="isReinstatable() | async"
|
||||||
|
(click)="reinstate()"><i
|
||||||
|
class="fas fa-undo-alt"></i>
|
||||||
|
<span class="d-none d-sm-inline"> {{"item.edit.metadata.reinstate-button" | translate}}</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-primary" [disabled]="!(hasChanges() | async)"
|
||||||
|
(click)="submit()"><i
|
||||||
|
class="fas fa-save"></i>
|
||||||
|
<span class="d-none d-sm-inline"> {{"item.edit.metadata.save-button" | translate}}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<table class="table table-responsive table-striped table-bordered">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>{{'item.edit.metadata.headers.field' | translate}}</th>
|
||||||
|
<th>{{'item.edit.metadata.headers.value' | translate}}</th>
|
||||||
|
<th class="text-center">{{'item.edit.metadata.headers.language' | translate}}</th>
|
||||||
|
<th class="text-center">{{'item.edit.metadata.headers.edit' | translate}}</th>
|
||||||
|
</tr>
|
||||||
|
<tr ds-edit-in-place-field
|
||||||
|
*ngFor="let updateValue of ((updates$ | async)| dsObjectValues); trackBy: trackUpdate"
|
||||||
|
[fieldUpdate]="updateValue || {}"
|
||||||
|
[url]="url"
|
||||||
|
[ngClass]="{
|
||||||
|
'table-warning': updateValue.changeType === 0,
|
||||||
|
'table-danger': updateValue.changeType === 2,
|
||||||
|
'table-success': updateValue.changeType === 1
|
||||||
|
}">
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="button-row bottom">
|
||||||
|
<div class="my-2 float-right">
|
||||||
|
<button class="btn btn-danger" *ngIf="!(isReinstatable() | async)"
|
||||||
|
[disabled]="!(hasChanges() | async)"
|
||||||
|
(click)="discard()"><i
|
||||||
|
class="fas fa-times"></i> {{"item.edit.metadata.discard-button" | translate}}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-warning" *ngIf="isReinstatable() | async"
|
||||||
|
(click)="reinstate()"><i
|
||||||
|
class="fas fa-undo-alt"></i> {{"item.edit.metadata.reinstate-button" | translate}}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-primary" [disabled]="!(hasChanges() | async)"
|
||||||
|
(click)="submit()"><i
|
||||||
|
class="fas fa-save"></i> {{"item.edit.metadata.save-button" | translate}}
|
||||||
</button>
|
</button>
|
||||||
<div class="my-2 spaced-btn-group">
|
|
||||||
<button class="btn btn-danger" *ngIf="!(isReinstatable() | async)"
|
|
||||||
[disabled]="!(hasChanges() | async)"
|
|
||||||
(click)="discard()"><i
|
|
||||||
class="fas fa-times"></i> {{"item.edit.metadata.discard-button" | translate}}
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-warning" *ngIf="isReinstatable() | async"
|
|
||||||
(click)="reinstate()"><i
|
|
||||||
class="fas fa-undo-alt"></i> {{"item.edit.metadata.reinstate-button" | translate}}
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-primary" [disabled]="!(hasChanges() | async)"
|
|
||||||
(click)="submit()"><i
|
|
||||||
class="fas fa-save"></i> {{"item.edit.metadata.save-button" | translate}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<table class="table table-responsive table-striped table-bordered">
|
|
||||||
<tbody>
|
|
||||||
<tr class="d-flex">
|
|
||||||
<th class="col-3">{{'item.edit.metadata.headers.field' | translate}}</th>
|
|
||||||
<th class="col-6">{{'item.edit.metadata.headers.value' | translate}}</th>
|
|
||||||
<th class="col-1 text-center">{{'item.edit.metadata.headers.language' | translate}}</th>
|
|
||||||
<th class="col-2 text-center">{{'item.edit.metadata.headers.edit' | translate}}</th>
|
|
||||||
</tr>
|
|
||||||
<tr *ngFor="let updateValue of ((updates$ | async)| dsObjectValues); trackBy: trackUpdate">
|
|
||||||
<ds-edit-in-place-field [fieldUpdate]="updateValue || {}"
|
|
||||||
[url]="url"></ds-edit-in-place-field>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div class="button-row">
|
|
||||||
<div class="my-2 float-right spaced-btn-group">
|
|
||||||
<button class="btn btn-danger" *ngIf="!(isReinstatable() | async)"
|
|
||||||
[disabled]="!(hasChanges() | async)"
|
|
||||||
(click)="discard()"><i
|
|
||||||
class="fas fa-times"></i> {{"item.edit.metadata.discard-button" | translate}}
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-warning" *ngIf="isReinstatable() | async"
|
|
||||||
(click)="reinstate()"><i
|
|
||||||
class="fas fa-undo-alt"></i> {{"item.edit.metadata.reinstate-button" | translate}}
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-primary" [disabled]="!(hasChanges() | async)"
|
|
||||||
(click)="submit()"><i
|
|
||||||
class="fas fa-save"></i> {{"item.edit.metadata.save-button" | translate}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,13 +1,22 @@
|
|||||||
@import '../../../../styles/variables.scss';
|
@import '../../../../styles/variables.scss';
|
||||||
|
|
||||||
.button-row {
|
.button-row {
|
||||||
.spaced-btn-group > .btn {
|
.btn {
|
||||||
margin-right: 0.5 * $spacer;
|
margin-right: 0.5 * $spacer;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: map-get($grid-breakpoints, sm)) {
|
||||||
|
min-width: $edit-item-button-min-width;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.btn {
|
|
||||||
min-width: $button-min-width;
|
&.top .btn {
|
||||||
|
margin-top: $spacer/2;
|
||||||
|
margin-bottom: $spacer/2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@@ -57,6 +57,9 @@ export class ItemMetadataComponent implements OnInit {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up and initialize all fields
|
||||||
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.route.parent.data.pipe(map((data) => data.item))
|
this.route.parent.data.pipe(map((data) => data.item))
|
||||||
.pipe(
|
.pipe(
|
||||||
@@ -116,8 +119,10 @@ export class ItemMetadataComponent implements OnInit {
|
|||||||
this.objectUpdatesService.initialize(this.url, this.item.metadata, this.item.lastModified);
|
this.objectUpdatesService.initialize(this.url, this.item.metadata, this.item.lastModified);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Prevent unnecessary rerendering so fields don't lose focus **/
|
/**
|
||||||
protected trackUpdate(index, update: FieldUpdate) {
|
* Prevent unnecessary rerendering so fields don't lose focus
|
||||||
|
*/
|
||||||
|
trackUpdate(index, update: FieldUpdate) {
|
||||||
return update && update.field ? update.field.uuid : undefined;
|
return update && update.field ? update.field.uuid : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,7 +12,7 @@
|
|||||||
{{'item.edit.tabs.status.labels.itemPage' | translate}}:
|
{{'item.edit.tabs.status.labels.itemPage' | translate}}:
|
||||||
</div>
|
</div>
|
||||||
<div class="col-9 float-left status-data" id="status-itemPage">
|
<div class="col-9 float-left status-data" id="status-itemPage">
|
||||||
<a href="{{getItemPage()}}">{{getItemPage()}}</a>
|
<a href="{{getItemPage((itemRD$ | async)?.payload)}}">{{getItemPage((itemRD$ | async)?.payload)}}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngFor="let operation of operations" class="w-100 pt-3">
|
<div *ngFor="let operation of operations" class="w-100 pt-3">
|
||||||
|
@@ -6,6 +6,7 @@ import { ItemOperation } from '../item-operation/itemOperation.model';
|
|||||||
import { first, map } from 'rxjs/operators';
|
import { first, map } from 'rxjs/operators';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { getItemEditPath, getItemPageRoute } from '../../item-page-routing.module';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-item-status',
|
selector: 'ds-item-status',
|
||||||
@@ -68,16 +69,16 @@ export class ItemStatusComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
this.operations = [];
|
this.operations = [];
|
||||||
if (item.isWithdrawn) {
|
if (item.isWithdrawn) {
|
||||||
this.operations.push(new ItemOperation('reinstate', this.getCurrentUrl() + '/reinstate'));
|
this.operations.push(new ItemOperation('reinstate', this.getCurrentUrl(item) + '/reinstate'));
|
||||||
} else {
|
} else {
|
||||||
this.operations.push(new ItemOperation('withdraw', this.getCurrentUrl() + '/withdraw'));
|
this.operations.push(new ItemOperation('withdraw', this.getCurrentUrl(item) + '/withdraw'));
|
||||||
}
|
}
|
||||||
if (item.isDiscoverable) {
|
if (item.isDiscoverable) {
|
||||||
this.operations.push(new ItemOperation('private', this.getCurrentUrl() + '/private'));
|
this.operations.push(new ItemOperation('private', this.getCurrentUrl(item) + '/private'));
|
||||||
} else {
|
} else {
|
||||||
this.operations.push(new ItemOperation('public', this.getCurrentUrl() + '/public'));
|
this.operations.push(new ItemOperation('public', this.getCurrentUrl(item) + '/public'));
|
||||||
}
|
}
|
||||||
this.operations.push(new ItemOperation('delete', this.getCurrentUrl() + '/delete'));
|
this.operations.push(new ItemOperation('delete', this.getCurrentUrl(item) + '/delete'));
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -86,20 +87,16 @@ export class ItemStatusComponent implements OnInit {
|
|||||||
* Get the url to the simple item page
|
* Get the url to the simple item page
|
||||||
* @returns {string} url
|
* @returns {string} url
|
||||||
*/
|
*/
|
||||||
getItemPage(): string {
|
getItemPage(item: Item): string {
|
||||||
return this.router.url.substr(0, this.router.url.lastIndexOf('/'));
|
return getItemPageRoute(item.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current url without query params
|
* Get the current url without query params
|
||||||
* @returns {string} url
|
* @returns {string} url
|
||||||
*/
|
*/
|
||||||
getCurrentUrl(): string {
|
getCurrentUrl(item: Item): string {
|
||||||
if (this.router.url.indexOf('?') > -1) {
|
return getItemEditPath(item.id);
|
||||||
return this.router.url.substr(0, this.router.url.indexOf('?'));
|
|
||||||
} else {
|
|
||||||
return this.router.url;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -68,6 +68,7 @@ import { MenuService } from '../shared/menu/menu.service';
|
|||||||
import { NormalizedObjectBuildService } from './cache/builders/normalized-object-build.service';
|
import { NormalizedObjectBuildService } from './cache/builders/normalized-object-build.service';
|
||||||
import { DSOChangeAnalyzer } from './data/dso-change-analyzer.service';
|
import { DSOChangeAnalyzer } from './data/dso-change-analyzer.service';
|
||||||
import { ObjectUpdatesService } from './data/object-updates/object-updates.service';
|
import { ObjectUpdatesService } from './data/object-updates/object-updates.service';
|
||||||
|
import { DefaultChangeAnalyzer } from './data/default-change-analyzer.service';
|
||||||
|
|
||||||
const IMPORTS = [
|
const IMPORTS = [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@@ -133,6 +134,7 @@ const PROVIDERS = [
|
|||||||
UUIDService,
|
UUIDService,
|
||||||
DSpaceObjectDataService,
|
DSpaceObjectDataService,
|
||||||
DSOChangeAnalyzer,
|
DSOChangeAnalyzer,
|
||||||
|
DefaultChangeAnalyzer,
|
||||||
CSSVariableService,
|
CSSVariableService,
|
||||||
MenuService,
|
MenuService,
|
||||||
ObjectUpdatesService,
|
ObjectUpdatesService,
|
||||||
|
29
src/app/core/data/default-change-analyzer.service.ts
Normal file
29
src/app/core/data/default-change-analyzer.service.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { Operation } from 'fast-json-patch/lib/core';
|
||||||
|
import { compare } from 'fast-json-patch';
|
||||||
|
import { ChangeAnalyzer } from './change-analyzer';
|
||||||
|
import { NormalizedDSpaceObject } from '../cache/models/normalized-dspace-object.model';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||||
|
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||||
|
import { NormalizedObject } from '../cache/models/normalized-object.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class to determine what differs between two
|
||||||
|
* CacheableObjects
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class DefaultChangeAnalyzer<T extends CacheableObject> implements ChangeAnalyzer<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare the metadata of two CacheableObject and return the differences as
|
||||||
|
* a JsonPatch Operation Array
|
||||||
|
*
|
||||||
|
* @param {NormalizedObject} object1
|
||||||
|
* The first object to compare
|
||||||
|
* @param {NormalizedObject} object2
|
||||||
|
* The second object to compare
|
||||||
|
*/
|
||||||
|
diff(object1: T | NormalizedObject<T>, object2: T | NormalizedObject<T>): Operation[] {
|
||||||
|
return compare(object1, object2);
|
||||||
|
}
|
||||||
|
}
|
@@ -11,10 +11,10 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
|||||||
import { FindAllOptions } from './request.models';
|
import { FindAllOptions } from './request.models';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { MetadataSchema } from '../metadata/metadataschema.model';
|
import { MetadataSchema } from '../metadata/metadataschema.model';
|
||||||
import { ChangeAnalyzer } from './change-analyzer';
|
|
||||||
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MetadataSchemaDataService extends DataService<MetadataSchema> {
|
export class MetadataSchemaDataService extends DataService<MetadataSchema> {
|
||||||
@@ -27,7 +27,7 @@ export class MetadataSchemaDataService extends DataService<MetadataSchema> {
|
|||||||
private bs: BrowseService,
|
private bs: BrowseService,
|
||||||
protected halService: HALEndpointService,
|
protected halService: HALEndpointService,
|
||||||
protected objectCache: ObjectCacheService,
|
protected objectCache: ObjectCacheService,
|
||||||
protected comparator: ChangeAnalyzer<MetadataSchema>,
|
protected comparator: DefaultChangeAnalyzer<MetadataSchema>,
|
||||||
protected dataBuildService: NormalizedObjectBuildService,
|
protected dataBuildService: NormalizedObjectBuildService,
|
||||||
protected http: HttpClient,
|
protected http: HttpClient,
|
||||||
protected notificationsService: NotificationsService) {
|
protected notificationsService: NotificationsService) {
|
||||||
|
@@ -228,6 +228,7 @@ describe('MetadataService', () => {
|
|||||||
const mockPublisher = (mockItem: Item): Item => {
|
const mockPublisher = (mockItem: Item): Item => {
|
||||||
const publishedMockItem = Object.assign(new Item(), mockItem) as Item;
|
const publishedMockItem = Object.assign(new Item(), mockItem) as Item;
|
||||||
publishedMockItem.metadata.push({
|
publishedMockItem.metadata.push({
|
||||||
|
uuid: 'b3826cf5-5f07-44cf-88d8-2da968354d18',
|
||||||
key: 'dc.publisher',
|
key: 'dc.publisher',
|
||||||
language: 'en_US',
|
language: 'en_US',
|
||||||
value: 'Mock Publisher'
|
value: 'Mock Publisher'
|
||||||
|
@@ -19,7 +19,6 @@ $gray-700: lighten($gray-base, 46.6%) !default; // #777
|
|||||||
$gray-600: lighten($gray-base, 73.3%) !default; // #bbb
|
$gray-600: lighten($gray-base, 73.3%) !default; // #bbb
|
||||||
$gray-100: lighten($gray-base, 93.5%) !default; // #eee
|
$gray-100: lighten($gray-base, 93.5%) !default; // #eee
|
||||||
|
|
||||||
|
|
||||||
/* Reassign color vars to semantic color scheme */
|
/* Reassign color vars to semantic color scheme */
|
||||||
$blue: #2B4E72 !default;
|
$blue: #2B4E72 !default;
|
||||||
$green: #94BA65 !default;
|
$green: #94BA65 !default;
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
$content-spacing: $spacer * 1.5;
|
$content-spacing: $spacer * 1.5;
|
||||||
|
|
||||||
$button-height: $input-btn-padding-y * 2 + $input-btn-line-height + calculateRem($input-btn-border-width*2);
|
$button-height: $input-btn-padding-y * 2 + $input-btn-line-height + calculateRem($input-btn-border-width*2);
|
||||||
$button-min-width: 100px;
|
|
||||||
|
|
||||||
$card-height-percentage:98%;
|
$card-height-percentage:98%;
|
||||||
$card-thumbnail-height:240px;
|
$card-thumbnail-height:240px;
|
||||||
@@ -24,3 +23,6 @@ $admin-sidebar-header-bg: darken($dark, 7%);
|
|||||||
|
|
||||||
$dark-scrollbar-background: $admin-sidebar-active-bg;
|
$dark-scrollbar-background: $admin-sidebar-active-bg;
|
||||||
$dark-scrollbar-foreground: #47495d;
|
$dark-scrollbar-foreground: #47495d;
|
||||||
|
|
||||||
|
$edit-item-button-min-width: 100px;
|
||||||
|
$edit-item-metadata-field-width: 190px;
|
Reference in New Issue
Block a user