mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
63945: Edit bitstream tab intermediate commit
This commit is contained in:
@@ -269,6 +269,24 @@
|
|||||||
"content": "Your changes to this item's metadata were saved."
|
"content": "Your changes to this item's metadata were saved."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"bitstreams": {
|
||||||
|
"discard-button": "Discard",
|
||||||
|
"reinstate-button": "Undo",
|
||||||
|
"save-button": "Save",
|
||||||
|
"headers": {
|
||||||
|
"name": "Name",
|
||||||
|
"description": "Description",
|
||||||
|
"format": "Format",
|
||||||
|
"actions": "Actions",
|
||||||
|
"bundle": "Bundle"
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"buttons": {
|
||||||
|
"remove": "Remove",
|
||||||
|
"undo": "Undo changes"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -15,6 +15,7 @@ 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';
|
import { ItemBitstreamsComponent } from './item-bitstreams/item-bitstreams.component';
|
||||||
|
import { ItemEditBitstreamComponent } from './item-bitstreams/item-edit-bitstream/item-edit-bitstream.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
|
||||||
@@ -38,7 +39,8 @@ import { ItemBitstreamsComponent } from './item-bitstreams/item-bitstreams.compo
|
|||||||
ItemStatusComponent,
|
ItemStatusComponent,
|
||||||
ItemMetadataComponent,
|
ItemMetadataComponent,
|
||||||
ItemBitstreamsComponent,
|
ItemBitstreamsComponent,
|
||||||
EditInPlaceFieldComponent
|
EditInPlaceFieldComponent,
|
||||||
|
ItemEditBitstreamComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class EditItemPageModule {
|
export class EditItemPageModule {
|
||||||
|
@@ -1,3 +1,71 @@
|
|||||||
<div>
|
<div class="item-bitstreams">
|
||||||
|
<div class="button-row top d-flex">
|
||||||
|
<button class="btn btn-danger ml-auto" *ngIf="!(isReinstatable() | async)"
|
||||||
|
[disabled]="!(hasChanges() | async)"
|
||||||
|
(click)="discard()"><i
|
||||||
|
class="fas fa-times"></i>
|
||||||
|
<span class="d-none d-sm-inline"> {{"item.edit.bitstreams.discard-button" | translate}}</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-warning ml-auto" *ngIf="isReinstatable() | async"
|
||||||
|
(click)="reinstate()"><i
|
||||||
|
class="fas fa-undo-alt"></i>
|
||||||
|
<span class="d-none d-sm-inline"> {{"item.edit.bitstreams.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.bitstreams.save-button" | translate}}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table table-responsive table-striped table-bordered">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>{{'item.edit.bitstreams.headers.name' | translate}}</th>
|
||||||
|
<th>{{'item.edit.bitstreams.headers.description' | translate}}</th>
|
||||||
|
<th class="text-center">{{'item.edit.bitstreams.headers.format' | translate}}</th>
|
||||||
|
<th class="text-center">{{'item.edit.bitstreams.headers.actions' | translate}}</th>
|
||||||
|
</tr>
|
||||||
|
<ng-container *ngFor="let updatesItem of updatesMap | keyvalue">
|
||||||
|
<tr>
|
||||||
|
<th>{{'item.edit.bitstreams.headers.bundle' | translate}}: {{ updatesItem.key }}</th>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<ng-container *ngVar="((updatesItem.value | async) | dsObjectValues) as updateValues">
|
||||||
|
<tr *ngFor="let updateValue of updateValues"
|
||||||
|
ds-item-edit-bitstream
|
||||||
|
[fieldUpdate]="updateValue"
|
||||||
|
[url]="url"
|
||||||
|
[ngClass]="{
|
||||||
|
'table-warning': updateValue.changeType === 0,
|
||||||
|
'table-danger': updateValue.changeType === 2,
|
||||||
|
'table-success': updateValue.changeType === 1
|
||||||
|
}">
|
||||||
|
</tr>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="button-row bottom">
|
||||||
|
<div class="float-right">
|
||||||
|
<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.bitstreams.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.bitstreams.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.bitstreams.save-button" | translate}}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,4 +1,11 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component, OnDestroy } from '@angular/core';
|
||||||
|
import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component';
|
||||||
|
import { switchMap, take } from 'rxjs/operators';
|
||||||
|
import { Bitstream } from '../../../core/shared/bitstream.model';
|
||||||
|
import { getBundleNames, toBitstreamsArray, toBundleMap } from '../../../core/shared/item-bitstreams-utils';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer';
|
||||||
|
import { Subscription } from 'rxjs/internal/Subscription';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-item-bitstreams',
|
selector: 'ds-item-bitstreams',
|
||||||
@@ -8,6 +15,56 @@ import { Component } from '@angular/core';
|
|||||||
/**
|
/**
|
||||||
* Component for displaying an item's bitstreams edit page
|
* Component for displaying an item's bitstreams edit page
|
||||||
*/
|
*/
|
||||||
export class ItemBitstreamsComponent {
|
export class ItemBitstreamsComponent extends AbstractItemUpdateComponent implements OnDestroy {
|
||||||
/* TODO implement */
|
|
||||||
|
bundleNames$: Observable<string[]>;
|
||||||
|
|
||||||
|
updatesMap: Map<string, Observable<FieldUpdates>>;
|
||||||
|
|
||||||
|
updatesMapSub: Subscription;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up and initialize all fields
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
super.ngOnInit();
|
||||||
|
this.bundleNames$ = this.item.bitstreams.pipe(getBundleNames());
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeNotificationsPrefix(): void {
|
||||||
|
this.notificationsPrefix = 'item.edit.bitstreams.notifications.';
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeOriginalFields(): void {
|
||||||
|
this.item.bitstreams.pipe(
|
||||||
|
toBitstreamsArray(),
|
||||||
|
take(1)
|
||||||
|
).subscribe((bitstreams: Bitstream[]) => {
|
||||||
|
this.objectUpdatesService.initialize(this.url, bitstreams, this.item.lastModified);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeUpdates(): void {
|
||||||
|
this.updates$ = this.item.bitstreams.pipe(
|
||||||
|
toBitstreamsArray(),
|
||||||
|
switchMap((bitstreams: Bitstream[]) => this.objectUpdatesService.getFieldUpdates(this.url, bitstreams))
|
||||||
|
);
|
||||||
|
this.updatesMapSub = this.item.bitstreams.pipe(
|
||||||
|
toBundleMap()
|
||||||
|
).subscribe((bundleMap: Map<string, Bitstream[]>) => {
|
||||||
|
const updatesMap = new Map();
|
||||||
|
bundleMap.forEach((bitstreams: Bitstream[], bundleName: string) => {
|
||||||
|
updatesMap.set(bundleName, this.objectUpdatesService.getFieldUpdatesExclusive(this.url, bitstreams));
|
||||||
|
});
|
||||||
|
this.updatesMap = updatesMap;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
submit() {
|
||||||
|
// TODO: submit changes
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.updatesMapSub.unsubscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,23 @@
|
|||||||
|
<td>
|
||||||
|
{{ bitstream.name }}
|
||||||
|
</td>
|
||||||
|
<td class="w-100">
|
||||||
|
{{ bitstream.description }}
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
{{ (format$ | async).shortDescription }}
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<div class="btn-group relationship-action-buttons">
|
||||||
|
<button [disabled]="!canRemove()" (click)="remove()"
|
||||||
|
class="btn btn-outline-danger btn-sm"
|
||||||
|
title="{{'item.edit.bitstreams.edit.buttons.remove' | translate}}">
|
||||||
|
<i class="fas fa-trash-alt fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
<button [disabled]="!canUndo()" (click)="undo()"
|
||||||
|
class="btn btn-outline-warning btn-sm"
|
||||||
|
title="{{'item.edit.bitstreams.edit.buttons.undo' | translate}}">
|
||||||
|
<i class="fas fa-undo-alt fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
@@ -0,0 +1,64 @@
|
|||||||
|
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||||
|
import { FieldUpdate } from '../../../../core/data/object-updates/object-updates.reducer';
|
||||||
|
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
||||||
|
import { cloneDeep } from 'lodash';
|
||||||
|
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
|
||||||
|
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
|
||||||
|
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
// tslint:disable-next-line:component-selector
|
||||||
|
selector: '[ds-item-edit-bitstream]',
|
||||||
|
templateUrl: './item-edit-bitstream.component.html',
|
||||||
|
})
|
||||||
|
export class ItemEditBitstreamComponent implements OnChanges {
|
||||||
|
@Input() fieldUpdate: FieldUpdate;
|
||||||
|
|
||||||
|
@Input() url: string;
|
||||||
|
|
||||||
|
bitstream: Bitstream;
|
||||||
|
|
||||||
|
format$: Observable<BitstreamFormat>;
|
||||||
|
|
||||||
|
constructor(private objectUpdatesService: ObjectUpdatesService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
this.bitstream = cloneDeep(this.fieldUpdate.field) as Bitstream;
|
||||||
|
this.format$ = this.bitstream.format.pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a new remove update for this field to the object updates service
|
||||||
|
*/
|
||||||
|
remove(): void {
|
||||||
|
this.objectUpdatesService.saveRemoveFieldUpdate(this.url, this.bitstream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the current update for this field in the object updates service
|
||||||
|
*/
|
||||||
|
undo(): void {
|
||||||
|
this.objectUpdatesService.removeSingleFieldUpdate(this.url, this.bitstream.uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a user should be allowed to remove this field
|
||||||
|
*/
|
||||||
|
canRemove(): boolean {
|
||||||
|
return this.fieldUpdate.changeType !== FieldChangeType.REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a user should be allowed to cancel the update to this field
|
||||||
|
*/
|
||||||
|
canUndo(): boolean {
|
||||||
|
return this.fieldUpdate.changeType >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -105,6 +105,27 @@ export class ObjectUpdatesService {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method that combines the state's updates (excluding updates that aren't part of the initialFields) with
|
||||||
|
* the initial values (when there's no update) to create a FieldUpdates object
|
||||||
|
* @param url The URL of the page for which the FieldUpdates should be requested
|
||||||
|
* @param initialFields The initial values of the fields
|
||||||
|
*/
|
||||||
|
getFieldUpdatesExclusive(url: string, initialFields: Identifiable[]): Observable<FieldUpdates> {
|
||||||
|
const objectUpdates = this.getObjectEntry(url);
|
||||||
|
return objectUpdates.pipe(map((objectEntry) => {
|
||||||
|
const fieldUpdates: FieldUpdates = {};
|
||||||
|
for (const object of initialFields) {
|
||||||
|
let fieldUpdate = objectEntry.fieldUpdates[object.uuid];
|
||||||
|
if (isEmpty(fieldUpdate)) {
|
||||||
|
fieldUpdate = { field: object, changeType: undefined };
|
||||||
|
}
|
||||||
|
fieldUpdates[object.uuid] = fieldUpdate;
|
||||||
|
}
|
||||||
|
return fieldUpdates;
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to check if a specific field is currently editable in the store
|
* Method to check if a specific field is currently editable in the store
|
||||||
* @param url The URL of the page on which the field resides
|
* @param url The URL of the page on which the field resides
|
||||||
|
50
src/app/core/shared/item-bitstreams-utils.ts
Normal file
50
src/app/core/shared/item-bitstreams-utils.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { RemoteData } from '../data/remote-data';
|
||||||
|
import { PaginatedList } from '../data/paginated-list';
|
||||||
|
import { Bitstream } from './bitstream.model';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { combineLatest as observableCombineLatest } from 'rxjs';
|
||||||
|
import { getSucceededRemoteData } from './operators';
|
||||||
|
|
||||||
|
export const toBitstreamsArray = () =>
|
||||||
|
(source: Observable<RemoteData<PaginatedList<Bitstream>>>): Observable<Bitstream[]> =>
|
||||||
|
source.pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
map((bitstreamRD: RemoteData<PaginatedList<Bitstream>>) => bitstreamRD.payload.page)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getBundleNames = () =>
|
||||||
|
(source: Observable<RemoteData<PaginatedList<Bitstream>>>): Observable<string[]> =>
|
||||||
|
source.pipe(
|
||||||
|
toBitstreamsArray(),
|
||||||
|
map((bitstreams: Bitstream[]) => {
|
||||||
|
const result = [];
|
||||||
|
bitstreams.forEach((bitstream: Bitstream) => {
|
||||||
|
if (result.indexOf(bitstream.bundleName) < 0) {
|
||||||
|
result.push(bitstream.bundleName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export const filterByBundleName = (bundleName: string) =>
|
||||||
|
(source: Observable<RemoteData<PaginatedList<Bitstream>>>): Observable<Bitstream[]> =>
|
||||||
|
source.pipe(
|
||||||
|
toBitstreamsArray(),
|
||||||
|
map((bitstreams: Bitstream[]) =>
|
||||||
|
bitstreams.filter((bitstream: Bitstream) => bitstream.bundleName === bundleName)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const toBundleMap = () =>
|
||||||
|
(source: Observable<RemoteData<PaginatedList<Bitstream>>>): Observable<Map<string, Bitstream[]>> =>
|
||||||
|
observableCombineLatest(source.pipe(toBitstreamsArray()), source.pipe(getBundleNames())).pipe(
|
||||||
|
map(([bitstreams, bundleNames]) => {
|
||||||
|
const bundleMap = new Map();
|
||||||
|
bundleNames.forEach((bundleName: string) => {
|
||||||
|
bundleMap.set(bundleName, bitstreams.filter((bitstream: Bitstream) => bitstream.bundleName === bundleName));
|
||||||
|
});
|
||||||
|
return bundleMap;
|
||||||
|
})
|
||||||
|
);
|
Reference in New Issue
Block a user