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