diff --git a/src/core/features/block/components/side-blocks-button/side-blocks-button.ts b/src/core/features/block/components/side-blocks-button/side-blocks-button.ts index 74027b05a..0290aa29e 100644 --- a/src/core/features/block/components/side-blocks-button/side-blocks-button.ts +++ b/src/core/features/block/components/side-blocks-button/side-blocks-button.ts @@ -40,6 +40,8 @@ export class CoreBlockSideBlocksButtonComponent implements OnInit, OnDestroy { component: CoreBlockSideBlocksTourComponent, side: CoreUserToursSide.Start, alignment: CoreUserToursAlignment.Center, + after: 'user-menu', + afterTimeout: 1000, getFocusedElement: nativeButton => { const innerButton = Array.from(nativeButton.shadowRoot?.children ?? []).find(child => child.tagName === 'BUTTON'); diff --git a/src/core/features/usertours/services/user-tours.ts b/src/core/features/usertours/services/user-tours.ts index a098732cb..2dfd3fde6 100644 --- a/src/core/features/usertours/services/user-tours.ts +++ b/src/core/features/usertours/services/user-tours.ts @@ -26,6 +26,7 @@ import { CoreDom } from '@singletons/dom'; 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'; +import { CorePromisedValue } from '@classes/promised-value'; /** * Service to manage User Tours. @@ -35,6 +36,7 @@ export class CoreUserToursService { protected table = asyncInstance>(); protected tours: { component: CoreUserToursUserTourComponent; visible: boolean }[] = []; + protected toursListeners: Partial[]>> = {}; /** * Initialize database. @@ -114,6 +116,8 @@ export class CoreUserToursService { await CoreUtils.wait(delay ?? 200); + options.after && await this.waitForUserTour(options.after, options.afterTimeout); + const container = document.querySelector('ion-app') ?? document.body; const viewContainer = container.querySelector('ion-router-outlet, ion-nav, #ion-view-container-root'); const element = await AngularFrameworkDelegate.attachViewToDom( @@ -125,6 +129,8 @@ export class CoreUserToursService { viewContainer?.setAttribute('aria-hidden', 'true'); + this.toursListeners[options.id]?.forEach(listener => listener.resolve()); + return this.startTour(tour, options.watch ?? (options as CoreUserToursFocusedOptions).focus); } @@ -298,7 +304,7 @@ export class CoreUserToursService { * Is user Tour disabled? * * @param tourId Tour Id or undefined to check all user tours. - * @returns Wether a particular or all user tours are disabled. + * @returns Whether a particular or all user tours are disabled. */ isDisabled(tourId?: string): boolean { if (CoreConstants.CONFIG.disableUserTours) { @@ -319,6 +325,30 @@ export class CoreUserToursService { await this.table.delete(); } + /** + * Wait for another user tour to be shown. + * + * @param id Id of the user tour to wait for. + * @param timeout Timeout after which the waiting will end regardless of the user tour having been shown or not. + */ + async waitForUserTour(id: string, timeout?: number): Promise { + const listener = new CorePromisedValue(); + const listeners = this.toursListeners[id] = this.toursListeners[id] ?? []; + const removeListener = () => { + const index = listeners.indexOf(listener); + + if (index !== -1) { + listeners.splice(index, 1); + } + }; + + listeners.push(listener); + listener.then(removeListener).catch(removeListener); + timeout && setTimeout(() => listener.resolve(), timeout); + + await listener; + } + } export const CoreUserTours = makeSingleton(CoreUserToursService); @@ -329,8 +359,8 @@ export const CoreUserTours = makeSingleton(CoreUserToursService); export interface CoreUserToursUserTour { /** - * Cancelling a User Tours removed it from the queue if it was pending or dimisses it without - * acknowledging if it existed. + * Cancelling a User Tour removes it from the queue if it was pending or dismisses it without + * acknowledging. */ cancel(): Promise; @@ -391,6 +421,16 @@ export interface CoreUserToursBasicOptions { */ watch?: HTMLElement | false; + /** + * Whether to show this user tour only after another user tour with the given id has been shown. + */ + after?: string; + + /** + * Whether to show this user tour after the given timeout (in milliseconds) if the other user-tour hasn't been shown. + */ + afterTimeout?: number; + } /**