From 0db4781814337dd36ae45275b3df30e12bbc240f Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Thu, 17 Mar 2022 09:28:47 +0100 Subject: [PATCH 1/3] MOBILE-3153 usertours: Update design --- .../fonts/moodle/moodle/swipe-navigation.svg | 1 + src/assets/img/user-tours/course-index.svg | 1 - src/assets/img/user-tours/side-blocks.svg | 1 - .../img/user-tours/swipe-navigation.svg | 1 - src/assets/img/user-tours/user-menu.svg | 1 - .../core-swipe-navigation-tour.html | 4 +- .../swipe-navigation-tour.scss | 13 ++++- .../side-blocks-tour/side-blocks-tour.html | 1 - .../side-blocks-tour/side-blocks-tour.scss | 2 +- .../course-index-tour/course-index-tour.html | 1 - .../course-index-tour/course-index-tour.scss | 2 +- .../user-menu-button/user-menu-button.ts | 6 ++- .../user-menu-tour/user-menu-tour.html | 1 - .../user-menu-tour/user-menu-tour.scss | 19 ++----- .../components/user-tour/user-tour.ts | 28 ++++------- .../features/usertours/services/user-tours.ts | 50 ++----------------- 16 files changed, 38 insertions(+), 94 deletions(-) create mode 100644 src/assets/fonts/moodle/moodle/swipe-navigation.svg delete mode 100644 src/assets/img/user-tours/course-index.svg delete mode 100644 src/assets/img/user-tours/side-blocks.svg delete mode 100644 src/assets/img/user-tours/swipe-navigation.svg delete mode 100644 src/assets/img/user-tours/user-menu.svg diff --git a/src/assets/fonts/moodle/moodle/swipe-navigation.svg b/src/assets/fonts/moodle/moodle/swipe-navigation.svg new file mode 100644 index 000000000..e4c38b55b --- /dev/null +++ b/src/assets/fonts/moodle/moodle/swipe-navigation.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/user-tours/course-index.svg b/src/assets/img/user-tours/course-index.svg deleted file mode 100644 index c3ecbf014..000000000 --- a/src/assets/img/user-tours/course-index.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/assets/img/user-tours/side-blocks.svg b/src/assets/img/user-tours/side-blocks.svg deleted file mode 100644 index c2c8d4372..000000000 --- a/src/assets/img/user-tours/side-blocks.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/assets/img/user-tours/swipe-navigation.svg b/src/assets/img/user-tours/swipe-navigation.svg deleted file mode 100644 index ac0c1732c..000000000 --- a/src/assets/img/user-tours/swipe-navigation.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/assets/img/user-tours/user-menu.svg b/src/assets/img/user-tours/user-menu.svg deleted file mode 100644 index 33528d215..000000000 --- a/src/assets/img/user-tours/user-menu.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/core/components/swipe-navigation-tour/core-swipe-navigation-tour.html b/src/core/components/swipe-navigation-tour/core-swipe-navigation-tour.html index e8b94a568..349247d15 100644 --- a/src/core/components/swipe-navigation-tour/core-swipe-navigation-tour.html +++ b/src/core/components/swipe-navigation-tour/core-swipe-navigation-tour.html @@ -1,5 +1,5 @@ - +

{{ 'core.swipenavigationtourdescription' | translate }}

- + {{ 'core.endonesteptour' | translate }} diff --git a/src/core/components/swipe-navigation-tour/swipe-navigation-tour.scss b/src/core/components/swipe-navigation-tour/swipe-navigation-tour.scss index f71e25d12..66877c3df 100644 --- a/src/core/components/swipe-navigation-tour/swipe-navigation-tour.scss +++ b/src/core/components/swipe-navigation-tour/swipe-navigation-tour.scss @@ -1,12 +1,21 @@ :host { max-width: 85vw; - img { - max-width: 300px; + ion-icon { + width: 100%; + height: 150px; + min-width: 300px; } p { text-align: center; } + ion-button { + --border-width: 2px; + --border-color: white; + --background: transparent; + --color: white; + } + } diff --git a/src/core/features/block/components/side-blocks-tour/side-blocks-tour.html b/src/core/features/block/components/side-blocks-tour/side-blocks-tour.html index 47596569a..00793c3ff 100644 --- a/src/core/features/block/components/side-blocks-tour/side-blocks-tour.html +++ b/src/core/features/block/components/side-blocks-tour/side-blocks-tour.html @@ -1,5 +1,4 @@

{{ 'core.block.tour_navigation_dashboard_title' | translate }}

-

{{ 'core.block.tour_navigation_dashboard_content' | translate }}

{{ 'core.endonesteptour' | translate }} diff --git a/src/core/features/block/components/side-blocks-tour/side-blocks-tour.scss b/src/core/features/block/components/side-blocks-tour/side-blocks-tour.scss index b94fabe85..e02e83905 100644 --- a/src/core/features/block/components/side-blocks-tour/side-blocks-tour.scss +++ b/src/core/features/block/components/side-blocks-tour/side-blocks-tour.scss @@ -4,7 +4,7 @@ margin-top: 0; } - p { + h2, p { text-align: center; } diff --git a/src/core/features/course/components/course-index-tour/course-index-tour.html b/src/core/features/course/components/course-index-tour/course-index-tour.html index c519d3152..dbf65101b 100644 --- a/src/core/features/course/components/course-index-tour/course-index-tour.html +++ b/src/core/features/course/components/course-index-tour/course-index-tour.html @@ -1,5 +1,4 @@

{{ 'core.course.tour_navigation_course_index_student_title' | translate }}

-

{{ 'core.course.tour_navigation_course_index_student_content' | translate }}

{{ 'core.endonesteptour' | translate }} diff --git a/src/core/features/course/components/course-index-tour/course-index-tour.scss b/src/core/features/course/components/course-index-tour/course-index-tour.scss index b94fabe85..e02e83905 100644 --- a/src/core/features/course/components/course-index-tour/course-index-tour.scss +++ b/src/core/features/course/components/course-index-tour/course-index-tour.scss @@ -4,7 +4,7 @@ margin-top: 0; } - p { + h2, p { text-align: center; } diff --git a/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts b/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts index ebde9fb45..8c8efb728 100644 --- a/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts +++ b/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts @@ -14,8 +14,9 @@ import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; import { CoreSiteInfo } from '@classes/site'; -import { CoreUserTours, CoreUserToursStyle } from '@features/usertours/services/user-tours'; +import { CoreUserTours, CoreUserToursAlignment, CoreUserToursSide } from '@features/usertours/services/user-tours'; import { IonRouterOutlet } from '@ionic/angular'; +import { CoreScreen } from '@services/screen'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreMainMenuUserMenuTourComponent } from '../user-menu-tour/user-menu-tour'; @@ -77,7 +78,8 @@ export class CoreMainMenuUserButtonComponent implements OnInit { id: 'user-menu', component: CoreMainMenuUserMenuTourComponent, focus: this.avatar.nativeElement, - style: CoreUserToursStyle.Overlay, + alignment: CoreUserToursAlignment.Start, + side: CoreScreen.isMobile ? CoreUserToursSide.Start : CoreUserToursSide.End, }); } diff --git a/src/core/features/mainmenu/components/user-menu-tour/user-menu-tour.html b/src/core/features/mainmenu/components/user-menu-tour/user-menu-tour.html index 697f56fcb..efef95b50 100644 --- a/src/core/features/mainmenu/components/user-menu-tour/user-menu-tour.html +++ b/src/core/features/mainmenu/components/user-menu-tour/user-menu-tour.html @@ -1,4 +1,3 @@ -

{{ 'core.mainmenu.usermenutourtitle' | translate }}

{{ 'core.mainmenu.usermenutourdescription' | translate }}

diff --git a/src/core/features/mainmenu/components/user-menu-tour/user-menu-tour.scss b/src/core/features/mainmenu/components/user-menu-tour/user-menu-tour.scss index faecdd58f..e02e83905 100644 --- a/src/core/features/mainmenu/components/user-menu-tour/user-menu-tour.scss +++ b/src/core/features/mainmenu/components/user-menu-tour/user-menu-tour.scss @@ -1,26 +1,15 @@ :host { - width: 100%; - height: 100%; - display: flex; - max-width: 85vw; - align-items: center; - flex-direction: column; - img { - width: calc(100vw - var(--core-avatar-size) * 2 - 16px); - margin-top: 12px; + h2 { + margin-top: 0; } - p { + h2, p { text-align: center; } ion-button { - width: 100%; + margin: 0; } } - -:host-context([dir=rtl]) img { - transform: scaleX(-1); -} 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 5ac56633f..f0853164c 100644 --- a/src/core/features/usertours/components/user-tour/user-tour.ts +++ b/src/core/features/usertours/components/user-tour/user-tour.ts @@ -16,12 +16,7 @@ import { AfterViewInit, Component, ElementRef, HostBinding, Input, ViewChild } f import { CorePromisedValue } from '@classes/promised-value'; import { CoreUserToursFocusLayout } from '@features/usertours/classes/focus-layout'; import { CoreUserToursPopoverLayout } from '@features/usertours/classes/popover-layout'; -import { - CoreUserTours, - CoreUserToursAlignment, - CoreUserToursSide, - CoreUserToursStyle, -} from '@features/usertours/services/user-tours'; +import { CoreUserTours, CoreUserToursAlignment, CoreUserToursSide } from '@features/usertours/services/user-tours'; import { CoreDomUtils } from '@services/utils/dom'; import { AngularFrameworkDelegate } from '@singletons'; import { CoreComponentsRegistry } from '@singletons/components-registry'; @@ -45,7 +40,6 @@ export class CoreUserToursUserTourComponent implements AfterViewInit { @Input() component!: unknown; @Input() componentProps?: Record; @Input() focus?: HTMLElement; - @Input() style?: CoreUserToursStyle; // When this is undefined in a tour with a focused element, popover style will be used. @Input() side?: CoreUserToursSide; @Input() alignment?: CoreUserToursAlignment; @HostBinding('class.is-active') active = false; @@ -121,18 +115,16 @@ export class CoreUserToursUserTourComponent implements AfterViewInit { this.focusStyles = focusLayout.inlineStyles; // Calculate popup styles. - if ((this.style ?? CoreUserToursStyle.Popover) === CoreUserToursStyle.Popover) { - if (!this.side || !this.alignment) { - throw new Error('Cannot create a popover user tour without side and alignment'); - } - - const popoverLayout = new CoreUserToursPopoverLayout(this.focus, this.side, this.alignment); - - this.popover = true; - this.popoverWrapperStyles = popoverLayout.wrapperInlineStyles; - this.popoverWrapperArrowStyles = popoverLayout.wrapperArrowInlineStyles; - this.wrapperTransform = `${popoverLayout.wrapperStyles.transform ?? ''}`; + if (!this.side || !this.alignment) { + throw new Error('Cannot create a focused user tour without side and alignment'); } + + const popoverLayout = new CoreUserToursPopoverLayout(this.focus, this.side, this.alignment); + + this.popover = true; + this.popoverWrapperStyles = popoverLayout.wrapperInlineStyles; + this.popoverWrapperArrowStyles = popoverLayout.wrapperArrowInlineStyles; + this.wrapperTransform = `${popoverLayout.wrapperStyles.transform ?? ''}`; } /** diff --git a/src/core/features/usertours/services/user-tours.ts b/src/core/features/usertours/services/user-tours.ts index ff42e12f1..9fedfff74 100644 --- a/src/core/features/usertours/services/user-tours.ts +++ b/src/core/features/usertours/services/user-tours.ts @@ -85,9 +85,8 @@ export class CoreUserToursService { * @param options User Tour options. */ async showIfPending(options: CoreUserToursBasicOptions): Promise; - async showIfPending(options: CoreUserToursPopoverFocusedOptions): Promise; - async showIfPending(options: CoreUserToursOverlayFocusedOptions): Promise; - async showIfPending(options: CoreUserToursOptions): Promise { + async showIfPending(options: CoreUserToursFocusedOptions): Promise; + async showIfPending(options: CoreUserToursBasicOptions | CoreUserToursFocusedOptions): Promise { const isPending = await CoreUserTours.isPending(options.id); if (!isPending) { @@ -103,9 +102,8 @@ export class CoreUserToursService { * @param options User Tour options. */ protected async show(options: CoreUserToursBasicOptions): Promise; - protected async show(options: CoreUserToursPopoverFocusedOptions): Promise; - protected async show(options: CoreUserToursOverlayFocusedOptions): Promise; - protected async show(options: CoreUserToursOptions): Promise { + protected async show(options: CoreUserToursFocusedOptions): Promise; + protected async show(options: CoreUserToursBasicOptions | CoreUserToursFocusedOptions): Promise { const { delay, ...componentOptions } = options; await CoreUtils.wait(delay ?? 200); @@ -149,14 +147,6 @@ export class CoreUserToursService { export const CoreUserTours = makeSingleton(CoreUserToursService); -/** - * User Tour style. - */ -export const enum CoreUserToursStyle { - Overlay = 'overlay', - Popover = 'popover', -} - /** * User Tour side. */ @@ -217,18 +207,6 @@ export interface CoreUserToursFocusedOptions extends CoreUserToursBasicOptions { */ focus: HTMLElement; -} - -/** - * Options to create a focused User Tour using the Popover style. - */ -export interface CoreUserToursPopoverFocusedOptions extends CoreUserToursFocusedOptions { - - /** - * User Tour style. - */ - style?: CoreUserToursStyle.Popover; - /** * Position relative to the focused element. */ @@ -240,23 +218,3 @@ export interface CoreUserToursPopoverFocusedOptions extends CoreUserToursFocused alignment: CoreUserToursAlignment; } - -/** - * Options to create a focused User Tour using the Overlay style. - */ -export interface CoreUserToursOverlayFocusedOptions extends CoreUserToursFocusedOptions { - - /** - * User Tour style. - */ - style: CoreUserToursStyle.Overlay; - -} - -/** - * Options to create a User Tour. - */ -export type CoreUserToursOptions = - CoreUserToursBasicOptions | - CoreUserToursPopoverFocusedOptions | - CoreUserToursOverlayFocusedOptions; From 2e473ee55d3a38a09e931ef785667094bf4414bc Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Thu, 17 Mar 2022 11:46:45 +0100 Subject: [PATCH 2/3] MOBILE-3153 usertours: Dismiss on removed elements --- .../components/user-tour/user-tour.ts | 21 +++++++++--- .../features/usertours/services/user-tours.ts | 33 +++++++++++-------- 2 files changed, 37 insertions(+), 17 deletions(-) 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); } } From b1dad1ef64a200c3e9fd0ebb55065d6530a0cd00 Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Thu, 17 Mar 2022 12:23:50 +0100 Subject: [PATCH 3/3] MOBILE-3153 usertours: Silence back button --- .../usertours/components/user-tour/user-tour.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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 0f0e3d2a1..65f6e63dd 100644 --- a/src/core/features/usertours/components/user-tour/user-tour.ts +++ b/src/core/features/usertours/components/user-tour/user-tour.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { BackButtonEvent } from '@ionic/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'; @@ -22,6 +23,7 @@ import { AngularFrameworkDelegate } from '@singletons'; import { CoreComponentsRegistry } from '@singletons/components-registry'; const ANIMATION_DURATION = 200; +const USER_TOURS_BACK_BUTTON_PRIORITY = 100; /** * User Tour wrapper component. @@ -54,6 +56,7 @@ export class CoreUserToursUserTourComponent implements AfterViewInit { private element: HTMLElement; private wrapperTransform = ''; private wrapperElement = new CorePromisedValue(); + private backButtonListener?: (event: BackButtonEvent) => void; constructor({ nativeElement: element }: ElementRef) { this.element = element; @@ -94,6 +97,16 @@ export class CoreUserToursUserTourComponent implements AfterViewInit { // Show tour. this.active = true; + document.addEventListener( + 'ionBackButton', + this.backButtonListener = ({ detail }) => detail.register( + USER_TOURS_BACK_BUTTON_PRIORITY, + () => { + // Silence back button. + }, + ), + ); + await this.playEnterAnimation(); } @@ -111,6 +124,7 @@ export class CoreUserToursUserTourComponent implements AfterViewInit { acknowledge && CoreUserTours.acknowledge(this.id), ]); + this.backButtonListener && document.removeEventListener('ionBackButton', this.backButtonListener); this.afterDismiss.emit(); }