97075: Edit-metadata redesign messages + JSDocs

This commit is contained in:
Kristof De Langhe
2022-12-01 14:33:23 +01:00
parent 6be343cccf
commit 532a59006f
7 changed files with 295 additions and 28 deletions

View File

@@ -6,19 +6,50 @@ import { MetadataPatchRemoveOperation } from '../../core/data/object-updates/pat
import { MetadataPatchAddOperation } from '../../core/data/object-updates/patch-operation-service/operations/metadata/metadata-patch-add-operation.model';
import { MetadataPatchOperation } from '../../core/data/object-updates/patch-operation-service/operations/metadata/metadata-patch-operation.model';
/* tslint:disable:max-classes-per-file */
/**
* Enumeration for the type of change occurring on a metadata value
*/
export enum DsoEditMetadataChangeType {
UPDATE = 1,
ADD = 2,
REMOVE = 3
}
/**
* Class holding information about a metadata value and its changes within an edit form
*/
export class DsoEditMetadataValue {
/**
* The original metadata value (should stay the same!) used to compare changes with
*/
originalValue: MetadataValue;
/**
* The new value, dynamically changing
*/
newValue: MetadataValue;
/**
* A value that can be used to undo any discarding that took place
*/
reinstatableValue: MetadataValue;
/**
* Whether or not this value is currently being edited or not
*/
editing = false;
/**
* The type of change that's taking place on this metadata value
* Empty if no changes are made
*/
change: DsoEditMetadataChangeType;
/**
* A type or change that can be used to undo any discarding that took place
*/
reinstatableChange: DsoEditMetadataChangeType;
constructor(value: MetadataValue, added = false) {
@@ -30,6 +61,12 @@ export class DsoEditMetadataValue {
}
}
/**
* Save the current changes made to the metadata value
* This will set the type of change to UPDATE if the new metadata value's value and/or language are different from
* the original value
* It will also set the editing flag to false
*/
confirmChanges() {
if (hasNoValue(this.change) || this.change === DsoEditMetadataChangeType.UPDATE) {
if ((this.originalValue.value !== this.newValue.value || this.originalValue.language !== this.newValue.language)) {
@@ -41,10 +78,19 @@ export class DsoEditMetadataValue {
this.editing = false;
}
/**
* Returns if the current value contains changes or not
* If the metadata value contains changes, but they haven't been confirmed yet through confirmChanges(), this might
* return false (which is desired)
*/
hasChanges(): boolean {
return hasValue(this.change);
}
/**
* Discard the current changes and mark the value and change type re-instatable by storing them in their relevant
* properties
*/
discardAndMarkReinstatable(): void {
if (this.change === DsoEditMetadataChangeType.UPDATE) {
this.reinstatableValue = this.newValue;
@@ -53,12 +99,19 @@ export class DsoEditMetadataValue {
this.discard();
}
/**
* Discard the current changes
* Call discardAndMarkReinstatable() instead, if the discard should be re-instatable
*/
discard(): void {
this.change = undefined;
this.newValue = Object.assign(new MetadataValue(), this.originalValue);
this.editing = false;
}
/**
* Re-instate (undo) the last discard by replacing the value and change type with their reinstate properties (if present)
*/
reinstate(): void {
if (hasValue(this.reinstatableValue)) {
this.newValue = this.reinstatableValue;
@@ -70,20 +123,50 @@ export class DsoEditMetadataValue {
}
}
/**
* Returns if either the value or change type have a re-instatable property
* This will be the case if a discard has taken place that undid changes to the value or type
*/
isReinstatable(): boolean {
return hasValue(this.reinstatableValue) || hasValue(this.reinstatableChange);
}
}
/**
* Class holding information about the metadata of a DSpaceObject and its changes within an edit form
*/
export class DsoEditMetadataForm {
/**
* List of original metadata field keys (before any changes took place)
*/
originalFieldKeys: string[];
/**
* List of current metadata field keys (includes new fields for values added by the user)
*/
fieldKeys: string[];
/**
* Current state of the form
* Key: Metadata field
* Value: List of {@link DsoEditMetadataValue}s for the metadata field
*/
fields: {
[mdField: string]: DsoEditMetadataValue[],
};
/**
* A map of previously added metadata values before a discard of the form took place
* This can be used to re-instate the entire form to before the discard taking place
*/
reinstatableNewValues: {
[mdField: string]: DsoEditMetadataValue[],
};
/**
* A (temporary) new metadata value added by the user, not belonging to a metadata field yet
* This value will be finalised and added to a field using setMetadataField()
*/
newValue: DsoEditMetadataValue;
constructor(metadata: MetadataMap) {
@@ -98,18 +181,32 @@ export class DsoEditMetadataForm {
});
}
/**
* Add a new temporary value for the user to edit
*/
add(): void {
if (hasNoValue(this.newValue)) {
this.newValue = new DsoEditMetadataValue(new MetadataValue(), true);
}
}
/**
* Add the temporary value to a metadata field
* Clear the temporary value afterwards
* @param mdField
*/
setMetadataField(mdField: string) {
this.newValue.editing = false;
this.addValueToField(this.newValue, mdField);
this.newValue = undefined;
}
/**
* Add a value to a metadata field within the map
* @param value
* @param mdField
* @private
*/
private addValueToField(value: DsoEditMetadataValue, mdField: string) {
if (isEmpty(this.fields[mdField])) {
this.fieldKeys.push(mdField);
@@ -118,6 +215,11 @@ export class DsoEditMetadataForm {
this.fields[mdField].push(value);
}
/**
* Remove a value from a metadata field on a given index (this actually removes the value, not just marking it deleted)
* @param mdField
* @param index
*/
remove(mdField: string, index: number) {
if (isNotEmpty(this.fields[mdField])) {
this.fields[mdField].splice(index, 1);
@@ -128,10 +230,17 @@ export class DsoEditMetadataForm {
}
}
/**
* Returns if at least one value within the form contains a change
*/
hasChanges(): boolean {
return Object.values(this.fields).some((values) => values.some((value) => value.hasChanges()));
}
/**
* Discard all changes within the form and store their current values within re-instatable properties so they can be
* undone afterwards
*/
discard(): void {
Object.entries(this.fields).forEach(([field, values]) => {
let removeFromIndex = -1;
@@ -174,6 +283,9 @@ export class DsoEditMetadataForm {
this.reinstatableNewValues = {};
}
/**
* Returns if at least one value contains a re-instatable property, meaning a discard can be reversed
*/
isReinstatable(): boolean {
return isNotEmpty(this.reinstatableNewValues) ||
Object.values(this.fields)
@@ -181,6 +293,9 @@ export class DsoEditMetadataForm {
.some((value) => value.isReinstatable()));
}
/**
* Get the json PATCH operations for the current changes within this form
*/
getOperations(): Operation[] {
const operations: Operation[] = [];
Object.entries(this.fields).forEach(([field, values]) => {
@@ -211,3 +326,4 @@ export class DsoEditMetadataForm {
return operations;
}
}
/* tslint:enable:max-classes-per-file */

View File

@@ -20,12 +20,12 @@
</div>
<div class="d-flex flex-row ds-field-row ds-header-row">
<div class="lbl-cell">Field</div>
<div class="lbl-cell">{{ dsoType + '.edit.metadata.headers.field' | translate }}</div>
<div class="flex-grow-1">
<div class="d-flex flex-row">
<div class="flex-grow-1 ds-flex-cell ds-value-cell"><b class="dont-break-out preserve-line-breaks">Value</b></div>
<div class="ds-flex-cell ds-lang-cell"><b>Lang</b></div>
<div class="text-center ds-flex-cell ds-edit-cell"><b>Edit</b></div>
<div class="flex-grow-1 ds-flex-cell ds-value-cell"><b class="dont-break-out preserve-line-breaks">{{ dsoType + '.edit.metadata.headers.value' | translate }}</b></div>
<div class="ds-flex-cell ds-lang-cell"><b>{{ dsoType + '.edit.metadata.headers.language' | translate }}</b></div>
<div class="text-center ds-flex-cell ds-edit-cell"><b>{{ dsoType + '.edit.metadata.headers.edit' | translate }}</b></div>
</div>
</div>
</div>
@@ -43,19 +43,19 @@
</div>
<div class="text-center ds-flex-cell ds-edit-cell">
<div class="btn-group edit-field">
<button class="btn btn-outline-success btn-sm ng-star-inserted" ngbTooltip="Confirm changes"
<button class="btn btn-outline-success btn-sm ng-star-inserted" ngbTooltip="{{ dsoType + '.edit.buttons.confirm' | translate }}"
[disabled]="!newMdField || (saving$ | async) || (loadingFieldValidation$ | async)" (click)="setMetadataField()">
<i class="fas fa-check fa-fw"></i>
</button>
<button class="btn btn-outline-danger btn-sm" ngbTooltip="Remove"
<button class="btn btn-outline-danger btn-sm" ngbTooltip="{{ dsoType + '.edit.buttons.remove' | translate }}"
[disabled]="true">
<i class="fas fa-trash-alt fa-fw"></i>
</button>
<button class="btn btn-outline-warning btn-sm" ngbTooltip="Undo changes"
<button class="btn btn-outline-warning btn-sm" ngbTooltip="{{ dsoType + '.edit.buttons.undo' | translate }}"
[disabled]="(saving$ | async) || (loadingFieldValidation$ | async)" (click)="form.newValue = undefined">
<i class="fas fa-undo-alt fa-fw"></i>
</button>
<button class="btn btn-outline-secondary ds-drag-handle btn-sm disabled" ngbTooltip="Drag to reorder"
<button class="btn btn-outline-secondary ds-drag-handle btn-sm disabled" ngbTooltip="{{ dsoType + '.edit.buttons.drag' | translate }}"
[disabled]="true">
<i class="fas fa-grip-vertical fa-fw"></i>
</button>
@@ -82,25 +82,25 @@
</div>
<div class="text-center ds-flex-cell ds-edit-cell">
<div class="btn-group edit-field">
<button class="btn btn-outline-primary btn-sm ng-star-inserted" ngbTooltip="Edit" *ngIf="!mdValue.editing"
<button class="btn btn-outline-primary btn-sm ng-star-inserted" ngbTooltip="{{ dsoType + '.edit.buttons.edit' | translate }}" *ngIf="!mdValue.editing"
[disabled]="mdValue.change === DsoEditMetadataChangeTypeEnum.REMOVE || (saving$ | async)" (click)="mdValue.editing = true">
<i class="fas fa-edit fa-fw"></i>
</button>
<button class="btn btn-outline-success btn-sm ng-star-inserted" ngbTooltip="Confirm changes" *ngIf="mdValue.editing"
<button class="btn btn-outline-success btn-sm ng-star-inserted" ngbTooltip="{{ dsoType + '.edit.buttons.confirm' | translate }}" *ngIf="mdValue.editing"
[disabled]="(saving$ | async)" (click)="mdValue.confirmChanges(); onValueSaved()">
<i class="fas fa-check fa-fw"></i>
</button>
<button class="btn btn-outline-danger btn-sm" ngbTooltip="Remove"
<button class="btn btn-outline-danger btn-sm" ngbTooltip="{{ dsoType + '.edit.buttons.remove' | translate }}"
[disabled]="mdValue.change || mdValue.editing || (saving$ | async)" (click)="mdValue.change = DsoEditMetadataChangeTypeEnum.REMOVE; onValueSaved()">
<i class="fas fa-trash-alt fa-fw"></i>
</button>
<button class="btn btn-outline-warning btn-sm" ngbTooltip="Undo changes"
<button class="btn btn-outline-warning btn-sm" ngbTooltip="{{ dsoType + '.edit.buttons.undo' | translate }}"
[disabled]="(!mdValue.change && !mdValue.editing) || (saving$ | async)" (click)="mdValue.change === DsoEditMetadataChangeTypeEnum.ADD ? form.remove(mdField, idx) : mdValue.discard(); onValueSaved()">
<i class="fas fa-undo-alt fa-fw"></i>
</button>
<!-- TODO: Enable drag -->
<button class="btn btn-outline-secondary ds-drag-handle btn-sm"
[ngClass]="{'disabled': form.fields[mdField].length === 1 || (saving$ | async)}" ngbTooltip="Drag to reorder" [disabled]="form.fields[mdField].length === 1 || (saving$ | async)">
[ngClass]="{'disabled': form.fields[mdField].length === 1 || (saving$ | async)}" ngbTooltip="{{ dsoType + '.edit.buttons.drag' | translate }}" [disabled]="form.fields[mdField].length === 1 || (saving$ | async)">
<i class="fas fa-grip-vertical fa-fw"></i>
</button>
</div>

View File

@@ -1,28 +1,22 @@
import { Component, Injector, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { AlertType } from '../../shared/alert/aletr-type';
import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { DsoEditMetadataChangeType, DsoEditMetadataForm, DsoEditMetadataValue } from './dso-edit-metadata-form';
import { map, switchMap } from 'rxjs/operators';
import { DsoEditMetadataChangeType, DsoEditMetadataForm } from './dso-edit-metadata-form';
import { map } from 'rxjs/operators';
import { ActivatedRoute, Data } from '@angular/router';
import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest';
import { Subscription } from 'rxjs/internal/Subscription';
import { RemoteData } from '../../core/data/remote-data';
import { hasNoValue, hasValue } from '../../shared/empty.util';
import { RegistryService } from '../../core/registry/registry.service';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { Observable } from 'rxjs/internal/Observable';
import { followLink } from '../../shared/utils/follow-link-config.model';
import {
getFirstCompletedRemoteData,
getFirstSucceededRemoteData,
metadataFieldsToString
} from '../../core/shared/operators';
import { UpdateDataService } from '../../core/data/update-data.service';
import { getDataServiceFor } from '../../core/cache/builders/build-decorators';
import { ResourceType } from '../../core/shared/resource-type';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
import { DataService } from '../../core/data/data.service';
import { MetadataFieldSelectorComponent } from './metadata-field-selector/metadata-field-selector.component';
@Component({
@@ -30,19 +24,56 @@ import { MetadataFieldSelectorComponent } from './metadata-field-selector/metada
styleUrls: ['./dso-edit-metadata.component.scss'],
templateUrl: './dso-edit-metadata.component.html',
})
/**
* Component showing a table of all metadata on a DSpaceObject and options to modify them
*/
export class DsoEditMetadataComponent implements OnInit, OnDestroy {
/**
* DSpaceObject to edit metadata for
*/
@Input() dso: DSpaceObject;
/**
* Reference to the component responsible for showing a metadata-field selector
* Used to validate its contents (existing metadata field) before adding a new metadata value
*/
@ViewChild(MetadataFieldSelectorComponent) metadataFieldSelectorComponent: MetadataFieldSelectorComponent;
/**
* Resolved update data-service for the given DSpaceObject (depending on its type, e.g. ItemDataService for an Item)
* Used to send the PATCH request
*/
updateDataService: UpdateDataService<DSpaceObject>;
/**
* Type of the DSpaceObject in String
* Used to resolve i18n messages
*/
dsoType: string;
/**
* A dynamic form object containing all information about the metadata and the changes made to them, see {@link DsoEditMetadataForm}
*/
form: DsoEditMetadataForm;
/**
* The metadata field entered by the user for a new metadata value
*/
newMdField: string;
// Properties determined by the state of the dynamic form, updated by onValueSaved()
isReinstatable: boolean;
hasChanges: boolean;
isEmpty: boolean;
/**
* Whether or not the form is currently being submitted
*/
saving$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
/**
* Whether or not the metadata field is currently being validated
*/
loadingFieldValidation$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
/**
@@ -57,6 +88,10 @@ export class DsoEditMetadataComponent implements OnInit, OnDestroy {
*/
public DsoEditMetadataChangeTypeEnum = DsoEditMetadataChangeType;
/**
* Subscription for updating the current DSpaceObject
* Unsubscribed from in ngOnDestroy()
*/
dsoUpdateSubscription: Subscription;
constructor(protected route: ActivatedRoute,
@@ -65,6 +100,10 @@ export class DsoEditMetadataComponent implements OnInit, OnDestroy {
protected parentInjector: Injector) {
}
/**
* Read the route (or parent route)'s data to retrieve the current DSpaceObject
* After it's retrieved, initialise the data-service and form
*/
ngOnInit(): void {
if (hasNoValue(this.dso)) {
this.dsoUpdateSubscription = observableCombineLatest([this.route.data, this.route.parent.data]).pipe(
@@ -81,6 +120,9 @@ export class DsoEditMetadataComponent implements OnInit, OnDestroy {
}
}
/**
* Initialise (resolve) the data-service for the current DSpaceObject
*/
initDataService(): void {
let type: ResourceType;
if (typeof this.dso.type === 'string') {
@@ -96,17 +138,29 @@ export class DsoEditMetadataComponent implements OnInit, OnDestroy {
this.dsoType = type.value;
}
/**
* Initialise the dynamic form object by passing the DSpaceObject's metadata
* Call onValueSaved() to update the form's state properties
*/
initForm(): void {
this.form = new DsoEditMetadataForm(this.dso.metadata);
this.onValueSaved();
}
/**
* Update the form's state properties
*/
onValueSaved(): void {
this.hasChanges = this.form.hasChanges();
this.isReinstatable = this.form.isReinstatable();
this.isEmpty = Object.keys(this.form.fields).length === 0;
}
/**
* Submit the current changes to the form by retrieving json PATCH operations from the form and sending it to the
* DSpaceObject's data-service
* Display notificiations and reset the form afterwards if successful
*/
submit(): void {
this.saving$.next(true);
this.updateDataService.patch(this.dso, this.form.getOperations()).pipe(
@@ -114,15 +168,23 @@ export class DsoEditMetadataComponent implements OnInit, OnDestroy {
).subscribe((rd: RemoteData<DSpaceObject>) => {
this.saving$.next(false);
if (rd.hasFailed) {
this.notificationsService.error('error', rd.errorMessage);
this.notificationsService.error(this.translateService.instant(`${this.dsoType}.edit.metadata.notifications.error.title`), rd.errorMessage);
} else {
this.notificationsService.success('saved', 'saved');
this.notificationsService.success(
this.translateService.instant(`${this.dsoType}.edit.metadata.notifications.saved.title`),
this.translateService.instant(`${this.dsoType}.edit.metadata.notifications.saved.content`)
);
this.dso = rd.payload;
this.initForm();
}
});
}
/**
* Set the metadata field of the temporary added new metadata value
* This will move the new value to its respective parent metadata field
* Validate the metadata field first
*/
setMetadataField() {
this.loadingFieldValidation$.next(true);
this.metadataFieldSelectorComponent.validate().subscribe((valid) => {
@@ -134,21 +196,33 @@ export class DsoEditMetadataComponent implements OnInit, OnDestroy {
});
}
/**
* Add a new temporary metadata value
*/
add(): void {
this.newMdField = undefined;
this.form.add();
}
/**
* Discard all changes within the current form
*/
discard(): void {
this.form.discard();
this.onValueSaved();
}
/**
* Restore any changes previously discarded from the form
*/
reinstate(): void {
this.form.reinstate();
this.onValueSaved();
}
/**
* Unsubscribe from any open subscriptions
*/
ngOnDestroy() {
if (hasValue(this.dsoUpdateSubscription)) {
this.dsoUpdateSubscription.unsubscribe();

View File

@@ -6,7 +6,7 @@
(focusin)="query$.next(mdField)"
(dsClickOutside)="query$.next(null)"
(click)="$event.stopPropagation();" />
<div class="invalid-feedback show-feedback" *ngIf="showInvalid">Invalid metadata field, please pick an existing one from the suggestions when searching</div>
<div class="invalid-feedback show-feedback" *ngIf="showInvalid">{{ dsoType + '.edit.metadata.metadatafield.invalid' | translate }}</div>
<div class="autocomplete dropdown-menu" [ngClass]="{'show': (mdFieldOptions$ | async)?.length > 0}">
<div class="dropdown-list">
<div *ngFor="let mdFieldOption of (mdFieldOptions$ | async)">

View File

@@ -12,14 +12,14 @@ import {
import { switchMap, debounceTime, distinctUntilChanged, map, tap, take } from 'rxjs/operators';
import { followLink } from '../../../shared/utils/follow-link-config.model';
import {
getAllSucceededRemoteData, getFirstCompletedRemoteData, getFirstSucceededRemoteData,
getAllSucceededRemoteData, getFirstSucceededRemoteData,
metadataFieldsToString
} from '../../../core/shared/operators';
import { Observable } from 'rxjs/internal/Observable';
import { RegistryService } from '../../../core/registry/registry.service';
import { FormControl } from '@angular/forms';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { hasValue, isNotEmpty } from '../../../shared/empty.util';
import { hasValue } from '../../../shared/empty.util';
import { Subscription } from 'rxjs/internal/Subscription';
@Component({
@@ -27,25 +27,83 @@ import { Subscription } from 'rxjs/internal/Subscription';
styleUrls: ['./metadata-field-selector.component.scss'],
templateUrl: './metadata-field-selector.component.html'
})
/**
* Component displaying a searchable input for metadata-fields
*/
export class MetadataFieldSelectorComponent implements OnInit, OnDestroy, AfterViewInit {
/**
* Type of the DSpaceObject
* Used to resolve i18n messages
*/
@Input() dsoType: string;
/**
* The currently entered metadata field
*/
@Input() mdField: string;
/**
* If true, the input will be automatically focussed upon when the component is first loaded
*/
@Input() autofocus = false;
/**
* Emit any changes made to the metadata field
* This will only emit after a debounce takes place to avoid constant emits when the user is typing
*/
@Output() mdFieldChange = new EventEmitter<string>();
/**
* Reference to the metadata-field's input
*/
@ViewChild('mdFieldInput', { static: true }) mdFieldInput: ElementRef;
/**
* List of available metadata field options to choose from, dependent on the current query the user entered
* Shows up in a dropdown below the input
*/
mdFieldOptions$: Observable<string[]>;
/**
* FormControl for the input
*/
public input: FormControl = new FormControl();
/**
* The current query to update mdFieldOptions$ for
* This is controlled by a debounce, to avoid too many requests
*/
query$: BehaviorSubject<string> = new BehaviorSubject<string>(null);
/**
* The amount of time to debounce the query for (in ms)
*/
debounceTime = 300;
/**
* Whether or not the the user just selected a value
* This flag avoids the metadata field from updating twice, which would result in the dropdown opening again right after selecting a value
*/
selectedValueLoading = false;
/**
* Whether or not to show the invalid feedback
* True when validate() is called and the mdField isn't present in the available metadata fields retrieved from the server
*/
showInvalid = false;
/**
* Subscriptions to unsubscribe from on destroy
*/
subs: Subscription[] = [];
constructor(protected registryService: RegistryService) {
}
/**
* Subscribe to any changes made to the input, with a debounce and fire a query, as well as emit the change from this component
* Update the mdFieldOptions$ depending on the query$ fired by querying the server
*/
ngOnInit(): void {
this.subs.push(
this.input.valueChanges.pipe(
@@ -75,10 +133,19 @@ export class MetadataFieldSelectorComponent implements OnInit, OnDestroy, AfterV
);
}
/**
* Focus the input if autofocus is enabled
*/
ngAfterViewInit(): void {
if (this.autofocus) {
this.mdFieldInput.nativeElement.focus();
}
}
/**
* Validate the metadata field to check if it exists on the server and return an observable boolean for success/error
* Upon subscribing to the returned observable, the showInvalid flag is updated accordingly to show the feedback under the input
*/
validate(): Observable<boolean> {
return this.registryService.queryMetadataFields(this.mdField, null, true, false, followLink('schema')).pipe(
getFirstSucceededRemoteData(),
@@ -89,11 +156,18 @@ export class MetadataFieldSelectorComponent implements OnInit, OnDestroy, AfterV
);
}
/**
* Select a metadata field from the dropdown optipons
* @param mdFieldOption
*/
select(mdFieldOption: string) {
this.selectedValueLoading = true;
this.input.setValue(mdFieldOption);
}
/**
* Unsubscribe from any open subscriptions
*/
ngOnDestroy(): void {
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
}

View File

@@ -7,7 +7,6 @@ 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 { ItemStatusComponent } from './item-status/item-status.component';
import { ItemMetadataComponent } from './item-metadata/item-metadata.component';
import { ItemBitstreamsComponent } from './item-bitstreams/item-bitstreams.component';
import { ItemCollectionMapperComponent } from './item-collection-mapper/item-collection-mapper.component';
import { ItemMoveComponent } from './item-move/item-move.component';

View File

@@ -1791,6 +1791,10 @@
"item.edit.metadata.discard-button": "Discard",
"item.edit.metadata.edit.buttons.confirm": "Confirm",
"item.edit.metadata.edit.buttons.drag": "Drag to reorder",
"item.edit.metadata.edit.buttons.edit": "Edit",
"item.edit.metadata.edit.buttons.remove": "Remove",