diff --git a/scripts/langindex.json b/scripts/langindex.json index a06e71a3d..093646fcf 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,9 +1926,9 @@ "core.login.youcanstillconnectwithcredentials": "local_moodlemobileapp", "core.login.yourenteredsite": "local_moodlemobileapp", "core.lostconnection": "local_moodlemobileapp", - "core.mainmenu.changesite": "local_moodlemobileapp", "core.mainmenu.home": "moodle", "core.mainmenu.logout": "moodle", + "core.mainmenu.switchaccount": "local_moodlemobileapp", "core.maxfilesize": "moodle", "core.maxsizeandattachments": "moodle", "core.min": "moodle", @@ -2230,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", 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..20ee59b87 --- /dev/null +++ b/src/core/features/login/components/sites/sites.html @@ -0,0 +1,92 @@ + + + + + + + + +

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

+ + + + + + +
+
+ + + + + + +

+ +

+

{{ accountsList.currentSite.siteUrl }} +

+
+
+ + + + {{ 'core.pictureof' | translate:{$a: accountsList.currentSite.fullName} }} + + +

{{accountsList.currentSite.fullName}}

+
+ +
+ + +
+ + + + +

+ +

+

{{ sites[0].siteUrl }}

+
+
+ + +
+ +
+
+ + + + {{ 'core.login.add' | translate }} + + +
+ + + + + + {{ 'core.pictureof' | translate:{$a: site.fullName} }} + + +

{{site.fullName}}

+
+ + + {{ '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..b9d3ed2b7 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 }}

- + @@ -18,31 +18,43 @@ - - - - {{ 'core.pictureof' | translate:{$a: site.fullName} }} - - -

{{site.fullName}}

-

-

{{site.siteUrl}}

-
- - - {{ 'core.login.sitebadgedescription' | translate:{ count: site.badge } }} - - - - -
-
+ + + + + +

+ +

+

{{ sites[0].siteUrl }}

+
+
+ + + + {{ 'core.pictureof' | translate:{$a: site.fullName} }} + + +

{{site.fullName}}

+
+ + + {{ '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..545898b2b 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,121 @@ 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.siteUrl.replace(/^https?:\/\//, '').toLowerCase(); + accountsList.currentSite.siteUrl = siteUrl; + } + + const otherSites: Record = {}; + + // Add site counter and classify sites. + await Promise.all(sites.map(async (site) => { + site.siteUrl = site.siteUrl.replace(/^https?:\/\//, '').toLowerCase(); + site.badge = await CoreUtils.ignoreErrors(CorePushNotifications.getSiteCounter(site.id)) || 0; + + if (site.siteUrl == siteUrl) { + accountsList.sameSite.push(site); + } else { + if (!otherSites[site.siteUrl]) { + otherSites[site.siteUrl] = []; + } + + otherSites[site.siteUrl].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.siteUrl; + let index = 0; + + // Found on same site. + if (accountsList.sameSite.length > 0 && accountsList.sameSite[0].siteUrl == 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].siteUrl == 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 index 9a688ae75..613c1c790 100644 --- a/src/core/features/mainmenu/components/components.module.ts +++ b/src/core/features/mainmenu/components/components.module.ts @@ -16,6 +16,7 @@ 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: [ @@ -24,6 +25,7 @@ import { CoreMainMenuUserMenuComponent } from './user-menu/user-menu'; ], imports: [ CoreSharedModule, + CoreLoginComponentsModule, ], exports: [ CoreMainMenuUserButtonComponent, diff --git a/src/core/features/mainmenu/components/user-menu/user-menu.html b/src/core/features/mainmenu/components/user-menu/user-menu.html index 0b2127f8d..4cb01be69 100644 --- a/src/core/features/mainmenu/components/user-menu/user-menu.html +++ b/src/core/features/mainmenu/components/user-menu/user-menu.html @@ -8,59 +8,71 @@

{{'core.user.account' | translate}}

+ + + + + + + + - - - + + - - - -

{{ 'core.user.details' | translate }}

-
-
+ + + +

{{ 'core.user.details' | translate }}

+
+
- - - + + + + + - - - -

{{ handler.title | translate }}

-
-
+ + + +

{{ handler.title | translate }}

+
+
- - - -

{{ 'core.settings.preferences' | translate }}

-
-
-
-
+ + + +

{{ 'core.settings.preferences' | translate }}

+
+
+
- + - {{ logoutLabel | translate }} + {{ 'core.mainmenu.logout' | translate }} - \ No newline at end of file + diff --git a/src/core/features/mainmenu/components/user-menu/user-menu.ts b/src/core/features/mainmenu/components/user-menu/user-menu.ts index 71c8cc9f7..1ec0e39ad 100644 --- a/src/core/features/mainmenu/components/user-menu/user-menu.ts +++ b/src/core/features/mainmenu/components/user-menu/user-menu.ts @@ -14,11 +14,13 @@ 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'; @@ -34,11 +36,12 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy { siteInfo?: CoreSiteInfo; siteName?: string; - logoutLabel = 'core.mainmenu.changesite'; siteUrl?: string; handlers: CoreUserProfileHandlerData[] = []; handlersLoaded = false; + loaded = false; user?: CoreUserProfile; + moreSites = false; protected subscription!: Subscription; @@ -46,13 +49,16 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy { * @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.logoutLabel = CoreLoginHelper.getLogoutLabel(currentSite); + + this.loaded = true; // Load the handlers. if (this.siteInfo) { @@ -133,6 +139,38 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy { 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. */ diff --git a/src/core/features/mainmenu/lang.json b/src/core/features/mainmenu/lang.json index 0d188b98d..9e9977545 100644 --- a/src/core/features/mainmenu/lang.json +++ b/src/core/features/mainmenu/lang.json @@ -1,5 +1,5 @@ { - "changesite": "Change site", "home": "Home", - "logout": "Log out" + "logout": "Log out", + "switchaccount": "Switch account" } 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 3435a4c4a..61d41e287 100644 --- a/src/core/features/user/lang.json +++ b/src/core/features/user/lang.json @@ -15,12 +15,12 @@ "interests": "Interests", "lastname": "Surname", "manager": "Manager", - "myprofile": "My profile", "newpicture": "New picture", "noparticipants": "No participants found for this course", "participants": "Participants", "phone1": "Phone", "phone2": "Mobile phone", + "profile": "Profile", "roles": "Roles", "sendemail": "Email", "student": "Student", 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 dd0c39a2b..3b77bf38b 100644 --- a/src/core/services/sites.ts +++ b/src/core/services/sites.ts @@ -1095,7 +1095,7 @@ 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(); @@ -1754,40 +1754,13 @@ 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. + 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. }; /**