forked from EVOgeek/Vmeda.Online
		
	Merge pull request #2795 from NoelDeMartin/MOBILE-3320
MOBILE-3320: Support alerts on top of loading
This commit is contained in:
		
						commit
						ad9bcc1810
					
				
							
								
								
									
										2
									
								
								.vscode/moodle.code-snippets
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.vscode/moodle.code-snippets
									
									
									
									
										vendored
									
									
								
							| @ -89,7 +89,7 @@ | ||||
|             "", | ||||
|             "}", | ||||
|             "", | ||||
|             "export class ${1:${TM_FILENAME_BASE}} extends makeSingleton(${1:${TM_FILENAME_BASE}}Service) {}", | ||||
|             "export const ${1:${TM_FILENAME_BASE}} = makeSingleton(${1:${TM_FILENAME_BASE}}Service);", | ||||
|             "" | ||||
|         ], | ||||
|         "description": "[Moodle] Create a Service Singleton" | ||||
|  | ||||
| @ -13,43 +13,155 @@ | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { LoadingController } from '@singletons'; | ||||
| 
 | ||||
| /** | ||||
|  * Dismiss listener. | ||||
|  */ | ||||
| export type CoreIonLoadingElementDismissListener = () => unknown; | ||||
| 
 | ||||
| /** | ||||
|  * Class to improve the behaviour of HTMLIonLoadingElement. | ||||
|  * It's not a subclass of HTMLIonLoadingElement because we cannot override the dismiss function. | ||||
|  * | ||||
|  * In addition to present/dismiss, this loader can also be paused/resumed in order to allow stacking | ||||
|  * modals in top of one another without interfering. Conceptually, a paused loader is still | ||||
|  * active but will not be shown in the UI. | ||||
|  */ | ||||
| export class CoreIonLoadingElement { | ||||
| 
 | ||||
|     protected isPresented = false; | ||||
|     protected isDismissed = false; | ||||
|     protected scheduled = false; | ||||
|     protected paused = false; | ||||
|     protected listeners: CoreIonLoadingElementDismissListener[] = []; | ||||
|     protected asyncLoadingElement?: Promise<HTMLIonLoadingElement>; | ||||
| 
 | ||||
|     constructor(public loading: HTMLIonLoadingElement) { } | ||||
|     constructor(protected text?: string) { } | ||||
| 
 | ||||
|     // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||||
|     async dismiss(data?: any, role?: string): Promise<boolean> { | ||||
|         if (!this.isPresented || this.isDismissed) { | ||||
|             this.isDismissed = true; | ||||
|     /** | ||||
|      * Dismiss the loading element. | ||||
|      * | ||||
|      * @param data Dismiss data. | ||||
|      * @param role Dismiss role. | ||||
|      */ | ||||
|     async dismiss(data?: unknown, role?: string): Promise<void> { | ||||
|         if (this.paused) { | ||||
|             this.paused = false; | ||||
|             this.listeners.forEach(listener => listener()); | ||||
| 
 | ||||
|             return true; | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.isDismissed = true; | ||||
|         if (!this.asyncLoadingElement) { | ||||
|             if (this.scheduled) { | ||||
|                 this.scheduled = false; | ||||
|                 this.listeners.forEach(listener => listener()); | ||||
|             } | ||||
| 
 | ||||
|         return this.loading.dismiss(data, role); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const asyncLoadingElement = this.asyncLoadingElement; | ||||
|         delete this.asyncLoadingElement; | ||||
| 
 | ||||
|         const loadingElement = await asyncLoadingElement; | ||||
|         await loadingElement.dismiss(data, role); | ||||
| 
 | ||||
|         this.listeners.forEach(listener => listener()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Present the loading. | ||||
|      * Register dismiss listener. | ||||
|      * | ||||
|      * @param listener Listener. | ||||
|      */ | ||||
|     onDismiss(listener: CoreIonLoadingElementDismissListener): void { | ||||
|         this.listeners.push(listener); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Hide the loading element. | ||||
|      */ | ||||
|     async pause(): Promise<void> { | ||||
|         if (!this.asyncLoadingElement) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.paused = true; | ||||
| 
 | ||||
|         const asyncLoadingElement = this.asyncLoadingElement; | ||||
|         delete this.asyncLoadingElement; | ||||
| 
 | ||||
|         const loadingElement = await asyncLoadingElement; | ||||
|         loadingElement.dismiss(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Present the loading element. | ||||
|      */ | ||||
|     async present(): Promise<void> { | ||||
|         if (this.paused || this.scheduled || this.asyncLoadingElement) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // Wait a bit before presenting the modal, to prevent it being displayed if dismiss is called fast.
 | ||||
|         this.scheduled = true; | ||||
| 
 | ||||
|         await CoreUtils.wait(40); | ||||
| 
 | ||||
|         if (!this.isDismissed) { | ||||
|             this.isPresented = true; | ||||
| 
 | ||||
|             await this.loading.present(); | ||||
|         if (!this.scheduled) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // Present modal.
 | ||||
|         this.scheduled = false; | ||||
| 
 | ||||
|         await this.presentLoadingElement(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Show loading element. | ||||
|      */ | ||||
|     async resume(): Promise<void> { | ||||
|         if (!this.paused) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.paused = false; | ||||
| 
 | ||||
|         await this.presentLoadingElement(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Update text in the loading element. | ||||
|      * | ||||
|      * @param text Text. | ||||
|      */ | ||||
|     async updateText(text: string): Promise<void> { | ||||
|         this.text = text; | ||||
| 
 | ||||
|         if (!this.asyncLoadingElement) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const loadingElement = await this.asyncLoadingElement; | ||||
|         const contentElement = loadingElement.querySelector('.loading-content'); | ||||
| 
 | ||||
|         if (contentElement) { | ||||
|             contentElement.innerHTML = text; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Create and present the loading element. | ||||
|      */ | ||||
|     private async presentLoadingElement(): Promise<void> { | ||||
|         let resolveLoadingElement!: ((loadingElement: HTMLIonLoadingElement) => void); | ||||
|         this.asyncLoadingElement = new Promise(resolve => resolveLoadingElement = resolve); | ||||
| 
 | ||||
|         const loadingElement = await LoadingController.create({ message: this.text }); | ||||
| 
 | ||||
|         await loadingElement.present(); | ||||
| 
 | ||||
|         resolveLoadingElement(loadingElement); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -873,10 +873,7 @@ export class CoreFileUploaderHelperProvider { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const contentElement = modal.loading?.querySelector('.loading-content'); | ||||
|         if (contentElement) { | ||||
|             contentElement.innerHTML = Translate.instant(stringKey, { $a: perc.toFixed(1) }); | ||||
|         } | ||||
|         modal.updateText(Translate.instant(stringKey, { $a: perc.toFixed(1) })); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -30,15 +30,7 @@ import { CoreIonLoadingElement } from '@classes/ion-loading'; | ||||
| import { CoreCanceledError } from '@classes/errors/cancelederror'; | ||||
| import { CoreAnyError, CoreError } from '@classes/errors/error'; | ||||
| import { CoreSilentError } from '@classes/errors/silenterror'; | ||||
| import { | ||||
|     makeSingleton, | ||||
|     Translate, | ||||
|     AlertController, | ||||
|     LoadingController, | ||||
|     ToastController, | ||||
|     PopoverController, | ||||
|     ModalController, | ||||
| } from '@singletons'; | ||||
| import { makeSingleton, Translate, AlertController, ToastController, PopoverController, ModalController } from '@singletons'; | ||||
| import { CoreLogger } from '@singletons/logger'; | ||||
| import { CoreFileSizeSum } from '@services/plugin-file-delegate'; | ||||
| import { CoreNetworkError } from '@classes/errors/network-error'; | ||||
| @ -66,6 +58,7 @@ export class CoreDomUtilsProvider { | ||||
|     protected lastInstanceId = 0; | ||||
|     protected debugDisplay = false; // Whether to display debug messages. Store it in a variable to make it synchronous.
 | ||||
|     protected displayedAlerts: Record<string, HTMLIonAlertElement> = {}; // To prevent duplicated alerts.
 | ||||
|     protected activeLoadingModals: CoreIonLoadingElement[] = []; | ||||
|     protected logger: CoreLogger; | ||||
| 
 | ||||
|     constructor(protected domSanitizer: DomSanitizer) { | ||||
| @ -1216,6 +1209,10 @@ export class CoreDomUtilsProvider { | ||||
| 
 | ||||
|         const alert = await AlertController.create(options); | ||||
| 
 | ||||
|         if (Object.keys(this.displayedAlerts).length === 0) { | ||||
|             await Promise.all(this.activeLoadingModals.slice(0).reverse().map(modal => modal.pause())); | ||||
|         } | ||||
| 
 | ||||
|         // eslint-disable-next-line promise/catch-or-return
 | ||||
|         alert.present().then(() => { | ||||
|             if (hasHTMLTags) { | ||||
| @ -1231,9 +1228,14 @@ export class CoreDomUtilsProvider { | ||||
|         this.displayedAlerts[alertId] = alert; | ||||
| 
 | ||||
|         // Set the callbacks to trigger an observable event.
 | ||||
|         // eslint-disable-next-line promise/catch-or-return, promise/always-return
 | ||||
|         alert.onDidDismiss().then(() => { | ||||
|         // eslint-disable-next-line promise/catch-or-return
 | ||||
|         alert.onDidDismiss().then(async () => { | ||||
|             delete this.displayedAlerts[alertId]; | ||||
| 
 | ||||
|             // eslint-disable-next-line promise/always-return
 | ||||
|             if (Object.keys(this.displayedAlerts).length === 0) { | ||||
|                 await Promise.all(this.activeLoadingModals.map(modal => modal.resume())); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         if (autocloseTime && autocloseTime > 0) { | ||||
| @ -1447,11 +1449,17 @@ export class CoreDomUtilsProvider { | ||||
|             text = Translate.instant(text); | ||||
|         } | ||||
| 
 | ||||
|         const loadingElement = await LoadingController.create({ | ||||
|             message: text, | ||||
|         const loading = new CoreIonLoadingElement(text); | ||||
| 
 | ||||
|         loading.onDismiss(() => { | ||||
|             const index = this.activeLoadingModals.indexOf(loading); | ||||
| 
 | ||||
|             if (index !== -1) { | ||||
|                 this.activeLoadingModals.splice(index, 1); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         const loading = new CoreIonLoadingElement(loadingElement); | ||||
|         this.activeLoadingModals.push(loading); | ||||
| 
 | ||||
|         await loading.present(); | ||||
| 
 | ||||
| @ -1480,19 +1488,19 @@ export class CoreDomUtilsProvider { | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         const alert = await AlertController.create({ | ||||
|         const alert = await this.showAlertWithOptions({ | ||||
|             message: message, | ||||
|             buttons: buttons, | ||||
|         }); | ||||
| 
 | ||||
|         await alert.present(); | ||||
| 
 | ||||
|         const isDevice = CoreApp.isAndroid() || CoreApp.isIOS(); | ||||
|         if (!isDevice) { | ||||
|             // Treat all anchors so they don't override the app.
 | ||||
|             const alertMessageEl: HTMLElement | null = alert.querySelector('.alert-message'); | ||||
|             alertMessageEl && this.treatAnchors(alertMessageEl); | ||||
|         } | ||||
| 
 | ||||
|         await alert.onDidDismiss(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user