diff --git a/src/app/app.component.ts b/src/app/app.component.ts index db217ce161..8ef569e416 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -21,7 +21,7 @@ import { import { isEqual } from 'lodash'; import { BehaviorSubject, Observable, of } from 'rxjs'; import { select, Store } from '@ngrx/store'; -import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { NgbModal, NgbModalConfig } from '@ng-bootstrap/ng-bootstrap'; import { TranslateService } from '@ngx-translate/core'; import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; @@ -48,6 +48,7 @@ import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service'; import { IdleModalComponent } from './shared/idle-modal/idle-modal.component'; import { getDefaultThemeConfig } from '../config/config.util'; import { AppConfig, APP_CONFIG } from 'src/config/app-config.interface'; +import { ModalBeforeDismiss } from './shared/interfaces/modal-before-dismiss.interface'; @Component({ selector: 'ds-app', @@ -105,6 +106,7 @@ export class AppComponent implements OnInit, AfterViewInit { private localeService: LocaleService, private breadcrumbsService: BreadcrumbsService, private modalService: NgbModal, + private modalConfig: NgbModalConfig, @Optional() private cookiesService: KlaroService, @Optional() private googleAnalyticsService: GoogleAnalyticsService, ) { @@ -165,6 +167,16 @@ export class AppComponent implements OnInit, AfterViewInit { } ngOnInit() { + /** Implement behavior for interface {@link ModalBeforeDismiss} */ + this.modalConfig.beforeDismiss = async function () { + if (typeof this?.componentInstance?.beforeDismiss === 'function') { + return this.componentInstance.beforeDismiss() + } + + // fall back to default behavior + return true; + } + this.isAuthBlocking$ = this.store.pipe(select(isAuthenticationBlocking)).pipe( distinctUntilChanged() ); diff --git a/src/app/item-page/simple/item-types/versioned-item/versioned-item.component.ts b/src/app/item-page/simple/item-types/versioned-item/versioned-item.component.ts index cd2eb3a19b..7f61cee10b 100644 --- a/src/app/item-page/simple/item-types/versioned-item/versioned-item.component.ts +++ b/src/app/item-page/simple/item-types/versioned-item/versioned-item.component.ts @@ -62,6 +62,8 @@ export class VersionedItemComponent extends ItemComponent { activeModal.componentInstance.createVersionEvent.pipe( switchMap((summary: string) => this.versionHistoryService.createVersion(item._links.self.href, summary)), getFirstCompletedRemoteData(), + // close model (should be displaying loading/waiting indicator) when version creation failed/succeeded + tap(() => activeModal.close()), // show success/failure notification tap((res: RemoteData) => { this.itemVersionShared.notifyCreateNewVersion(res); }), // get workspace item diff --git a/src/app/shared/interfaces/modal-before-dismiss.interface.ts b/src/app/shared/interfaces/modal-before-dismiss.interface.ts new file mode 100644 index 0000000000..c07f8d329f --- /dev/null +++ b/src/app/shared/interfaces/modal-before-dismiss.interface.ts @@ -0,0 +1,25 @@ +import { NgbModalConfig, NgbModal } from '@ng-bootstrap/ng-bootstrap'; + +/** + * If a component implementing this interface is used to create a modal (i.e. it is passed to {@link NgbModal#open}), + * and that modal is dismissed, then {@link #beforeDismiss} will be called. + * + * This behavior is implemented for the whole app, by setting a default value for {@link NgbModalConfig#beforeDismiss} + * in {@link AppComponent}. + * + * Docs: https://ng-bootstrap.github.io/#/components/modal/api + */ +export interface ModalBeforeDismiss { + + /** + * Callback right before the modal will be dismissed. + * If this function returns: + * - false + * - a promise resolved with false + * - a promise that is rejected + * then the modal won't be dismissed. + * Docs: https://ng-bootstrap.github.io/#/components/modal/api#NgbModalOptions + */ + beforeDismiss(): boolean | Promise + +} diff --git a/src/app/shared/item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component.html b/src/app/shared/item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component.html index a083679108..0aa22f80cf 100644 --- a/src/app/shared/item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component.html +++ b/src/app/shared/item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component.html @@ -1,4 +1,4 @@ -
+
+ + + + + diff --git a/src/app/shared/item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component.ts b/src/app/shared/item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component.ts index 31bb3078c0..e20737ee57 100644 --- a/src/app/shared/item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component.ts +++ b/src/app/shared/item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component.ts @@ -1,16 +1,19 @@ -import { Component, EventEmitter, Output } from '@angular/core'; +import { Component, EventEmitter, OnInit, Output } from '@angular/core'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { BehaviorSubject } from 'rxjs'; +import { ModalBeforeDismiss } from '../../../interfaces/modal-before-dismiss.interface'; @Component({ selector: 'ds-item-versions-summary-modal', templateUrl: './item-versions-summary-modal.component.html', styleUrls: ['./item-versions-summary-modal.component.scss'] }) -export class ItemVersionsSummaryModalComponent { +export class ItemVersionsSummaryModalComponent implements OnInit, ModalBeforeDismiss { versionNumber: number; newVersionSummary: string; firstVersion = true; + submitted$: BehaviorSubject @Output() createVersionEvent: EventEmitter = new EventEmitter(); @@ -19,13 +22,24 @@ export class ItemVersionsSummaryModalComponent { ) { } + ngOnInit() { + this.submitted$ = new BehaviorSubject(false); + } + onModalClose() { this.activeModal.dismiss(); } + beforeDismiss(): boolean | Promise { + // prevent the modal from being dismissed after version creation is initiated + return !this.submitted$.getValue() + } + onModalSubmit() { this.createVersionEvent.emit(this.newVersionSummary); - this.activeModal.close(); + this.submitted$.next(true); + // NOTE: the caller of this modal is responsible for closing it, + // e.g. after the version creation POST request completed. } } diff --git a/src/app/shared/item/item-versions/item-versions.component.ts b/src/app/shared/item/item-versions/item-versions.component.ts index 2457cf76c4..ef28109870 100644 --- a/src/app/shared/item/item-versions/item-versions.component.ts +++ b/src/app/shared/item/item-versions/item-versions.component.ts @@ -342,6 +342,9 @@ export class ItemVersionsComponent implements OnInit { version.item.pipe(getFirstSucceededRemoteDataPayload()) ])), mergeMap(([summary, item]: [string, Item]) => this.versionHistoryService.createVersion(item._links.self.href, summary)), + getFirstCompletedRemoteData(), + // close model (should be displaying loading/waiting indicator) when version creation failed/succeeded + tap(() => activeModal.close()), // show success/failure notification tap((newVersionRD: RemoteData) => { this.itemVersionShared.notifyCreateNewVersion(newVersionRD); diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index e3ba4019ee..8b9c7f6218 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2233,6 +2233,10 @@ "item.version.create.modal.form.summary.placeholder": "Insert the summary for the new version", + "item.version.create.modal.submitted.header": "Creating new version...", + + "item.version.create.modal.submitted.text": "The new version is being created. This may take some time if the item has a lot of relationships.", + "item.version.create.notification.success" : "New version has been created with version number {{version}}", "item.version.create.notification.failure" : "New version has not been created",