forked from EVOgeek/Vmeda.Online
		
	MOBILE-2314 core: Implement custom modals
This commit is contained in:
		
							parent
							
								
									f5fa9d12dc
								
							
						
					
					
						commit
						ba723dd899
					
				
							
								
								
									
										45
									
								
								src/core/classes/modal-component.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/core/classes/modal-component.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | ||||
| // (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 { ElementRef } from '@angular/core'; | ||||
| import { CorePromisedValue } from '@classes/promised-value'; | ||||
| import { CoreDirectivesRegistry } from '@singletons/directives-registry'; | ||||
| 
 | ||||
| /** | ||||
|  * Helper class to build modals. | ||||
|  */ | ||||
| export class CoreModalComponent<T=unknown> { | ||||
| 
 | ||||
|     result: CorePromisedValue<T> = new CorePromisedValue(); | ||||
| 
 | ||||
|     constructor({ nativeElement: element }: ElementRef<HTMLElement>) { | ||||
|         CoreDirectivesRegistry.register(element, this); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Close the modal. | ||||
|      * | ||||
|      * @param result Result data, or error instance if the modal was closed with a failure. | ||||
|      */ | ||||
|     async close(result: T | Error): Promise<void> { | ||||
|         if (result instanceof Error) { | ||||
|             this.result.reject(result); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.result.resolve(result); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -64,6 +64,7 @@ import { CoreSwipeNavigationTourComponent } from './swipe-navigation-tour/swipe- | ||||
| import { CoreMessageComponent } from './message/message'; | ||||
| import { CoreGroupSelectorComponent } from './group-selector/group-selector'; | ||||
| import { CoreRefreshButtonModalComponent } from './refresh-button-modal/refresh-button-modal'; | ||||
| import { CoreSheetModalComponent } from '@components/sheet-modal/sheet-modal'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     declarations: [ | ||||
| @ -110,6 +111,7 @@ import { CoreRefreshButtonModalComponent } from './refresh-button-modal/refresh- | ||||
|         CoreHorizontalScrollControlsComponent, | ||||
|         CoreSwipeNavigationTourComponent, | ||||
|         CoreRefreshButtonModalComponent, | ||||
|         CoreSheetModalComponent, | ||||
|     ], | ||||
|     imports: [ | ||||
|         CommonModule, | ||||
| @ -163,6 +165,7 @@ import { CoreRefreshButtonModalComponent } from './refresh-button-modal/refresh- | ||||
|         CoreHorizontalScrollControlsComponent, | ||||
|         CoreSwipeNavigationTourComponent, | ||||
|         CoreRefreshButtonModalComponent, | ||||
|         CoreSheetModalComponent, | ||||
|     ], | ||||
| }) | ||||
| export class CoreComponentsModule {} | ||||
|  | ||||
							
								
								
									
										2
									
								
								src/core/components/sheet-modal/sheet-modal.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/core/components/sheet-modal/sheet-modal.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | ||||
| <ion-backdrop></ion-backdrop> | ||||
| <div class="sheet-modal--wrapper" #wrapper></div> | ||||
							
								
								
									
										40
									
								
								src/core/components/sheet-modal/sheet-modal.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/core/components/sheet-modal/sheet-modal.scss
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | ||||
| @import "~theme/globals"; | ||||
| 
 | ||||
| :host { | ||||
|     --backdrop-opacity: var(--ion-backdrop-opacity, 0.4); | ||||
| 
 | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     justify-content: flex-end; | ||||
|     isolation: isolate; | ||||
| 
 | ||||
|     ion-backdrop { | ||||
|         opacity: 0; | ||||
|         transition: opacity 300ms ease-in; | ||||
|     } | ||||
| 
 | ||||
|     .sheet-modal--wrapper { | ||||
|         border-radius: var(--big-radius) var(--big-radius) 0 0; | ||||
|         @include padding(24px, 16px, 24px, 16px); | ||||
| 
 | ||||
|         background-color: var(--ion-overlay-background-color, var(--ion-background-color, #fff)); | ||||
|         z-index: 3; // ion-backdrop has z-index 2 | ||||
|         transform: translateY(100%); | ||||
|         transition: transform 300ms ease-in; | ||||
|     } | ||||
| 
 | ||||
|     &.active { | ||||
| 
 | ||||
|         ion-backdrop { | ||||
|             opacity: var(--backdrop-opacity); | ||||
|         } | ||||
| 
 | ||||
|         .sheet-modal--wrapper { | ||||
|             transform: translateY(0%); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										93
									
								
								src/core/components/sheet-modal/sheet-modal.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								src/core/components/sheet-modal/sheet-modal.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | ||||
| // (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 { Constructor } from '@/core/utils/types'; | ||||
| import { AfterViewInit, Component, ElementRef, Input, ViewChild } from '@angular/core'; | ||||
| import { CoreModalComponent } from '@classes/modal-component'; | ||||
| import { CorePromisedValue } from '@classes/promised-value'; | ||||
| import { CoreModals } from '@services/modals'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { AngularFrameworkDelegate } from '@singletons'; | ||||
| import { CoreDirectivesRegistry } from '@singletons/directives-registry'; | ||||
| 
 | ||||
| @Component({ | ||||
|     selector: 'core-sheet-modal', | ||||
|     templateUrl: 'sheet-modal.html', | ||||
|     styleUrls: ['sheet-modal.scss'], | ||||
| }) | ||||
| export class CoreSheetModalComponent<T extends CoreModalComponent> implements AfterViewInit { | ||||
| 
 | ||||
|     @Input() component!: Constructor<T>; | ||||
|     @Input() componentProps?: Record<string, unknown>; | ||||
|     @ViewChild('wrapper') wrapper?: ElementRef<HTMLElement>; | ||||
| 
 | ||||
|     private element: HTMLElement; | ||||
|     private wrapperElement = new CorePromisedValue<HTMLElement>(); | ||||
| 
 | ||||
|     constructor({ nativeElement: element }: ElementRef<HTMLElement>) { | ||||
|         this.element = element; | ||||
| 
 | ||||
|         CoreDirectivesRegistry.register(element, this); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     ngAfterViewInit(): void { | ||||
|         if (!this.wrapper) { | ||||
|             this.wrapperElement.reject(new Error('CoreSheetModalComponent wasn\'t mounted properly')); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.wrapperElement.resolve(this.wrapper.nativeElement); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Show modal. | ||||
|      * | ||||
|      * @returns Component instance. | ||||
|      */ | ||||
|     async show(): Promise<T> { | ||||
|         const wrapper = await this.wrapperElement; | ||||
|         const element = await AngularFrameworkDelegate.attachViewToDom(wrapper, this.component, this.componentProps ?? {}); | ||||
| 
 | ||||
|         await CoreUtils.nextTick(); | ||||
| 
 | ||||
|         this.element.classList.add('active'); | ||||
|         this.element.style.zIndex = `${20000 + CoreModals.getTopOverlayIndex()}`; | ||||
| 
 | ||||
|         await CoreUtils.nextTick(); | ||||
|         await CoreUtils.wait(300); | ||||
| 
 | ||||
|         const instance = CoreDirectivesRegistry.resolve(element, this.component); | ||||
| 
 | ||||
|         if (!instance) { | ||||
|             throw new Error('Modal not mounted properly'); | ||||
|         } | ||||
| 
 | ||||
|         return instance; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Hide modal. | ||||
|      */ | ||||
|     async hide(): Promise<void> { | ||||
|         this.element.classList.remove('active'); | ||||
| 
 | ||||
|         await CoreUtils.nextTick(); | ||||
|         await CoreUtils.wait(300); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										89
									
								
								src/core/services/modals.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/core/services/modals.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,89 @@ | ||||
| // (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 { Constructor } from '@/core/utils/types'; | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { CoreModalComponent } from '@classes/modal-component'; | ||||
| import { CoreSheetModalComponent } from '@components/sheet-modal/sheet-modal'; | ||||
| import { AngularFrameworkDelegate, makeSingleton } from '@singletons'; | ||||
| import { CoreDirectivesRegistry } from '@singletons/directives-registry'; | ||||
| 
 | ||||
| /** | ||||
|  * Handles application modals. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class CoreModalsService { | ||||
| 
 | ||||
|     /** | ||||
|      * Get index of the overlay on top of the stack. | ||||
|      * | ||||
|      * @returns Z-index of the overlay on top. | ||||
|      */ | ||||
|     getTopOverlayIndex(): number { | ||||
|         // This has to be done manually because Ionic's overlay mechanisms are not exposed externally, thus making it more difficult
 | ||||
|         // to implement custom overlays.
 | ||||
|         //
 | ||||
|         // eslint-disable-next-line max-len
 | ||||
|         // See https://github.com/ionic-team/ionic-framework/blob/a9b12a5aa4c150a1f8a80a826dda0df350bc0092/core/src/utils/overlays.ts#L39
 | ||||
| 
 | ||||
|         const overlays = document.querySelectorAll<HTMLElement>( | ||||
|             'ion-action-sheet, ion-alert, ion-loading, ion-modal, ion-picker, ion-popover, ion-toast', | ||||
|         ); | ||||
| 
 | ||||
|         return Array.from(overlays).reduce((maxIndex, element) => { | ||||
|             const index = parseInt(element.style.zIndex); | ||||
| 
 | ||||
|             if (isNaN(index)) { | ||||
|                 return maxIndex; | ||||
|             } | ||||
| 
 | ||||
|             return Math.max(maxIndex, index % 10000); | ||||
|         }, 0); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Open a sheet modal component. | ||||
|      * | ||||
|      * @param component Component to render inside the modal. | ||||
|      * @returns Modal result once it's been closed. | ||||
|      */ | ||||
|     async openSheet<T>(component: Constructor<CoreModalComponent<T>>): Promise<T> { | ||||
|         const container = document.querySelector('ion-app') ?? document.body; | ||||
|         const viewContainer = container.querySelector('ion-router-outlet, ion-nav, #ion-view-container-root'); | ||||
|         const element = await AngularFrameworkDelegate.attachViewToDom( | ||||
|             container, | ||||
|             CoreSheetModalComponent, | ||||
|             { component }, | ||||
|         ); | ||||
|         const sheetModal = CoreDirectivesRegistry.require<CoreSheetModalComponent<CoreModalComponent<T>>>( | ||||
|             element, | ||||
|             CoreSheetModalComponent, | ||||
|         ); | ||||
|         const modal = await sheetModal.show(); | ||||
| 
 | ||||
|         viewContainer?.setAttribute('aria-hidden', 'true'); | ||||
| 
 | ||||
|         modal.result.finally(async () => { | ||||
|             await sheetModal.hide(); | ||||
| 
 | ||||
|             element.remove(); | ||||
|             viewContainer?.removeAttribute('aria-hidden'); | ||||
|         }); | ||||
| 
 | ||||
|         return modal.result; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export const CoreModals = makeSingleton(CoreModalsService); | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user