Merge pull request #3127 from alexandrevryghem/w2p-113560_edit-item-add-relationships-one-by-one_contribute-main

[Port main] Remove Bitstreams on edit-item-page doesn't work
This commit is contained in:
Tim Donohue
2024-06-18 12:11:52 -05:00
committed by GitHub
7 changed files with 63 additions and 53 deletions

View File

@@ -24,11 +24,13 @@ export const ROUTES: Route[] = [
{ {
// Resolve XMLUI bitstream download URLs // Resolve XMLUI bitstream download URLs
path: 'handle/:prefix/:suffix/:filename', path: 'handle/:prefix/:suffix/:filename',
component: BitstreamDownloadPageComponent,
canActivate: [legacyBitstreamURLRedirectGuard], canActivate: [legacyBitstreamURLRedirectGuard],
}, },
{ {
// Resolve JSPUI bitstream download URLs // Resolve JSPUI bitstream download URLs
path: ':prefix/:suffix/:sequence_id/:filename', path: ':prefix/:suffix/:sequence_id/:filename',
component: BitstreamDownloadPageComponent,
canActivate: [legacyBitstreamURLRedirectGuard], canActivate: [legacyBitstreamURLRedirectGuard],
}, },
{ {

View File

@@ -1,18 +1,18 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="d-inline-block float-right space-children-mr"> <div class="d-inline-block float-right space-children-mr">
<button class=" btn btn-danger" *ngIf="(isReinstatable() | async) !== true" <button class=" btn btn-danger" *ngIf="(isReinstatable$ | async) !== true"
[disabled]="(hasChanges() | async) !== true" [disabled]="(hasChanges$ | async) !== true"
(click)="discard()"><i (click)="discard()"><i
class="fas fa-times"></i> class="fas fa-times"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.discard-button" | translate}}</span> <span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.discard-button" | translate}}</span>
</button> </button>
<button class="btn btn-warning" *ngIf="isReinstatable() | async" <button class="btn btn-warning" *ngIf="isReinstatable$ | async"
(click)="reinstate()"><i (click)="reinstate()"><i
class="fas fa-undo-alt"></i> class="fas fa-undo-alt"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.reinstate-button" | translate}}</span> <span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.reinstate-button" | translate}}</span>
</button> </button>
<button class="btn btn-primary" <button class="btn btn-primary"
[disabled]="(hasChanges() | async) !== true || !isValid() || (initialHarvestType === harvestTypeNone && contentSource.harvestType === initialHarvestType)" [disabled]="(hasChanges$ | async) !== true || !isValid() || (initialHarvestType === harvestTypeNone && contentSource.harvestType === initialHarvestType)"
(click)="onSubmit()"><i (click)="onSubmit()"><i
class="fas fa-save"></i> class="fas fa-save"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.save-button" | translate}}</span> <span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.save-button" | translate}}</span>
@@ -44,19 +44,19 @@
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="d-inline-block float-right ml-1 space-children-mr"> <div class="d-inline-block float-right ml-1 space-children-mr">
<button class=" btn btn-danger" *ngIf="(isReinstatable() | async) !== true" <button class=" btn btn-danger" *ngIf="(isReinstatable$ | async) !== true"
[disabled]="(hasChanges() | async) !== true" [disabled]="(hasChanges$ | async) !== true"
(click)="discard()"><i (click)="discard()"><i
class="fas fa-times"></i> class="fas fa-times"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.discard-button" | translate}}</span> <span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.discard-button" | translate}}</span>
</button> </button>
<button class="btn btn-warning" *ngIf="isReinstatable() | async" <button class="btn btn-warning" *ngIf="isReinstatable$ | async"
(click)="reinstate()"><i (click)="reinstate()"><i
class="fas fa-undo-alt"></i> class="fas fa-undo-alt"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.reinstate-button" | translate}}</span> <span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.reinstate-button" | translate}}</span>
</button> </button>
<button class="btn btn-primary" <button class="btn btn-primary"
[disabled]="(hasChanges() | async) !== true || !isValid() || (initialHarvestType === harvestTypeNone && contentSource.harvestType === initialHarvestType)" [disabled]="(hasChanges$ | async) !== true || !isValid() || (initialHarvestType === harvestTypeNone && contentSource.harvestType === initialHarvestType)"
(click)="onSubmit()"><i (click)="onSubmit()"><i
class="fas fa-save"></i> class="fas fa-save"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.save-button" | translate}}</span> <span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.save-button" | translate}}</span>
@@ -66,7 +66,7 @@
</div> </div>
</div> </div>
<ds-collection-source-controls <ds-collection-source-controls
[isEnabled]="(hasChanges() | async) !== true" [isEnabled]="(hasChanges$ | async) !== true"
[shouldShow]="contentSource?.harvestType !== harvestTypeNone" [shouldShow]="contentSource?.harvestType !== harvestTypeNone"
[collection]="(collectionRD$ |async)?.payload" [collection]="(collectionRD$ |async)?.payload"
> >

View File

@@ -249,11 +249,6 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
*/ */
formGroup: UntypedFormGroup; formGroup: UntypedFormGroup;
/**
* Subscription to update the current form
*/
updateSub: Subscription;
/** /**
* The content harvesting type used when harvesting is disabled * The content harvesting type used when harvesting is disabled
*/ */
@@ -273,28 +268,29 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
*/ */
displayedNotifications: INotification[] = []; displayedNotifications: INotification[] = [];
public constructor(public objectUpdatesService: ObjectUpdatesService, subs: Subscription[] = [];
public constructor(
public objectUpdatesService: ObjectUpdatesService,
public notificationsService: NotificationsService, public notificationsService: NotificationsService,
public translateService: TranslateService,
public router: Router,
protected location: Location, protected location: Location,
protected formService: DynamicFormService, protected formService: DynamicFormService,
protected translate: TranslateService,
protected route: ActivatedRoute, protected route: ActivatedRoute,
protected router: Router,
protected collectionService: CollectionDataService, protected collectionService: CollectionDataService,
protected requestService: RequestService) { protected requestService: RequestService,
super(objectUpdatesService, notificationsService, translate); ) {
super(objectUpdatesService, notificationsService, translateService, router);
} }
/** /**
* Initialize properties to setup the Field Update and Form * Initialize properties to setup the Field Update and Form
*/ */
ngOnInit(): void { ngOnInit(): void {
super.ngOnInit();
this.notificationsPrefix = 'collection.edit.tabs.source.notifications.'; this.notificationsPrefix = 'collection.edit.tabs.source.notifications.';
this.discardTimeOut = environment.collection.edit.undoTimeout; this.discardTimeOut = environment.collection.edit.undoTimeout;
this.url = this.router.url;
if (this.url.indexOf('?') > 0) {
this.url = this.url.substr(0, this.url.indexOf('?'));
}
this.formGroup = this.formService.createFormGroup(this.formModel); this.formGroup = this.formService.createFormGroup(this.formModel);
this.collectionRD$ = this.route.parent.data.pipe(first(), map((data) => data.dso)); this.collectionRD$ = this.route.parent.data.pipe(first(), map((data) => data.dso));
@@ -308,10 +304,9 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
}); });
this.updateFieldTranslations(); this.updateFieldTranslations();
this.translate.onLangChange this.subs.push(this.translateService.onLangChange.subscribe(() => {
.subscribe(() => {
this.updateFieldTranslations(); this.updateFieldTranslations();
}); }));
} }
/** /**
@@ -326,7 +321,7 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
this.update$ = this.objectUpdatesService.getFieldUpdates(this.url, [initialContentSource]).pipe( this.update$ = this.objectUpdatesService.getFieldUpdates(this.url, [initialContentSource]).pipe(
map((updates: FieldUpdates) => updates[initialContentSource.uuid]), map((updates: FieldUpdates) => updates[initialContentSource.uuid]),
); );
this.updateSub = this.update$.subscribe((update: FieldUpdate) => { this.subs.push(this.update$.subscribe((update: FieldUpdate) => {
if (update) { if (update) {
const field = update.field as ContentSource; const field = update.field as ContentSource;
let configId; let configId;
@@ -353,7 +348,7 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
} }
this.contentSource.metadataConfigId = configId; this.contentSource.metadataConfigId = configId;
} }
}); }));
} }
/** /**
@@ -387,18 +382,18 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
* @param fieldModel * @param fieldModel
*/ */
private updateFieldTranslation(fieldModel: DynamicFormControlModel) { private updateFieldTranslation(fieldModel: DynamicFormControlModel) {
fieldModel.label = this.translate.instant(this.LABEL_KEY_PREFIX + fieldModel.id); fieldModel.label = this.translateService.instant(this.LABEL_KEY_PREFIX + fieldModel.id);
if (isNotEmpty(fieldModel.validators)) { if (isNotEmpty(fieldModel.validators)) {
fieldModel.errorMessages = {}; fieldModel.errorMessages = {};
Object.keys(fieldModel.validators).forEach((key) => { Object.keys(fieldModel.validators).forEach((key) => {
fieldModel.errorMessages[key] = this.translate.instant(this.ERROR_KEY_PREFIX + fieldModel.id + '.' + key); fieldModel.errorMessages[key] = this.translateService.instant(this.ERROR_KEY_PREFIX + fieldModel.id + '.' + key);
}); });
} }
if (fieldModel instanceof DynamicOptionControlModel) { if (fieldModel instanceof DynamicOptionControlModel) {
if (isNotEmpty(fieldModel.options)) { if (isNotEmpty(fieldModel.options)) {
fieldModel.options.forEach((option) => { fieldModel.options.forEach((option) => {
if (hasNoValue(option.label)) { if (hasNoValue(option.label)) {
option.label = this.translate.instant(this.OPTIONS_KEY_PREFIX + fieldModel.id + '.' + option.value); option.label = this.translateService.instant(this.OPTIONS_KEY_PREFIX + fieldModel.id + '.' + option.value);
} }
}); });
} }
@@ -515,8 +510,6 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
* Make sure open subscriptions are closed * Make sure open subscriptions are closed
*/ */
ngOnDestroy(): void { ngOnDestroy(): void {
if (this.updateSub) { this.subs.forEach((sub: Subscription) => sub.unsubscribe());
this.updateSub.unsubscribe();
}
} }
} }

View File

@@ -55,10 +55,6 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl
*/ */
updates$: Observable<FieldUpdates>; updates$: Observable<FieldUpdates>;
hasChanges$: Observable<boolean>;
isReinstatable$: Observable<boolean>;
/** /**
* Route to the item's page * Route to the item's page
*/ */
@@ -78,7 +74,7 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl
public translateService: TranslateService, public translateService: TranslateService,
public route: ActivatedRoute, public route: ActivatedRoute,
) { ) {
super(objectUpdatesService, notificationsService, translateService); super(objectUpdatesService, notificationsService, translateService, router);
} }
/** /**
@@ -103,11 +99,9 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl
this.setItem(rd.payload); this.setItem(rd.payload);
}); });
} }
super.ngOnInit();
this.discardTimeOut = environment.item.edit.undoTimeout; this.discardTimeOut = environment.item.edit.undoTimeout;
this.url = this.router.url.split('?')[0];
this.hasChanges$ = this.hasChanges();
this.isReinstatable$ = this.isReinstatable();
this.hasChanges().pipe(first()).subscribe((hasChanges) => { this.hasChanges().pipe(first()).subscribe((hasChanges) => {
if (!hasChanges) { if (!hasChanges) {
this.initializeOriginalFields(); this.initializeOriginalFields();

View File

@@ -20,6 +20,7 @@ import {
} from '@ngx-translate/core'; } from '@ngx-translate/core';
import { Operation } from 'fast-json-patch'; import { Operation } from 'fast-json-patch';
import { import {
combineLatest,
Observable, Observable,
Subscription, Subscription,
zip as observableZip, zip as observableZip,
@@ -273,7 +274,7 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
*/ */
isReinstatable(): Observable<boolean> { isReinstatable(): Observable<boolean> {
return this.bundles$.pipe( return this.bundles$.pipe(
switchMap((bundles: Bundle[]) => observableZip(...bundles.map((bundle: Bundle) => this.objectUpdatesService.isReinstatable(bundle.self)))), switchMap((bundles: Bundle[]) => combineLatest(bundles.map((bundle: Bundle) => this.objectUpdatesService.isReinstatable(bundle.self)))),
map((reinstatable: boolean[]) => reinstatable.includes(true)), map((reinstatable: boolean[]) => reinstatable.includes(true)),
); );
} }
@@ -283,7 +284,7 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
*/ */
hasChanges(): Observable<boolean> { hasChanges(): Observable<boolean> {
return this.bundles$.pipe( return this.bundles$.pipe(
switchMap((bundles: Bundle[]) => observableZip(...bundles.map((bundle: Bundle) => this.objectUpdatesService.hasUpdates(bundle.self)))), switchMap((bundles: Bundle[]) => combineLatest(bundles.map((bundle: Bundle) => this.objectUpdatesService.hasUpdates(bundle.self)))),
map((hasChanges: boolean[]) => hasChanges.includes(true)), map((hasChanges: boolean[]) => hasChanges.includes(true)),
); );
} }

View File

@@ -4,6 +4,7 @@ import {
TestBed, TestBed,
waitForAsync, waitForAsync,
} from '@angular/core/testing'; } from '@angular/core/testing';
import { Router } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { getTestScheduler } from 'jasmine-marbles'; import { getTestScheduler } from 'jasmine-marbles';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
@@ -16,6 +17,7 @@ import {
} from '../notifications/models/notification.model'; } from '../notifications/models/notification.model';
import { NotificationType } from '../notifications/models/notification-type'; import { NotificationType } from '../notifications/models/notification-type';
import { NotificationsService } from '../notifications/notifications.service'; import { NotificationsService } from '../notifications/notifications.service';
import { RouterStub } from '../testing/router.stub';
import { AbstractTrackableComponent } from './abstract-trackable.component'; import { AbstractTrackableComponent } from './abstract-trackable.component';
describe('AbstractTrackableComponent', () => { describe('AbstractTrackableComponent', () => {
@@ -35,6 +37,7 @@ describe('AbstractTrackableComponent', () => {
success: successNotification, success: successNotification,
}, },
); );
let router: RouterStub;
const url = 'http://test-url.com/test-url'; const url = 'http://test-url.com/test-url';
@@ -50,6 +53,8 @@ describe('AbstractTrackableComponent', () => {
isValidPage: observableOf(true), isValidPage: observableOf(true),
}, },
); );
router = new RouterStub();
router.url = url;
scheduler = getTestScheduler(); scheduler = getTestScheduler();
@@ -58,6 +63,7 @@ describe('AbstractTrackableComponent', () => {
providers: [ providers: [
{ provide: ObjectUpdatesService, useValue: objectUpdatesService }, { provide: ObjectUpdatesService, useValue: objectUpdatesService },
{ provide: NotificationsService, useValue: notificationsService }, { provide: NotificationsService, useValue: notificationsService },
{ provide: Router, useValue: router },
], schemas: [ ], schemas: [
NO_ERRORS_SCHEMA, NO_ERRORS_SCHEMA,
], ],
@@ -67,7 +73,6 @@ describe('AbstractTrackableComponent', () => {
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(AbstractTrackableComponent); fixture = TestBed.createComponent(AbstractTrackableComponent);
comp = fixture.componentInstance; comp = fixture.componentInstance;
comp.url = url;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -1,4 +1,8 @@
import { Component } from '@angular/core'; import {
Component,
OnInit,
} from '@angular/core';
import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@@ -13,7 +17,7 @@ import { NotificationsService } from '../notifications/notifications.service';
template: '', template: '',
standalone: true, standalone: true,
}) })
export class AbstractTrackableComponent { export class AbstractTrackableComponent implements OnInit {
/** /**
* The time span for being able to undo discarding changes * The time span for being able to undo discarding changes
@@ -23,14 +27,25 @@ export class AbstractTrackableComponent {
public url: string; public url: string;
public notificationsPrefix = 'static-pages.form.notification'; public notificationsPrefix = 'static-pages.form.notification';
hasChanges$: Observable<boolean>;
isReinstatable$: Observable<boolean>;
constructor( constructor(
public objectUpdatesService: ObjectUpdatesService, public objectUpdatesService: ObjectUpdatesService,
public notificationsService: NotificationsService, public notificationsService: NotificationsService,
public translateService: TranslateService, public translateService: TranslateService,
public router: Router,
) { ) {
} }
ngOnInit(): void {
this.url = this.router.url.split('?')[0];
this.hasChanges$ = this.hasChanges();
this.isReinstatable$ = this.isReinstatable();
}
/** /**
* Request the object updates service to discard all current changes to this item * Request the object updates service to discard all current changes to this item
* Shows a notification to remind the user that they can undo this * Shows a notification to remind the user that they can undo this