MOBILE-3153 mainmenu: Implement User Menu tour

main
Noel De Martin 2022-03-08 18:00:41 +01:00
parent 58e6be64e4
commit c5f6b058db
13 changed files with 180 additions and 3 deletions

View File

@ -2003,6 +2003,8 @@
"core.mainmenu.home": "moodle",
"core.mainmenu.logout": "moodle",
"core.mainmenu.switchaccount": "local_moodlemobileapp",
"core.mainmenu.usermenutourdescription": "local_moodlemobileapp",
"core.mainmenu.usermenutourtitle": "local_moodlemobileapp",
"core.maxfilesize": "moodle",
"core.maxsizeandattachments": "moodle",
"core.min": "moodle",
@ -2335,6 +2337,7 @@
"core.usernotfullysetup": "error",
"core.users": "moodle",
"core.usersuspended": "tool_reportbuilder",
"core.endonesteptour": "tool_usertours",
"core.view": "moodle",
"core.viewcode": "local_moodlemobileapp",
"core.vieweditor": "local_moodlemobileapp",

View File

@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 269 173"><path d="M130.298 171.955a1.999 1.999 0 1 0 2.404-3.196l-2.404 3.196Zm137.887-153.65a2 2 0 0 0 .426-2.796l-10.665-14.5a2 2 0 1 0-3.222 2.37l9.48 12.89-12.889 9.48a2 2 0 1 0 2.37 3.222l14.5-10.665ZM124.847 36.115c14.837-13.515 33.816-20.396 57.372-22.633 23.592-2.242 51.644.187 84.48 5.19l.602-3.954c-32.918-5.016-61.365-7.507-85.46-5.218-24.131 2.292-44.025 9.391-59.688 23.657l2.694 2.958Zm34.44 79.302c-13.382 1.869-23.94-.552-31.827-5.493-7.898-4.946-13.278-12.519-16.124-21.225-5.717-17.49-1.113-39.265 13.511-52.584l-2.694-2.958c-15.876 14.46-20.786 37.916-14.619 56.785 3.096 9.473 9.008 17.864 17.803 23.372 8.804 5.515 20.34 8.043 34.504 6.064l-.554-3.961Zm-26.585 53.343c-25.76-19.374-35.531-44.979-33.124-66.267 2.404-21.255 16.912-38.345 40.27-41.227l-.49-3.97c-25.369 3.13-41.163 21.836-43.754 44.747-2.587 22.876 7.954 49.803 34.694 69.913l2.404-3.196Zm7.146-107.494c23.592-2.91 41.413 9.316 46.813 22.682 2.682 6.636 2.305 13.48-1.745 19.109-4.087 5.679-12.172 10.48-25.629 12.36l.554 3.961c14.089-1.968 23.372-7.107 28.321-13.985 4.986-6.928 5.312-15.26 2.208-22.943-6.171-15.272-25.876-28.255-51.012-25.154l.49 3.97Z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -32,6 +32,7 @@ import { CoreSwipeNavigationDirective } from './swipe-navigation';
import { CoreCollapsibleItemDirective } from './collapsible-item';
import { CoreCollapsibleFooterDirective } from './collapsible-footer';
import { CoreContentDirective } from './content';
import { CoreOnAppearDirective } from './on-appear';
@NgModule({
declarations: [
@ -46,6 +47,7 @@ import { CoreContentDirective } from './content';
CoreSupressEventsDirective,
CoreUserLinkDirective,
CoreAriaButtonClickDirective,
CoreOnAppearDirective,
CoreOnResizeDirective,
CoreDownloadFileDirective,
CoreCollapsibleHeaderDirective,
@ -66,6 +68,7 @@ import { CoreContentDirective } from './content';
CoreSupressEventsDirective,
CoreUserLinkDirective,
CoreAriaButtonClickDirective,
CoreOnAppearDirective,
CoreOnResizeDirective,
CoreDownloadFileDirective,
CoreCollapsibleHeaderDirective,

View File

@ -0,0 +1,58 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Directive, ElementRef, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
import { CoreDomUtils } from '@services/utils/dom';
/**
* Directive to listen when an element becomes visible.
*/
@Directive({
selector: '[onAppear]',
})
export class CoreOnAppearDirective implements OnInit, OnDestroy {
@Output() onAppear = new EventEmitter();
private element: HTMLElement;
private interval?: number;
constructor(element: ElementRef) {
this.element = element.nativeElement;
}
/**
* @inheritdoc
*/
ngOnInit(): void {
this.interval = window.setInterval(() => {
if (!CoreDomUtils.isElementVisible(this.element)) {
return;
}
this.onAppear.emit();
window.clearInterval(this.interval);
delete this.interval;
}, 50);
}
/**
* @inheritdoc
*/
ngOnDestroy(): void {
this.interval && window.clearInterval(this.interval);
}
}

View File

@ -17,11 +17,13 @@ import { CoreSharedModule } from '@/core/shared.module';
import { CoreMainMenuUserButtonComponent } from './user-menu-button/user-menu-button';
import { CoreMainMenuUserMenuComponent } from './user-menu/user-menu';
import { CoreLoginComponentsModule } from '@features/login/components/components.module';
import { CoreMainMenuUserMenuTourComponent } from './user-menu-tour/user-menu-tour';
@NgModule({
declarations: [
CoreMainMenuUserButtonComponent,
CoreMainMenuUserMenuComponent,
CoreMainMenuUserMenuTourComponent,
],
imports: [
CoreSharedModule,
@ -30,6 +32,7 @@ import { CoreLoginComponentsModule } from '@features/login/components/components
exports: [
CoreMainMenuUserButtonComponent,
CoreMainMenuUserMenuComponent,
CoreMainMenuUserMenuTourComponent,
],
})
export class CoreMainMenuComponentsModule {}

View File

@ -1,3 +1,4 @@
<core-user-avatar *ngIf="isMainScreen && siteInfo" [user]="siteInfo" class="core-bar-button-image clickable" [linkProfile]="false"
(ariaButtonClick)="openUserMenu($event)" role="button" tabindex="0" [attr.aria-label]="'core.user.useraccount' | translate">
(ariaButtonClick)="openUserMenu($event)" (onAppear)="showTour()" role="button" tabindex="0"
[attr.aria-label]="'core.user.useraccount' | translate" #avatar>
</core-user-avatar>

View File

@ -12,11 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnInit } from '@angular/core';
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { CoreSiteInfo } from '@classes/site';
import { CoreUserTours, CoreUserToursStyle } from '@features/usertours/services/user-tours';
import { IonRouterOutlet } from '@ionic/angular';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreMainMenuUserMenuTourComponent } from '../user-menu-tour/user-menu-tour';
import { CoreMainMenuUserMenuComponent } from '../user-menu/user-menu';
/**
@ -34,6 +36,8 @@ export class CoreMainMenuUserButtonComponent implements OnInit {
siteInfo?: CoreSiteInfo;
isMainScreen = false;
@ViewChild('avatar', { read: ElementRef }) avatar?: ElementRef<HTMLElement>;
constructor(protected routerOutlet: IonRouterOutlet) {
const currentSite = CoreSites.getRequiredCurrentSite();
@ -61,4 +65,20 @@ export class CoreMainMenuUserButtonComponent implements OnInit {
});
}
/**
* Show User Tour.
*/
async showTour(): Promise<void> {
if (!this.avatar) {
return;
}
await CoreUserTours.showIfPending({
id: 'user-menu',
component: CoreMainMenuUserMenuTourComponent,
focus: this.avatar.nativeElement,
style: CoreUserToursStyle.Overlay,
});
}
}

View File

@ -0,0 +1,6 @@
<img src="assets/img/user-tours/user-menu.svg" alt="" />
<h2>{{ 'core.mainmenu.usermenutourtitle' | translate }}</h2>
<p>{{ 'core.mainmenu.usermenutourdescription' | translate }}</p>
<ion-button (click)="dismiss()" expand="block">
{{ 'core.endonesteptour' | translate }}
</ion-button>

View File

@ -0,0 +1,26 @@
: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;
}
p {
text-align: center;
}
ion-button {
width: 100%;
}
}
:host-context([dir=rtl]) img {
transform: scaleX(-1);
}

View File

@ -0,0 +1,35 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component } from '@angular/core';
import { CoreUserTours } from '@features/usertours/services/user-tours';
/**
* Component showing the User Tour for the User Menu feature.
*/
@Component({
selector: 'core-mainmenu-user-menu-tour',
templateUrl: 'user-menu-tour.html',
styleUrls: ['user-menu-tour.scss'],
})
export class CoreMainMenuUserMenuTourComponent {
/**
* Dismiss the User Tour.
*/
async dismiss(): Promise<void> {
await CoreUserTours.dismiss();
}
}

View File

@ -1,5 +1,7 @@
{
"home": "Home",
"logout": "Log out",
"switchaccount": "Switch account"
"switchaccount": "Switch account",
"usermenutourdescription": "The place to check your grades, change your preferences or switch accounts.",
"usermenutourtitle": "Explore your personal area"
}

View File

@ -329,6 +329,7 @@
"usernotfullysetup": "User not fully set-up",
"usernologin": "Authentication has been revoked for this account",
"usersuspended": "Registration suspended",
"endonesteptour": "Got it",
"users": "Users",
"view": "View",
"viewcode": "View code",

View File

@ -806,6 +806,24 @@ export class CoreDomUtilsProvider {
return elementPoint > window.innerHeight || elementPoint < scrollTopPos;
}
/**
* Check whether an element is visible or not.
*
* @param element Element.
*/
isElementVisible(element: HTMLElement): boolean {
if (element.clientWidth === 0 || element.clientHeight === 0) {
return false;
}
const style = getComputedStyle(element);
if (style.opacity === '0' || style.display === 'none' || style.visibility === 'hidden') {
return false;
}
return element.offsetParent !== null;
}
/**
* Check if rich text editor is enabled.
*