diff --git a/scripts/langindex.json b/scripts/langindex.json
index 15dc8e894..b269f6b62 100644
--- a/scripts/langindex.json
+++ b/scripts/langindex.json
@@ -1798,6 +1798,8 @@
"core.loading": "moodle",
"core.loadmore": "local_moodlemobileapp",
"core.location": "moodle",
+ "core.login.accounts": "admin",
+ "core.login.add": "moodle",
"core.login.auth_email": "auth_email/pluginname",
"core.login.authenticating": "local_moodlemobileapp",
"core.login.cancel": "moodle",
@@ -1894,6 +1896,7 @@
"core.login.reconnect": "local_moodlemobileapp",
"core.login.reconnectdescription": "local_moodlemobileapp",
"core.login.reconnectssodescription": "local_moodlemobileapp",
+ "core.login.removeaccount": "local_moodlemobileapp",
"core.login.resendemail": "moodle",
"core.login.searchby": "local_moodlemobileapp",
"core.login.security_question": "auth",
@@ -1913,6 +1916,7 @@
"core.login.startsignup": "moodle",
"core.login.stillcantconnect": "local_moodlemobileapp",
"core.login.supplyinfo": "moodle",
+ "core.login.toggleremove": "local_moodlemobileapp",
"core.login.username": "moodle",
"core.login.usernameoremail": "moodle",
"core.login.usernamerequired": "local_moodlemobileapp",
@@ -1922,11 +1926,9 @@
"core.login.youcanstillconnectwithcredentials": "local_moodlemobileapp",
"core.login.yourenteredsite": "local_moodlemobileapp",
"core.lostconnection": "local_moodlemobileapp",
- "core.mainmenu.changesite": "local_moodlemobileapp",
- "core.mainmenu.help": "moodle",
"core.mainmenu.home": "moodle",
"core.mainmenu.logout": "moodle",
- "core.mainmenu.website": "local_moodlemobileapp",
+ "core.mainmenu.switchaccount": "local_moodlemobileapp",
"core.maxfilesize": "moodle",
"core.maxsizeandattachments": "moodle",
"core.min": "moodle",
@@ -1960,7 +1962,7 @@
"core.mod_wiki": "wiki/pluginname",
"core.mod_workshop": "workshop/pluginname",
"core.moduleintro": "moodle",
- "core.more": "moodle",
+ "core.more": "moodle/moremenu",
"core.mygroups": "group",
"core.name": "moodle",
"core.needhelp": "local_moodlemobileapp",
@@ -2232,6 +2234,7 @@
"core.updaterequireddesc": "local_moodlemobileapp",
"core.upgraderunning": "error",
"core.user": "moodle",
+ "core.user.account": "local_moodlemobileapp",
"core.user.address": "moodle",
"core.user.city": "moodle",
"core.user.contact": "local_moodlemobileapp",
@@ -2252,6 +2255,7 @@
"core.user.participants": "moodle",
"core.user.phone1": "moodle",
"core.user.phone2": "moodle",
+ "core.user.profile": "moodle",
"core.user.roles": "moodle",
"core.user.sendemail": "local_moodlemobileapp",
"core.user.student": "moodle/defaultcoursestudent",
diff --git a/src/addons/block/privatefiles/services/block-handler.ts b/src/addons/block/privatefiles/services/block-handler.ts
index 73c160eaf..bf4c9aaf7 100644
--- a/src/addons/block/privatefiles/services/block-handler.ts
+++ b/src/addons/block/privatefiles/services/block-handler.ts
@@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreBlockHandlerData } from '@features/block/services/block-delegate';
import { CoreBlockOnlyTitleComponent } from '@features/block/components/only-title-block/only-title-block';
import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler';
-import { AddonPrivateFilesMainMenuHandlerService } from '@/addons/privatefiles/services/handlers/mainmenu';
+import { AddonPrivateFilesUserHandlerService } from '@addons/privatefiles/services/handlers/user';
import { makeSingleton } from '@singletons';
/**
@@ -39,7 +39,7 @@ export class AddonBlockPrivateFilesHandlerService extends CoreBlockBaseHandler {
title: 'addon.block_privatefiles.pluginname',
class: 'addon-block-private-files',
component: CoreBlockOnlyTitleComponent,
- link: AddonPrivateFilesMainMenuHandlerService.PAGE_NAME,
+ link: AddonPrivateFilesUserHandlerService.PAGE_NAME,
linkParams: { root: 'my' },
navOptions: {
preferCurrentTab: false,
diff --git a/src/addons/blog/blog-lazy.module.ts b/src/addons/blog/blog-lazy.module.ts
index 5fab11220..2bd801405 100644
--- a/src/addons/blog/blog-lazy.module.ts
+++ b/src/addons/blog/blog-lazy.module.ts
@@ -22,6 +22,7 @@ import { CoreCommentsComponentsModule } from '@features/comments/components/comp
import { CoreTagComponentsModule } from '@features/tag/components/components.module';
import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module';
import { AddonBlogMainMenuHandlerService } from './services/handlers/mainmenu';
+import { CoreMainMenuComponentsModule } from '@features/mainmenu/components/components.module';
function buildRoutes(injector: Injector): Routes {
return [
@@ -39,6 +40,7 @@ function buildRoutes(injector: Injector): Routes {
CoreSharedModule,
CoreCommentsComponentsModule,
CoreTagComponentsModule,
+ CoreMainMenuComponentsModule,
],
exports: [RouterModule],
providers: [
diff --git a/src/addons/blog/pages/entries/entries.html b/src/addons/blog/pages/entries/entries.html
index 6dd13b87c..60d027fcb 100644
--- a/src/addons/blog/pages/entries/entries.html
+++ b/src/addons/blog/pages/entries/entries.html
@@ -4,7 +4,9 @@
{{ title | translate }}
-
+
+
+
diff --git a/src/addons/calendar/pages/index/index.html b/src/addons/calendar/pages/index/index.html
index 1533cc434..b67f85700 100644
--- a/src/addons/calendar/pages/index/index.html
+++ b/src/addons/calendar/pages/index/index.html
@@ -22,6 +22,7 @@
[content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(undefined, $event, true)"
[iconAction]="syncIcon" [closeOnClick]="false">
+
diff --git a/src/addons/calendar/pages/index/index.module.ts b/src/addons/calendar/pages/index/index.module.ts
index be80e63e6..0ecbc7b6e 100644
--- a/src/addons/calendar/pages/index/index.module.ts
+++ b/src/addons/calendar/pages/index/index.module.ts
@@ -19,6 +19,7 @@ import { CoreSharedModule } from '@/core/shared.module';
import { AddonCalendarComponentsModule } from '../../components/components.module';
import { AddonCalendarIndexPage } from './index.page';
+import { CoreMainMenuComponentsModule } from '@features/mainmenu/components/components.module';
const routes: Routes = [
{
@@ -32,6 +33,7 @@ const routes: Routes = [
RouterModule.forChild(routes),
CoreSharedModule,
AddonCalendarComponentsModule,
+ CoreMainMenuComponentsModule,
],
declarations: [
AddonCalendarIndexPage,
diff --git a/src/addons/messages/pages/discussions-35/discussions.html b/src/addons/messages/pages/discussions-35/discussions.html
index 5c0b2b5e3..8824fe578 100644
--- a/src/addons/messages/pages/discussions-35/discussions.html
+++ b/src/addons/messages/pages/discussions-35/discussions.html
@@ -5,6 +5,7 @@
{{ 'addon.messages.messages' | translate }}
+
diff --git a/src/addons/messages/pages/discussions-35/discussions.module.ts b/src/addons/messages/pages/discussions-35/discussions.module.ts
index 15ec2604e..f26263d62 100644
--- a/src/addons/messages/pages/discussions-35/discussions.module.ts
+++ b/src/addons/messages/pages/discussions-35/discussions.module.ts
@@ -23,6 +23,7 @@ import { CoreSearchComponentsModule } from '@features/search/components/componen
import { AddonMessagesDiscussions35Page } from './discussions.page';
import { AddonMessagesMainMenuHandlerService } from '@addons/messages/services/handlers/mainmenu';
+import { CoreMainMenuComponentsModule } from '@features/mainmenu/components/components.module';
const mobileRoutes: Routes = [
{
@@ -58,6 +59,7 @@ const routes: Routes = [
RouterModule.forChild(routes),
CoreSharedModule,
CoreSearchComponentsModule,
+ CoreMainMenuComponentsModule,
],
declarations: [
AddonMessagesDiscussions35Page,
diff --git a/src/addons/messages/pages/group-conversations/group-conversations.html b/src/addons/messages/pages/group-conversations/group-conversations.html
index 7d02aa119..d55475d6c 100644
--- a/src/addons/messages/pages/group-conversations/group-conversations.html
+++ b/src/addons/messages/pages/group-conversations/group-conversations.html
@@ -11,6 +11,7 @@
+
diff --git a/src/addons/messages/pages/group-conversations/group-conversations.module.ts b/src/addons/messages/pages/group-conversations/group-conversations.module.ts
index a4390583e..8fbcc9bae 100644
--- a/src/addons/messages/pages/group-conversations/group-conversations.module.ts
+++ b/src/addons/messages/pages/group-conversations/group-conversations.module.ts
@@ -22,6 +22,7 @@ import { CoreSharedModule } from '@/core/shared.module';
import { AddonMessagesGroupConversationsPage } from './group-conversations.page';
import { AddonMessagesMainMenuHandlerService } from '@addons/messages/services/handlers/mainmenu';
+import { CoreMainMenuComponentsModule } from '@features/mainmenu/components/components.module';
const mobileRoutes: Routes = [
{
@@ -56,6 +57,7 @@ const routes: Routes = [
imports: [
RouterModule.forChild(routes),
CoreSharedModule,
+ CoreMainMenuComponentsModule,
],
declarations: [
AddonMessagesGroupConversationsPage,
diff --git a/src/addons/notifications/pages/list/list.html b/src/addons/notifications/pages/list/list.html
index 3aba09f1e..a500d7296 100644
--- a/src/addons/notifications/pages/list/list.html
+++ b/src/addons/notifications/pages/list/list.html
@@ -4,6 +4,9 @@
{{ 'addon.notifications.notifications' | translate }}
+
+
+
diff --git a/src/addons/notifications/pages/list/list.module.ts b/src/addons/notifications/pages/list/list.module.ts
index 5a4f6144d..b592079e7 100644
--- a/src/addons/notifications/pages/list/list.module.ts
+++ b/src/addons/notifications/pages/list/list.module.ts
@@ -18,6 +18,7 @@ import { RouterModule, Routes } from '@angular/router';
import { CoreSharedModule } from '@/core/shared.module';
import { AddonNotificationsComponentsModule } from '../../components/components.module';
import { AddonNotificationsListPage } from './list';
+import { CoreMainMenuComponentsModule } from '@features/mainmenu/components/components.module';
const routes: Routes = [
{
@@ -31,6 +32,7 @@ const routes: Routes = [
RouterModule.forChild(routes),
CoreSharedModule,
AddonNotificationsComponentsModule,
+ CoreMainMenuComponentsModule,
],
declarations: [
AddonNotificationsListPage,
diff --git a/src/addons/privatefiles/privatefiles-lazy.module.ts b/src/addons/privatefiles/privatefiles-lazy.module.ts
index fa78f9da5..5791c5dc0 100644
--- a/src/addons/privatefiles/privatefiles-lazy.module.ts
+++ b/src/addons/privatefiles/privatefiles-lazy.module.ts
@@ -16,14 +16,14 @@ import { Injector, NgModule } from '@angular/core';
import { RouterModule, ROUTES, Routes } from '@angular/router';
import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module';
-import { AddonPrivateFilesMainMenuHandlerService } from './services/handlers/mainmenu';
+import { AddonPrivateFilesUserHandlerService } from './services/handlers/user';
function buildRoutes(injector: Injector): Routes {
return [
{
path: 'root',
data: {
- mainMenuTabRoot: AddonPrivateFilesMainMenuHandlerService.PAGE_NAME,
+ mainMenuTabRoot: AddonPrivateFilesUserHandlerService.PAGE_NAME,
},
loadChildren: () => import('./pages/index/index.module').then(m => m.AddonPrivateFilesIndexPageModule),
},
diff --git a/src/addons/privatefiles/privatefiles.module.ts b/src/addons/privatefiles/privatefiles.module.ts
index ca91c9d3f..635a80afb 100644
--- a/src/addons/privatefiles/privatefiles.module.ts
+++ b/src/addons/privatefiles/privatefiles.module.ts
@@ -15,12 +15,12 @@
import { APP_INITIALIZER, NgModule, Type } from '@angular/core';
import { Routes } from '@angular/router';
-import { CoreMainMenuDelegate } from '@features/mainmenu/services/mainmenu-delegate';
import { CoreMainMenuRoutingModule } from '@features/mainmenu/mainmenu-routing.module';
-import { AddonPrivateFilesMainMenuHandler, AddonPrivateFilesMainMenuHandlerService } from './services/handlers/mainmenu';
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
import { AddonPrivateFilesProvider } from './services/privatefiles';
import { AddonPrivateFilesHelperProvider } from './services/privatefiles-helper';
+import { CoreUserDelegate } from '@features/user/services/user-delegate';
+import { AddonPrivateFilesUserHandler, AddonPrivateFilesUserHandlerService } from './services/handlers/user';
export const ADDON_PRIVATEFILES_SERVICES: Type[] = [
AddonPrivateFilesProvider,
@@ -29,7 +29,7 @@ export const ADDON_PRIVATEFILES_SERVICES: Type[] = [
const routes: Routes = [
{
- path: AddonPrivateFilesMainMenuHandlerService.PAGE_NAME,
+ path: AddonPrivateFilesUserHandlerService.PAGE_NAME,
loadChildren: () => import('@/addons/privatefiles/privatefiles-lazy.module').then(m => m.AddonPrivateFilesLazyModule),
},
];
@@ -45,7 +45,7 @@ const routes: Routes = [
provide: APP_INITIALIZER,
multi: true,
useValue: () => {
- CoreMainMenuDelegate.registerHandler(AddonPrivateFilesMainMenuHandler.instance);
+ CoreUserDelegate.registerHandler(AddonPrivateFilesUserHandler.instance);
},
},
],
diff --git a/src/addons/privatefiles/services/handlers/mainmenu.ts b/src/addons/privatefiles/services/handlers/user.ts
similarity index 50%
rename from src/addons/privatefiles/services/handlers/mainmenu.ts
rename to src/addons/privatefiles/services/handlers/user.ts
index 0ec18da4c..4672385b3 100644
--- a/src/addons/privatefiles/services/handlers/mainmenu.ts
+++ b/src/addons/privatefiles/services/handlers/user.ts
@@ -14,44 +14,57 @@
import { Injectable } from '@angular/core';
-import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@features/mainmenu/services/mainmenu-delegate';
import { AddonPrivateFiles } from '@/addons/privatefiles/services/privatefiles';
import { makeSingleton } from '@singletons';
+import { CoreUserDelegateService, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@features/user/services/user-delegate';
+import { CoreUserProfile } from '@features/user/services/user';
+import { CoreNavigator } from '@services/navigator';
+import { CoreSites } from '@services/sites';
/**
- * Handler to inject an option into main menu.
+ * Handler to inject an option into user menu.
*/
@Injectable({ providedIn: 'root' })
-export class AddonPrivateFilesMainMenuHandlerService implements CoreMainMenuHandler {
+export class AddonPrivateFilesUserHandlerService implements CoreUserProfileHandler {
static readonly PAGE_NAME = 'private';
name = 'AddonPrivateFiles';
- priority = 400;
+ priority = 300;
+ type = CoreUserDelegateService.TYPE_NEW_PAGE;
+ cacheEnabled = true;
/**
- * Check if the handler is enabled on a site level.
- *
- * @return Whether or not the handler is enabled on a site level.
+ * @inheritdoc
*/
async isEnabled(): Promise {
return AddonPrivateFiles.isPluginEnabled();
}
/**
- * Returns the data needed to render the handler.
- *
- * @return Data needed to render the handler.
+ * @inheritdoc
*/
- getDisplayData(): CoreMainMenuHandlerData {
+ async isEnabledForUser(user: CoreUserProfile): Promise {
+ // Private files only available for the current user.
+ return user.id == CoreSites.getCurrentSiteUserId();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ getDisplayData(): CoreUserProfileHandlerData {
return {
icon: 'fas-folder',
title: 'addon.privatefiles.files',
- page: AddonPrivateFilesMainMenuHandlerService.PAGE_NAME,
class: 'addon-privatefiles-handler',
+ action: (event): void => {
+ event.preventDefault();
+ event.stopPropagation();
+ CoreNavigator.navigateToSitePath(AddonPrivateFilesUserHandlerService.PAGE_NAME);
+ },
};
}
}
-export const AddonPrivateFilesMainMenuHandler = makeSingleton(AddonPrivateFilesMainMenuHandlerService);
+export const AddonPrivateFilesUserHandler = makeSingleton(AddonPrivateFilesUserHandlerService);
diff --git a/src/core/classes/modal-lateral-transition.ts b/src/core/classes/modal-lateral-transition.ts
index 551d288d7..bc481f705 100644
--- a/src/core/classes/modal-lateral-transition.ts
+++ b/src/core/classes/modal-lateral-transition.ts
@@ -22,37 +22,61 @@ import { Platform } from '@singletons';
export function CoreModalLateralTransitionEnter(baseEl: HTMLElement): Animation {
const OFF_RIGHT = Platform.isRTL ? '-100%' : '100%';
- const backdropAnimation = createAnimation()
- .addElement(baseEl.querySelector('ion-backdrop')!)
- .fromTo('opacity', 0.01, 0.4);
+ const otherAnimations: Animation[] = [];
- const wrapperAnimation = createAnimation()
- .addElement(baseEl.querySelector('.modal-wrapper')!)
- .fromTo('transform', 'translateX(' + OFF_RIGHT + ')', 'translateX(0)')
- .fromTo('opacity', 0.8, 1);
+ const backdrop = baseEl.querySelector('ion-backdrop');
+ if (backdrop) {
+ const backdropAnimation = createAnimation()
+ .addElement(backdrop)
+ .fromTo('opacity', 0.01, 0.4);
+
+ otherAnimations.push(backdropAnimation);
+ }
+
+ const wrapper = baseEl.querySelector('.modal-wrapper');
+ if (wrapper) {
+ const wrapperAnimation = createAnimation()
+ .addElement(wrapper)
+ .fromTo('transform', 'translateX(' + OFF_RIGHT + ')', 'translateX(0)')
+ .fromTo('opacity', 0.8, 1);
+
+ otherAnimations.push(wrapperAnimation);
+ }
return createAnimation()
.addElement(baseEl)
.easing('cubic-bezier(0.36,0.66,0.04,1)')
.duration(300)
- .addAnimation([backdropAnimation, wrapperAnimation]);
+ .addAnimation(otherAnimations);
}
export function CoreModalLateralTransitionLeave(baseEl: HTMLElement): Animation {
const OFF_RIGHT = Platform.isRTL ? '-100%' : '100%';
- const backdropAnimation = createAnimation()
- .addElement(baseEl.querySelector('ion-backdrop')!)
- .fromTo('opacity', 0.4, 0.0);
+ const otherAnimations: Animation[] = [];
- const wrapperAnimation = createAnimation()
- .addElement(baseEl.querySelector('.modal-wrapper')!)
- .beforeStyles({ opacity: 1 })
- .fromTo('transform', 'translateX(0)', 'translateX(' + OFF_RIGHT + ')');
+ const backdrop = baseEl.querySelector('ion-backdrop');
+ if (backdrop) {
+ const backdropAnimation = createAnimation()
+ .addElement(backdrop)
+ .fromTo('opacity', 0.4, 0.0);
+
+ otherAnimations.push(backdropAnimation);
+ }
+
+ const wrapper = baseEl.querySelector('.modal-wrapper');
+ if (wrapper) {
+ const wrapperAnimation = createAnimation()
+ .addElement(wrapper)
+ .beforeStyles({ opacity: 1 })
+ .fromTo('transform', 'translateX(0)', 'translateX(' + OFF_RIGHT + ')');
+
+ otherAnimations.push(wrapperAnimation);
+ }
return createAnimation()
.addElement(baseEl)
.easing('cubic-bezier(0.36,0.66,0.04,1)')
.duration(300)
- .addAnimation([backdropAnimation, wrapperAnimation]);
+ .addAnimation(otherAnimations);
}
diff --git a/src/core/components/navbar-buttons/navbar-buttons.ts b/src/core/components/navbar-buttons/navbar-buttons.ts
index 39b69167e..48439908e 100644
--- a/src/core/components/navbar-buttons/navbar-buttons.ts
+++ b/src/core/components/navbar-buttons/navbar-buttons.ts
@@ -56,7 +56,7 @@ const BUTTON_HIDDEN_CLASS = 'core-navbar-button-hidden';
})
export class CoreNavBarButtonsComponent implements OnInit, OnDestroy {
- @ViewChild('contextMenuContainer', { read: ViewContainerRef }) container?: ViewContainerRef;
+ @ViewChild('contextMenuContainer', { read: ViewContainerRef }) container!: ViewContainerRef;
// If the hidden input is true, hide all buttons.
// eslint-disable-next-line @angular-eslint/no-input-rename
@@ -113,7 +113,13 @@ export class CoreNavBarButtonsComponent implements OnInit, OnDestroy {
// Make sure that context-menu is always at the end of buttons if any.
const contextMenu = buttonsContainer.querySelector('core-context-menu');
- contextMenu?.parentElement?.appendChild(contextMenu);
+ const userMenu = buttonsContainer.querySelector('core-user-menu-button');
+
+ if (userMenu) {
+ contextMenu?.parentElement?.insertBefore(contextMenu, userMenu);
+ } else {
+ contextMenu?.parentElement?.appendChild(contextMenu);
+ }
} else {
this.logger.warn('The header was found, but it didn\'t have the right ion-buttons.', selector);
}
@@ -177,7 +183,7 @@ export class CoreNavBarButtonsComponent implements OnInit, OnDestroy {
*/
protected createMainContextMenu(): CoreContextMenuComponent {
const factory = this.factoryResolver.resolveComponentFactory(CoreContextMenuComponent);
- const componentRef = this.container!.createComponent(factory);
+ const componentRef = this.container.createComponent(factory);
this.createdMainContextMenuElement = componentRef.location.nativeElement;
diff --git a/src/core/components/user-avatar/user-avatar.scss b/src/core/components/user-avatar/user-avatar.scss
index 5d99ba41d..f0e1ff20d 100644
--- a/src/core/components/user-avatar/user-avatar.scss
+++ b/src/core/components/user-avatar/user-avatar.scss
@@ -36,13 +36,19 @@
content: "";
}
}
- &.core-bar-button-image img {
+ &.core-bar-button-image {
padding: 0;
width: var(--core-header-toolbar-button-image-size);
height: var(--core-header-toolbar-button-image-size);
max-width: var(--core-header-toolbar-button-image-size);
max-height: var(--core-header-toolbar-button-image-size);
border-radius: 50%;
+ display: block;
+
+ img {
+ padding: 4px;
+ border-radius: 50%;
+ }
}
.contact-status {
diff --git a/src/core/features/courses/pages/dashboard/dashboard.html b/src/core/features/courses/pages/dashboard/dashboard.html
index c4315e7d1..c79854f7a 100644
--- a/src/core/features/courses/pages/dashboard/dashboard.html
+++ b/src/core/features/courses/pages/dashboard/dashboard.html
@@ -1,4 +1,4 @@
-
+
diff --git a/src/core/features/courses/pages/list/list.html b/src/core/features/courses/pages/list/list.html
index 114a5a78b..ffb6b4b22 100644
--- a/src/core/features/courses/pages/list/list.html
+++ b/src/core/features/courses/pages/list/list.html
@@ -5,20 +5,20 @@
{{ 'core.courses.availablecourses' | translate }}
{{ 'core.courses.mycourses' | translate }}
-
+
+
+
+
+
+
+
-
-
-
-
-
-
diff --git a/src/core/features/courses/pages/list/list.module.ts b/src/core/features/courses/pages/list/list.module.ts
index c0d3a2c36..23dd52438 100644
--- a/src/core/features/courses/pages/list/list.module.ts
+++ b/src/core/features/courses/pages/list/list.module.ts
@@ -20,6 +20,7 @@ import { CoreCoursesComponentsModule } from '../../components/components.module'
import { CoreSearchComponentsModule } from '@features/search/components/components.module';
import { CoreCoursesListPage } from './list';
+import { CoreMainMenuComponentsModule } from '@features/mainmenu/components/components.module';
const routes: Routes = [
{
@@ -34,6 +35,7 @@ const routes: Routes = [
CoreSharedModule,
CoreCoursesComponentsModule,
CoreSearchComponentsModule,
+ CoreMainMenuComponentsModule,
],
declarations: [
CoreCoursesListPage,
diff --git a/src/core/features/grades/grades-lazy.module.ts b/src/core/features/grades/grades-lazy.module.ts
index e60242302..d825a2621 100644
--- a/src/core/features/grades/grades-lazy.module.ts
+++ b/src/core/features/grades/grades-lazy.module.ts
@@ -23,13 +23,13 @@ import { CoreGradesCoursePage } from './pages/course/course.page';
import { CoreGradesCoursePageModule } from './pages/course/course.module';
import { CoreGradesCoursesPage } from './pages/courses/courses.page';
import { CoreGradesGradePage } from './pages/grade/grade.page';
-import { CoreGradesMainMenuHandlerService } from './services/handlers/mainmenu';
+import { CoreGradesUserHandlerService } from './services/handlers/user';
const mobileRoutes: Routes = [
{
path: '',
data: {
- mainMenuTabRoot: CoreGradesMainMenuHandlerService.PAGE_NAME,
+ mainMenuTabRoot: CoreGradesUserHandlerService.PAGE_NAME,
},
component: CoreGradesCoursesPage,
},
@@ -47,7 +47,7 @@ const tabletRoutes: Routes = [
{
path: '',
data: {
- mainMenuTabRoot: CoreGradesMainMenuHandlerService.PAGE_NAME,
+ mainMenuTabRoot: CoreGradesUserHandlerService.PAGE_NAME,
},
component: CoreGradesCoursesPage,
children: [
diff --git a/src/core/features/grades/grades.module.ts b/src/core/features/grades/grades.module.ts
index b359d575c..d7ca58cf3 100644
--- a/src/core/features/grades/grades.module.ts
+++ b/src/core/features/grades/grades.module.ts
@@ -19,14 +19,12 @@ import { CoreCourseIndexRoutingModule } from '@features/course/pages/index/index
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
import { CoreMainMenuRoutingModule } from '@features/mainmenu/mainmenu-routing.module';
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
-import { CoreMainMenuDelegate } from '@features/mainmenu/services/mainmenu-delegate';
import { CoreUserDelegate } from '@features/user/services/user-delegate';
import { CoreGradesProvider } from './services/grades';
import { CoreGradesHelperProvider } from './services/grades-helper';
import { CoreGradesCourseOptionHandler } from './services/handlers/course-option';
-import { CoreGradesMainMenuHandler, CoreGradesMainMenuHandlerService } from './services/handlers/mainmenu';
import { CoreGradesOverviewLinkHandler } from './services/handlers/overview-link';
-import { CoreGradesUserHandler } from './services/handlers/user';
+import { CoreGradesUserHandler, CoreGradesUserHandlerService } from './services/handlers/user';
import { CoreGradesUserLinkHandler } from './services/handlers/user-link';
export const CORE_GRADES_SERVICES: Type[] = [
@@ -36,7 +34,7 @@ export const CORE_GRADES_SERVICES: Type[] = [
const routes: Routes = [
{
- path: CoreGradesMainMenuHandlerService.PAGE_NAME,
+ path: CoreGradesUserHandlerService.PAGE_NAME,
loadChildren: () => import('@features/grades/grades-lazy.module').then(m => m.CoreGradesLazyModule),
},
{
@@ -63,7 +61,6 @@ const courseIndexRoutes: Routes = [
provide: APP_INITIALIZER,
multi: true,
useValue: () => {
- CoreMainMenuDelegate.registerHandler(CoreGradesMainMenuHandler.instance);
CoreUserDelegate.registerHandler(CoreGradesUserHandler.instance);
CoreContentLinksDelegate.registerHandler(CoreGradesUserLinkHandler.instance);
CoreContentLinksDelegate.registerHandler(CoreGradesOverviewLinkHandler.instance);
diff --git a/src/core/features/grades/services/handlers/mainmenu.ts b/src/core/features/grades/services/handlers/mainmenu.ts
deleted file mode 100644
index 6e7a7b915..000000000
--- a/src/core/features/grades/services/handlers/mainmenu.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-// (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 { Injectable } from '@angular/core';
-import { CoreGrades } from '@features/grades/services/grades';
-import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@features/mainmenu/services/mainmenu-delegate';
-import { makeSingleton } from '@singletons';
-
-/**
- * Handler to inject an option into main menu.
- */
-@Injectable({ providedIn: 'root' })
-export class CoreGradesMainMenuHandlerService implements CoreMainMenuHandler {
-
- static readonly PAGE_NAME = 'grades';
-
- name = 'CoreGrades';
- priority = 600;
-
- /**
- * Check if the handler is enabled on a site level.
- *
- * @return Whether or not the handler is enabled on a site level.
- */
- isEnabled(): Promise {
- return CoreGrades.isCourseGradesEnabled();
- }
-
- /**
- * Returns the data needed to render the handler.
- *
- * @return Data needed to render the handler.
- */
- getDisplayData(): CoreMainMenuHandlerData {
- return {
- icon: 'fas-chart-bar',
- title: 'core.grades.grades',
- page: CoreGradesMainMenuHandlerService.PAGE_NAME,
- class: 'core-grades-coursesgrades-handler',
- };
- }
-
-}
-
-export const CoreGradesMainMenuHandler = makeSingleton(CoreGradesMainMenuHandlerService);
diff --git a/src/core/features/grades/services/handlers/user.ts b/src/core/features/grades/services/handlers/user.ts
index 544044018..fab0752f6 100644
--- a/src/core/features/grades/services/handlers/user.ts
+++ b/src/core/features/grades/services/handlers/user.ts
@@ -22,6 +22,7 @@ import {
CoreUserProfileHandlerData,
} from '@features/user/services/user-delegate';
import { CoreNavigator } from '@services/navigator';
+import { CoreSites } from '@services/sites';
import { CoreUtils } from '@services/utils/utils';
import { makeSingleton } from '@singletons';
@@ -31,6 +32,8 @@ import { makeSingleton } from '@singletons';
@Injectable({ providedIn: 'root' })
export class CoreGradesUserHandlerService implements CoreUserProfileHandler {
+ static readonly PAGE_NAME = 'grades';
+
name = 'CoreGrades:viewGrades';
priority = 400;
type = CoreUserDelegateService.TYPE_NEW_PAGE;
@@ -47,32 +50,54 @@ export class CoreGradesUserHandlerService implements CoreUserProfileHandler {
* @inheritdoc
*/
async isEnabledForCourse(courseId?: number): Promise {
- return CoreUtils.ignoreErrors(CoreGrades.isPluginEnabledForCourse(courseId), false);
+ if (courseId) {
+ return CoreUtils.ignoreErrors(CoreGrades.isPluginEnabledForCourse(courseId), false);
+ } else {
+ return CoreGrades.isCourseGradesEnabled();
+ }
}
/**
* @inheritdoc
*/
async isEnabledForUser(user: CoreUserProfile, courseId?: number): Promise {
- return CoreUtils.promiseWorks(CoreGrades.getCourseGradesTable(courseId!, user.id));
+ if (courseId) {
+ return CoreUtils.promiseWorks(CoreGrades.getCourseGradesTable(courseId, user.id));
+ }
+
+ // All course grades only available for the current user.
+ return user.id == CoreSites.getCurrentSiteUserId();
}
/**
* @inheritdoc
*/
- getDisplayData(): CoreUserProfileHandlerData {
- return {
- icon: 'fas-chart-bar',
- title: 'core.grades.grades',
- class: 'core-grades-user-handler',
- action: (event, user, courseId): void => {
- event.preventDefault();
- event.stopPropagation();
- CoreNavigator.navigateToSitePath(`/user-grades/${courseId}`, {
- params: { userId: user.id },
- });
- },
- };
+ getDisplayData(user: CoreUserProfile, courseId?: number): CoreUserProfileHandlerData {
+ if (courseId) {
+ return {
+ icon: 'fas-chart-bar',
+ title: 'core.grades.grades',
+ class: 'core-grades-user-handler',
+ action: (event, user, courseId): void => {
+ event.preventDefault();
+ event.stopPropagation();
+ CoreNavigator.navigateToSitePath(`/user-grades/${courseId}`, {
+ params: { userId: user.id },
+ });
+ },
+ };
+ } else {
+ return {
+ icon: 'fas-chart-bar',
+ title: 'core.grades.grades',
+ class: 'core-grades-coursesgrades-handler',
+ action: (event): void => {
+ event.preventDefault();
+ event.stopPropagation();
+ CoreNavigator.navigateToSitePath(CoreGradesUserHandlerService.PAGE_NAME);
+ },
+ };
+ }
}
}
diff --git a/src/core/features/login/components/components.module.ts b/src/core/features/login/components/components.module.ts
new file mode 100644
index 000000000..37d326e79
--- /dev/null
+++ b/src/core/features/login/components/components.module.ts
@@ -0,0 +1,36 @@
+// (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 { NgModule } from '@angular/core';
+import { CoreSharedModule } from '@/core/shared.module';
+import { CoreLoginSiteOnboardingComponent } from './site-onboarding/site-onboarding';
+import { CoreLoginSiteHelpComponent } from './site-help/site-help';
+import { CoreLoginSitesComponent } from './sites/sites';
+
+@NgModule({
+ declarations: [
+ CoreLoginSiteOnboardingComponent,
+ CoreLoginSiteHelpComponent,
+ CoreLoginSitesComponent,
+ ],
+ imports: [
+ CoreSharedModule,
+ ],
+ exports: [
+ CoreLoginSiteOnboardingComponent,
+ CoreLoginSiteHelpComponent,
+ CoreLoginSitesComponent,
+ ],
+})
+export class CoreLoginComponentsModule {}
diff --git a/src/core/features/login/components/sites/sites.html b/src/core/features/login/components/sites/sites.html
new file mode 100644
index 000000000..5cf1c7fae
--- /dev/null
+++ b/src/core/features/login/components/sites/sites.html
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+ {{ 'core.mainmenu.switchaccount' | translate }}
+
+
+ 1" (click)="toggleDelete()"
+ [attr.aria-label]="'core.login.toggleremove' | translate">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{
+ accountsList.currentSite.siteUrlWithoutProtocol }}
+
+
+
+
+
+
+
+
+
+ {{accountsList.currentSite.fullName}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ sites[0].siteUrlWithoutProtocol }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ 'core.login.add' | translate }}
+
+
+
+
+
+
+
+
+
+
+
+ {{site.fullName}}
+
+
+ {{site.badge}}
+ {{ 'core.login.sitebadgedescription' | translate:{ count: site.badge }
+ }}
+
+
+
+
+
+
diff --git a/src/core/features/login/components/sites/sites.ts b/src/core/features/login/components/sites/sites.ts
new file mode 100644
index 000000000..1f14b51a7
--- /dev/null
+++ b/src/core/features/login/components/sites/sites.ts
@@ -0,0 +1,131 @@
+// (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 { CoreDomUtils } from '@services/utils/dom';
+import { Component, OnInit } from '@angular/core';
+
+import { CoreSiteBasicInfo, CoreSites } from '@services/sites';
+import { CoreAccountsList, CoreLoginHelper } from '@features/login/services/login-helper';
+import { CoreNavigator } from '@services/navigator';
+import { CoreFilter } from '@features/filter/services/filter';
+import { CoreAnimations } from '@components/animations';
+import { ModalController } from '@singletons';
+
+/**
+ * Component that displays a "splash screen" while the app is being initialized.
+ */
+@Component({
+ selector: 'core-login-sites',
+ templateUrl: 'sites.html',
+ animations: [CoreAnimations.SLIDE_IN_OUT, CoreAnimations.SHOW_HIDE],
+})
+export class CoreLoginSitesComponent implements OnInit {
+
+ accountsList: CoreAccountsList = {
+ sameSite: [],
+ otherSites: [],
+ count: 0,
+ };
+
+ showDelete = false;
+ currentSiteId: string;
+ loaded = false;
+
+ constructor() {
+ this.currentSiteId = CoreSites.getRequiredCurrentSite().getId();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ async ngOnInit(): Promise {
+ this.accountsList = await CoreLoginHelper.getAccountsList(this.currentSiteId);
+ this.loaded = true;
+ }
+
+ /**
+ * Go to the page to add a site.
+ *
+ * @param event Click event.
+ */
+ async add(event: Event): Promise {
+ await this.close(event, true);
+
+ await CoreLoginHelper.goToAddSite(true, true);
+ }
+
+ /**
+ * Delete a site.
+ *
+ * @param event Click event.
+ * @param site Site to delete.
+ * @return Promise resolved when done.
+ */
+ async deleteSite(event: Event, site: CoreSiteBasicInfo): Promise {
+ event.stopPropagation();
+
+ let siteName = site.siteName || '';
+
+ siteName = await CoreFilter.formatText(siteName, { clean: true, singleLine: true, filter: false }, [], site.id);
+
+ try {
+ await CoreDomUtils.showDeleteConfirm('core.login.confirmdeletesite', { sitename: siteName });
+ } catch {
+ // User cancelled, stop.
+ return;
+ }
+
+ try {
+ await CoreLoginHelper.deleteAccountFromList(this.accountsList, site);
+
+ this.showDelete = false;
+ } catch (error) {
+ CoreDomUtils.showErrorModalDefault(error, 'core.login.errordeletesite', true);
+ }
+ }
+
+ /**
+ * Login in a site.
+ *
+ * @param event Click event.
+ * @param siteId The site ID.
+ * @return Promise resolved when done.
+ */
+ async login(event: Event, siteId: string): Promise {
+ await this.close(event, true);
+
+ // This navigation will logout and navigate to the site home.
+ await CoreNavigator.navigateToSiteHome({ preferCurrentTab: false , siteId });
+ }
+
+ /**
+ * Toggle delete.
+ */
+ toggleDelete(): void {
+ this.showDelete = !this.showDelete;
+ }
+
+ /**
+ * Close modal.
+ *
+ * @param event Click event.
+ */
+ async close(event: Event, closeAll = false): Promise {
+ event.preventDefault();
+ event.stopPropagation();
+
+ await ModalController.dismiss(closeAll);
+ }
+
+}
diff --git a/src/core/features/login/lang.json b/src/core/features/login/lang.json
index 8ebeb7fa5..53a7bf0ed 100644
--- a/src/core/features/login/lang.json
+++ b/src/core/features/login/lang.json
@@ -1,4 +1,6 @@
{
+ "accounts": "Accounts",
+ "add": "Add a new account",
"auth_email": "Email-based self-registration",
"authenticating": "Authenticating",
"cancel": "Cancel",
@@ -8,7 +10,7 @@
"changepasswordinstructions": "You cannot change your password in the app. Please click the following button to open the site in a web browser to change your password. Take into account you need to close the browser after changing the password as you will not be redirected to the app.",
"changepasswordlogoutinstructions": "If you prefer to change site or log out, please click the following button:",
"changepasswordreconnectinstructions": "Click the following button to reconnect to the site. (Take into account that if you didn't change your password successfully, you would return to the previous screen).",
- "confirmdeletesite": "Are you sure you want to delete the site {{sitename}}?",
+ "confirmdeletesite": "Are you sure you want to remove the account on {{sitename}}?",
"connect": "Connect!",
"connecttomoodle": "Connect to Moodle",
"contactyouradministrator": "Contact your site administrator for further help.",
@@ -23,7 +25,7 @@
"emailconfirmsentsuccess": "Confirmation email sent successfully",
"emailnotmatch": "Emails do not match",
"erroraccesscontrolalloworigin": "The cross-origin call you're trying to perform has been rejected. Please check https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium",
- "errordeletesite": "An error occurred while deleting this site. Please try again.",
+ "errordeletesite": "An error occurred while deleting this account. Please try again.",
"errorexampleurl": "The URL https://campus.example.edu is only an example URL, it's not a real site. Please use the URL of your school or organization's site.",
"errorqrnoscheme": "This URL isn't a valid login URL.",
"errorupdatesite": "An error occurred while updating the site's token.",
@@ -95,11 +97,12 @@
"reconnect": "Reconnect",
"reconnectdescription": "Your authentication token is invalid or has expired. You have to reconnect to the site.",
"reconnectssodescription": "Your authentication token is invalid or has expired. You have to reconnect to the site. You need to log in to the site in a browser window.",
+ "removeaccount": "Remove account",
"resendemail": "Resend email",
"searchby": "Search by:",
"security_question": "Security question",
"selectacountry": "Select a country",
- "selectsite": "Please select your site:",
+ "selectsite": "Please select your account:",
"signupplugindisabled": "{{$a}} is not enabled.",
"signuprequiredfieldnotsupported": "The signup form contains a required custom field that isn't supported in the app. Please create your account using a web browser.",
"siteaddress": "Your site",
@@ -114,6 +117,7 @@
"startsignup": "Create new account",
"stillcantconnect": "Still can't connect?",
"supplyinfo": "More details",
+ "toggleremove": "Edit accounts list",
"username": "Username",
"usernameoremail": "Enter either username or email address",
"usernamerequired": "Username required",
diff --git a/src/core/features/login/login-lazy.module.ts b/src/core/features/login/login-lazy.module.ts
index bab14255b..bad7099b5 100644
--- a/src/core/features/login/login-lazy.module.ts
+++ b/src/core/features/login/login-lazy.module.ts
@@ -16,9 +16,8 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CoreSharedModule } from '@/core/shared.module';
-import { CoreLoginSiteHelpComponent } from './components/site-help/site-help';
-import { CoreLoginSiteOnboardingComponent } from './components/site-onboarding/site-onboarding';
import { CoreLoginHasSitesGuard } from './guards/has-sites';
+import { CoreLoginComponentsModule } from './components/components.module';
const routes: Routes = [
{
@@ -67,11 +66,8 @@ const routes: Routes = [
@NgModule({
imports: [
CoreSharedModule,
+ CoreLoginComponentsModule,
RouterModule.forChild(routes),
],
- declarations: [
- CoreLoginSiteHelpComponent,
- CoreLoginSiteOnboardingComponent,
- ],
})
export class CoreLoginLazyModule {}
diff --git a/src/core/features/login/login.module.ts b/src/core/features/login/login.module.ts
index caa91ea91..fdbb39639 100644
--- a/src/core/features/login/login.module.ts
+++ b/src/core/features/login/login.module.ts
@@ -35,7 +35,9 @@ const appRoutes: Routes = [
];
@NgModule({
- imports: [AppRoutingModule.forChild(appRoutes)],
+ imports: [
+ AppRoutingModule.forChild(appRoutes),
+ ],
providers: [
{
provide: APP_INITIALIZER,
diff --git a/src/core/features/login/pages/site-policy/site-policy.ts b/src/core/features/login/pages/site-policy/site-policy.ts
index d58ee4c4e..8c4097f9e 100644
--- a/src/core/features/login/pages/site-policy/site-policy.ts
+++ b/src/core/features/login/pages/site-policy/site-policy.ts
@@ -35,17 +35,17 @@ export class CoreLoginSitePolicyPage implements OnInit {
showInline?: boolean;
policyLoaded?: boolean;
protected siteId?: string;
- protected currentSite?: CoreSite;
+ protected currentSite!: CoreSite;
/**
- * Component initialized.
+ * @inheritdoc
*/
ngOnInit(): void {
-
this.siteId = CoreNavigator.getRouteParam('siteId');
- this.currentSite = CoreSites.getCurrentSite();
- if (!this.currentSite) {
+ try {
+ this.currentSite = CoreSites.getRequiredCurrentSite();
+ } catch {
// Not logged in, stop.
this.cancel();
@@ -86,7 +86,7 @@ export class CoreLoginSitePolicyPage implements OnInit {
const extension = CoreMimetypeUtils.getExtension(mimeType, this.sitePolicy);
this.showInline = extension == 'html' || extension == 'htm';
- } catch (error) {
+ } catch {
// Unable to get mime type, assume it's not supported.
this.showInline = false;
} finally {
@@ -118,7 +118,7 @@ export class CoreLoginSitePolicyPage implements OnInit {
// Success accepting, go to site initial page.
// Invalidate cache since some WS don't return error if site policy is not accepted.
- await CoreUtils.ignoreErrors(this.currentSite!.invalidateWsCache());
+ await CoreUtils.ignoreErrors(this.currentSite.invalidateWsCache());
await CoreNavigator.navigateToSiteHome();
} catch (error) {
diff --git a/src/core/features/login/pages/sites/sites.html b/src/core/features/login/pages/sites/sites.html
index 704daedff..a47a65f2f 100644
--- a/src/core/features/login/pages/sites/sites.html
+++ b/src/core/features/login/pages/sites/sites.html
@@ -4,11 +4,11 @@
- {{ 'core.settings.sites' | translate }}
+ {{ 'core.login.accounts' | translate }}
- 0" (click)="toggleDelete()"
- [attr.aria-label]="'core.delete' | translate">
+ 0" (click)="toggleDelete()"
+ [attr.aria-label]="'core.login.toggleremove' | translate">
@@ -18,31 +18,43 @@
-
-
-
-
-
-
- {{site.fullName}}
-
- {{site.siteUrl}}
-
-
- {{site.badge}}
- {{ 'core.login.sitebadgedescription' | translate:{ count: site.badge } }}
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+ {{ sites[0].siteUrlWithoutProtocol }}
+
+
+
+
+
+
+
+
+ {{site.fullName}}
+
+
+ {{site.badge}}
+ {{ 'core.login.sitebadgedescription' | translate:{ count: site.badge }
+ }}
+
+
+
+
+
+
+
+
-
+
- {{ 'core.add' | translate }}
+ {{ 'core.login.add' | translate }}
diff --git a/src/core/features/login/pages/sites/sites.ts b/src/core/features/login/pages/sites/sites.ts
index 447d79f7d..a6d3e1eeb 100644
--- a/src/core/features/login/pages/sites/sites.ts
+++ b/src/core/features/login/pages/sites/sites.ts
@@ -13,14 +13,11 @@
// limitations under the License.
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreUtils } from '@services/utils/utils';
import { Component, OnInit } from '@angular/core';
import { CoreSiteBasicInfo, CoreSites } from '@services/sites';
-import { CoreLogger } from '@singletons/logger';
-import { CoreLoginHelper } from '@features/login/services/login-helper';
+import { CoreAccountsList, CoreLoginHelper } from '@features/login/services/login-helper';
import { CoreNavigator } from '@services/navigator';
-import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications';
import { CoreFilter } from '@features/filter/services/filter';
import { CoreAnimations } from '@components/animations';
@@ -30,40 +27,33 @@ import { CoreAnimations } from '@components/animations';
@Component({
selector: 'page-core-login-sites',
templateUrl: 'sites.html',
- animations: [CoreAnimations.SLIDE_IN_OUT],
+ animations: [CoreAnimations.SLIDE_IN_OUT, CoreAnimations.SHOW_HIDE],
})
export class CoreLoginSitesPage implements OnInit {
- sites: CoreSiteBasicInfo[] = [];
+ accountsList: CoreAccountsList = {
+ sameSite: [],
+ otherSites: [],
+ count: 0,
+ };
+
showDelete = false;
-
- protected logger: CoreLogger;
-
- constructor() {
- this.logger = CoreLogger.getInstance('CoreLoginSitesPage');
- }
+ loaded = false;
/**
- * Component being initialized.
- *
- * @return Promise resolved when done.
+ * @inheritdoc
*/
async ngOnInit(): Promise {
if (CoreNavigator.getRouteBooleanParam('openAddSite')) {
this.add();
}
- const sites = await CoreUtils.ignoreErrors(CoreSites.getSortedSites(), [] as CoreSiteBasicInfo[]);
+ this.accountsList = await CoreLoginHelper.getAccountsList();
+ this.loaded = true;
- // Remove protocol from the url to show more url text.
- this.sites = await Promise.all(sites.map(async (site) => {
- site.siteUrl = site.siteUrl.replace(/^https?:\/\//, '');
- site.badge = await CoreUtils.ignoreErrors(CorePushNotifications.getSiteCounter(site.id)) || 0;
-
- return site;
- }));
-
- this.showDelete = false;
+ if (this.accountsList.count == 0) {
+ this.add();
+ }
}
/**
@@ -76,12 +66,12 @@ export class CoreLoginSitesPage implements OnInit {
/**
* Delete a site.
*
- * @param e Click event.
+ * @param event Click event.
* @param site Site to delete.
* @return Promise resolved when done.
*/
- async deleteSite(e: Event, site: CoreSiteBasicInfo): Promise {
- e.stopPropagation();
+ async deleteSite(event: Event, site: CoreSiteBasicInfo): Promise {
+ event.stopPropagation();
let siteName = site.siteName || '';
@@ -95,20 +85,15 @@ export class CoreLoginSitesPage implements OnInit {
}
try {
- await CoreSites.deleteSite(site.id);
+ await CoreLoginHelper.deleteAccountFromList(this.accountsList, site);
- const index = this.sites.findIndex((listedSite) => listedSite.id == site.id);
- index >= 0 && this.sites.splice(index, 1);
this.showDelete = false;
// If there are no sites left, go to add site.
- const hasSites = await CoreSites.hasSites();
-
- if (!hasSites) {
+ if (this.accountsList.count == 0) {
CoreLoginHelper.goToAddSite(true, true);
}
} catch (error) {
- this.logger.error('Error deleting site ' + site.id, error);
CoreDomUtils.showErrorModalDefault(error, 'core.login.errordeletesite', true);
}
}
@@ -116,10 +101,14 @@ export class CoreLoginSitesPage implements OnInit {
/**
* Login in a site.
*
+ * @param event Click event.
* @param siteId The site ID.
* @return Promise resolved when done.
*/
- async login(siteId: string): Promise {
+ async login(event: Event, siteId: string): Promise {
+ event.preventDefault();
+ event.stopPropagation();
+
const modal = await CoreDomUtils.showModalLoading();
try {
@@ -131,7 +120,6 @@ export class CoreLoginSitesPage implements OnInit {
return;
}
} catch (error) {
- this.logger.error('Error loading site ' + siteId, error);
CoreDomUtils.showErrorModalDefault(error, 'Error loading site.');
} finally {
modal.dismiss();
diff --git a/src/core/features/login/services/login-helper.ts b/src/core/features/login/services/login-helper.ts
index d41c0b742..627547671 100644
--- a/src/core/features/login/services/login-helper.ts
+++ b/src/core/features/login/services/login-helper.ts
@@ -19,7 +19,7 @@ import { Md5 } from 'ts-md5/dist/md5';
import { CoreApp, CoreStoreConfig } from '@services/app';
import { CoreConfig } from '@services/config';
import { CoreEvents, CoreEventSessionExpiredData, CoreEventSiteData } from '@singletons/events';
-import { CoreSites, CoreLoginSiteInfo } from '@services/sites';
+import { CoreSites, CoreLoginSiteInfo, CoreSiteBasicInfo } from '@services/sites';
import { CoreWS, CoreWSExternalWarning } from '@services/ws';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreTextUtils } from '@services/utils/text';
@@ -35,6 +35,8 @@ import { CoreUrl } from '@singletons/url';
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
import { CoreCanceledError } from '@classes/errors/cancelederror';
import { CoreCustomURLSchemes } from '@services/urlschemes';
+import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
+import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications';
/**
* Helper provider that provides some common features regarding authentication.
@@ -311,7 +313,7 @@ export class CoreLoginHelperProvider {
site = site || CoreSites.getCurrentSite();
const config = site?.getStoredConfig();
- return 'core.mainmenu.' + (config && config.tool_mobile_forcelogout == '1' ? 'logout' : 'changesite');
+ return 'core.mainmenu.' + (config && config.tool_mobile_forcelogout == '1' ? 'logout' : 'switchaccount');
}
/**
@@ -407,8 +409,25 @@ export class CoreLoginHelperProvider {
* @param showKeyboard Whether to show keyboard in the new page. Only if no fixed URL set.
* @return Promise resolved when done.
*/
- async goToAddSite(setRoot?: boolean, showKeyboard?: boolean): Promise {
- const [path, params] = this.getAddSiteRouteInfo(showKeyboard);
+ async goToAddSite(setRoot = false, showKeyboard = false): Promise {
+ let path = '/login/sites';
+ let params: Params = { openAddSite: true , showKeyboard };
+
+ if (CoreSites.isLoggedIn()) {
+
+ if (CoreSitePlugins.hasSitePluginsLoaded) {
+ // The site has site plugins so the app will be restarted. Store the data and logout.
+ CoreApp.storeRedirect(CoreConstants.NO_SITE_ID, path, { params });
+
+ await CoreSites.logout();
+
+ return;
+ }
+
+ await CoreSites.logout();
+ } else {
+ [path, params] = this.getAddSiteRouteInfo(showKeyboard);
+ }
await CoreNavigator.navigate(path, { params, reset: setRoot });
}
@@ -1317,43 +1336,120 @@ export class CoreLoginHelperProvider {
}
}
+ /**
+ * Get the accounts list classified per site.
+ *
+ * @param currentSiteId If loggedin, current Site Id.
+ * @return Promise resolved with account list.
+ */
+ async getAccountsList(currentSiteId?: string): Promise {
+ const sites = await CoreUtils.ignoreErrors(CoreSites.getSortedSites(), [] as CoreSiteBasicInfo[]);
+
+ const accountsList: CoreAccountsList = {
+ sameSite: [],
+ otherSites: [],
+ count: sites.length,
+ };
+
+ let siteUrl = '';
+
+ if (currentSiteId) {
+ const index = sites.findIndex((site) => site.id == currentSiteId);
+
+ accountsList.currentSite = sites.splice(index, 1)[0];
+ siteUrl = accountsList.currentSite.siteUrlWithoutProtocol;
+ }
+
+ const otherSites: Record = {};
+
+ // Add site counter and classify sites.
+ await Promise.all(sites.map(async (site) => {
+ site.badge = await CoreUtils.ignoreErrors(CorePushNotifications.getSiteCounter(site.id)) || 0;
+
+ if (site.siteUrlWithoutProtocol == siteUrl) {
+ accountsList.sameSite.push(site);
+ } else {
+ if (!otherSites[site.siteUrlWithoutProtocol]) {
+ otherSites[site.siteUrlWithoutProtocol] = [];
+ }
+
+ otherSites[site.siteUrlWithoutProtocol].push(site);
+ }
+
+ return;
+ }));
+
+ accountsList.otherSites = CoreUtils.objectToArray(otherSites);
+
+ return accountsList;
+ }
+
+ /**
+ * Find and delete a site from the list of sites.
+ *
+ * @param accountsList Account list.
+ * @param site Site to be deleted.
+ * @return Resolved when done.
+ */
+ async deleteAccountFromList(accountsList: CoreAccountsList, site: CoreSiteBasicInfo): Promise {
+ await CoreSites.deleteSite(site.id);
+
+ const siteUrl = site.siteUrlWithoutProtocol;
+ let index = 0;
+
+ // Found on same site.
+ if (accountsList.sameSite.length > 0 && accountsList.sameSite[0].siteUrlWithoutProtocol == siteUrl) {
+ index = accountsList.sameSite.findIndex((listedSite) => listedSite.id == site.id);
+ if (index >= 0) {
+ accountsList.sameSite.splice(index, 1);
+ accountsList.count--;
+ }
+
+ return;
+ }
+
+ const otherSiteIndex = accountsList.otherSites.findIndex((sites) =>
+ sites.length > 0 && sites[0].siteUrlWithoutProtocol == siteUrl);
+ if (otherSiteIndex < 0) {
+ // Site Url not found.
+ return;
+ }
+
+ index = accountsList.otherSites[otherSiteIndex].findIndex((listedSite) => listedSite.id == site.id);
+ if (index >= 0) {
+ accountsList.otherSites[otherSiteIndex].splice(index, 1);
+ accountsList.count--;
+ }
+
+ if (accountsList.otherSites[otherSiteIndex].length == 0) {
+ accountsList.otherSites.splice(otherSiteIndex, 1);
+ }
+ }
+
}
export const CoreLoginHelper = makeSingleton(CoreLoginHelperProvider);
+/**
+ * Accounts list for selecting sites interfaces.
+ */
+export type CoreAccountsList = {
+ currentSite?: CoreSiteBasicInfo; // If logged in, current site info.
+ sameSite: CoreSiteBasicInfo[]; // If logged in, accounts info on the same site.
+ otherSites: CoreSiteBasicInfo[][]; // Other accounts in other sites.
+ count: number; // Number of sites.
+};
+
/**
* Data related to a SSO authentication.
*/
export interface CoreLoginSSOData {
- /**
- * The site's URL.
- */
- siteUrl: string;
-
- /**
- * User's token.
- */
- token?: string;
-
- /**
- * User's private token.
- */
- privateToken?: string;
-
- /**
- * Name of the page to go after authenticated.
- */
- pageName?: string;
-
- /**
- * Options of the navigation to the page.
- */
- pageOptions?: CoreNavigationOptions;
-
- /**
- * Other params added to the login url.
- */
- ssoUrlParams?: CoreUrlParams;
+ siteUrl: string; // The site's URL.
+ token?: string; // User's token.
+ privateToken?: string; // User's private token.
+ pageName?: string; // Name of the page to go after authenticated.
+ pageOptions?: CoreNavigationOptions; // Options of the navigation to the page.
+ ssoUrlParams?: CoreUrlParams; // Other params added to the login url.
};
/**
diff --git a/src/core/features/mainmenu/components/components.module.ts b/src/core/features/mainmenu/components/components.module.ts
new file mode 100644
index 000000000..613c1c790
--- /dev/null
+++ b/src/core/features/mainmenu/components/components.module.ts
@@ -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 { NgModule } from '@angular/core';
+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';
+
+@NgModule({
+ declarations: [
+ CoreMainMenuUserButtonComponent,
+ CoreMainMenuUserMenuComponent,
+ ],
+ imports: [
+ CoreSharedModule,
+ CoreLoginComponentsModule,
+ ],
+ exports: [
+ CoreMainMenuUserButtonComponent,
+ CoreMainMenuUserMenuComponent,
+ ],
+})
+export class CoreMainMenuComponentsModule {}
diff --git a/src/core/features/mainmenu/components/user-menu-button/user-menu-button.html b/src/core/features/mainmenu/components/user-menu-button/user-menu-button.html
new file mode 100644
index 000000000..998f7e7c0
--- /dev/null
+++ b/src/core/features/mainmenu/components/user-menu-button/user-menu-button.html
@@ -0,0 +1,3 @@
+
+
diff --git a/src/core/features/mainmenu/components/user-menu-button/user-menu-button.scss b/src/core/features/mainmenu/components/user-menu-button/user-menu-button.scss
new file mode 100644
index 000000000..15aa50df5
--- /dev/null
+++ b/src/core/features/mainmenu/components/user-menu-button/user-menu-button.scss
@@ -0,0 +1,3 @@
+:host-context(ion-tabs.placement-side div.tabs-inner) {
+ display: none;
+}
\ No newline at end of file
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
new file mode 100644
index 000000000..a27782b73
--- /dev/null
+++ b/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts
@@ -0,0 +1,65 @@
+// (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, OnInit } from '@angular/core';
+import { CoreSiteInfo } from '@classes/site';
+import { IonRouterOutlet } from '@ionic/angular';
+import { CoreSites } from '@services/sites';
+import { CoreDomUtils } from '@services/utils/dom';
+import { CoreMainMenuUserMenuComponent } from '../user-menu/user-menu';
+
+/**
+ * Component to display an avatar on the header to open user menu.
+ *
+ * Example:
+ */
+@Component({
+ selector: 'core-user-menu-button',
+ templateUrl: 'user-menu-button.html',
+ styleUrls: ['user-menu-button.scss'],
+})
+export class CoreMainMenuUserButtonComponent implements OnInit {
+
+ siteInfo?: CoreSiteInfo;
+ isMainScreen = false;
+
+ constructor(protected routerOutlet: IonRouterOutlet) {
+ const currentSite = CoreSites.getRequiredCurrentSite();
+
+ this.siteInfo = currentSite.getInfo();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ ngOnInit(): void {
+ this.isMainScreen = !this.routerOutlet.canGoBack();
+ }
+
+ /**
+ * Open User menu
+ *
+ * @param event Click event.
+ */
+ openUserMenu(event: Event): void {
+ event.preventDefault();
+ event.stopPropagation();
+
+ CoreDomUtils.openSideModal({
+ component: CoreMainMenuUserMenuComponent,
+ cssClass: 'core-modal-lateral-sm',
+ });
+ }
+
+}
diff --git a/src/core/features/mainmenu/components/user-menu/user-menu.html b/src/core/features/mainmenu/components/user-menu/user-menu.html
new file mode 100644
index 000000000..34a9184f7
--- /dev/null
+++ b/src/core/features/mainmenu/components/user-menu/user-menu.html
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+ {{'core.user.account' | translate}}
+
+
+
+
+
+
+
+
+ {{ siteInfo.fullname }}
+
+
+
+
+
+
+
+ {{ 'core.user.profile' | translate }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ handler.title | translate }}
+
+
+ {{handler.badge}}
+
+
+ {{ handler.badgeA11yText | translate: {$a : handler.badge } }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ 'core.mainmenu.logout' | translate }}
+
+
diff --git a/src/core/features/mainmenu/components/user-menu/user-menu.scss b/src/core/features/mainmenu/components/user-menu/user-menu.scss
new file mode 100644
index 000000000..74e3b93a4
--- /dev/null
+++ b/src/core/features/mainmenu/components/user-menu/user-menu.scss
@@ -0,0 +1,26 @@
+@import "~theme/globals";
+
+:host {
+ .core-user-menu-preferences {
+ --inner-border-width: 0;
+ --border-width: 1px 0 0 0;
+ }
+}
+
+@if ($core-user-hide-siteinfo) {
+ .core-usermenu-siteinfo {
+ display: none;
+ }
+}
+
+@if ($core-user-hide-sitename) {
+ .core-usermenu-sitename {
+ display: none;
+ }
+}
+
+@if ($core-user-hide-siteurl) {
+ .core-usermenu-siteurl {
+ display: none;
+ }
+}
diff --git a/src/core/features/mainmenu/components/user-menu/user-menu.ts b/src/core/features/mainmenu/components/user-menu/user-menu.ts
new file mode 100644
index 000000000..1ec0e39ad
--- /dev/null
+++ b/src/core/features/mainmenu/components/user-menu/user-menu.ts
@@ -0,0 +1,191 @@
+// (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, OnDestroy, OnInit } from '@angular/core';
+import { CoreSiteInfo } from '@classes/site';
+import { CoreLoginSitesComponent } from '@features/login/components/sites/sites';
+import { CoreLoginHelper } from '@features/login/services/login-helper';
+import { CoreUser, CoreUserProfile } from '@features/user/services/user';
+import { CoreUserProfileHandlerData, CoreUserDelegate, CoreUserDelegateService } from '@features/user/services/user-delegate';
+import { CoreNavigator } from '@services/navigator';
+import { CoreSites } from '@services/sites';
+import { CoreDomUtils } from '@services/utils/dom';
+import { ModalController } from '@singletons';
+import { Subscription } from 'rxjs';
+
+/**
+ * Component to display a user menu.
+ */
+@Component({
+ selector: 'core-main-menu-user-menu',
+ templateUrl: 'user-menu.html',
+ styleUrls: ['user-menu.scss'],
+})
+export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
+
+ siteInfo?: CoreSiteInfo;
+ siteName?: string;
+ siteUrl?: string;
+ handlers: CoreUserProfileHandlerData[] = [];
+ handlersLoaded = false;
+ loaded = false;
+ user?: CoreUserProfile;
+ moreSites = false;
+
+ protected subscription!: Subscription;
+
+ /**
+ * @inheritdoc
+ */
+ async ngOnInit(): Promise {
+ // Check if there are more sites to switch.
+ const sites = await CoreSites.getSites();
+ this.moreSites = sites.length > 1;
+
+ const currentSite = CoreSites.getRequiredCurrentSite();
+ this.siteInfo = currentSite.getInfo();
+ this.siteName = currentSite.getSiteName();
+ this.siteUrl = currentSite.getURL();
+
+ this.loaded = true;
+
+ // Load the handlers.
+ if (this.siteInfo) {
+ this.user = await CoreUser.getProfile(this.siteInfo.userid);
+
+ this.subscription = CoreUserDelegate.getProfileHandlersFor(this.user).subscribe((handlers) => {
+ if (!handlers || !this.user) {
+ return;
+ }
+
+ this.handlers = [];
+ handlers.forEach((handler) => {
+ if (handler.type == CoreUserDelegateService.TYPE_NEW_PAGE) {
+ this.handlers.push(handler.data);
+ }
+ });
+
+ this.handlersLoaded = CoreUserDelegate.areHandlersLoaded(this.user.id);
+ });
+
+ }
+ }
+
+ /**
+ * Opens User profile page.
+ *
+ * @param event Click event.
+ */
+ async openUserProfile(event: Event): Promise {
+ if (!this.siteInfo) {
+ return;
+ }
+
+ await this.close(event);
+
+ CoreNavigator.navigateToSitePath('user/about', {
+ params: {
+ userId: this.siteInfo.userid,
+ },
+ });
+ }
+
+ /**
+ * Opens preferences.
+ *
+ * @param event Click event.
+ */
+ async openPreferences(event: Event): Promise {
+ await this.close(event);
+
+ CoreNavigator.navigateToSitePath('preferences');
+ }
+
+ /**
+ * A handler was clicked.
+ *
+ * @param event Click event.
+ * @param handler Handler that was clicked.
+ */
+ async handlerClicked(event: Event, handler: CoreUserProfileHandlerData): Promise {
+ if (!this.user) {
+ return;
+ }
+
+ await this.close(event);
+
+ handler.action(event, this.user);
+ }
+
+ /**
+ * Logout the user.
+ *
+ * @param event Click event
+ */
+ async logout(event: Event): Promise {
+ await this.close(event);
+
+ CoreSites.logout();
+ }
+
+ /**
+ * Show account selector.
+ *
+ * @param event Click event
+ */
+ async switchAccounts(event: Event): Promise {
+ const thisModal = await ModalController.getTop();
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ const closeAll = await CoreDomUtils.openSideModal({
+ component: CoreLoginSitesComponent,
+ cssClass: 'core-modal-lateral-sm',
+ });
+
+ if (closeAll) {
+ await ModalController.dismiss(undefined, undefined, thisModal.id);
+ }
+ }
+
+ /**
+ * Add account.
+ *
+ * @param event Click event
+ */
+ async addAccount(event: Event): Promise {
+ await this.close(event);
+
+ await CoreLoginHelper.goToAddSite(true, true);
+ }
+
+ /**
+ * Close modal.
+ */
+ async close(event: Event): Promise {
+ event.preventDefault();
+ event.stopPropagation();
+
+ await ModalController.dismiss();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ ngOnDestroy(): void {
+ this.subscription?.unsubscribe();
+ }
+
+}
diff --git a/src/core/features/mainmenu/lang.json b/src/core/features/mainmenu/lang.json
index a6558e06e..9e9977545 100644
--- a/src/core/features/mainmenu/lang.json
+++ b/src/core/features/mainmenu/lang.json
@@ -1,7 +1,5 @@
{
- "changesite": "Change site",
- "help": "Help",
"home": "Home",
"logout": "Log out",
- "website": "Website"
+ "switchaccount": "Switch account"
}
diff --git a/src/core/features/mainmenu/mainmenu-lazy.module.ts b/src/core/features/mainmenu/mainmenu-lazy.module.ts
index ff9c67189..22f30d18b 100644
--- a/src/core/features/mainmenu/mainmenu-lazy.module.ts
+++ b/src/core/features/mainmenu/mainmenu-lazy.module.ts
@@ -22,6 +22,7 @@ import { MAIN_MENU_ROUTES } from './mainmenu-routing.module';
import { CoreMainMenuPage } from './pages/menu/menu';
import { CoreMainMenuHomeHandlerService } from './services/handlers/mainmenu';
import { CoreMainMenuProvider } from './services/mainmenu';
+import { CoreMainMenuComponentsModule } from './components/components.module';
function buildRoutes(injector: Injector): Routes {
const routes = resolveModuleRoutes(injector, MAIN_MENU_ROUTES);
@@ -54,6 +55,7 @@ function buildRoutes(injector: Injector): Routes {
@NgModule({
imports: [
CoreSharedModule,
+ CoreMainMenuComponentsModule,
],
declarations: [
CoreMainMenuPage,
diff --git a/src/core/features/mainmenu/pages/home/home.html b/src/core/features/mainmenu/pages/home/home.html
index 903bcb8ff..e953b24c2 100644
--- a/src/core/features/mainmenu/pages/home/home.html
+++ b/src/core/features/mainmenu/pages/home/home.html
@@ -8,6 +8,7 @@
+
diff --git a/src/core/features/mainmenu/pages/home/home.module.ts b/src/core/features/mainmenu/pages/home/home.module.ts
index c60828634..7dcde146c 100644
--- a/src/core/features/mainmenu/pages/home/home.module.ts
+++ b/src/core/features/mainmenu/pages/home/home.module.ts
@@ -22,6 +22,7 @@ import { CoreMainMenuHomePage } from './home';
import { MAIN_MENU_HOME_ROUTES } from './home-routing.module';
import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module';
import { CoreMainMenuHomeHandlerService } from '@features/mainmenu/services/handlers/mainmenu';
+import { CoreMainMenuComponentsModule } from '@features/mainmenu/components/components.module';
function buildRoutes(injector: Injector): Routes {
const routes = resolveModuleRoutes(injector, MAIN_MENU_HOME_ROUTES);
@@ -42,6 +43,7 @@ function buildRoutes(injector: Injector): Routes {
@NgModule({
imports: [
CoreSharedModule,
+ CoreMainMenuComponentsModule,
],
providers: [
{ provide: ROUTES, multi: true, useFactory: buildRoutes, deps: [Injector] },
diff --git a/src/core/features/mainmenu/pages/menu/menu.html b/src/core/features/mainmenu/pages/menu/menu.html
index b888971dc..18a5bb380 100644
--- a/src/core/features/mainmenu/pages/menu/menu.html
+++ b/src/core/features/mainmenu/pages/menu/menu.html
@@ -4,6 +4,8 @@
[@menuShowHideAnimation]="tabsPlacement == 'side' ? '' : (isMainScreen ? 'visible' : 'hidden')">
+
+
@@ -18,7 +20,7 @@
-
+
{{ 'core.more' | translate }}
{{ 'core.more' | translate }}
diff --git a/src/core/features/mainmenu/pages/menu/menu.scss b/src/core/features/mainmenu/pages/menu/menu.scss
index 7f362ec1d..b359d98f0 100644
--- a/src/core/features/mainmenu/pages/menu/menu.scss
+++ b/src/core/features/mainmenu/pages/menu/menu.scss
@@ -71,7 +71,7 @@
@include padding(var(--ion-safe-area-top), 0px, var(--ion-safe-area-bottom), var(--ion-safe-area-left));
- ion-tab-button {
+ ion-tab-button, core-user-menu-button {
width: 100%;
min-height: var(--menutabbar-size);
flex: 0;
@@ -83,6 +83,12 @@
}
}
+ core-user-menu-button {
+ align-items: center;
+ display: flex;
+ justify-content: center;
+ }
+
.core-network-message {
--network-message-height: 16px;
position: absolute;
diff --git a/src/core/features/mainmenu/pages/more/more.html b/src/core/features/mainmenu/pages/more/more.html
index 3f3fcff68..2d111d407 100644
--- a/src/core/features/mainmenu/pages/more/more.html
+++ b/src/core/features/mainmenu/pages/more/more.html
@@ -4,98 +4,63 @@
-
+ {{ 'core.more' | translate }}
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+ {{ handler.title | translate}}
+
+
+ {{handler.badge}}
+
+
+ {{ handler.badgeA11yText | translate: {$a : handler.badge } }}
+
+
+
+
+
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
- {{ 'core.scanqr' | translate }}
+ {{item.label}}
-
-
-
- {{ 'core.mainmenu.website' | translate }}
-
-
-
-
-
- {{ 'core.mainmenu.help' | translate }}
-
-
-
-
-
- {{ 'core.settings.preferences' | translate }}
-
-
-
-
-
- {{ logoutLabel | translate }}
-
-
-
-
-
-
- {{ 'core.settings.appsettings' | translate }}
-
-
-
-
+
+
+
+
+ {{ 'core.scanqr' | translate }}
+
+
+
+
+
+
+
+ {{ 'core.settings.appsettings' | translate }}
+
+
+
diff --git a/src/core/features/mainmenu/pages/more/more.module.ts b/src/core/features/mainmenu/pages/more/more.module.ts
index 1999f1b49..770fa3d9b 100644
--- a/src/core/features/mainmenu/pages/more/more.module.ts
+++ b/src/core/features/mainmenu/pages/more/more.module.ts
@@ -19,10 +19,12 @@ import { CoreSharedModule } from '@/core/shared.module';
import { CoreMainMenuMorePage } from './more';
import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module';
import { CoreMainMenuProvider } from '@features/mainmenu/services/mainmenu';
+import { CoreMainMenuComponentsModule } from '@features/mainmenu/components/components.module';
@NgModule({
imports: [
CoreSharedModule,
+ CoreMainMenuComponentsModule,
],
providers: [
{
diff --git a/src/core/features/mainmenu/pages/more/more.scss b/src/core/features/mainmenu/pages/more/more.scss
index c5b62e00a..9b1b544d2 100644
--- a/src/core/features/mainmenu/pages/more/more.scss
+++ b/src/core/features/mainmenu/pages/more/more.scss
@@ -17,21 +17,3 @@ ion-item {
color: var(--core-more-icon, inherit);
}
}
-
-@if ($core-more-hide-siteinfo) {
- .core-moremenu-siteinfo {
- display: none;
- }
-}
-
-@if ($core-more-hide-sitename) {
- .core-moremenu-sitename {
- display: none;
- }
-}
-
-@if ($core-more-hide-siteurl) {
- .core-moremenu-siteurl {
- display: none;
- }
-}
diff --git a/src/core/features/mainmenu/pages/more/more.ts b/src/core/features/mainmenu/pages/more/more.ts
index 733c8a0aa..626a18d27 100644
--- a/src/core/features/mainmenu/pages/more/more.ts
+++ b/src/core/features/mainmenu/pages/more/more.ts
@@ -17,8 +17,6 @@ import { Subscription } from 'rxjs';
import { CoreSites } from '@services/sites';
import { CoreUtils } from '@services/utils/utils';
-import { CoreSiteInfo } from '@classes/site';
-import { CoreLoginHelper } from '@features/login/services/login-helper';
import { CoreMainMenuDelegate, CoreMainMenuHandlerData } from '../../services/mainmenu-delegate';
import { CoreMainMenu, CoreMainMenuCustomItem } from '../../services/mainmenu';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
@@ -29,7 +27,7 @@ import { CoreTextUtils } from '@services/utils/text';
import { Translate } from '@singletons';
/**
- * Page that displays the main menu of the app.
+ * Page that displays the more page of the app.
*/
@Component({
selector: 'page-core-mainmenu-more',
@@ -39,38 +37,30 @@ import { Translate } from '@singletons';
export class CoreMainMenuMorePage implements OnInit, OnDestroy {
handlers?: CoreMainMenuHandlerData[];
- allHandlers?: CoreMainMenuHandlerData[];
handlersLoaded = false;
- siteInfo?: CoreSiteInfo;
- siteName?: string;
- logoutLabel = 'core.mainmenu.changesite';
showScanQR: boolean;
- showWeb?: boolean;
- showHelp?: boolean;
- docsUrl?: string;
customItems?: CoreMainMenuCustomItem[];
- siteUrl?: string;
- loggedOut = false;
+ protected allHandlers?: CoreMainMenuHandlerData[];
protected subscription!: Subscription;
protected langObserver: CoreEventObserver;
protected updateSiteObserver: CoreEventObserver;
constructor() {
+ this.langObserver = CoreEvents.on(CoreEvents.LANGUAGE_CHANGED, this.loadCustomMenuItems.bind(this));
+
+ this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, async () => {
+ this.customItems = await CoreMainMenu.getCustomMenuItems();
+ }, CoreSites.getCurrentSiteId());
+
+ this.loadCustomMenuItems();
- this.langObserver = CoreEvents.on(CoreEvents.LANGUAGE_CHANGED, this.loadSiteInfo.bind(this));
- this.updateSiteObserver = CoreEvents.on(
- CoreEvents.SITE_UPDATED,
- this.loadSiteInfo.bind(this),
- CoreSites.getCurrentSiteId(),
- );
- this.loadSiteInfo();
this.showScanQR = CoreUtils.canScanQR() &&
!CoreSites.getCurrentSite()?.isFeatureDisabled('CoreMainMenuDelegate_QrReader');
}
/**
- * Initialize component.
+ * @inheritdoc
*/
ngOnInit(): void {
// Load the handlers.
@@ -84,7 +74,7 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy {
}
/**
- * Page destroyed.
+ * @inheritdoc
*/
ngOnDestroy(): void {
window.removeEventListener('resize', this.initHandlers.bind(this));
@@ -113,24 +103,9 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy {
}
/**
- * Load the site info required by the view.
+ * Load custom menu items.
*/
- protected async loadSiteInfo(): Promise {
- const currentSite = CoreSites.getCurrentSite();
-
- if (!currentSite) {
- return;
- }
-
- this.siteInfo = currentSite.getInfo();
- this.siteName = currentSite.getSiteName();
- this.siteUrl = currentSite.getURL();
- this.logoutLabel = CoreLoginHelper.getLogoutLabel(currentSite);
- this.showWeb = !currentSite.isFeatureDisabled('CoreMainMenuDelegate_website');
- this.showHelp = !currentSite.isFeatureDisabled('CoreMainMenuDelegate_help');
-
- this.docsUrl = await currentSite.getDocsUrl();
-
+ protected async loadCustomMenuItems(): Promise {
this.customItems = await CoreMainMenu.getCustomMenuItems();
}
@@ -154,13 +129,6 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy {
CoreNavigator.navigateToSitePath('viewer/iframe', { params: { title: item.label, url: item.url } });
}
- /**
- * Open preferences.
- */
- openPreferences(): void {
- CoreNavigator.navigateToSitePath('preferences');
- }
-
/**
* Open settings.
*/
@@ -200,12 +168,4 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy {
}
}
- /**
- * Logout the user.
- */
- logout(): void {
- this.loggedOut = true;
- CoreSites.logout();
- }
-
}
diff --git a/src/core/features/settings/pages/site/site.html b/src/core/features/settings/pages/site/site.html
index b28da0563..af29f5ee0 100644
--- a/src/core/features/settings/pages/site/site.html
+++ b/src/core/features/settings/pages/site/site.html
@@ -15,18 +15,6 @@
-
-
- {{siteInfo!.fullname}}
-
-
-
- {{ siteUrl }}
-
-
-
-
@@ -36,38 +24,37 @@
{{ handler.title | translate}}
-
-
-
-
- {{ 'core.settings.spaceusage' | translate }}
- {{ spaceUsage.spaceUsage | coreBytesToSize }}
-
-
-
-
-
-
-
-
-
-
- {{ 'core.settings.synchronizenow' | translate }}
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ {{ 'core.settings.spaceusage' | translate }}
+ {{ spaceUsage.spaceUsage | coreBytesToSize }}
+
+
+
+
+
+
+
+
+
+
+ {{ 'core.settings.synchronizenow' | translate }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/core/features/settings/pages/site/site.ts b/src/core/features/settings/pages/site/site.ts
index be7d0e313..77a366b73 100644
--- a/src/core/features/settings/pages/site/site.ts
+++ b/src/core/features/settings/pages/site/site.ts
@@ -22,7 +22,6 @@ import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreSettingsHelper, CoreSiteSpaceUsage } from '../../services/settings-helper';
import { CoreApp } from '@services/app';
-import { CoreSiteInfo } from '@classes/site';
import { Translate } from '@singletons';
import { CoreNavigator } from '@services/navigator';
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
@@ -43,9 +42,6 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy {
isIOS: boolean;
siteId: string;
- siteInfo?: CoreSiteInfo;
- siteName?: string;
- siteUrl?: string;
spaceUsage: CoreSiteSpaceUsage = {
cacheEntries: 0,
spaceUsage: 0,
@@ -60,11 +56,9 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy {
this.siteId = CoreSites.getCurrentSiteId();
this.handlers = new CoreSettingsSitePreferencesManager(CoreSitePreferencesPage);
- this.sitesObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, (data) => {
- if (data.siteId == this.siteId) {
- this.refreshData();
- }
- });
+ this.sitesObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
+ this.refreshData();
+ }, this.siteId);
}
/**
@@ -94,11 +88,6 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy {
protected async fetchData(): Promise {
this.handlers.setItems(CoreSettingsDelegate.getHandlers());
- const currentSite = CoreSites.getCurrentSite();
- this.siteInfo = currentSite!.getInfo();
- this.siteName = currentSite!.getSiteName();
- this.siteUrl = currentSite!.getURL();
-
this.spaceUsage = await CoreSettingsHelper.getSiteSpaceUsage(this.siteId);
}
@@ -145,7 +134,9 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy {
*/
async deleteSiteStorage(): Promise {
try {
- this.spaceUsage = await CoreSettingsHelper.deleteSiteStorage(this.siteName || '', this.siteId);
+ const siteName = CoreSites.getRequiredCurrentSite().getSiteName();
+
+ this.spaceUsage = await CoreSettingsHelper.deleteSiteStorage(siteName, this.siteId);
} catch {
// Ignore cancelled confirmation modal.
}
diff --git a/src/core/features/settings/pages/space-usage/space-usage.html b/src/core/features/settings/pages/space-usage/space-usage.html
index aa17b099a..6d1343a40 100644
--- a/src/core/features/settings/pages/space-usage/space-usage.html
+++ b/src/core/features/settings/pages/space-usage/space-usage.html
@@ -25,7 +25,7 @@
{{ site.fullName }}
- {{ site.siteUrl }}
+ {{ site.siteUrlWithoutProtocol }}
{{ site.spaceUsage | coreBytesToSize }}
diff --git a/src/core/features/settings/pages/synchronization/synchronization.html b/src/core/features/settings/pages/synchronization/synchronization.html
index 625d67b88..31fe30857 100644
--- a/src/core/features/settings/pages/synchronization/synchronization.html
+++ b/src/core/features/settings/pages/synchronization/synchronization.html
@@ -37,7 +37,7 @@
{{ site.fullName }}
- {{ site.siteUrl }}
+ {{ site.siteUrlWithoutProtocol }}
+
diff --git a/src/core/features/tag/pages/search/search.html b/src/core/features/tag/pages/search/search.html
index 5afa3e371..1f6ff37a9 100644
--- a/src/core/features/tag/pages/search/search.html
+++ b/src/core/features/tag/pages/search/search.html
@@ -4,6 +4,9 @@
{{ 'core.tag.searchtags' | translate }}
+
+
+
diff --git a/src/core/features/tag/pages/search/search.page.module.ts b/src/core/features/tag/pages/search/search.page.module.ts
index 3da4821d2..5519cb3ac 100644
--- a/src/core/features/tag/pages/search/search.page.module.ts
+++ b/src/core/features/tag/pages/search/search.page.module.ts
@@ -19,6 +19,7 @@ import { CoreSharedModule } from '@/core/shared.module';
import { CoreSearchComponentsModule } from '@features/search/components/components.module';
import { CoreTagSearchPage } from './search.page';
+import { CoreMainMenuComponentsModule } from '@features/mainmenu/components/components.module';
const routes: Routes = [
{
@@ -34,6 +35,7 @@ const routes: Routes = [
RouterModule.forChild(routes),
CoreSharedModule,
CoreSearchComponentsModule,
+ CoreMainMenuComponentsModule,
],
exports: [RouterModule],
})
diff --git a/src/core/features/tag/tag-lazy.module.ts b/src/core/features/tag/tag-lazy.module.ts
index e0c6f7b0e..6ced82dfe 100644
--- a/src/core/features/tag/tag-lazy.module.ts
+++ b/src/core/features/tag/tag-lazy.module.ts
@@ -35,7 +35,7 @@ function buildRoutes(injector: Injector): Routes {
data: {
mainMenuTabRoot: CoreTagMainMenuHandlerService.PAGE_NAME,
},
- loadChildren: () => import('@features/tag//pages/search/search.page.module').then(m => m.CoreTagSearchPageModule),
+ loadChildren: () => import('@features/tag/pages/search/search.page.module').then(m => m.CoreTagSearchPageModule),
},
CoreTagIndexAreaRoute,
...buildTabMainRoutes(injector, {
diff --git a/src/core/features/user/lang.json b/src/core/features/user/lang.json
index 610e0fae4..61d41e287 100644
--- a/src/core/features/user/lang.json
+++ b/src/core/features/user/lang.json
@@ -1,5 +1,6 @@
{
"address": "Address",
+ "account": "Account",
"city": "City/town",
"contact": "Contact",
"country": "Country",
@@ -19,6 +20,7 @@
"participants": "Participants",
"phone1": "Phone",
"phone2": "Mobile phone",
+ "profile": "Profile",
"roles": "Roles",
"sendemail": "Email",
"student": "Student",
diff --git a/src/core/features/user/pages/about/about.html b/src/core/features/user/pages/about/about.html
index fb08137f1..03abbef38 100644
--- a/src/core/features/user/pages/about/about.html
+++ b/src/core/features/user/pages/about/about.html
@@ -3,7 +3,7 @@
- {{ title }}
+ {{ 'core.user.profile' | translate }}
@@ -12,39 +12,57 @@
+
+
+
+
+
+
+
+ {{ user.fullname }}
+
+ {{ user.address }}
+
+
+
+
- {{ 'core.user.contact' | translate}}
+
+
+ {{ 'core.user.contact' | translate}}
+
+
{{ 'core.user.email' | translate }}
-
- {{ user.email }}
-
+
+ {{ user.email }}
+
{{ 'core.user.phone1' | translate}}
- {{ user.phone1 }}
-
+ {{ user.phone1 }}
+
{{ 'core.user.phone2' | translate}}
- {{ user.phone2 }}
-
+ {{ user.phone2 }}
+
{{ 'core.user.address' | translate}}
- {{ formattedAddress }}
-
+ {{ formattedAddress }}
+
@@ -61,13 +79,17 @@
- {{ 'core.userdetails' | translate}}
+
+
+ {{ 'core.userdetails' | translate}}
+
+
{{ 'core.user.webpage' | translate}}
- {{ user.url }}
-
+ {{ user.url }}
+
@@ -81,11 +103,17 @@
- {{ 'core.user.description' | translate}}
+
+
+ {{ 'core.user.description' | translate}}
+
+
-
-
+
+
+
+
diff --git a/src/core/features/user/pages/about/about.page.ts b/src/core/features/user/pages/about/about.page.ts
index 2c3e5808f..33bbdb8f9 100644
--- a/src/core/features/user/pages/about/about.page.ts
+++ b/src/core/features/user/pages/about/about.page.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { Component, OnInit } from '@angular/core';
+import { Component, OnDestroy, OnInit } from '@angular/core';
import { SafeUrl } from '@angular/platform-browser';
import { IonRefresher } from '@ionic/angular';
@@ -20,10 +20,15 @@ import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreTextUtils } from '@services/utils/text';
import { CoreUtils } from '@services/utils/utils';
-import { CoreEvents } from '@singletons/events';
+import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreUser, CoreUserProfile, CoreUserProvider } from '@features/user/services/user';
import { CoreUserHelper } from '@features/user/services/user-helper';
import { CoreNavigator } from '@services/navigator';
+import { CoreIonLoadingElement } from '@classes/ion-loading';
+import { CoreSite } from '@classes/site';
+import { CoreFileUploaderHelper } from '@features/fileuploader/services/fileuploader-helper';
+import { CoreMimetypeUtils } from '@services/utils/mimetype';
+import { Translate } from '@singletons';
/**
* Page that displays info about a user.
@@ -31,11 +36,9 @@ import { CoreNavigator } from '@services/navigator';
@Component({
selector: 'page-core-user-about',
templateUrl: 'about.html',
+ styleUrls: ['about.scss'],
})
-export class CoreUserAboutPage implements OnInit {
-
- protected userId!: number;
- protected siteId: string;
+export class CoreUserAboutPage implements OnInit, OnDestroy {
courseId!: number;
userLoaded = false;
@@ -45,20 +48,46 @@ export class CoreUserAboutPage implements OnInit {
title?: string;
formattedAddress?: string;
encodedAddress?: SafeUrl;
+ canChangeProfilePicture = false;
+
+ protected userId!: number;
+ protected site!: CoreSite;
+ protected obsProfileRefreshed?: CoreEventObserver;
constructor() {
- this.siteId = CoreSites.getCurrentSiteId();
+ try {
+ this.site = CoreSites.getRequiredCurrentSite();
+ } catch (error) {
+ CoreDomUtils.showErrorModal(error);
+ CoreNavigator.back();
+
+ return;
+ }
+
+ this.obsProfileRefreshed = CoreEvents.on(CoreUserProvider.PROFILE_REFRESHED, (data) => {
+ if (!this.user || !data.user) {
+ return;
+ }
+
+ this.user.email = data.user.email;
+ this.user.address = CoreUserHelper.formatAddress('', data.user.city, data.user.country);
+ }, CoreSites.getCurrentSiteId());
}
/**
- * On init.
- *
- * @return Promise resolved when done.
+ * @inheritdoc
*/
async ngOnInit(): Promise {
this.userId = CoreNavigator.getRouteNumberParam('userId') || 0;
this.courseId = CoreNavigator.getRouteNumberParam('courseId') || 0;
+ // Allow to change the profile image only in the app profile page.
+ this.canChangeProfilePicture =
+ !this.courseId &&
+ this.userId == this.site.getUserId() &&
+ this.site.canUploadFiles() &&
+ !CoreUser.isUpdatePictureDisabledInSite(this.site);
+
this.fetchUser().finally(() => {
this.userLoaded = true;
});
@@ -83,11 +112,85 @@ export class CoreUserAboutPage implements OnInit {
this.user = user;
this.title = user.fullname;
+
+ this.user.address = CoreUserHelper.formatAddress('', user.city, user.country);
+
+ await this.checkUserImageUpdated();
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'core.user.errorloaduser', true);
}
}
+ /**
+ * Check if current user image has changed.
+ *
+ * @return Promise resolved when done.
+ */
+ protected async checkUserImageUpdated(): Promise {
+ if (!this.site || !this.site.getInfo() || !this.user) {
+ return;
+ }
+
+ if (this.userId != this.site.getUserId() || !this.isUserAvatarDirty()) {
+ // Not current user or hasn't changed.
+ return;
+ }
+
+ // The current user image received is different than the one stored in site info. Assume the image was updated.
+ // Update the site info to get the right avatar in there.
+ try {
+ await CoreSites.updateSiteInfo(this.site.getId());
+ } catch {
+ // Cannot update site info. Assume the profile image is the right one.
+ CoreEvents.trigger(CoreUserProvider.PROFILE_PICTURE_UPDATED, {
+ userId: this.userId,
+ picture: this.user.profileimageurl,
+ }, this.site.getId());
+ }
+
+ if (this.isUserAvatarDirty()) {
+ // The image is still different, this means that the good one is the one in site info.
+ await this.refreshUser();
+ } else {
+ // Now they're the same, send event to use the right avatar in the rest of the app.
+ CoreEvents.trigger(CoreUserProvider.PROFILE_PICTURE_UPDATED, {
+ userId: this.userId,
+ picture: this.user.profileimageurl,
+ }, this.site.getId());
+ }
+ }
+
+ /**
+ * Opens dialog to change profile picture.
+ */
+ async changeProfilePicture(): Promise {
+ const maxSize = -1;
+ const title = Translate.instant('core.user.newpicture');
+ const mimetypes = CoreMimetypeUtils.getGroupMimeInfo('image', 'mimetypes');
+ let modal: CoreIonLoadingElement | undefined;
+
+ try {
+ const result = await CoreFileUploaderHelper.selectAndUploadFile(maxSize, title, mimetypes);
+
+ modal = await CoreDomUtils.showModalLoading('core.sending', true);
+
+ const profileImageURL = await CoreUser.changeProfilePicture(result.itemid, this.userId, this.site.getId());
+
+ CoreEvents.trigger(CoreUserProvider.PROFILE_PICTURE_UPDATED, {
+ userId: this.userId,
+ picture: profileImageURL,
+ }, this.site.getId());
+
+ CoreSites.updateSiteInfo(this.site.getId());
+
+ this.refreshUser();
+ } catch (error) {
+ CoreDomUtils.showErrorModal(error);
+ } finally {
+ modal?.dismiss();
+ }
+ }
+
/**
* Refresh the user data.
*
@@ -106,8 +209,52 @@ export class CoreUserAboutPage implements OnInit {
courseId: this.courseId,
userId: this.userId,
user: this.user,
- }, this.siteId);
+ }, this.site.getId());
}
}
+ /**
+ * Check whether the user avatar is not up to date with site info.
+ *
+ * @return Whether the user avatar differs from site info cache.
+ */
+ protected isUserAvatarDirty(): boolean {
+ if (!this.user || !this.site) {
+ return false;
+ }
+
+ const courseAvatarUrl = this.normalizeAvatarUrl(this.user.profileimageurl);
+ const siteAvatarUrl = this.normalizeAvatarUrl(this.site.getInfo()?.userpictureurl);
+
+ return courseAvatarUrl !== siteAvatarUrl;
+ }
+
+ /**
+ * Normalize an avatar url regardless of theme.
+ *
+ * Given that the default image is the only one that can be changed per theme, any other url will stay the same. Note that
+ * the values returned by this function may not be valid urls, given that they are intended for string comparison.
+ *
+ * @param avatarUrl Avatar url.
+ * @return Normalized avatar string (may not be a valid url).
+ */
+ protected normalizeAvatarUrl(avatarUrl?: string): string {
+ if (!avatarUrl) {
+ return 'undefined';
+ }
+
+ if (avatarUrl.startsWith(`${this.site?.siteUrl}/theme/image.php`)) {
+ return 'default';
+ }
+
+ return avatarUrl;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ ngOnDestroy(): void {
+ this.obsProfileRefreshed?.off();
+ }
+
}
diff --git a/src/core/features/user/pages/about/about.scss b/src/core/features/user/pages/about/about.scss
new file mode 100644
index 000000000..c5c1284cb
--- /dev/null
+++ b/src/core/features/user/pages/about/about.scss
@@ -0,0 +1,44 @@
+:host {
+
+ .core-user-profile-maininfo::part(native) {
+ flex-direction: column;
+ }
+ ::ng-deep {
+ core-user-avatar {
+ display: block;
+ --core-avatar-size: var(--core-large-avatar-size);
+ height: calc(var(--core-avatar-size) + 16px);
+
+ img {
+ margin: 8px auto;
+ }
+
+ .contact-status {
+ width: 24px !important;
+ height: 24px !important;
+ right: calc(50% - 12px - var(--core-avatar-size) / 2) !important;
+ }
+
+ .edit-avatar {
+ position: absolute;
+ right: calc(50% - 15px - var(--core-avatar-size) / 2);
+ bottom: -12px;
+
+ :host-context([dir="rtl"]) & {
+ left: 0;
+ right: unset;
+ }
+ &::part(native) {
+ border-radius: 50%;
+ background: var(--ion-item-background);
+ }
+ }
+ }
+ }
+
+}
+
+:host-context([dir="rtl"]) ::ng-deep core-user-avatar .edit-avatar {
+ left: -24px;
+ right: unset;
+}
diff --git a/src/core/features/user/pages/profile/profile.html b/src/core/features/user/pages/profile/profile.html
index 2bedd26e5..ce58f5ae1 100644
--- a/src/core/features/user/pages/profile/profile.html
+++ b/src/core/features/user/pages/profile/profile.html
@@ -14,20 +14,12 @@
-
-
-
{{ user.fullname }}
- {{ user.address }}
+
+ {{ user.address }}
+
{{ 'core.user.roles' | translate}}{{'core.labelsep' | translate}}
{{ rolesFormatted }}
@@ -61,7 +53,9 @@
-
+
+
+
{{ handler.title | translate }}
+
+ {{handler.badge}}
+
+
+ {{ handler.badgeA11yText | translate: {$a : handler.badge } }}
+
+
+
@@ -86,8 +88,7 @@
-
+
diff --git a/src/core/features/user/pages/profile/profile.page.ts b/src/core/features/user/pages/profile/profile.page.ts
index 6e0f72013..d8ffe46e4 100644
--- a/src/core/features/user/pages/profile/profile.page.ts
+++ b/src/core/features/user/pages/profile/profile.page.ts
@@ -19,8 +19,6 @@ import { Subscription } from 'rxjs';
import { CoreSite } from '@classes/site';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreMimetypeUtils } from '@services/utils/mimetype';
-import { Translate } from '@singletons';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import {
CoreUser,
@@ -29,8 +27,6 @@ import {
} from '@features/user/services/user';
import { CoreUserHelper } from '@features/user/services/user-helper';
import { CoreUserDelegate, CoreUserDelegateService, CoreUserProfileHandlerData } from '@features/user/services/user-delegate';
-import { CoreFileUploaderHelper } from '@features/fileuploader/services/fileuploader-helper';
-import { CoreIonLoadingElement } from '@classes/ion-loading';
import { CoreUtils } from '@services/utils/utils';
import { CoreNavigator } from '@services/navigator';
import { CoreCourses } from '@features/courses/services/courses';
@@ -54,7 +50,6 @@ export class CoreUserProfilePage implements OnInit, OnDestroy {
title?: string;
isDeleted = false;
isEnrolled = true;
- canChangeProfilePicture = false;
rolesFormatted?: string;
actionHandlers: CoreUserProfileHandlerData[] = [];
newPageHandlers: CoreUserProfileHandlerData[] = [];
@@ -72,7 +67,7 @@ export class CoreUserProfilePage implements OnInit, OnDestroy {
}
/**
- * On init.
+ * @inheritdoc
*/
async ngOnInit(): Promise {
try {
@@ -91,13 +86,6 @@ export class CoreUserProfilePage implements OnInit, OnDestroy {
this.courseId = undefined;
}
- // Allow to change the profile image only in the app profile page.
- this.canChangeProfilePicture =
- !this.courseId &&
- this.userId == this.site.getUserId() &&
- this.site.canUploadFiles() &&
- !CoreUser.isUpdatePictureDisabledInSite(this.site);
-
try {
await this.fetchUser();
@@ -154,84 +142,12 @@ export class CoreUserProfilePage implements OnInit, OnDestroy {
this.isLoadingHandlers = !CoreUserDelegate.areHandlersLoaded(user.id);
});
- await this.checkUserImageUpdated();
-
} catch (error) {
// Error is null for deleted users, do not show the modal.
CoreDomUtils.showErrorModal(error);
}
}
- /**
- * Check if current user image has changed.
- *
- * @return Promise resolved when done.
- */
- protected async checkUserImageUpdated(): Promise {
- if (!this.site || !this.site.getInfo() || !this.user) {
- return;
- }
-
- if (this.userId != this.site.getUserId() || !this.isUserAvatarDirty()) {
- // Not current user or hasn't changed.
- return;
- }
-
- // The current user image received is different than the one stored in site info. Assume the image was updated.
- // Update the site info to get the right avatar in there.
- try {
- await CoreSites.updateSiteInfo(this.site.getId());
- } catch {
- // Cannot update site info. Assume the profile image is the right one.
- CoreEvents.trigger(CoreUserProvider.PROFILE_PICTURE_UPDATED, {
- userId: this.userId,
- picture: this.user.profileimageurl,
- }, this.site.getId());
- }
-
- if (this.isUserAvatarDirty()) {
- // The image is still different, this means that the good one is the one in site info.
- await this.refreshUser();
- } else {
- // Now they're the same, send event to use the right avatar in the rest of the app.
- CoreEvents.trigger(CoreUserProvider.PROFILE_PICTURE_UPDATED, {
- userId: this.userId,
- picture: this.user.profileimageurl,
- }, this.site.getId());
- }
- }
-
- /**
- * Opens dialog to change profile picture.
- */
- async changeProfilePicture(): Promise {
- const maxSize = -1;
- const title = Translate.instant('core.user.newpicture');
- const mimetypes = CoreMimetypeUtils.getGroupMimeInfo('image', 'mimetypes');
- let modal: CoreIonLoadingElement | undefined;
-
- try {
- const result = await CoreFileUploaderHelper.selectAndUploadFile(maxSize, title, mimetypes);
-
- modal = await CoreDomUtils.showModalLoading('core.sending', true);
-
- const profileImageURL = await CoreUser.changeProfilePicture(result.itemid, this.userId, this.site.getId());
-
- CoreEvents.trigger(CoreUserProvider.PROFILE_PICTURE_UPDATED, {
- userId: this.userId,
- picture: profileImageURL,
- }, this.site.getId());
-
- CoreSites.updateSiteInfo(this.site.getId());
-
- this.refreshUser();
- } catch (error) {
- CoreDomUtils.showErrorModal(error);
- } finally {
- modal?.dismiss();
- }
- }
-
/**
* Refresh the user.
*
@@ -285,48 +201,11 @@ export class CoreUserProfilePage implements OnInit, OnDestroy {
}
/**
- * Page destroyed.
+ * @inheritdoc
*/
ngOnDestroy(): void {
this.subscription?.unsubscribe();
this.obsProfileRefreshed.off();
}
- /**
- * Check whether the user avatar is not up to date with site info.
- *
- * @return Whether the user avatar differs from site info cache.
- */
- private isUserAvatarDirty(): boolean {
- if (!this.user || !this.site) {
- return false;
- }
-
- const courseAvatarUrl = this.normalizeAvatarUrl(this.user.profileimageurl);
- const siteAvatarUrl = this.normalizeAvatarUrl(this.site.getInfo()?.userpictureurl);
-
- return courseAvatarUrl !== siteAvatarUrl;
- }
-
- /**
- * Normalize an avatar url regardless of theme.
- *
- * Given that the default image is the only one that can be changed per theme, any other url will stay the same. Note that
- * the values returned by this function may not be valid urls, given that they are intended for string comparison.
- *
- * @param avatarUrl Avatar url.
- * @return Normalized avatar string (may not be a valid url).
- */
- private normalizeAvatarUrl(avatarUrl?: string): string {
- if (!avatarUrl) {
- return 'undefined';
- }
-
- if (avatarUrl.startsWith(`${this.site?.siteUrl}/theme/image.php`)) {
- return 'default';
- }
-
- return avatarUrl;
- }
-
}
diff --git a/src/core/features/user/pages/profile/profile.scss b/src/core/features/user/pages/profile/profile.scss
index c5c1284cb..41723e265 100644
--- a/src/core/features/user/pages/profile/profile.scss
+++ b/src/core/features/user/pages/profile/profile.scss
@@ -18,21 +18,6 @@
height: 24px !important;
right: calc(50% - 12px - var(--core-avatar-size) / 2) !important;
}
-
- .edit-avatar {
- position: absolute;
- right: calc(50% - 15px - var(--core-avatar-size) / 2);
- bottom: -12px;
-
- :host-context([dir="rtl"]) & {
- left: 0;
- right: unset;
- }
- &::part(native) {
- border-radius: 50%;
- background: var(--ion-item-background);
- }
- }
}
}
diff --git a/src/core/features/user/services/user-delegate.ts b/src/core/features/user/services/user-delegate.ts
index 5901fa17d..b16a36787 100644
--- a/src/core/features/user/services/user-delegate.ts
+++ b/src/core/features/user/services/user-delegate.ts
@@ -111,6 +111,26 @@ export interface CoreUserProfileHandlerData {
*/
spinner?: boolean;
+ /**
+ * If the handler has badge to show or not. Only for TYPE_NEW_PAGE.
+ */
+ showBadge?: boolean;
+
+ /**
+ * Text to display on the badge. Only used if showBadge is true and only for TYPE_NEW_PAGE.
+ */
+ badge?: string;
+
+ /**
+ * Accessibility text to add on the badge. Only used if showBadge is true and only for TYPE_NEW_PAGE.
+ */
+ badgeA11yText?: string;
+
+ /**
+ * If true, the badge number is being loaded. Only used if showBadge is true and only for TYPE_NEW_PAGE.
+ */
+ loading?: boolean;
+
/**
* Action to do when clicked.
*
diff --git a/src/core/lang.json b/src/core/lang.json
index f067e4b9e..2a7408ed0 100644
--- a/src/core/lang.json
+++ b/src/core/lang.json
@@ -185,7 +185,7 @@
"mod_wiki": "Wiki",
"mod_workshop": "Workshop",
"moduleintro": "Description",
- "more": "more",
+ "more": "More",
"mygroups": "My groups",
"name": "Name",
"needhelp": "Need help?",
diff --git a/src/core/services/app.ts b/src/core/services/app.ts
index 4c5cfc1e2..b3419ce9e 100644
--- a/src/core/services/app.ts
+++ b/src/core/services/app.ts
@@ -606,7 +606,7 @@ export class CoreAppProvider {
};
localStorage.setItem('CoreRedirect', JSON.stringify(redirect));
- } catch (ex) {
+ } catch {
// Ignore errors.
}
}
diff --git a/src/core/services/sites.ts b/src/core/services/sites.ts
index 702ca7a50..abbcc0de6 100644
--- a/src/core/services/sites.ts
+++ b/src/core/services/sites.ts
@@ -428,7 +428,7 @@ export class CoreSitesProvider {
const result = this.isValidMoodleVersion(info);
if (result != CoreSitesProvider.VALID_VERSION) {
- return this.treatInvalidAppVersion(result, siteUrl);
+ return this.treatInvalidAppVersion(result);
}
const siteId = this.createSiteID(info.siteurl, info.username);
@@ -492,7 +492,7 @@ export class CoreSitesProvider {
} catch (error) {
// Error invaliddevice is returned by Workplace server meaning the same as connecttoworkplaceapp.
if (error && error.errorcode == 'invaliddevice') {
- return this.treatInvalidAppVersion(CoreSitesProvider.WORKPLACE_APP, siteUrl);
+ return this.treatInvalidAppVersion(CoreSitesProvider.WORKPLACE_APP);
}
throw error;
@@ -503,14 +503,13 @@ export class CoreSitesProvider {
* Having the result of isValidMoodleVersion, it treats the error message to be shown.
*
* @param result Result returned by isValidMoodleVersion function.
- * @param siteUrl The site url.
* @param siteId If site is already added, it will invalidate the token.
* @return A promise rejected with the error info.
*/
- protected async treatInvalidAppVersion(result: number, siteUrl: string, siteId?: string): Promise {
+ protected async treatInvalidAppVersion(result: number, siteId?: string): Promise {
let errorCode: string | undefined;
let errorKey: string | undefined;
- let translateParams;
+ let translateParams = {};
switch (result) {
case CoreSitesProvider.MOODLE_APP:
@@ -528,7 +527,7 @@ export class CoreSitesProvider {
}
if (siteId) {
- await this.setSiteLoggedOut(siteId, true);
+ await this.setSiteLoggedOut(siteId);
}
throw new CoreSiteError({
@@ -746,7 +745,7 @@ export class CoreSitesProvider {
if (siteId) {
// Logout the currentSite and expire the token.
this.logout();
- this.setSiteLoggedOut(siteId, true);
+ this.setSiteLoggedOut(siteId);
}
});
@@ -1053,8 +1052,10 @@ export class CoreSitesProvider {
* @param siteId The site ID. If not defined, current site (if available).
* @return Promise resolved with site home ID.
*/
- getSiteHomeId(siteId?: string): Promise {
- return this.getSite(siteId).then((site) => site.getSiteHomeId());
+ async getSiteHomeId(siteId?: string): Promise {
+ const site = await this.getSite(siteId);
+
+ return site.getSiteHomeId();
}
/**
@@ -1075,6 +1076,7 @@ export class CoreSitesProvider {
const basicInfo: CoreSiteBasicInfo = {
id: site.id,
siteUrl: site.siteUrl,
+ siteUrlWithoutProtocol: site.siteUrl.replace(/^https?:\/\//, '').toLowerCase(),
fullName: siteInfo?.fullname,
siteName: CoreConstants.CONFIG.sitename == '' ? siteInfo?.sitename: CoreConstants.CONFIG.sitename,
avatar: siteInfo?.userpictureurl,
@@ -1096,12 +1098,10 @@ export class CoreSitesProvider {
async getSortedSites(ids?: string[]): Promise {
const sites = await this.getSites(ids);
- // Sort sites by url and ful lname.
+ // Sort sites by url and fullname.
sites.sort((a, b) => {
// First compare by site url without the protocol.
- const urlA = a.siteUrl.replace(/^https?:\/\//, '').toLowerCase();
- const urlB = b.siteUrl.replace(/^https?:\/\//, '').toLowerCase();
- const compare = urlA.localeCompare(urlB);
+ const compare = a.siteUrlWithoutProtocol.localeCompare(b.siteUrlWithoutProtocol);
if (compare !== 0) {
return compare;
@@ -1191,7 +1191,7 @@ export class CoreSitesProvider {
this.currentSite = undefined;
if (siteConfig && siteConfig.tool_mobile_forcelogout == '1') {
- promises.push(this.setSiteLoggedOut(siteId, true));
+ promises.push(this.setSiteLoggedOut(siteId));
}
promises.push(this.removeStoredCurrentSite());
@@ -1221,34 +1221,24 @@ export class CoreSitesProvider {
this.logger.debug(`Restore session in site ${siteId}`);
await this.loadSite(siteId);
- } catch (err) {
+ } catch {
// No current session.
}
}
/**
- * Mark or unmark a site as logged out so the user needs to authenticate again.
+ * Mark a site as logged out so the user needs to authenticate again.
*
* @param siteId ID of the site.
- * @param loggedOut True to set the site as logged out, false otherwise.
* @return Promise resolved when done.
*/
- async setSiteLoggedOut(siteId: string, loggedOut: boolean): Promise {
+ protected async setSiteLoggedOut(siteId: string): Promise {
const db = await this.appDB;
const site = await this.getSite(siteId);
- const newValues: Partial = {
- loggedOut: loggedOut ? 1 : 0,
- };
- if (loggedOut) {
- // Erase the token for security.
- newValues.token = '';
- site.token = '';
- }
+ site.setLoggedOut(true);
- site.setLoggedOut(loggedOut);
-
- await db.updateRecords(SITES_TABLE_NAME, newValues, { id: siteId });
+ await db.updateRecords(SITES_TABLE_NAME, { loggedOut: 1 }, { id: siteId });
}
/**
@@ -1315,7 +1305,7 @@ export class CoreSitesProvider {
const versionCheck = this.isValidMoodleVersion(info);
if (versionCheck != CoreSitesProvider.VALID_VERSION) {
// The Moodle version is not supported, reject.
- return this.treatInvalidAppVersion(versionCheck, site.getURL(), site.getId());
+ return this.treatInvalidAppVersion(versionCheck, site.getId());
}
// Try to get the site config.
@@ -1344,7 +1334,7 @@ export class CoreSitesProvider {
} finally {
CoreEvents.trigger(CoreEvents.SITE_UPDATED, info, siteId);
}
- } catch (error) {
+ } catch {
// Ignore that we cannot fetch site info. Probably the auth token is invalid.
}
}
@@ -1417,7 +1407,7 @@ export class CoreSitesProvider {
await Promise.all(promises);
return ids;
- } catch (error) {
+ } catch {
// Shouldn't happen.
return [];
}
@@ -1475,8 +1465,10 @@ export class CoreSitesProvider {
* @param siteId The site ID. If not defined, current site (if available).
* @return Promise resolved with true if disabled.
*/
- isFeatureDisabled(name: string, siteId?: string): Promise {
- return this.getSite(siteId).then((site) => site.isFeatureDisabled(name));
+ async isFeatureDisabled(name: string, siteId?: string): Promise {
+ const site = await this.getSite(siteId);
+
+ return site.isFeatureDisabled(name);
}
/**
@@ -1763,40 +1755,14 @@ export type CoreSiteUserTokenResponse = {
* Site's basic info.
*/
export type CoreSiteBasicInfo = {
- /**
- * Site ID.
- */
- id: string;
-
- /**
- * Site URL.
- */
- siteUrl: string;
-
- /**
- * User's full name.
- */
- fullName?: string;
-
- /**
- * Site's name.
- */
- siteName?: string;
-
- /**
- * User's avatar.
- */
- avatar?: string;
-
- /**
- * Badge to display in the site.
- */
- badge?: number;
-
- /**
- * Site home ID.
- */
- siteHomeId?: number;
+ id: string; // Site ID.
+ siteUrl: string; // Site URL.
+ siteUrlWithoutProtocol: string; // Site URL without protocol.
+ fullName?: string; // User's full name.
+ siteName?: string; // Site's name.
+ avatar?: string; // User's avatar.
+ badge?: number; // Badge to display in the site.
+ siteHomeId?: number; // Site home ID.
};
/**
diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts
index 093e072a9..e4b6deb5a 100644
--- a/src/core/services/utils/dom.ts
+++ b/src/core/services/utils/dom.ts
@@ -1677,13 +1677,13 @@ export class CoreDomUtilsProvider {
modalOptions: ModalOptions,
): Promise {
- modalOptions = Object.assign(modalOptions, {
+ modalOptions = Object.assign({
cssClass: 'core-modal-lateral',
showBackdrop: true,
backdropDismiss: true,
enterAnimation: CoreModalLateralTransitionEnter,
leaveAnimation: CoreModalLateralTransitionLeave,
- });
+ }, modalOptions);
return await this.openModal(modalOptions);
}
diff --git a/src/theme/globals.variables.scss b/src/theme/globals.variables.scss
index e2a39b886..3a1dbff38 100644
--- a/src/theme/globals.variables.scss
+++ b/src/theme/globals.variables.scss
@@ -102,6 +102,8 @@ $screen-breakpoints: (
xl: 1200px
) !default;
+$modal-lateral-width: 360px;
+
$core-course-image-background: #81ecec, #74b9ff, #a29bfe, #dfe6e9, #00b894, #0984e3, #b2bec3, #fdcb6e, #fd79a8, #6c5ce7 !default;
$core-dd-question-colors: #FFFFFF, #B0C4DE, #DCDCDC, #D8BFD8, #87CEFA, #DAA520, #FFD700, #F0E68C !default;
$core-text-hightlight-background-color: lighten($blue, 40%) !default;
@@ -127,7 +129,15 @@ $core-login-loading-color-dark: $text-color-dark !default;
$core-login-hide-forgot-password: false !default;
$core-login-hide-need-help: false !default;
-// Configuration options for more page.
+// Configuration options for more page. (deprecated on 4.0)
$core-more-hide-siteinfo: false !default;
$core-more-hide-sitename: false !default;
$core-more-hide-siteurl: false !default;
+
+// Configuration options for user page.
+$core-user-hide-siteinfo: $core-more-hide-siteinfo !default;
+$core-user-hide-sitename: $core-more-hide-sitename !default;
+$core-user-hide-siteurl: $core-more-hide-siteurl !default;
+
+
+
diff --git a/src/theme/theme.base.scss b/src/theme/theme.base.scss
index c040c18d3..c74dae05a 100644
--- a/src/theme/theme.base.scss
+++ b/src/theme/theme.base.scss
@@ -508,7 +508,7 @@ body.core-iframe-fullscreen ion-router-outlet {
z-index: 100000 !important;
}
-@media only screen and (min-height: 400px) and (min-width: 300px) {
+@media only screen and (min-height: 400px) and (min-width: #{$modal-lateral-width}) {
.core-modal-lateral {
--ion-safe-area-left: 0px;
--ion-safe-area-right: 0px;
@@ -519,7 +519,7 @@ body.core-iframe-fullscreen ion-router-outlet {
display: block;
height: 100% !important;
width: auto;
- min-width: 300px;
+ min-width: #{$modal-lateral-width};
box-shadow: 0 28px 48px rgba(0, 0, 0, 0.4);
}
ion-backdrop {
@@ -528,6 +528,29 @@ body.core-iframe-fullscreen ion-router-outlet {
}
}
+@each $breakpoint, $width in $screen-breakpoints {
+ @media only screen and (min-height: 400px) and (min-width: #{$width}) {
+ .core-modal-lateral-#{$breakpoint} {
+ --ion-safe-area-left: 0px;
+ --ion-safe-area-right: 0px;
+
+ .modal-wrapper {
+ position: absolute;
+ @include position(0 !important, 0 !important, 0 !important, unset !important);
+ display: block;
+ height: 100% !important;
+ width: auto;
+ min-width: #{$width};
+ box-shadow: 0 28px 48px rgba(0, 0, 0, 0.4);
+ }
+ ion-backdrop {
+ visibility: visible;
+ }
+ }
+ }
+
+}
+
// Hidden submit button.
.core-submit-hidden-enter {
position: absolute;
@@ -655,12 +678,13 @@ ion-card ion-item:only-child {
ion-toolbar h1 img.core-bar-button-image,
ion-toolbar h1 .core-bar-button-image img {
- padding: 0;
+ padding: 4px;
width: var(--core-header-toolbar-button-image-size);
height: var(--core-header-toolbar-button-image-size);
max-width: var(--core-header-toolbar-button-image-size);
max-height: var(--core-header-toolbar-button-image-size);
border-radius: 50%;
+ display: block;
}
// Action sheet.
@@ -1038,6 +1062,10 @@ ion-item.item-multiple-inputs.only-links {
}
}
+a {
+ cursor: pointer;
+}
+
// Case with ion-input + ion-select inside.
ion-item.item-input.item-multiple-inputs {
.flex-row {