diff --git a/src/core/features/usertours/components/user-tour/user-tour.ts b/src/core/features/usertours/components/user-tour/user-tour.ts index f0853164c..0f0e3d2a1 100644 --- a/src/core/features/usertours/components/user-tour/user-tour.ts +++ b/src/core/features/usertours/components/user-tour/user-tour.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { AfterViewInit, Component, ElementRef, HostBinding, Input, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, ElementRef, EventEmitter, HostBinding, Input, Output, ViewChild } from '@angular/core'; import { CorePromisedValue } from '@classes/promised-value'; import { CoreUserToursFocusLayout } from '@features/usertours/classes/focus-layout'; import { CoreUserToursPopoverLayout } from '@features/usertours/classes/popover-layout'; @@ -42,6 +42,8 @@ export class CoreUserToursUserTourComponent implements AfterViewInit { @Input() focus?: HTMLElement; @Input() side?: CoreUserToursSide; @Input() alignment?: CoreUserToursAlignment; + @Output() beforeDismiss = new EventEmitter(); + @Output() afterDismiss = new EventEmitter(); @HostBinding('class.is-active') active = false; @HostBinding('class.is-popover') popover = false; @ViewChild('wrapper') wrapper?: ElementRef; @@ -80,6 +82,13 @@ export class CoreUserToursUserTourComponent implements AfterViewInit { await CoreDomUtils.waitForImages(tour); + // Calculate focus styles or dismiss if the element is gone. + if (this.focus && !CoreDomUtils.isElementVisible(this.focus)) { + await this.dismiss(false); + + return; + } + this.calculateStyles(); // Show tour. @@ -94,11 +103,15 @@ export class CoreUserToursUserTourComponent implements AfterViewInit { * @param acknowledge Whether to confirm that the user has seen the User Tour. */ async dismiss(acknowledge: boolean = true): Promise { + this.beforeDismiss.emit(); + await this.playLeaveAnimation(); + await Promise.all([ + AngularFrameworkDelegate.removeViewFromDom(this.container, this.element), + acknowledge && CoreUserTours.acknowledge(this.id), + ]); - AngularFrameworkDelegate.removeViewFromDom(this.container, this.element); - - acknowledge && CoreUserTours.acknowledge(this.id); + this.afterDismiss.emit(); } /** diff --git a/src/core/features/usertours/services/user-tours.ts b/src/core/features/usertours/services/user-tours.ts index 9fedfff74..749287f1b 100644 --- a/src/core/features/usertours/services/user-tours.ts +++ b/src/core/features/usertours/services/user-tours.ts @@ -21,6 +21,7 @@ import { CoreApp } from '@services/app'; import { CoreUtils } from '@services/utils/utils'; import { AngularFrameworkDelegate, makeSingleton } from '@singletons'; import { CoreComponentsRegistry } from '@singletons/components-registry'; +import { CoreSubscriptions } from '@singletons/subscriptions'; import { CoreUserToursUserTourComponent } from '../components/user-tour/user-tour'; import { APP_SCHEMA, CoreUserToursDBEntry, USER_TOURS_TABLE_NAME } from './database/user-tours'; @@ -106,8 +107,10 @@ export class CoreUserToursService { protected async show(options: CoreUserToursBasicOptions | CoreUserToursFocusedOptions): Promise { const { delay, ...componentOptions } = options; + // Delay start. await CoreUtils.wait(delay ?? 200); + // Create tour. const container = document.querySelector('ion-app') ?? document.body; const element = await AngularFrameworkDelegate.attachViewToDom( container, @@ -117,6 +120,22 @@ export class CoreUserToursService { const tour = CoreComponentsRegistry.require(element, CoreUserToursUserTourComponent); this.tours.push(tour); + + // Handle present/dismiss lifecycle. + CoreSubscriptions.once(tour.beforeDismiss, () => { + const index = this.tours.indexOf(tour); + + if (index === -1) { + return; + } + + this.tours.splice(index, 1); + + const nextTour = this.tours[0] as CoreUserToursUserTourComponent | undefined; + + nextTour?.present().then(() => this.tourReadyCallbacks.get(nextTour)?.()); + }); + this.tours.length > 1 ? await new Promise(resolve => this.tourReadyCallbacks.set(tour, resolve)) : await tour.present(); @@ -128,19 +147,7 @@ export class CoreUserToursService { * @param acknowledge Whether to acknowledge that the user has seen this User Tour or not. */ async dismiss(acknowledge: boolean = true): Promise { - if (this.tours.length === 0) { - return; - } - - const activeTour = this.tours.shift() as CoreUserToursUserTourComponent; - const nextTour = this.tours[0] as CoreUserToursUserTourComponent | undefined; - - await Promise.all([ - activeTour.dismiss(acknowledge), - nextTour?.present(), - ]); - - nextTour && this.tourReadyCallbacks.get(nextTour)?.(); + await this.tours[0]?.dismiss(acknowledge); } }