forked from EVOgeek/Vmeda.Online
		
	
						commit
						ca95f53134
					
				| @ -27,7 +27,11 @@ const routes: Routes = [ | ||||
|     }, | ||||
|     { | ||||
|         path: 'settings', | ||||
|         loadChildren: () => import('./core/settings/settings.module').then( m => m.CoreAppSettingsPageModule), | ||||
|         loadChildren: () => import('./core/settings/settings.module').then( m => m.CoreSettingsModule), | ||||
|     }, | ||||
|     { | ||||
|         path: 'mainmenu', | ||||
|         loadChildren: () => import('./core/mainmenu/mainmenu.module').then( m => m.CoreMainMenuModule), | ||||
|     }, | ||||
| ]; | ||||
| 
 | ||||
|  | ||||
| @ -13,6 +13,8 @@ | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Component, OnInit } from '@angular/core'; | ||||
| import { NavController } from '@ionic/angular'; | ||||
| 
 | ||||
| import { CoreLangProvider } from '@services/lang'; | ||||
| import { CoreEvents } from '@singletons/events'; | ||||
| 
 | ||||
| @ -24,7 +26,8 @@ import { CoreEvents } from '@singletons/events'; | ||||
| export class AppComponent implements OnInit { | ||||
| 
 | ||||
|     constructor( | ||||
|         private langProvider: CoreLangProvider, | ||||
|         protected langProvider: CoreLangProvider, | ||||
|         protected navCtrl: NavController, | ||||
|     ) { | ||||
|     } | ||||
| 
 | ||||
| @ -34,16 +37,13 @@ export class AppComponent implements OnInit { | ||||
|     ngOnInit(): void { | ||||
|         CoreEvents.on(CoreEvents.LOGOUT, () => { | ||||
|             // Go to sites page when user is logged out.
 | ||||
|             // Due to DeepLinker, we need to use the ViewCtrl instead of name.
 | ||||
|             // Otherwise some pages are re-created when they shouldn't.
 | ||||
|             // TODO
 | ||||
|             // CoreApp.instance.getRootNavController().setRoot(CoreLoginSitesPage);
 | ||||
|             this.navCtrl.navigateRoot('/login/sites'); | ||||
| 
 | ||||
|             // Unload lang custom strings.
 | ||||
|             this.langProvider.clearCustomStrings(); | ||||
| 
 | ||||
|             // Remove version classes from body.
 | ||||
|             // TODO
 | ||||
|             // @todo
 | ||||
|             // this.removeVersionClass();
 | ||||
|         }); | ||||
|     } | ||||
|  | ||||
| @ -53,6 +53,7 @@ import { CoreUtilsProvider } from '@services/utils/utils'; | ||||
| // Import core modules.
 | ||||
| import { CoreEmulatorModule } from '@core/emulator/emulator.module'; | ||||
| import { CoreLoginModule } from '@core/login/login.module'; | ||||
| import { CoreCoursesModule } from '@core/courses/courses.module'; | ||||
| 
 | ||||
| import { setSingletonsInjector } from '@singletons/core.singletons'; | ||||
| 
 | ||||
| @ -81,6 +82,7 @@ export function createTranslateLoader(http: HttpClient): TranslateHttpLoader { | ||||
|         AppRoutingModule, | ||||
|         CoreEmulatorModule, | ||||
|         CoreLoginModule, | ||||
|         CoreCoursesModule, | ||||
|     ], | ||||
|     providers: [ | ||||
|         { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, | ||||
|  | ||||
| @ -50,20 +50,20 @@ export class CoreLoadingComponent implements OnInit, OnChanges, AfterViewInit { | ||||
|     @Input() message?: string; // Message to show while loading.
 | ||||
|     @ViewChild('content') content?: ElementRef; | ||||
| 
 | ||||
|     protected uniqueId!: string; | ||||
|     uniqueId: string; | ||||
|     protected element: HTMLElement; // Current element.
 | ||||
| 
 | ||||
|     constructor(element: ElementRef) { | ||||
|         this.element = element.nativeElement; | ||||
| 
 | ||||
|         // Calculate the unique ID.
 | ||||
|         this.uniqueId = 'core-loading-content-' + CoreUtils.instance.getUniqueId('CoreLoadingComponent'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Component being initialized. | ||||
|      */ | ||||
|     ngOnInit(): void { | ||||
|         // Calculate the unique ID.
 | ||||
|         this.uniqueId = 'core-loading-content-' + CoreUtils.instance.getUniqueId('CoreLoadingComponent'); | ||||
| 
 | ||||
|         if (!this.message) { | ||||
|             // Default loading message.
 | ||||
|             this.message = Translate.instance.instant('core.loading'); | ||||
|  | ||||
| @ -45,8 +45,8 @@ export class CoreShowPasswordComponent implements OnInit, AfterViewInit { | ||||
|     @ContentChild(IonInput) ionInput?: IonInput; | ||||
| 
 | ||||
|     shown!: boolean; // Whether the password is shown.
 | ||||
|     label?: string; // Label for the button to show/hide.
 | ||||
|     iconName?: string; // Name of the icon of the button to show/hide.
 | ||||
|     label!: string; // Label for the button to show/hide.
 | ||||
|     iconName!: string; // Name of the icon of the button to show/hide.
 | ||||
|     selector = ''; // Selector to identify the input.
 | ||||
| 
 | ||||
|     protected input?: HTMLInputElement | null; // Input affected.
 | ||||
|  | ||||
							
								
								
									
										30
									
								
								src/app/core/courses/courses.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/app/core/courses/courses.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| // (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 { CoreMainMenuDelegate } from '@core/mainmenu/services/delegate'; | ||||
| import { CoreHomeMainMenuHandler } from './handlers/mainmenu'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     imports: [], | ||||
|     declarations: [], | ||||
| }) | ||||
| export class CoreCoursesModule { | ||||
| 
 | ||||
|     constructor(mainMenuDelegate: CoreMainMenuDelegate) { | ||||
|         mainMenuDelegate.registerHandler(new CoreHomeMainMenuHandler()); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										60
									
								
								src/app/core/courses/handlers/mainmenu.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/app/core/courses/handlers/mainmenu.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,60 @@ | ||||
| // (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 { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@core/mainmenu/services/delegate'; | ||||
| 
 | ||||
| /** | ||||
|  * Handler to add Home into main menu. | ||||
|  */ | ||||
| export class CoreHomeMainMenuHandler implements CoreMainMenuHandler { | ||||
| 
 | ||||
|     name = 'CoreHome'; | ||||
|     priority = 1100; | ||||
| 
 | ||||
|     /** | ||||
|      * Check if the handler is enabled on a site level. | ||||
|      * | ||||
|      * @return Whether or not the handler is enabled on a site level. | ||||
|      */ | ||||
|     isEnabled(): Promise<boolean> { | ||||
|         return this.isEnabledForSite(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if the handler is enabled on a certain site. | ||||
|      * | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Whether or not the handler is enabled on a site level. | ||||
|      */ | ||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||
|     async isEnabledForSite(siteId?: string): Promise<boolean> { | ||||
|         // @todo
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the data needed to render the handler. | ||||
|      * | ||||
|      * @return Data needed to render the handler. | ||||
|      */ | ||||
|     getDisplayData(): CoreMainMenuHandlerData { | ||||
|         return { | ||||
|             icon: 'fa-home', | ||||
|             title: 'core.courses.mymoodle', | ||||
|             page: 'home', | ||||
|             class: 'core-home-handler', | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										39
									
								
								src/app/core/courses/lang/en.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/app/core/courses/lang/en.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | ||||
| { | ||||
|     "addtofavourites": "Star this course", | ||||
|     "allowguests": "This course allows guest users to enter", | ||||
|     "availablecourses": "Available courses", | ||||
|     "cannotretrievemorecategories": "Categories deeper than level {{$a}} cannot be retrieved.", | ||||
|     "categories": "Course categories", | ||||
|     "confirmselfenrol": "Are you sure you want to enrol yourself in this course?", | ||||
|     "courses": "Courses", | ||||
|     "downloadcourses": "Download courses", | ||||
|     "enrolme": "Enrol me", | ||||
|     "errorloadcategories": "An error occurred while loading categories.", | ||||
|     "errorloadcourses": "An error occurred while loading courses.", | ||||
|     "errorloadplugins": "The plugins required by this course could not be loaded correctly. Please reload the app to try again.", | ||||
|     "errorsearching": "An error occurred while searching.", | ||||
|     "errorselfenrol": "An error occurred while self enrolling.", | ||||
|     "filtermycourses": "Filter my courses", | ||||
|     "frontpage": "Front page", | ||||
|     "hidecourse": "Remove from view", | ||||
|     "ignore": "Ignore", | ||||
|     "mycourses": "My courses", | ||||
|     "mymoodle": "Dashboard", | ||||
|     "nocourses": "No course information to show.", | ||||
|     "nocoursesyet": "No courses in this category", | ||||
|     "nosearchresults": "No results", | ||||
|     "notenroled": "You are not enrolled in this course", | ||||
|     "notenrollable": "You cannot enrol yourself in this course.", | ||||
|     "password": "Enrolment key", | ||||
|     "paymentrequired": "This course requires a payment for entry.", | ||||
|     "paypalaccepted": "PayPal payments accepted", | ||||
|     "reload": "Reload", | ||||
|     "removefromfavourites": "Unstar this course", | ||||
|     "search": "Search", | ||||
|     "searchcourses": "Search courses", | ||||
|     "searchcoursesadvice": "You can use the search courses button to find courses to access as a  guest or enrol yourself in courses that allow it.", | ||||
|     "selfenrolment": "Self enrolment", | ||||
|     "sendpaymentbutton": "Send payment via PayPal", | ||||
|     "show": "Restore to view", | ||||
|     "totalcoursesearchresults": "Total courses: {{$a}}" | ||||
| } | ||||
							
								
								
									
										20
									
								
								src/app/core/courses/pages/home/home.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/app/core/courses/pages/home/home.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| <ion-header> | ||||
|     <ion-toolbar> | ||||
|         <ion-buttons slot="start"> | ||||
|             <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button> | ||||
|         </ion-buttons> | ||||
| 
 | ||||
|         <ion-title> | ||||
|             <core-format-text [text]="siteName" contextLevel="system" [contextInstanceId]="0"></core-format-text> | ||||
|             <img src="assets/img/login_logo.png" class="core-header-logo" [alt]="siteName"> | ||||
|         </ion-title> | ||||
| 
 | ||||
|         <ion-buttons slot="end"> | ||||
|             <!-- @todo --> | ||||
|         </ion-buttons> | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <!-- @todo --> | ||||
|     Home page. | ||||
| </ion-content> | ||||
							
								
								
									
										47
									
								
								src/app/core/courses/pages/home/home.page.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/app/core/courses/pages/home/home.page.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| // (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 { CommonModule } from '@angular/common'; | ||||
| import { RouterModule, Routes } from '@angular/router'; | ||||
| import { IonicModule } from '@ionic/angular'; | ||||
| import { TranslateModule } from '@ngx-translate/core'; | ||||
| 
 | ||||
| import { CoreComponentsModule } from '@components/components.module'; | ||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | ||||
| 
 | ||||
| import { CoreCoursesHomePage } from './home.page'; | ||||
| 
 | ||||
| const routes: Routes = [ | ||||
|     { | ||||
|         path: '', | ||||
|         component: CoreCoursesHomePage, | ||||
|     }, | ||||
| ]; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     imports: [ | ||||
|         RouterModule.forChild(routes), | ||||
|         CommonModule, | ||||
|         IonicModule, | ||||
|         TranslateModule.forChild(), | ||||
|         CoreComponentsModule, | ||||
|         CoreDirectivesModule, | ||||
|     ], | ||||
|     declarations: [ | ||||
|         CoreCoursesHomePage, | ||||
|     ], | ||||
|     exports: [RouterModule], | ||||
| }) | ||||
| export class CoreCoursesHomePageModule {} | ||||
							
								
								
									
										36
									
								
								src/app/core/courses/pages/home/home.page.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/app/core/courses/pages/home/home.page.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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 { Component, OnInit } from '@angular/core'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays the Home. | ||||
|  */ | ||||
| @Component({ | ||||
|     selector: 'page-core-courses-home', | ||||
|     templateUrl: 'home.html', | ||||
|     styleUrls: ['home.scss'], | ||||
| }) | ||||
| export class CoreCoursesHomePage implements OnInit { | ||||
| 
 | ||||
|     siteName = 'Hello world'; | ||||
| 
 | ||||
|     /** | ||||
|      * Initialize the component. | ||||
|      */ | ||||
|     ngOnInit(): void { | ||||
|         // @todo
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										26
									
								
								src/app/core/courses/pages/home/home.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/app/core/courses/pages/home/home.scss
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | ||||
| $core-dashboard-logo: false !default; | ||||
| 
 | ||||
| @if $core-dashboard-logo { | ||||
|     .toolbar-title-default.md .title-default .core-header-logo { | ||||
|         max-height: $toolbar-md-height - 24; | ||||
|     } | ||||
| 
 | ||||
|     .toolbar-title-default.ios .title-default .core-header-logo { | ||||
|         max-height: $toolbar-ios-height - 24; | ||||
|     } | ||||
| 
 | ||||
|     .toolbar-title-default .title-default core-format-text { | ||||
|         display: none; | ||||
|     } | ||||
| } @else { | ||||
|     .toolbar-title-default .core-header-logo { | ||||
|         display: none; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| ion-badge.core-course-download-courses-progress { | ||||
|     display: block; | ||||
|     // @include float(start); | ||||
|     // @include margin(12px, 12px, null, 12px); | ||||
| } | ||||
| 
 | ||||
| @ -15,27 +15,27 @@ | ||||
| import { NgModule } from '@angular/core'; | ||||
| import { RouterModule, Routes } from '@angular/router'; | ||||
| 
 | ||||
| import { CoreLoginCredentialsPage } from './pages/credentials/credentials.page'; | ||||
| import { CoreLoginInitPage } from './pages/init/init.page'; | ||||
| import { CoreLoginSitePage } from './pages/site/site.page'; | ||||
| import { CoreLoginSitesPage } from './pages/sites/sites.page'; | ||||
| 
 | ||||
| const routes: Routes = [ | ||||
|     { | ||||
|         path: '', | ||||
|         component: CoreLoginInitPage, | ||||
|         redirectTo: 'init', | ||||
|         pathMatch: 'full', | ||||
|     }, | ||||
|     { | ||||
|         path: 'init', | ||||
|         loadChildren: () => import('./pages/init/init.page.module').then( m => m.CoreLoginInitPageModule), | ||||
|     }, | ||||
|     { | ||||
|         path: 'site', | ||||
|         component: CoreLoginSitePage, | ||||
|         loadChildren: () => import('./pages/site/site.page.module').then( m => m.CoreLoginSitePageModule), | ||||
|     }, | ||||
|     { | ||||
|         path: 'credentials', | ||||
|         component: CoreLoginCredentialsPage, | ||||
|         loadChildren: () => import('./pages/credentials/credentials.page.module').then( m => m.CoreLoginCredentialsPageModule), | ||||
|     }, | ||||
|     { | ||||
|         path: 'sites', | ||||
|         component: CoreLoginSitesPage, | ||||
|         loadChildren: () => import('./pages/sites/sites.page.module').then( m => m.CoreLoginSitesPageModule), | ||||
|     }, | ||||
| ]; | ||||
| 
 | ||||
|  | ||||
| @ -13,42 +13,12 @@ | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { NgModule } from '@angular/core'; | ||||
| import { CommonModule } from '@angular/common'; | ||||
| import { FormsModule, ReactiveFormsModule } from '@angular/forms'; | ||||
| 
 | ||||
| import { IonicModule } from '@ionic/angular'; | ||||
| import { TranslateModule } from '@ngx-translate/core'; | ||||
| 
 | ||||
| import { CoreComponentsModule } from '@components/components.module'; | ||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | ||||
| 
 | ||||
| import { CoreLoginRoutingModule } from './login-routing.module'; | ||||
| import { CoreLoginCredentialsPage } from './pages/credentials/credentials.page'; | ||||
| import { CoreLoginInitPage } from './pages/init/init.page'; | ||||
| import { CoreLoginSitePage } from './pages/site/site.page'; | ||||
| import { CoreLoginSitesPage } from './pages/sites/sites.page'; | ||||
| import { CoreLoginHelperProvider } from './services/helper'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     imports: [ | ||||
|         CommonModule, | ||||
|         IonicModule, | ||||
|         CoreLoginRoutingModule, | ||||
|         CoreComponentsModule, | ||||
|         TranslateModule.forChild(), | ||||
|         FormsModule, | ||||
|         ReactiveFormsModule, | ||||
|         CoreComponentsModule, | ||||
|         CoreDirectivesModule, | ||||
|     ], | ||||
|     declarations: [ | ||||
|         CoreLoginCredentialsPage, | ||||
|         CoreLoginInitPage, | ||||
|         CoreLoginSitePage, | ||||
|         CoreLoginSitesPage, | ||||
|     ], | ||||
|     providers: [ | ||||
|         CoreLoginHelperProvider, | ||||
|     ], | ||||
|     declarations: [], | ||||
| }) | ||||
| export class CoreLoginModule {} | ||||
|  | ||||
| @ -7,7 +7,10 @@ | ||||
|         <ion-title>{{ 'core.login.login' | translate }}</ion-title> | ||||
| 
 | ||||
|         <ion-buttons slot="end"> | ||||
|             <!-- @todo: Settings button. --> | ||||
|             <ion-button router-direction="forward" routerLink="/settings/app" | ||||
|                 [attr.aria-label]="'core.settings.appsettings' | translate"> | ||||
|                 <core-icon slot="icon-only" name="fa-cog"></core-icon> | ||||
|             </ion-button> | ||||
|         </ion-buttons> | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
|  | ||||
| @ -0,0 +1,50 @@ | ||||
| // (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 { CommonModule } from '@angular/common'; | ||||
| import { FormsModule, ReactiveFormsModule } from '@angular/forms'; | ||||
| import { RouterModule, Routes } from '@angular/router'; | ||||
| import { IonicModule } from '@ionic/angular'; | ||||
| import { TranslateModule } from '@ngx-translate/core'; | ||||
| 
 | ||||
| import { CoreComponentsModule } from '@components/components.module'; | ||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | ||||
| 
 | ||||
| import { CoreLoginCredentialsPage } from './credentials.page'; | ||||
| 
 | ||||
| const routes: Routes = [ | ||||
|     { | ||||
|         path: '', | ||||
|         component: CoreLoginCredentialsPage, | ||||
|     }, | ||||
| ]; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     imports: [ | ||||
|         RouterModule.forChild(routes), | ||||
|         CommonModule, | ||||
|         IonicModule, | ||||
|         TranslateModule.forChild(), | ||||
|         FormsModule, | ||||
|         ReactiveFormsModule, | ||||
|         CoreComponentsModule, | ||||
|         CoreDirectivesModule, | ||||
|     ], | ||||
|     declarations: [ | ||||
|         CoreLoginCredentialsPage, | ||||
|     ], | ||||
|     exports: [RouterModule], | ||||
| }) | ||||
| export class CoreLoginCredentialsPageModule {} | ||||
| @ -243,7 +243,7 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { | ||||
| 
 | ||||
|             this.siteId = id; | ||||
| 
 | ||||
|             await CoreLoginHelper.instance.goToSiteInitialPage(undefined, undefined, undefined, undefined, this.urlToOpen); | ||||
|             await CoreLoginHelper.instance.goToSiteInitialPage({ urlToOpen: this.urlToOpen }); | ||||
|         } catch (error) { | ||||
|             CoreLoginHelper.instance.treatUserTokenError(siteUrl, error, username, password); | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										38
									
								
								src/app/core/login/pages/init/init.page.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/app/core/login/pages/init/init.page.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | ||||
| // (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 { RouterModule, Routes } from '@angular/router'; | ||||
| import { IonicModule } from '@ionic/angular'; | ||||
| 
 | ||||
| import { CoreLoginInitPage } from './init.page'; | ||||
| 
 | ||||
| const routes: Routes = [ | ||||
|     { | ||||
|         path: '', | ||||
|         component: CoreLoginInitPage, | ||||
|     }, | ||||
| ]; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     imports: [ | ||||
|         RouterModule.forChild(routes), | ||||
|         IonicModule, | ||||
|     ], | ||||
|     declarations: [ | ||||
|         CoreLoginInitPage, | ||||
|     ], | ||||
|     exports: [RouterModule], | ||||
| }) | ||||
| export class CoreLoginInitPageModule {} | ||||
| @ -15,9 +15,13 @@ | ||||
| import { Component, OnInit } from '@angular/core'; | ||||
| import { NavController } from '@ionic/angular'; | ||||
| 
 | ||||
| import { CoreApp } from '@services/app'; | ||||
| import { CoreApp, CoreRedirectData } from '@services/app'; | ||||
| import { CoreInit } from '@services/init'; | ||||
| import { SplashScreen } from '@singletons/core.singletons'; | ||||
| import { CoreConstants } from '@core/constants'; | ||||
| import { CoreSite } from '@/app/classes/site'; | ||||
| import { CoreSites } from '@/app/services/sites'; | ||||
| import { CoreLoginHelper, CoreLoginHelperProvider } from '../../services/helper'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays a "splash screen" while the app is being initialized. | ||||
| @ -40,55 +44,75 @@ export class CoreLoginInitPage implements OnInit { | ||||
| 
 | ||||
|         // Check if there was a pending redirect.
 | ||||
|         const redirectData = CoreApp.instance.getRedirect(); | ||||
| 
 | ||||
|         if (redirectData.siteId) { | ||||
|             // Unset redirect data.
 | ||||
|             CoreApp.instance.storeRedirect('', '', {}); | ||||
| 
 | ||||
|             // Only accept the redirect if it was stored less than 20 seconds ago.
 | ||||
|             if (redirectData.timemodified && Date.now() - redirectData.timemodified < 20000) { | ||||
|                 // if (redirectData.siteId != CoreConstants.NO_SITE_ID) {
 | ||||
|                 //     // The redirect is pointing to a site, load it.
 | ||||
|                 //     return this.sitesProvider.loadSite(redirectData.siteId, redirectData.page, redirectData.params)
 | ||||
|                 //             .then((loggedIn) => {
 | ||||
| 
 | ||||
|                 //         if (loggedIn) {
 | ||||
|                 //             return this.loginHelper.goToSiteInitialPage(this.navCtrl, redirectData.page, redirectData.params,
 | ||||
|                 //                     { animate: false });
 | ||||
|                 //         }
 | ||||
|                 //     }).catch(() => {
 | ||||
|                 //         // Site doesn't exist.
 | ||||
|                 //         return this.loadPage();
 | ||||
|                 //     });
 | ||||
|                 // } else {
 | ||||
|                 //     // No site to load, open the page.
 | ||||
|                 //     return this.loginHelper.goToNoSitePage(this.navCtrl, redirectData.page, redirectData.params);
 | ||||
|                 // }
 | ||||
|             } | ||||
|             await this.handleRedirect(redirectData); | ||||
|         } else { | ||||
|             await this.loadPage(); | ||||
|         } | ||||
| 
 | ||||
|         await this.loadPage(); | ||||
| 
 | ||||
|         // If we hide the splash screen now, the init view is still seen for an instant. Wait a bit to make sure it isn't seen.
 | ||||
|         setTimeout(() => { | ||||
|             SplashScreen.instance.hide(); | ||||
|         }, 100); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Treat redirect data. | ||||
|      * | ||||
|      * @param redirectData Redirect data. | ||||
|      */ | ||||
|     protected async handleRedirect(redirectData: CoreRedirectData): Promise<void> { | ||||
|         // Unset redirect data.
 | ||||
|         CoreApp.instance.storeRedirect('', '', {}); | ||||
| 
 | ||||
|         // Only accept the redirect if it was stored less than 20 seconds ago.
 | ||||
|         if (redirectData.timemodified && Date.now() - redirectData.timemodified < 20000) { | ||||
|             if (redirectData.siteId != CoreConstants.NO_SITE_ID) { | ||||
|                 // The redirect is pointing to a site, load it.
 | ||||
|                 try { | ||||
|                     const loggedIn = await CoreSites.instance.loadSite( | ||||
|                         redirectData.siteId!, | ||||
|                         redirectData.page, | ||||
|                         redirectData.params, | ||||
|                     ); | ||||
| 
 | ||||
|                     if (!loggedIn) { | ||||
|                         return; | ||||
|                     } | ||||
| 
 | ||||
|                     return CoreLoginHelper.instance.goToSiteInitialPage({ | ||||
|                         redirectPage: redirectData.page, | ||||
|                         redirectParams: redirectData.params, | ||||
|                     }); | ||||
|                 } catch (error) { | ||||
|                     // Site doesn't exist.
 | ||||
|                     return this.loadPage(); | ||||
|                 } | ||||
|             } else { | ||||
|                 // No site to load, open the page.
 | ||||
|                 return CoreLoginHelper.instance.goToNoSitePage(redirectData.page, redirectData.params); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return this.loadPage(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Load the right page. | ||||
|      * | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected async loadPage(): Promise<void> { | ||||
|         // if (this.sitesProvider.isLoggedIn()) {
 | ||||
|         //     if (this.loginHelper.isSiteLoggedOut()) {
 | ||||
|         //         return this.sitesProvider.logout().then(() => {
 | ||||
|         //             return this.loadPage();
 | ||||
|         //         });
 | ||||
|         //     }
 | ||||
|         if (CoreSites.instance.isLoggedIn()) { | ||||
|             if (CoreLoginHelper.instance.isSiteLoggedOut()) { | ||||
|                 await CoreSites.instance.logout(); | ||||
| 
 | ||||
|         //     return this.loginHelper.goToSiteInitialPage();
 | ||||
|         // }
 | ||||
|                 return this.loadPage(); | ||||
|             } | ||||
| 
 | ||||
|             return CoreLoginHelper.instance.goToSiteInitialPage(); | ||||
|         } | ||||
| 
 | ||||
|         await this.navCtrl.navigateRoot('/login/sites'); | ||||
|     } | ||||
|  | ||||
							
								
								
									
										50
									
								
								src/app/core/login/pages/site/site.page.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/app/core/login/pages/site/site.page.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,50 @@ | ||||
| // (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 { CommonModule } from '@angular/common'; | ||||
| import { FormsModule, ReactiveFormsModule } from '@angular/forms'; | ||||
| import { RouterModule, Routes } from '@angular/router'; | ||||
| import { IonicModule } from '@ionic/angular'; | ||||
| import { TranslateModule } from '@ngx-translate/core'; | ||||
| 
 | ||||
| import { CoreComponentsModule } from '@components/components.module'; | ||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | ||||
| 
 | ||||
| import { CoreLoginSitePage } from './site.page'; | ||||
| 
 | ||||
| const routes: Routes = [ | ||||
|     { | ||||
|         path: '', | ||||
|         component: CoreLoginSitePage, | ||||
|     }, | ||||
| ]; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     imports: [ | ||||
|         RouterModule.forChild(routes), | ||||
|         CommonModule, | ||||
|         IonicModule, | ||||
|         TranslateModule.forChild(), | ||||
|         FormsModule, | ||||
|         ReactiveFormsModule, | ||||
|         CoreComponentsModule, | ||||
|         CoreDirectivesModule, | ||||
|     ], | ||||
|     declarations: [ | ||||
|         CoreLoginSitePage, | ||||
|     ], | ||||
|     exports: [RouterModule], | ||||
| }) | ||||
| export class CoreLoginSitePageModule {} | ||||
| @ -417,8 +417,8 @@ export class CoreLoginSitePage implements OnInit { | ||||
|      * @param event Received Event. | ||||
|      */ | ||||
|     // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||||
|     filterChanged(event: any): void { | ||||
|         const newValue = event.target.value?.trim().toLowerCase(); | ||||
|     filterChanged(event?: any): void { | ||||
|         const newValue = event?.target.value?.trim().toLowerCase(); | ||||
|         if (!newValue || !this.fixedSites) { | ||||
|             this.filteredSites = this.fixedSites; | ||||
|         } else { | ||||
|  | ||||
							
								
								
									
										47
									
								
								src/app/core/login/pages/sites/sites.page.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/app/core/login/pages/sites/sites.page.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| // (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 { CommonModule } from '@angular/common'; | ||||
| import { RouterModule, Routes } from '@angular/router'; | ||||
| import { IonicModule } from '@ionic/angular'; | ||||
| import { TranslateModule } from '@ngx-translate/core'; | ||||
| 
 | ||||
| import { CoreComponentsModule } from '@components/components.module'; | ||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | ||||
| 
 | ||||
| import { CoreLoginSitesPage } from './sites.page'; | ||||
| 
 | ||||
| const routes: Routes = [ | ||||
|     { | ||||
|         path: '', | ||||
|         component: CoreLoginSitesPage, | ||||
|     }, | ||||
| ]; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     imports: [ | ||||
|         RouterModule.forChild(routes), | ||||
|         CommonModule, | ||||
|         IonicModule, | ||||
|         TranslateModule.forChild(), | ||||
|         CoreComponentsModule, | ||||
|         CoreDirectivesModule, | ||||
|     ], | ||||
|     declarations: [ | ||||
|         CoreLoginSitesPage, | ||||
|     ], | ||||
|     exports: [RouterModule], | ||||
| }) | ||||
| export class CoreLoginSitesPageModule {} | ||||
| @ -34,11 +34,14 @@ import { CoreWSError } from '@classes/errors/wserror'; | ||||
| import { makeSingleton, Translate } from '@singletons/core.singletons'; | ||||
| import { CoreLogger } from '@singletons/logger'; | ||||
| import { CoreUrl } from '@singletons/url'; | ||||
| import { NavigationOptions } from '@ionic/angular/providers/nav-controller'; | ||||
| 
 | ||||
| /** | ||||
|  * Helper provider that provides some common features regarding authentication. | ||||
|  */ | ||||
| @Injectable() | ||||
| @Injectable({ | ||||
|     providedIn: 'root', | ||||
| }) | ||||
| export class CoreLoginHelperProvider { | ||||
| 
 | ||||
|     static readonly OPEN_COURSE = 'open_course'; | ||||
| @ -448,7 +451,7 @@ export class CoreLoginHelperProvider { | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||
|     goToNoSitePage(navCtrl: NavController, page: string, params?: Params): Promise<any> { | ||||
|     goToNoSitePage(page?: string, params?: Params): Promise<any> { | ||||
|         // @todo
 | ||||
|         return Promise.resolve(); | ||||
|     } | ||||
| @ -456,17 +459,11 @@ export class CoreLoginHelperProvider { | ||||
|     /** | ||||
|      * Go to the initial page of a site depending on 'userhomepage' setting. | ||||
|      * | ||||
|      * @param navCtrl NavController to use. Defaults to app root NavController. | ||||
|      * @param page Name of the page to load after loading the main page. | ||||
|      * @param params Params to pass to the page. | ||||
|      * @param options Navigation options. | ||||
|      * @param url URL to open once the main menu is loaded. | ||||
|      * @param options Options. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||
|     goToSiteInitialPage(navCtrl?: NavController, page?: string, params?: Params, options?: any, url?: string): Promise<any> { | ||||
|         // @todo
 | ||||
|         return Promise.resolve(); | ||||
|     goToSiteInitialPage(options?: OpenMainMenuOptions): Promise<void> { | ||||
|         return this.openMainMenu(options); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -664,17 +661,32 @@ export class CoreLoginHelperProvider { | ||||
|     /** | ||||
|      * Open the main menu, loading a certain page. | ||||
|      * | ||||
|      * @param navCtrl NavController. | ||||
|      * @param page Name of the page to load. | ||||
|      * @param params Params to pass to the page. | ||||
|      * @param options Navigation options. | ||||
|      * @param url URL to open once the main menu is loaded. | ||||
|      * @param options Options. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||
|     protected openMainMenu(navCtrl: NavController, page: string, params: Params, options?: any, url?: string): Promise<any> { | ||||
|         // @todo
 | ||||
|         return Promise.resolve(); | ||||
|     protected async openMainMenu(options?: OpenMainMenuOptions): Promise<void> { | ||||
| 
 | ||||
|         // Due to DeepLinker, we need to remove the path from the URL before going to main menu.
 | ||||
|         // IonTabs checks the URL to determine which path to load for deep linking, so we clear the URL.
 | ||||
|         // @todo this.location.replaceState('');
 | ||||
| 
 | ||||
|         if (options?.redirectPage == CoreLoginHelperProvider.OPEN_COURSE) { | ||||
|             // Load the main menu first, and then open the course.
 | ||||
|             try { | ||||
|                 await this.navCtrl.navigateRoot('/mainmenu'); | ||||
|             } finally { | ||||
|                 // @todo: Open course.
 | ||||
|             } | ||||
|         } else { | ||||
|             // Open the main menu.
 | ||||
|             const queryParams: Params = Object.assign({}, options); | ||||
|             delete queryParams.navigationOptions; | ||||
| 
 | ||||
|             await this.navCtrl.navigateRoot('/mainmenu', { | ||||
|                 queryParams, | ||||
|                 ...options?.navigationOptions, | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -1375,3 +1387,10 @@ type StoredLoginLaunchData = { | ||||
|     pageParams: Params; | ||||
|     ssoUrlParams: CoreUrlParams; | ||||
| }; | ||||
| 
 | ||||
| type OpenMainMenuOptions = { | ||||
|     redirectPage?: string; // Route of the page to open in main menu. If not defined, default tab will be selected.
 | ||||
|     redirectParams?: Params; // Params to pass to the selected tab if any.
 | ||||
|     urlToOpen?: string; // URL to open once the main menu is loaded.
 | ||||
|     navigationOptions?: NavigationOptions; // Navigation options.
 | ||||
| }; | ||||
|  | ||||
							
								
								
									
										6
									
								
								src/app/core/mainmenu/lang/en.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/app/core/mainmenu/lang/en.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| { | ||||
|     "changesite": "Change site", | ||||
|     "help": "Help", | ||||
|     "logout": "Log out", | ||||
|     "website": "Website" | ||||
| } | ||||
							
								
								
									
										47
									
								
								src/app/core/mainmenu/mainmenu-routing.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/app/core/mainmenu/mainmenu-routing.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| // (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 { RouterModule, Routes } from '@angular/router'; | ||||
| 
 | ||||
| import { CoreMainMenuPage } from './pages/menu/menu.page'; | ||||
| import { CoreMainMenuMorePage } from './pages/more/more.page'; | ||||
| 
 | ||||
| const routes: Routes = [ | ||||
|     { | ||||
|         path: '', | ||||
|         component: CoreMainMenuPage, | ||||
|         children: [ | ||||
|             { | ||||
|                 path: 'home', // @todo: Add this route dynamically.
 | ||||
|                 loadChildren: () => import('../courses/pages/home/home.page.module').then( m => m.CoreCoursesHomePageModule), | ||||
|             }, | ||||
|             { | ||||
|                 path: 'more', | ||||
|                 children: [ | ||||
|                     { | ||||
|                         path: '', | ||||
|                         component: CoreMainMenuMorePage, | ||||
|                     }, | ||||
|                 ], | ||||
|             }, | ||||
|         ], | ||||
|     }, | ||||
| ]; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     imports: [RouterModule.forChild(routes)], | ||||
|     exports: [RouterModule], | ||||
| }) | ||||
| export class CoreMainMenuRoutingModule {} | ||||
							
								
								
									
										42
									
								
								src/app/core/mainmenu/mainmenu.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/app/core/mainmenu/mainmenu.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| // (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 { CommonModule } from '@angular/common'; | ||||
| 
 | ||||
| import { IonicModule } from '@ionic/angular'; | ||||
| import { TranslateModule } from '@ngx-translate/core'; | ||||
| 
 | ||||
| import { CoreComponentsModule } from '@/app/components/components.module'; | ||||
| import { CoreDirectivesModule } from '@/app/directives/directives.module'; | ||||
| 
 | ||||
| import { CoreMainMenuRoutingModule } from './mainmenu-routing.module'; | ||||
| import { CoreMainMenuPage } from './pages/menu/menu.page'; | ||||
| import { CoreMainMenuMorePage } from './pages/more/more.page'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     imports: [ | ||||
|         CommonModule, | ||||
|         IonicModule, | ||||
|         CoreMainMenuRoutingModule, | ||||
|         TranslateModule.forChild(), | ||||
|         CoreComponentsModule, | ||||
|         CoreDirectivesModule, | ||||
|     ], | ||||
|     declarations: [ | ||||
|         CoreMainMenuPage, | ||||
|         CoreMainMenuMorePage, | ||||
|     ], | ||||
| }) | ||||
| export class CoreMainMenuModule {} | ||||
							
								
								
									
										27
									
								
								src/app/core/mainmenu/pages/menu/menu.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/app/core/mainmenu/pages/menu/menu.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | ||||
| <ion-tabs #mainTabs [hidden]="!showTabs"> | ||||
| 
 | ||||
|     <ion-tab-bar slot="bottom"> | ||||
|         <ion-spinner *ngIf="!loaded"></ion-spinner> | ||||
| 
 | ||||
|         <ion-tab-button tab="redirect" [disabled]="true" [hidden]="true"></ion-tab-button> <!-- [root]="redirectPage" [rootParams]="redirectParams" --> | ||||
| 
 | ||||
|         <ion-tab-button (ionTabButtonClick)="tabClicked($event, tab.page)" [hidden]="!loaded && tab.hide" *ngFor="let tab of tabs" [tab]="tab.page" [disabled]="tab.hide" layout="label-hide"  class="{{tab.class}}"> | ||||
|             <core-icon [name]="tab.icon"></core-icon> | ||||
|             <ion-label>{{ tab.title | translate }}</ion-label> | ||||
|             <ion-badge *ngIf="tab.badge">{{ tab.badge }}</ion-badge> | ||||
|         </ion-tab-button> | ||||
| 
 | ||||
|         <ion-tab-button (ionTabButtonClick)="tabClicked($event, 'more')" [hidden]="!loaded" tab="more" layout="label-hide"> | ||||
|             <core-icon name="fa-bars"></core-icon> | ||||
|             <ion-label>{{ 'core.more' | translate }}</ion-label> | ||||
|         </ion-tab-button> | ||||
|     </ion-tab-bar> | ||||
| </ion-tabs> | ||||
| <div class="core-network-message" [hidden]="!showTabs"> | ||||
|     <div class="core-online-message"> | ||||
|         {{ "core.youreonline" | translate }} | ||||
|     </div> | ||||
|     <div class="core-offline-message"> | ||||
|         {{ "core.youreoffline" | translate }} | ||||
|     </div> | ||||
| </div> | ||||
							
								
								
									
										255
									
								
								src/app/core/mainmenu/pages/menu/menu.page.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										255
									
								
								src/app/core/mainmenu/pages/menu/menu.page.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,255 @@ | ||||
| // (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, OnDestroy, ViewChild, ChangeDetectorRef } from '@angular/core'; | ||||
| import { ActivatedRoute, Params, Router } from '@angular/router'; | ||||
| import { NavController, IonTabs } from '@ionic/angular'; | ||||
| import { Subscription } from 'rxjs'; | ||||
| 
 | ||||
| import { CoreApp } from '@services/app'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreEvents, CoreEventObserver, CoreEventLoadPageMainMenuData } from '@singletons/events'; | ||||
| import { CoreMainMenu } from '../../services/mainmenu'; | ||||
| import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from '../../services/delegate'; | ||||
| import { CoreUtils } from '@/app/services/utils/utils'; | ||||
| import { CoreDomUtils } from '@/app/services/utils/dom'; | ||||
| import { Translate } from '@/app/singletons/core.singletons'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays the main menu of the app. | ||||
|  */ | ||||
| @Component({ | ||||
|     selector: 'page-core-mainmenu', | ||||
|     templateUrl: 'menu.html', | ||||
|     styleUrls: ['menu.scss'], | ||||
| }) | ||||
| export class CoreMainMenuPage implements OnInit, OnDestroy { | ||||
| 
 | ||||
|     tabs: CoreMainMenuHandlerToDisplay[] = []; | ||||
|     allHandlers?: CoreMainMenuHandlerToDisplay[]; | ||||
|     loaded = false; | ||||
|     redirectPage?: string; | ||||
|     redirectParams?: Params; | ||||
|     showTabs = false; | ||||
|     tabsPlacement = 'bottom'; | ||||
| 
 | ||||
|     protected subscription?: Subscription; | ||||
|     protected redirectObs?: CoreEventObserver; | ||||
|     protected pendingRedirect?: CoreEventLoadPageMainMenuData; | ||||
|     protected urlToOpen?: string; | ||||
|     protected mainMenuId: number; | ||||
|     protected keyboardObserver?: CoreEventObserver; | ||||
| 
 | ||||
|     @ViewChild('mainTabs') mainTabs?: IonTabs; | ||||
| 
 | ||||
|     constructor( | ||||
|         protected route: ActivatedRoute, | ||||
|         protected navCtrl: NavController, | ||||
|         protected menuDelegate: CoreMainMenuDelegate, | ||||
|         protected changeDetector: ChangeDetectorRef, | ||||
|         protected router: Router, | ||||
|     ) { | ||||
|         this.mainMenuId = CoreApp.instance.getMainMenuId(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Initialize the component. | ||||
|      */ | ||||
|     ngOnInit(): void { | ||||
|         if (!CoreSites.instance.isLoggedIn()) { | ||||
|             this.navCtrl.navigateRoot('/login/init'); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.route.queryParams.subscribe(params => { | ||||
|             const redirectPage = params['redirectPage']; | ||||
|             if (redirectPage) { | ||||
|                 this.pendingRedirect = { | ||||
|                     redirectPage: redirectPage, | ||||
|                     redirectParams: params['redirectParams'], | ||||
|                 }; | ||||
|             } | ||||
| 
 | ||||
|             this.urlToOpen = params['urlToOpen']; | ||||
|         }); | ||||
| 
 | ||||
|         this.showTabs = true; | ||||
| 
 | ||||
|         this.redirectObs = CoreEvents.on(CoreEvents.LOAD_PAGE_MAIN_MENU, (data: CoreEventLoadPageMainMenuData) => { | ||||
|             if (!this.loaded) { | ||||
|                 // View isn't ready yet, wait for it to be ready.
 | ||||
|                 this.pendingRedirect = data; | ||||
|             } else { | ||||
|                 delete this.pendingRedirect; | ||||
|                 this.handleRedirect(data); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         this.subscription = this.menuDelegate.getHandlers().subscribe((handlers) => { | ||||
|             // Remove the handlers that should only appear in the More menu.
 | ||||
|             this.allHandlers = handlers.filter((handler) => !handler.onlyInMore); | ||||
| 
 | ||||
|             this.initHandlers(); | ||||
| 
 | ||||
|             if (this.loaded && this.pendingRedirect) { | ||||
|                 // Wait for tabs to be initialized and then handle the redirect.
 | ||||
|                 setTimeout(() => { | ||||
|                     if (this.pendingRedirect) { | ||||
|                         this.handleRedirect(this.pendingRedirect); | ||||
|                         delete this.pendingRedirect; | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         window.addEventListener('resize', this.initHandlers.bind(this)); | ||||
| 
 | ||||
|         if (CoreApp.instance.isIOS()) { | ||||
|             // In iOS, the resize event is triggered before the keyboard is opened/closed and not triggered again once done.
 | ||||
|             // Init handlers again once keyboard is closed since the resize event doesn't have the updated height.
 | ||||
|             this.keyboardObserver = CoreEvents.on(CoreEvents.KEYBOARD_CHANGE, (kbHeight: number) => { | ||||
|                 if (kbHeight === 0) { | ||||
|                     this.initHandlers(); | ||||
| 
 | ||||
|                     // If the device is slow it can take a bit more to update the window height. Retry in a few ms.
 | ||||
|                     setTimeout(() => { | ||||
|                         this.initHandlers(); | ||||
|                     }, 250); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         CoreApp.instance.setMainMenuOpen(this.mainMenuId, true); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Init handlers on change (size or handlers). | ||||
|      */ | ||||
|     initHandlers(): void { | ||||
|         if (this.allHandlers) { | ||||
|             this.tabsPlacement = CoreMainMenu.instance.getTabPlacement(); | ||||
| 
 | ||||
|             const handlers = this.allHandlers.slice(0, CoreMainMenu.instance.getNumItems()); // Get main handlers.
 | ||||
| 
 | ||||
|             // Re-build the list of tabs. If a handler is already in the list, use existing object to prevent re-creating the tab.
 | ||||
|             const newTabs: CoreMainMenuHandlerToDisplay[] = []; | ||||
| 
 | ||||
|             for (let i = 0; i < handlers.length; i++) { | ||||
|                 const handler = handlers[i]; | ||||
| 
 | ||||
|                 // Check if the handler is already in the tabs list. If so, use it.
 | ||||
|                 const tab = this.tabs.find((tab) => tab.title == handler.title && tab.icon == handler.icon); | ||||
| 
 | ||||
|                 tab ? tab.hide = false : null; | ||||
|                 handler.hide = false; | ||||
| 
 | ||||
|                 newTabs.push(tab || handler); | ||||
|             } | ||||
| 
 | ||||
|             this.tabs = newTabs; | ||||
| 
 | ||||
|             // Sort them by priority so new handlers are in the right position.
 | ||||
|             this.tabs.sort((a, b) => (b.priority || 0) - (a.priority || 0)); | ||||
| 
 | ||||
|             this.loaded = this.menuDelegate.areHandlersLoaded(); | ||||
| 
 | ||||
|             if (this.loaded && this.mainTabs && !this.mainTabs.getSelected()) { | ||||
|                 // Select the first tab.
 | ||||
|                 setTimeout(() => { | ||||
|                     this.mainTabs!.select(this.tabs[0]?.page || 'more'); | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (this.urlToOpen) { | ||||
|             // There's a content link to open.
 | ||||
|             // @todo: Treat URL.
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Handle a redirect. | ||||
|      * | ||||
|      * @param data Data received. | ||||
|      */ | ||||
|     protected handleRedirect(data: CoreEventLoadPageMainMenuData): void { | ||||
|         // Check if the redirect page is the root page of any of the tabs.
 | ||||
|         const i = this.tabs.findIndex((tab) => tab.page == data.redirectPage); | ||||
| 
 | ||||
|         if (i >= 0) { | ||||
|             // Tab found. Open it with the params.
 | ||||
|             this.navCtrl.navigateForward(data.redirectPage, { | ||||
|                 queryParams: data.redirectParams, | ||||
|                 animated: false, | ||||
|             }); | ||||
|         } else { | ||||
|             // Tab not found, use a phantom tab.
 | ||||
|             // @todo
 | ||||
|         } | ||||
| 
 | ||||
|         // Force change detection, otherwise sometimes the tab was selected before the params were applied.
 | ||||
|         this.changeDetector.detectChanges(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Page destroyed. | ||||
|      */ | ||||
|     ngOnDestroy(): void { | ||||
|         this.subscription?.unsubscribe(); | ||||
|         this.redirectObs?.off(); | ||||
|         window.removeEventListener('resize', this.initHandlers.bind(this)); | ||||
|         CoreApp.instance.setMainMenuOpen(this.mainMenuId, false); | ||||
|         this.keyboardObserver?.off(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Tab clicked. | ||||
|      * | ||||
|      * @param e Event. | ||||
|      * @param page Page of the tab. | ||||
|      */ | ||||
|     async tabClicked(e: Event, page: string): Promise<void> { | ||||
|         if (this.mainTabs?.getSelected() != page) { | ||||
|             // Just change the tab.
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // Current tab was clicked. Check if user is already at root level.
 | ||||
|         if (this.router.url == '/mainmenu/' + page) { | ||||
|             // Already at root level, nothing to do.
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // Ask the user if he wants to go back to the root page of the tab.
 | ||||
|         e.preventDefault(); | ||||
|         e.stopPropagation(); | ||||
| 
 | ||||
|         try { | ||||
|             const tab = this.tabs.find((tab) => tab.page == page); | ||||
| 
 | ||||
|             if (tab?.title) { | ||||
|                 await CoreDomUtils.instance.showConfirm(Translate.instance.instant('core.confirmgotabroot', { name: tab.title })); | ||||
|             } else { | ||||
|                 await CoreDomUtils.instance.showConfirm(Translate.instance.instant('core.confirmgotabrootdefault')); | ||||
|             } | ||||
| 
 | ||||
|             // User confirmed, go to root.
 | ||||
|             this.mainTabs?.select(page); | ||||
|         } catch (error) { | ||||
|             // User canceled.
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										93
									
								
								src/app/core/mainmenu/pages/menu/menu.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								src/app/core/mainmenu/pages/menu/menu.scss
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | ||||
| ion-icon.tab-button-icon { | ||||
|     text-overflow: unset; | ||||
|     overflow: visible; | ||||
|     text-align: center; | ||||
|     transition: margin 500ms ease-in-out, transform 300ms ease-in-out; | ||||
| } | ||||
| 
 | ||||
| .ion-md-fa-graduation-cap, | ||||
| .ion-ios-fa-graduation-cap, | ||||
| .ion-ios-fa-graduation-cap-outline, | ||||
| .ion-fa-graduation-cap { | ||||
|     // @todo @extend .fa-graduation-cap; | ||||
|     // @todo @extend .fa; | ||||
|     font-size: 21px; | ||||
|     height: 21px; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| .ion-ios-fa-graduation-cap-outline { | ||||
|     color: transparent; | ||||
|     -webkit-text-stroke-width: 0.8px; | ||||
|     // @todo -webkit-text-stroke-color: $tabs-tab-color-inactive; | ||||
|     font-size: 23px; | ||||
|     height: 23px; | ||||
| } | ||||
| 
 | ||||
| .ion-md-fa-newspaper-o, | ||||
| .ion-ios-fa-newspaper-o, | ||||
| .ion-ios-fa-newspaper-o-outline, | ||||
| .ion-fa-newspaper-o { | ||||
|     // @todo @extend .fa-newspaper-o; | ||||
|     // @todo @extend .fa; | ||||
|     font-size: 22px; | ||||
|     height: 22px; | ||||
| } | ||||
| 
 | ||||
| .ion-ios-fa-newspaper-o-outline { | ||||
|     font-size: 23px; | ||||
|     height: 23px; | ||||
| } | ||||
| 
 | ||||
| .core-network-message { | ||||
|     position: absolute; | ||||
|     bottom: 0; | ||||
|     left: 0; | ||||
|     right: 0; | ||||
|     padding-left: 10px; | ||||
|     padding-right: 10px; | ||||
|     text-align: center; | ||||
|     color: white; | ||||
|     visibility: hidden; | ||||
|     height: 0; | ||||
|     transition: all 500ms ease-in-out; | ||||
|     opacity: .8; | ||||
| } | ||||
| 
 | ||||
| .core-online-message, | ||||
| .core-offline-message { | ||||
|     display: none; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
| .core-online ion-app.app-root page-core-mainmenu, | ||||
| .core-offline ion-app.app-root page-core-mainmenu { | ||||
| 
 | ||||
|     core-ion-tabs[tabsplacement="bottom"] ion-icon.tab-button-icon { | ||||
|         margin-bottom: $core-network-message-height / 2; | ||||
| 
 | ||||
|         &.icon-ios { | ||||
|             margin-bottom: 14px; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     .core-network-message { | ||||
|         visibility: visible; | ||||
|         height: $core-network-message-height; | ||||
|         pointer-events: none; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| .core-offline ion-app.app-root page-core-mainmenu .core-offline-message, | ||||
| .core-online ion-app.app-root page-core-mainmenu .core-online-message { | ||||
|     display: block; | ||||
| } | ||||
| 
 | ||||
| .core-online ion-app.app-root page-core-mainmenu .core-network-message { | ||||
|     background: $green; | ||||
| } | ||||
| 
 | ||||
| .core-offline ion-app.app-root page-core-mainmenu .core-network-message { | ||||
|     background: $red; | ||||
| }*/ | ||||
							
								
								
									
										84
									
								
								src/app/core/mainmenu/pages/more/more.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/app/core/mainmenu/pages/more/more.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,84 @@ | ||||
| <ion-header> | ||||
|     <ion-toolbar> | ||||
|         <ion-buttons slot="start"> | ||||
|             <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button> | ||||
|         </ion-buttons> | ||||
| 
 | ||||
|         <ion-title><core-format-text [text]="siteName" contextLevel="system" [contextInstanceId]="0"></core-format-text></ion-title> | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-list> | ||||
|         <ion-item *ngIf="siteInfo" class="ion-text-wrap"> <!-- @todo core-user-link [userId]="siteInfo.userid" --> | ||||
|             <ion-avatar slot="start"></ion-avatar> <!-- @todo core-user-avatar [user]="siteInfo" --> | ||||
|             <ion-label> | ||||
|                 <h2>{{siteInfo.fullname}}</h2> | ||||
|                 <p><core-format-text [text]="siteName" contextLevel="system" [contextInstanceId]="0" [wsNotFiltered]="true"></core-format-text></p> | ||||
|                 <p>{{ siteUrl }}</p> | ||||
|             </ion-label> | ||||
|         </ion-item> | ||||
|         <ion-item-divider></ion-item-divider> | ||||
|         <ion-item class="ion-text-center" *ngIf="(!handlers || !handlers.length) && !handlersLoaded"> | ||||
|             <ion-spinner></ion-spinner> | ||||
|         </ion-item> | ||||
|         <ion-item *ngFor="let handler of handlers" [ngClass]="['core-moremenu-handler', handler.class || '']" (click)="openHandler(handler)" title="{{ handler.title | translate }}" detail="true"> | ||||
|             <core-icon [name]="handler.icon" slot="start"></core-icon> | ||||
|             <ion-label> | ||||
|                 <h2>{{ handler.title | translate}}</h2> | ||||
|             </ion-label> | ||||
|             <ion-badge slot="end" *ngIf="handler.showBadge" [hidden]="handler.loading || !handler.badge">{{handler.badge}}</ion-badge> | ||||
|             <ion-spinner slot="end" *ngIf="handler.showBadge && handler.loading"></ion-spinner> | ||||
|         </ion-item> | ||||
|         <div *ngFor="let item of customItems" class="core-moremenu-customitem"> | ||||
|             <ion-item *ngIf="item.type != 'embedded'" [href]="item.url" title="{{item.label}}" core-link [capture]="item.type == 'app'" [inApp]="item.type == 'inappbrowser'"> | ||||
|                 <core-icon [name]="item.icon" slot="start"></core-icon> | ||||
|                 <ion-label> | ||||
|                     <h2>{{item.label}}</h2> | ||||
|                 </ion-label> | ||||
|             </ion-item> | ||||
|             <ion-item *ngIf="item.type == 'embedded'" (click)="openItem(item)" title="{{item.label}}"> | ||||
|                 <core-icon [name]="item.icon" slot="start"></core-icon> | ||||
|                 <ion-label> | ||||
|                     <h2>{{item.label}}</h2> | ||||
|                 </ion-label> | ||||
|             </ion-item> | ||||
|         </div> | ||||
|         <ion-item *ngIf="showScanQR" (click)="scanQR()"> | ||||
|             <core-icon name="fa-qrcode" slot="start" aria-hidden="true"></core-icon> | ||||
|             <ion-label> | ||||
|                 <h2>{{ 'core.scanqr' | translate }}</h2> | ||||
|             </ion-label> | ||||
|         </ion-item> | ||||
|         <ion-item *ngIf="showWeb && siteInfo" [href]="siteInfo.siteurl" core-link autoLogin="yes" title="{{ 'core.mainmenu.website' | translate }}"> | ||||
|             <ion-icon name="globe" slot="start" aria-hidden="true"></ion-icon> | ||||
|             <ion-label> | ||||
|                 <h2>{{ 'core.mainmenu.website' | translate }}</h2> | ||||
|             </ion-label> | ||||
|         </ion-item> | ||||
|         <ion-item *ngIf="showHelp" [href]="docsUrl" core-link autoLogin="no" title="{{ 'core.mainmenu.help' | translate }}"> | ||||
|             <ion-icon name="help-buoy" slot="start" aria-hidden="true"></ion-icon> | ||||
|             <ion-label> | ||||
|                 <h2>{{ 'core.mainmenu.help' | translate }}</h2> | ||||
|             </ion-label> | ||||
|         </ion-item> | ||||
|         <ion-item (click)="openSitePreferences()" title="{{ 'core.settings.preferences' | translate }}"> | ||||
|             <core-icon name="fa-wrench" slot="start"></core-icon> | ||||
|             <ion-label> | ||||
|                 <h2>{{ 'core.settings.preferences' | translate }}</h2> | ||||
|             </ion-label> | ||||
|         </ion-item> | ||||
|         <ion-item (click)="logout()" title="{{ logoutLabel | translate }}"> | ||||
|             <ion-icon name="log-out" slot="start" aria-hidden="true"></ion-icon> | ||||
|             <ion-label> | ||||
|                 <h2>{{ logoutLabel | translate }}</h2> | ||||
|             </ion-label> | ||||
|         </ion-item> | ||||
|         <ion-item-divider></ion-item-divider> | ||||
|         <ion-item (click)="openAppSettings()" title="{{ 'core.settings.appsettings' | translate }}"> | ||||
|             <core-icon name="fa-cogs" slot="start"></core-icon> | ||||
|             <ion-label> | ||||
|                 <h2>{{ 'core.settings.appsettings' | translate }}</h2> | ||||
|             </ion-label> | ||||
|         </ion-item> | ||||
|     </ion-list> | ||||
| </ion-content> | ||||
							
								
								
									
										182
									
								
								src/app/core/mainmenu/pages/more/more.page.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								src/app/core/mainmenu/pages/more/more.page.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,182 @@ | ||||
| // (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, OnDestroy } from '@angular/core'; | ||||
| import { Subscription } from 'rxjs'; | ||||
| 
 | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { CoreSiteInfo } from '@classes/site'; | ||||
| import { CoreLoginHelper } from '@core/login/services/helper'; | ||||
| import { CoreMainMenuDelegate, CoreMainMenuHandlerData } from '../../services/delegate'; | ||||
| import { CoreMainMenu, CoreMainMenuCustomItem } from '../../services/mainmenu'; | ||||
| import { CoreEventObserver, CoreEvents } from '@singletons/events'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays the main menu of the app. | ||||
|  */ | ||||
| @Component({ | ||||
|     selector: 'page-core-mainmenu-more', | ||||
|     templateUrl: 'more.html', | ||||
|     styleUrls: ['more.scss'], | ||||
| }) | ||||
| 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; | ||||
| 
 | ||||
|     protected subscription!: Subscription; | ||||
|     protected langObserver: CoreEventObserver; | ||||
|     protected updateSiteObserver: CoreEventObserver; | ||||
| 
 | ||||
|     constructor( | ||||
|         protected menuDelegate: CoreMainMenuDelegate, | ||||
|     ) { | ||||
| 
 | ||||
|         this.langObserver = CoreEvents.on(CoreEvents.LANGUAGE_CHANGED, this.loadSiteInfo.bind(this)); | ||||
|         this.updateSiteObserver = CoreEvents.on( | ||||
|             CoreEvents.SITE_UPDATED, | ||||
|             this.loadSiteInfo.bind(this), | ||||
|             CoreSites.instance.getCurrentSiteId(), | ||||
|         ); | ||||
|         this.loadSiteInfo(); | ||||
|         this.showScanQR = CoreUtils.instance.canScanQR() && | ||||
|                 !CoreSites.instance.getCurrentSite()?.isFeatureDisabled('CoreMainMenuDelegate_QrReader'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Initialize component. | ||||
|      */ | ||||
|     ngOnInit(): void { | ||||
|         // Load the handlers.
 | ||||
|         this.subscription = this.menuDelegate.getHandlers().subscribe((handlers) => { | ||||
|             this.allHandlers = handlers; | ||||
| 
 | ||||
|             this.initHandlers(); | ||||
|         }); | ||||
| 
 | ||||
|         window.addEventListener('resize', this.initHandlers.bind(this)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Page destroyed. | ||||
|      */ | ||||
|     ngOnDestroy(): void { | ||||
|         window.removeEventListener('resize', this.initHandlers.bind(this)); | ||||
|         this.langObserver?.off(); | ||||
|         this.updateSiteObserver?.off(); | ||||
|         this.subscription?.unsubscribe(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Init handlers on change (size or handlers). | ||||
|      */ | ||||
|     initHandlers(): void { | ||||
|         if (!this.allHandlers) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // Calculate the main handlers not to display them in this view.
 | ||||
|         const mainHandlers = this.allHandlers | ||||
|             .filter((handler) => !handler.onlyInMore) | ||||
|             .slice(0, CoreMainMenu.instance.getNumItems()); | ||||
| 
 | ||||
|         // Get only the handlers that don't appear in the main view.
 | ||||
|         this.handlers = this.allHandlers.filter((handler) => mainHandlers.indexOf(handler) == -1); | ||||
| 
 | ||||
|         this.handlersLoaded = this.menuDelegate.areHandlersLoaded(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Load the site info required by the view. | ||||
|      */ | ||||
|     protected async loadSiteInfo(): Promise<void> { | ||||
|         const currentSite = CoreSites.instance.getCurrentSite(); | ||||
| 
 | ||||
|         if (!currentSite) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.siteInfo = currentSite.getInfo(); | ||||
|         this.siteName = currentSite.getSiteName(); | ||||
|         this.siteUrl = currentSite.getURL(); | ||||
|         this.logoutLabel = CoreLoginHelper.instance.getLogoutLabel(currentSite); | ||||
|         this.showWeb = !currentSite.isFeatureDisabled('CoreMainMenuDelegate_website'); | ||||
|         this.showHelp = !currentSite.isFeatureDisabled('CoreMainMenuDelegate_help'); | ||||
| 
 | ||||
|         this.docsUrl = await currentSite.getDocsUrl(); | ||||
| 
 | ||||
|         this.customItems = await CoreMainMenu.instance.getCustomMenuItems(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Open a handler. | ||||
|      * | ||||
|      * @param handler Handler to open. | ||||
|      */ | ||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||
|     openHandler(handler: CoreMainMenuHandlerData): void { | ||||
|         // @todo
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Open an embedded custom item. | ||||
|      * | ||||
|      * @param item Item to open. | ||||
|      */ | ||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||
|     openItem(item: CoreMainMenuCustomItem): void { | ||||
|         // @todo
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Open app settings page. | ||||
|      */ | ||||
|     openAppSettings(): void { | ||||
|         // @todo
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Open site settings page. | ||||
|      */ | ||||
|     openSitePreferences(): void { | ||||
|         // @todo
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Scan and treat a QR code. | ||||
|      */ | ||||
|     async scanQR(): Promise<void> { | ||||
|         // Scan for a QR code.
 | ||||
|         // @todo
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Logout the user. | ||||
|      */ | ||||
|     logout(): void { | ||||
|         CoreSites.instance.logout(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										97
									
								
								src/app/core/mainmenu/pages/more/more.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								src/app/core/mainmenu/pages/more/more.scss
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,97 @@ | ||||
| /* | ||||
| $core-more-icon: $gray-darker !default; | ||||
| $core-more-background-ios: $list-ios-background-color !default; | ||||
| $core-more-background-md: $list-md-background-color !default; | ||||
| $core-more-activated-background-ios: color-shade($core-more-background-ios) !default; | ||||
| $core-more-activated-background-md: color-shade($core-more-background-md) !default; | ||||
| $core-more-divider-ios: $item-ios-divider-background !default; | ||||
| $core-more-divider-md: $item-md-divider-background !default; | ||||
| $core-more-border-ios: $list-ios-border-color !default; | ||||
| $core-more-border-md: $list-md-border-color !default; | ||||
| $core-more-color-ios: $list-ios-text-color!default; | ||||
| $core-more-color-md: $list-md-text-color !default; | ||||
| 
 | ||||
| .item-block { | ||||
|     &.item-ios { | ||||
|         background-color: $core-more-background-ios; | ||||
|         color: $core-more-color-ios; | ||||
|         p { | ||||
|             color: $core-more-color-ios; | ||||
|         } | ||||
| 
 | ||||
|         .item-inner { | ||||
|             border-bottom: $hairlines-width solid $core-more-border-ios; | ||||
|         } | ||||
|     } | ||||
|     &.item-md { | ||||
|         background-color: $core-more-background-md; | ||||
|         color: $core-more-color-md; | ||||
|         p { | ||||
|             color: $core-more-color-md; | ||||
|         } | ||||
| 
 | ||||
|         .item-inner { | ||||
|             border-bottom: 1px solid $core-more-border-md; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     &.activated { | ||||
|         &.item-ios { | ||||
|             background-color: $core-more-activated-background-ios; | ||||
|         } | ||||
|         &.item-md { | ||||
|             background-color: $core-more-activated-background-md; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| ion-icon { | ||||
|     color: $core-more-icon; | ||||
| } | ||||
| 
 | ||||
| .item-divider { | ||||
|     &.item-ios { | ||||
|         background-color: $core-more-divider-ios; | ||||
|     } | ||||
| 
 | ||||
|     &.item-md { | ||||
|         background-color: $core-more-divider-md; | ||||
|         border-bottom: $core-more-border-md; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @include darkmode() { | ||||
|     ion-icon { | ||||
|         color: $core-dark-text-color; | ||||
|     } | ||||
| 
 | ||||
|     .item-divider { | ||||
|         &.item-ios, | ||||
|         &.item-md { | ||||
|             color: $core-dark-text-color; | ||||
|             background-color: $core-dark-item-divider-bg-color; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     .item-block { | ||||
|         &.item-ios, | ||||
|         &.item-md { | ||||
|             color: $core-dark-text-color; | ||||
|             background-color: $core-dark-item-bg-color; | ||||
|             p { | ||||
|                 color: $core-dark-text-color; | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         &.activated { | ||||
|             &.item-ios { | ||||
|                 background-color: $core-more-activated-background-ios; | ||||
|             } | ||||
|             &.item-md { | ||||
|                 background-color: $core-more-activated-background-md; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| */ | ||||
							
								
								
									
										177
									
								
								src/app/core/mainmenu/services/delegate.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								src/app/core/mainmenu/services/delegate.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,177 @@ | ||||
| // (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 { Params } from '@angular/router'; | ||||
| import { Subject, BehaviorSubject } from 'rxjs'; | ||||
| 
 | ||||
| import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; | ||||
| import { CoreEvents } from '@singletons/events'; | ||||
| 
 | ||||
| /** | ||||
|  * Interface that all main menu handlers must implement. | ||||
|  */ | ||||
| export interface CoreMainMenuHandler extends CoreDelegateHandler { | ||||
|     /** | ||||
|      * The highest priority is displayed first. | ||||
|      */ | ||||
|     priority?: number; | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the data needed to render the handler. | ||||
|      * | ||||
|      * @return Data. | ||||
|      */ | ||||
|     getDisplayData(): CoreMainMenuHandlerData; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Data needed to render a main menu handler. It's returned by the handler. | ||||
|  */ | ||||
| export interface CoreMainMenuHandlerData { | ||||
|     /** | ||||
|      * Name of the page to load for the handler. | ||||
|      */ | ||||
|     page: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Title to display for the handler. | ||||
|      */ | ||||
|     title: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Name of the icon to display for the handler. | ||||
|      */ | ||||
|     icon: string; // Name of the icon to display in the tab.
 | ||||
| 
 | ||||
|     /** | ||||
|      * Class to add to the displayed handler. | ||||
|      */ | ||||
|     class?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * If the handler has badge to show or not. | ||||
|      */ | ||||
|     showBadge?: boolean; | ||||
| 
 | ||||
|     /** | ||||
|      * Text to display on the badge. Only used if showBadge is true. | ||||
|      */ | ||||
|     badge?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * If true, the badge number is being loaded. Only used if showBadge is true. | ||||
|      */ | ||||
|     loading?: boolean; | ||||
| 
 | ||||
|     /** | ||||
|      * Params to pass to the page. | ||||
|      */ | ||||
|     pageParams?: Params; | ||||
| 
 | ||||
|     /** | ||||
|      * Whether the handler should only appear in More menu. | ||||
|      */ | ||||
|     onlyInMore?: boolean; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by the delegate for each handler. | ||||
|  */ | ||||
| export interface CoreMainMenuHandlerToDisplay extends CoreMainMenuHandlerData { | ||||
|     /** | ||||
|      * Name of the handler. | ||||
|      */ | ||||
|     name?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Priority of the handler. | ||||
|      */ | ||||
|     priority?: number; | ||||
| 
 | ||||
|     /** | ||||
|      * Hide tab. Used then resizing. | ||||
|      */ | ||||
|     hide?: boolean; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Service to interact with plugins to be shown in the main menu. Provides functions to register a plugin | ||||
|  * and notify an update in the data. | ||||
|  */ | ||||
| @Injectable({ | ||||
|     providedIn: 'root', | ||||
| }) | ||||
| export class CoreMainMenuDelegate extends CoreDelegate { | ||||
| 
 | ||||
|     protected loaded = false; | ||||
|     protected siteHandlers: Subject<CoreMainMenuHandlerToDisplay[]> = new BehaviorSubject<CoreMainMenuHandlerToDisplay[]>([]); | ||||
|     protected featurePrefix = 'CoreMainMenuDelegate_'; | ||||
| 
 | ||||
|     constructor() { | ||||
|         super('CoreMainMenuDelegate', true); | ||||
| 
 | ||||
|         CoreEvents.on(CoreEvents.LOGOUT, this.clearSiteHandlers.bind(this)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if handlers are loaded. | ||||
|      * | ||||
|      * @return True if handlers are loaded, false otherwise. | ||||
|      */ | ||||
|     areHandlersLoaded(): boolean { | ||||
|         return this.loaded; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Clear current site handlers. Reserved for core use. | ||||
|      */ | ||||
|     protected clearSiteHandlers(): void { | ||||
|         this.loaded = false; | ||||
|         this.siteHandlers.next([]); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the handlers for the current site. | ||||
|      * | ||||
|      * @return An observable that will receive the handlers. | ||||
|      */ | ||||
|     getHandlers(): Subject<CoreMainMenuHandlerToDisplay[]> { | ||||
|         return this.siteHandlers; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Update handlers Data. | ||||
|      */ | ||||
|     updateData(): void { | ||||
|         const displayData: CoreMainMenuHandlerToDisplay[] = []; | ||||
| 
 | ||||
|         for (const name in this.enabledHandlers) { | ||||
|             const handler = <CoreMainMenuHandler> this.enabledHandlers[name]; | ||||
|             const data = <CoreMainMenuHandlerToDisplay> handler.getDisplayData(); | ||||
| 
 | ||||
|             data.name = name; | ||||
|             data.priority = handler.priority; | ||||
| 
 | ||||
|             displayData.push(data); | ||||
|         } | ||||
| 
 | ||||
|         // Sort them by priority.
 | ||||
|         displayData.sort((a, b) => (b.priority || 0) - (a.priority || 0)); | ||||
| 
 | ||||
|         this.loaded = true; | ||||
|         this.siteHandlers.next(displayData); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										275
									
								
								src/app/core/mainmenu/services/mainmenu.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								src/app/core/mainmenu/services/mainmenu.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,275 @@ | ||||
| // (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 { CoreApp } from '@services/app'; | ||||
| import { CoreLang } from '@services/lang'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { CoreConstants } from '@core/constants'; | ||||
| import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from './delegate'; | ||||
| import { makeSingleton } from '@singletons/core.singletons'; | ||||
| 
 | ||||
| /** | ||||
|  * Service that provides some features regarding Main Menu. | ||||
|  */ | ||||
| @Injectable({ | ||||
|     providedIn: 'root', | ||||
| }) | ||||
| export class CoreMainMenuProvider { | ||||
| 
 | ||||
|     static readonly NUM_MAIN_HANDLERS = 4; | ||||
|     static readonly ITEM_MIN_WIDTH = 72; // Min with of every item, based on 5 items on a 360 pixel wide screen.
 | ||||
| 
 | ||||
|     protected tablet = false; | ||||
| 
 | ||||
|     constructor(protected menuDelegate: CoreMainMenuDelegate) { | ||||
|         this.tablet = !!(window?.innerWidth && window.innerWidth >= 576 && window.innerHeight >= 576); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the current main menu handlers. | ||||
|      * | ||||
|      * @return Promise resolved with the current main menu handlers. | ||||
|      */ | ||||
|     getCurrentMainMenuHandlers(): Promise<CoreMainMenuHandlerToDisplay[]> { | ||||
|         const deferred = CoreUtils.instance.promiseDefer<CoreMainMenuHandlerToDisplay[]>(); | ||||
| 
 | ||||
|         const subscription = this.menuDelegate.getHandlers().subscribe((handlers) => { | ||||
|             subscription?.unsubscribe(); | ||||
| 
 | ||||
|             // Remove the handlers that should only appear in the More menu.
 | ||||
|             handlers = handlers.filter(handler => !handler.onlyInMore); | ||||
| 
 | ||||
|             // Return main handlers.
 | ||||
|             deferred.resolve(handlers.slice(0, this.getNumItems())); | ||||
|         }); | ||||
| 
 | ||||
|         return deferred.promise; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get a list of custom menu items for a certain site. | ||||
|      * | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return List of custom menu items. | ||||
|      */ | ||||
|     async getCustomMenuItems(siteId?: string): Promise<CoreMainMenuCustomItem[]> { | ||||
|         const site = await CoreSites.instance.getSite(siteId); | ||||
| 
 | ||||
|         const itemsString = site.getStoredConfig('tool_mobile_custommenuitems'); | ||||
|         const map: CustomMenuItemsMap = {}; | ||||
|         const result: CoreMainMenuCustomItem[] = []; | ||||
| 
 | ||||
|         let position = 0; // Position of each item, to keep the same order as it's configured.
 | ||||
| 
 | ||||
|         if (!itemsString || typeof itemsString != 'string') { | ||||
|             // Setting not valid.
 | ||||
|             return result; | ||||
|         } | ||||
| 
 | ||||
|         // Add items to the map.
 | ||||
|         const items = itemsString.split(/(?:\r\n|\r|\n)/); | ||||
|         items.forEach((item) => { | ||||
|             const values = item.split('|'); | ||||
|             const label = values[0] ? values[0].trim() : values[0]; | ||||
|             const url = values[1] ? values[1].trim() : values[1]; | ||||
|             const type = values[2] ? values[2].trim() : values[2]; | ||||
|             const lang = (values[3] ? values[3].trim() : values[3]) || 'none'; | ||||
|             let icon = values[4] ? values[4].trim() : values[4]; | ||||
| 
 | ||||
|             if (!label || !url || !type) { | ||||
|                 // Invalid item, ignore it.
 | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             const id = url + '#' + type; | ||||
|             if (!icon) { | ||||
|                 // Icon not defined, use default one.
 | ||||
|                 icon = type == 'embedded' ? 'fa-square-o' : 'fa-link'; // @todo: Find a better icon for embedded.
 | ||||
|             } | ||||
| 
 | ||||
|             if (!map[id]) { | ||||
|                 // New entry, add it to the map.
 | ||||
|                 map[id] = { | ||||
|                     url: url, | ||||
|                     type: type, | ||||
|                     position: position, | ||||
|                     labels: {}, | ||||
|                 }; | ||||
|                 position++; | ||||
|             } | ||||
| 
 | ||||
|             map[id].labels[lang.toLowerCase()] = { | ||||
|                 label: label, | ||||
|                 icon: icon, | ||||
|             }; | ||||
|         }); | ||||
| 
 | ||||
|         if (!position) { | ||||
|             // No valid items found, stop.
 | ||||
|             return result; | ||||
|         } | ||||
| 
 | ||||
|         const currentLang = await CoreLang.instance.getCurrentLanguage(); | ||||
| 
 | ||||
|         const fallbackLang = CoreConstants.CONFIG.default_lang || 'en'; | ||||
| 
 | ||||
|         // Get the right label for each entry and add it to the result.
 | ||||
|         for (const id in map) { | ||||
|             const entry = map[id]; | ||||
|             let data = entry.labels[currentLang] || entry.labels[currentLang + '_only'] || | ||||
|                     entry.labels.none || entry.labels[fallbackLang]; | ||||
| 
 | ||||
|             if (!data) { | ||||
|                 // No valid label found, get the first one that is not "_only".
 | ||||
|                 for (const lang in entry.labels) { | ||||
|                     if (lang.indexOf('_only') == -1) { | ||||
|                         data = entry.labels[lang]; | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if (!data) { | ||||
|                     // No valid label, ignore this entry.
 | ||||
|                     continue; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             result[entry.position] = { | ||||
|                 url: entry.url, | ||||
|                 type: entry.type, | ||||
|                 label: data.label, | ||||
|                 icon: data.icon, | ||||
|             }; | ||||
|         } | ||||
| 
 | ||||
|         // Remove undefined values.
 | ||||
|         return result.filter((entry) => typeof entry != 'undefined'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the number of items to be shown on the main menu bar. | ||||
|      * | ||||
|      * @return Number of items depending on the device width. | ||||
|      */ | ||||
|     getNumItems(): number { | ||||
|         if (!this.isResponsiveMainMenuItemsDisabledInCurrentSite() && window && window.innerWidth) { | ||||
|             let numElements: number; | ||||
| 
 | ||||
|             if (this.tablet) { | ||||
|                 // Tablet, menu will be displayed vertically.
 | ||||
|                 numElements = Math.floor(window.innerHeight / CoreMainMenuProvider.ITEM_MIN_WIDTH); | ||||
|             } else { | ||||
|                 numElements = Math.floor(window.innerWidth / CoreMainMenuProvider.ITEM_MIN_WIDTH); | ||||
| 
 | ||||
|                 // Set a maximum elements to show and skip more button.
 | ||||
|                 numElements = numElements >= 5 ? 5 : numElements; | ||||
|             } | ||||
| 
 | ||||
|             // Set a mínimum elements to show and skip more button.
 | ||||
|             return numElements > 1 ? numElements - 1 : 1; | ||||
|         } | ||||
| 
 | ||||
|         return CoreMainMenuProvider.NUM_MAIN_HANDLERS; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get tabs placement depending on the device size. | ||||
|      * | ||||
|      * @return Tabs placement including side value. | ||||
|      */ | ||||
|     getTabPlacement(): string { | ||||
|         const tablet = !!(window.innerWidth && window.innerWidth >= 576 && (window.innerHeight >= 576 || | ||||
|                 ((CoreApp.instance.isKeyboardVisible() || CoreApp.instance.isKeyboardOpening()) && window.innerHeight >= 200))); | ||||
| 
 | ||||
|         if (tablet != this.tablet) { | ||||
|             this.tablet = tablet; | ||||
| 
 | ||||
|             // @todo Resize so content margins can be updated.
 | ||||
|         } | ||||
| 
 | ||||
|         return tablet ? 'side' : 'bottom'; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if a certain page is the root of a main menu handler currently displayed. | ||||
|      * | ||||
|      * @param page Name of the page. | ||||
|      * @param pageParams Page params. | ||||
|      * @return Promise resolved with boolean: whether it's the root of a main menu handler. | ||||
|      */ | ||||
|     async isCurrentMainMenuHandler(pageName: string): Promise<boolean> { | ||||
|         const handlers = await this.getCurrentMainMenuHandlers(); | ||||
| 
 | ||||
|         const handler = handlers.find((handler) => handler.page == pageName); | ||||
| 
 | ||||
|         return !!handler; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if responsive main menu items is disabled in the current site. | ||||
|      * | ||||
|      * @return Whether it's disabled. | ||||
|      */ | ||||
|     protected isResponsiveMainMenuItemsDisabledInCurrentSite(): boolean { | ||||
|         const site = CoreSites.instance.getCurrentSite(); | ||||
| 
 | ||||
|         return !!site?.isFeatureDisabled('NoDelegate_ResponsiveMainMenuItems'); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export class CoreMainMenu extends makeSingleton(CoreMainMenuProvider) {} | ||||
| 
 | ||||
| /** | ||||
|  * Custom main menu item. | ||||
|  */ | ||||
| export interface CoreMainMenuCustomItem { | ||||
|     /** | ||||
|      * Type of the item: app, inappbrowser, browser or embedded. | ||||
|      */ | ||||
|     type: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Url of the item. | ||||
|      */ | ||||
|     url: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Label to display for the item. | ||||
|      */ | ||||
|     label: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Name of the icon to display for the item. | ||||
|      */ | ||||
|     icon: string; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Map of custom menu items. | ||||
|  */ | ||||
| type CustomMenuItemsMap = Record<string, { | ||||
|     url: string; | ||||
|     type: string; | ||||
|     position: number; | ||||
|     labels: { | ||||
|         [lang: string]: { | ||||
|             label: string; | ||||
|             icon: string; | ||||
|         }; | ||||
|     }; | ||||
| }>; | ||||
							
								
								
									
										47
									
								
								src/app/core/settings/pages/about/about.page.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/app/core/settings/pages/about/about.page.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| // (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 { CommonModule } from '@angular/common'; | ||||
| import { RouterModule, Routes } from '@angular/router'; | ||||
| import { IonicModule } from '@ionic/angular'; | ||||
| import { TranslateModule } from '@ngx-translate/core'; | ||||
| 
 | ||||
| import { CoreComponentsModule } from '@components/components.module'; | ||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | ||||
| 
 | ||||
| import { CoreSettingsAboutPage } from './about.page'; | ||||
| 
 | ||||
| const routes: Routes = [ | ||||
|     { | ||||
|         path: '', | ||||
|         component: CoreSettingsAboutPage, | ||||
|     }, | ||||
| ]; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     imports: [ | ||||
|         RouterModule.forChild(routes), | ||||
|         CommonModule, | ||||
|         IonicModule, | ||||
|         TranslateModule.forChild(), | ||||
|         CoreComponentsModule, | ||||
|         CoreDirectivesModule, | ||||
|     ], | ||||
|     declarations: [ | ||||
|         CoreSettingsAboutPage, | ||||
|     ], | ||||
|     exports: [RouterModule], | ||||
| }) | ||||
| export class CoreSettingsAboutPageModule {} | ||||
							
								
								
									
										47
									
								
								src/app/core/settings/pages/app/app.page.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/app/core/settings/pages/app/app.page.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| // (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 { CommonModule } from '@angular/common'; | ||||
| import { RouterModule, Routes } from '@angular/router'; | ||||
| import { IonicModule } from '@ionic/angular'; | ||||
| import { TranslateModule } from '@ngx-translate/core'; | ||||
| 
 | ||||
| import { CoreComponentsModule } from '@components/components.module'; | ||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | ||||
| 
 | ||||
| import { CoreSettingsAppPage } from './app.page'; | ||||
| 
 | ||||
| const routes: Routes = [ | ||||
|     { | ||||
|         path: '', | ||||
|         component: CoreSettingsAppPage, | ||||
|     }, | ||||
| ]; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     imports: [ | ||||
|         RouterModule.forChild(routes), | ||||
|         CommonModule, | ||||
|         IonicModule, | ||||
|         TranslateModule.forChild(), | ||||
|         CoreComponentsModule, | ||||
|         CoreDirectivesModule, | ||||
|     ], | ||||
|     declarations: [ | ||||
|         CoreSettingsAppPage, | ||||
|     ], | ||||
|     exports: [RouterModule], | ||||
| }) | ||||
| export class CoreSettingsAppPageModule {} | ||||
| @ -20,7 +20,7 @@ import { ActivatedRoute, Params, Router } from '@angular/router'; | ||||
|     selector: 'app-settings', | ||||
|     templateUrl: 'app.html', | ||||
| }) | ||||
| export class CoreAppSettingsPage { | ||||
| export class CoreSettingsAppPage { | ||||
| 
 | ||||
|     // @ViewChild(CoreSplitViewComponent) splitviewCtrl?: CoreSplitViewComponent;
 | ||||
| 
 | ||||
|  | ||||
| @ -0,0 +1,47 @@ | ||||
| // (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 { CommonModule } from '@angular/common'; | ||||
| import { RouterModule, Routes } from '@angular/router'; | ||||
| import { IonicModule } from '@ionic/angular'; | ||||
| import { TranslateModule } from '@ngx-translate/core'; | ||||
| 
 | ||||
| import { CoreComponentsModule } from '@components/components.module'; | ||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | ||||
| 
 | ||||
| import { CoreSettingsDeviceInfoPage } from './deviceinfo.page'; | ||||
| 
 | ||||
| const routes: Routes = [ | ||||
|     { | ||||
|         path: '', | ||||
|         component: CoreSettingsDeviceInfoPage, | ||||
|     }, | ||||
| ]; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     imports: [ | ||||
|         RouterModule.forChild(routes), | ||||
|         CommonModule, | ||||
|         IonicModule, | ||||
|         TranslateModule.forChild(), | ||||
|         CoreComponentsModule, | ||||
|         CoreDirectivesModule, | ||||
|     ], | ||||
|     declarations: [ | ||||
|         CoreSettingsDeviceInfoPage, | ||||
|     ], | ||||
|     exports: [RouterModule], | ||||
| }) | ||||
| export class CoreSettingsDeviceInfoPageModule {} | ||||
| @ -14,22 +14,19 @@ | ||||
| 
 | ||||
| import { NgModule } from '@angular/core'; | ||||
| import { RouterModule, Routes } from '@angular/router'; | ||||
| import { CoreAppSettingsPage } from './pages/app/app.page'; | ||||
| import { CoreSettingsAboutPage } from './pages/about/about.page'; | ||||
| import { CoreSettingsDeviceInfoPage } from './pages/deviceinfo/deviceinfo.page'; | ||||
| 
 | ||||
| const routes: Routes = [ | ||||
|     { | ||||
|         path: 'about', | ||||
|         component: CoreSettingsAboutPage, | ||||
|         loadChildren: () => import('./pages/about/about.page.module').then( m => m.CoreSettingsAboutPageModule), | ||||
|     }, | ||||
|     { | ||||
|         path: 'deviceinfo', | ||||
|         component: CoreSettingsDeviceInfoPage, | ||||
|         loadChildren: () => import('./pages/deviceinfo/deviceinfo.page.module').then( m => m.CoreSettingsDeviceInfoPageModule), | ||||
|     }, | ||||
|     { | ||||
|         path: 'app', | ||||
|         component: CoreAppSettingsPage, | ||||
|         loadChildren: () => import('./pages/app/app.page.module').then( m => m.CoreSettingsAppPageModule), | ||||
|     }, | ||||
|     { | ||||
|         path: '', | ||||
| @ -42,4 +39,4 @@ const routes: Routes = [ | ||||
|     imports: [RouterModule.forChild(routes)], | ||||
|     exports: [RouterModule], | ||||
| }) | ||||
| export class CoreAppSettingsRoutingModule {} | ||||
| export class CoreSettingsRoutingModule {} | ||||
|  | ||||
| @ -13,34 +13,13 @@ | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { NgModule } from '@angular/core'; | ||||
| import { CommonModule } from '@angular/common'; | ||||
| 
 | ||||
| import { IonicModule } from '@ionic/angular'; | ||||
| import { TranslateModule } from '@ngx-translate/core'; | ||||
| 
 | ||||
| import { CoreAppSettingsRoutingModule } from './settings-routing.module'; | ||||
| import { CorePipesModule } from '@pipes/pipes.module'; | ||||
| import { CoreComponentsModule } from '@components/components.module'; | ||||
| import { CoreDirectivesModule } from '@directives/directives.module'; | ||||
| 
 | ||||
| import { CoreAppSettingsPage } from './pages/app/app.page'; | ||||
| import { CoreSettingsAboutPage } from './pages/about/about.page'; | ||||
| import { CoreSettingsDeviceInfoPage } from './pages/deviceinfo/deviceinfo.page'; | ||||
| import { CoreSettingsRoutingModule } from './settings-routing.module'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     imports: [ | ||||
|         CommonModule, | ||||
|         IonicModule, | ||||
|         CoreAppSettingsRoutingModule, | ||||
|         CorePipesModule, | ||||
|         CoreComponentsModule, | ||||
|         CoreDirectivesModule, | ||||
|         TranslateModule.forChild(), | ||||
|     ], | ||||
|     declarations: [ | ||||
|         CoreAppSettingsPage, | ||||
|         CoreSettingsAboutPage, | ||||
|         CoreSettingsDeviceInfoPage, | ||||
|         CoreSettingsRoutingModule, | ||||
|     ], | ||||
|     declarations: [], | ||||
| }) | ||||
| export class CoreAppSettingsPageModule {} | ||||
| export class CoreSettingsModule {} | ||||
|  | ||||
| @ -16,16 +16,18 @@ import { NgModule } from '@angular/core'; | ||||
| 
 | ||||
| import { CoreAutoFocusDirective } from './auto-focus'; | ||||
| import { CoreExternalContentDirective } from './external-content'; | ||||
| import { CoreFabDirective } from './fab'; | ||||
| import { CoreFormatTextDirective } from './format-text'; | ||||
| import { CoreLinkDirective } from './link'; | ||||
| import { CoreLongPressDirective } from './long-press'; | ||||
| import { CoreSupressEventsDirective } from './supress-events'; | ||||
| import { CoreFabDirective } from './fab'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     declarations: [ | ||||
|         CoreAutoFocusDirective, | ||||
|         CoreExternalContentDirective, | ||||
|         CoreFormatTextDirective, | ||||
|         CoreLinkDirective, | ||||
|         CoreLongPressDirective, | ||||
|         CoreSupressEventsDirective, | ||||
|         CoreFabDirective, | ||||
| @ -35,6 +37,7 @@ import { CoreFabDirective } from './fab'; | ||||
|         CoreAutoFocusDirective, | ||||
|         CoreExternalContentDirective, | ||||
|         CoreFormatTextDirective, | ||||
|         CoreLinkDirective, | ||||
|         CoreLongPressDirective, | ||||
|         CoreSupressEventsDirective, | ||||
|         CoreFabDirective, | ||||
|  | ||||
| @ -24,6 +24,7 @@ import { CoreUtils } from '@services/utils/utils'; | ||||
| import { CoreSite } from '@classes/site'; | ||||
| import { Translate } from '@singletons/core.singletons'; | ||||
| import { CoreExternalContentDirective } from './external-content'; | ||||
| import { CoreLinkDirective } from './link'; | ||||
| 
 | ||||
| /** | ||||
|  * Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective | ||||
| @ -454,7 +455,9 @@ export class CoreFormatTextDirective implements OnChanges { | ||||
|         // Important: We need to look for links first because in 'img' we add new links without core-link.
 | ||||
|         anchors.forEach((anchor) => { | ||||
|             // Angular 2 doesn't let adding directives dynamically. Create the CoreLinkDirective manually.
 | ||||
|             // @todo
 | ||||
|             const linkDir = new CoreLinkDirective(new ElementRef(anchor), this.content); | ||||
|             linkDir.capture = true; | ||||
|             linkDir.ngOnInit(); | ||||
| 
 | ||||
|             this.addExternalContent(anchor); | ||||
|         }); | ||||
|  | ||||
							
								
								
									
										198
									
								
								src/app/directives/link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								src/app/directives/link.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,198 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Directive, Input, OnInit, ElementRef, Optional } from '@angular/core'; | ||||
| import { IonContent } from '@ionic/angular'; | ||||
| 
 | ||||
| import { CoreFileHelper } from '@services/file-helper'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreUrlUtils } from '@services/utils/url'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| import { CoreConstants } from '@core/constants'; | ||||
| 
 | ||||
| /** | ||||
|  * Directive to open a link in external browser or in the app. | ||||
|  */ | ||||
| @Directive({ | ||||
|     selector: '[core-link]', | ||||
| }) | ||||
| export class CoreLinkDirective implements OnInit { | ||||
| 
 | ||||
|     @Input() capture?: boolean | string; // If the link needs to be captured by the app.
 | ||||
|     @Input() inApp?: boolean | string; // True to open in embedded browser, false to open in system browser.
 | ||||
|     /* Whether the link should be opened with auto-login. Accepts the following values: | ||||
|        "yes" -> Always auto-login. | ||||
|        "no" -> Never auto-login. | ||||
|        "check" -> Auto-login only if it points to the current site. Default value. */ | ||||
|     @Input() autoLogin = 'check'; | ||||
| 
 | ||||
|     protected element: Element; | ||||
| 
 | ||||
|     constructor( | ||||
|         element: ElementRef, | ||||
|         @Optional() protected content: IonContent, | ||||
|     ) { | ||||
|         this.element = element.nativeElement; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Function executed when the component is initialized. | ||||
|      */ | ||||
|     ngOnInit(): void { | ||||
|         this.inApp = typeof this.inApp == 'undefined' ? this.inApp : CoreUtils.instance.isTrueOrOne(this.inApp); | ||||
| 
 | ||||
|         // @todo: Handle split view?
 | ||||
| 
 | ||||
|         this.element.addEventListener('click', (event) => { | ||||
|             if (event.defaultPrevented) { | ||||
|                 return; // Link already treated, stop.
 | ||||
|             } | ||||
| 
 | ||||
|             let href = this.element.getAttribute('href') || this.element.getAttribute('ng-reflect-href') || | ||||
|                 this.element.getAttribute('xlink:href'); | ||||
| 
 | ||||
|             if (!href || CoreUrlUtils.instance.getUrlScheme(href) == 'javascript') { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             event.preventDefault(); | ||||
|             event.stopPropagation(); | ||||
| 
 | ||||
|             const openIn = this.element.getAttribute('data-open-in'); | ||||
| 
 | ||||
|             if (CoreUtils.instance.isTrueOrOne(this.capture)) { | ||||
|                 href = CoreTextUtils.instance.decodeURI(href); | ||||
| 
 | ||||
|                 // @todo: Handle link.
 | ||||
|                 this.navigate(href, openIn); | ||||
|             } else { | ||||
|                 this.navigate(href, openIn); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Convenience function to correctly navigate, open file or url in the browser. | ||||
|      * | ||||
|      * @param href HREF to be opened. | ||||
|      * @param openIn Open In App value coming from data-open-in attribute. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected async navigate(href: string, openIn?: string | null): Promise<void> { | ||||
| 
 | ||||
|         if (CoreUrlUtils.instance.isLocalFileUrl(href)) { | ||||
|             return this.openLocalFile(href); | ||||
|         } | ||||
| 
 | ||||
|         if (href.charAt(0) == '#') { | ||||
|             // Look for id or name.
 | ||||
|             href = href.substr(1); | ||||
|             CoreDomUtils.instance.scrollToElementBySelector(this.content, '#' + href + ', [name=\'' + href + '\']'); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // @todo: Custom URL schemes.
 | ||||
| 
 | ||||
|         return this.openExternalLink(href, openIn); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Open a local file. | ||||
|      * | ||||
|      * @param path Path to the file. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected async openLocalFile(path: string): Promise<void> { | ||||
|         const filename = path.substr(path.lastIndexOf('/') + 1); | ||||
| 
 | ||||
|         if (!CoreFileHelper.instance.isOpenableInApp({ filename })) { | ||||
|             try { | ||||
|                 await CoreFileHelper.instance.showConfirmOpenUnsupportedFile(); | ||||
|             } catch (error) { | ||||
|                 return; // Cancelled, stop.
 | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             await CoreUtils.instance.openFile(path); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.instance.showErrorModal(error); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Open an external link in the app or in browser. | ||||
|      * | ||||
|      * @param href HREF to be opened. | ||||
|      * @param openIn Open In App value coming from data-open-in attribute. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected async openExternalLink(href: string, openIn?: string | null): Promise<void> { | ||||
|         // It's an external link, we will open with browser. Check if we need to auto-login.
 | ||||
|         if (!CoreSites.instance.isLoggedIn()) { | ||||
|             // Not logged in, cannot auto-login.
 | ||||
|             if (this.inApp) { | ||||
|                 CoreUtils.instance.openInApp(href); | ||||
|             } else { | ||||
|                 CoreUtils.instance.openInBrowser(href); | ||||
|             } | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // Check if URL does not have any protocol, so it's a relative URL.
 | ||||
|         if (!CoreUrlUtils.instance.isAbsoluteURL(href)) { | ||||
|             // Add the site URL at the begining.
 | ||||
|             if (href.charAt(0) == '/') { | ||||
|                 href = CoreSites.instance.getCurrentSite()!.getURL() + href; | ||||
|             } else { | ||||
|                 href = CoreSites.instance.getCurrentSite()!.getURL() + '/' + href; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (this.autoLogin == 'yes') { | ||||
|             if (this.inApp) { | ||||
|                 await CoreSites.instance.getCurrentSite()!.openInAppWithAutoLogin(href); | ||||
|             } else { | ||||
|                 await CoreSites.instance.getCurrentSite()!.openInBrowserWithAutoLogin(href); | ||||
|             } | ||||
|         } else if (this.autoLogin == 'no') { | ||||
|             if (this.inApp) { | ||||
|                 CoreUtils.instance.openInApp(href); | ||||
|             } else { | ||||
|                 CoreUtils.instance.openInBrowser(href); | ||||
|             } | ||||
|         } else { | ||||
|             // Priority order is: core-link inApp attribute > forceOpenLinksIn setting > data-open-in HTML attribute.
 | ||||
|             let openInApp = this.inApp; | ||||
|             if (typeof this.inApp == 'undefined') { | ||||
|                 if (CoreConstants.CONFIG.forceOpenLinksIn == 'browser') { | ||||
|                     openInApp = false; | ||||
|                 } else if (CoreConstants.CONFIG.forceOpenLinksIn == 'app' || openIn == 'app') { | ||||
|                     openInApp = true; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (openInApp) { | ||||
|                 await CoreSites.instance.getCurrentSite()!.openInAppWithAutoLoginIfSameSite(href); | ||||
|             } else { | ||||
|                 await CoreSites.instance.getCurrentSite()!.openInBrowserWithAutoLoginIfSameSite(href); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1312,7 +1312,7 @@ export class CoreSitesProvider { | ||||
|     async logout(): Promise<void> { | ||||
|         await this.dbReady; | ||||
| 
 | ||||
|         let siteId; | ||||
|         let siteId: string | undefined; | ||||
|         const promises: Promise<unknown>[] = []; | ||||
| 
 | ||||
|         if (this.currentSite) { | ||||
|  | ||||
| @ -208,3 +208,11 @@ export type CoreEventLoadingChangedData = { | ||||
|     loaded: boolean; | ||||
|     uniqueId: string; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data passed to LOAD_PAGE_MAIN_MENU event. | ||||
|  */ | ||||
| export type CoreEventLoadPageMainMenuData = { | ||||
|     redirectPage: string; | ||||
|     redirectParams?: Params; | ||||
| }; | ||||
|  | ||||
| @ -303,7 +303,48 @@ | ||||
|     "core.back": "Back", | ||||
|     "core.browser": "Browser", | ||||
|     "core.copiedtoclipboard": "Text copied to clipboard", | ||||
|     "core.courses.addtofavourites": "Star this course", | ||||
|     "core.courses.allowguests": "This course allows guest users to enter", | ||||
|     "core.courses.availablecourses": "Available courses", | ||||
|     "core.courses.cannotretrievemorecategories": "Categories deeper than level {{$a}} cannot be retrieved.", | ||||
|     "core.courses.categories": "Course categories", | ||||
|     "core.courses.confirmselfenrol": "Are you sure you want to enrol yourself in this course?", | ||||
|     "core.courses.courses": "Courses", | ||||
|     "core.courses.downloadcourses": "Download courses", | ||||
|     "core.courses.enrolme": "Enrol me", | ||||
|     "core.courses.errorloadcategories": "An error occurred while loading categories.", | ||||
|     "core.courses.errorloadcourses": "An error occurred while loading courses.", | ||||
|     "core.courses.errorloadplugins": "The plugins required by this course could not be loaded correctly. Please reload the app to try again.", | ||||
|     "core.courses.errorsearching": "An error occurred while searching.", | ||||
|     "core.courses.errorselfenrol": "An error occurred while self enrolling.", | ||||
|     "core.courses.filtermycourses": "Filter my courses", | ||||
|     "core.courses.frontpage": "Front page", | ||||
|     "core.courses.hidecourse": "Remove from view", | ||||
|     "core.courses.ignore": "Ignore", | ||||
|     "core.courses.mycourses": "My courses", | ||||
|     "core.courses.mymoodle": "Dashboard", | ||||
|     "core.courses.nocourses": "No course information to show.", | ||||
|     "core.courses.nocoursesyet": "No courses in this category", | ||||
|     "core.courses.nosearchresults": "No results", | ||||
|     "core.courses.notenroled": "You are not enrolled in this course", | ||||
|     "core.courses.notenrollable": "You cannot enrol yourself in this course.", | ||||
|     "core.courses.password": "Enrolment key", | ||||
|     "core.courses.paymentrequired": "This course requires a payment for entry.", | ||||
|     "core.courses.paypalaccepted": "PayPal payments accepted", | ||||
|     "core.courses.reload": "Reload", | ||||
|     "core.courses.removefromfavourites": "Unstar this course", | ||||
|     "core.courses.search": "Search", | ||||
|     "core.courses.searchcourses": "Search courses", | ||||
|     "core.courses.searchcoursesadvice": "You can use the search courses button to find courses to access as a  guest or enrol yourself in courses that allow it.", | ||||
|     "core.courses.selfenrolment": "Self enrolment", | ||||
|     "core.courses.sendpaymentbutton": "Send payment via PayPal", | ||||
|     "core.courses.show": "Restore to view", | ||||
|     "core.courses.totalcoursesearchresults": "Total courses: {{$a}}", | ||||
|     "core.login.yourenteredsite": "Connect to your site", | ||||
|     "core.mainmenu.changesite": "Change site", | ||||
|     "core.mainmenu.help": "Help", | ||||
|     "core.mainmenu.logout": "Log out", | ||||
|     "core.mainmenu.website": "Website", | ||||
|     "core.no": "No", | ||||
|     "core.offline": "Offline", | ||||
|     "core.ok": "OK", | ||||
|  | ||||
							
								
								
									
										1
									
								
								src/types/global.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								src/types/global.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -66,6 +66,7 @@ declare global { | ||||
|                 appstores: Record<string, string>; | ||||
|                 displayqroncredentialscreen?: boolean; | ||||
|                 displayqronsitescreen?: boolean; | ||||
|                 forceOpenLinksIn: 'app' | 'browser'; | ||||
|             }; | ||||
| 
 | ||||
|             BUILD: { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user