commit
						4ec6096482
					
				| @ -2327,5 +2327,7 @@ | |||||||
|   "core.years": "moodle", |   "core.years": "moodle", | ||||||
|   "core.yes": "moodle", |   "core.yes": "moodle", | ||||||
|   "core.youreoffline": "local_moodlemobileapp", |   "core.youreoffline": "local_moodlemobileapp", | ||||||
|   "core.youreonline": "local_moodlemobileapp" |   "core.youreonline": "local_moodlemobileapp", | ||||||
|  |   "core.zoomin": "local_moodlemobileapp", | ||||||
|  |   "core.zoomout": "local_moodlemobileapp" | ||||||
| } | } | ||||||
|  | |||||||
| @ -9,7 +9,7 @@ ion-slide { | |||||||
| ::ng-deep { | ::ng-deep { | ||||||
| 
 | 
 | ||||||
|     core-loading .core-loading-content { |     core-loading .core-loading-content { | ||||||
|         display: block; |         display: block !important; | ||||||
|         width: 100%; |         width: 100%; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -235,7 +235,7 @@ export class CoreFormatTextDirective implements OnChanges { | |||||||
|             button.addEventListener('click', (e: Event) => { |             button.addEventListener('click', (e: Event) => { | ||||||
|                 e.preventDefault(); |                 e.preventDefault(); | ||||||
|                 e.stopPropagation(); |                 e.stopPropagation(); | ||||||
|                 CoreDomUtils.viewImage(imgSrc, img.getAttribute('alt'), this.component, this.componentId, true); |                 CoreDomUtils.viewImage(imgSrc, img.getAttribute('alt'), this.component, this.componentId); | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|             img.parentNode?.appendChild(button); |             img.parentNode?.appendChild(button); | ||||||
|  | |||||||
| @ -1,16 +1,29 @@ | |||||||
| <ion-header> | <ion-content> | ||||||
|     <ion-toolbar> |     <ion-slides [options]="slidesOpts"> | ||||||
|         <ion-title> |         <ion-slide> | ||||||
|             <h2>{{ title }}</h2> |             <div class="swiper-zoom-container"> | ||||||
|         </ion-title> |                 <img [src]="image" [alt]="title" core-external-content [component]="component" [componentId]="componentId"> | ||||||
|         <ion-buttons slot="end"> |             </div> | ||||||
|             <ion-button fill="clear" (click)="closeModal()" [attr.aria-label]="'core.close' | translate"> |         </ion-slide> | ||||||
|                 <ion-icon name="fas-times" slot="icon-only" aria-hidden=true></ion-icon> |     </ion-slides> | ||||||
|             </ion-button> |  | ||||||
|         </ion-buttons> |  | ||||||
|     </ion-toolbar> |  | ||||||
| </ion-header> |  | ||||||
| <!-- @todo: zoom="true" maxZoom="2" . Now we need to use ionSlider? --> |  | ||||||
| <ion-content [scrollX]="true" [scrollY]="true" class="core-zoom-pane"> |  | ||||||
|     <img [src]="image" [alt]="title" core-external-content [component]="component" [componentId]="componentId"> |  | ||||||
| </ion-content> | </ion-content> | ||||||
|  | <ion-footer> | ||||||
|  |     <ion-row class="ion-justify-content-between ion-align-items-center ion-no-padding"> | ||||||
|  |         <ion-col size="auto"> | ||||||
|  |             <ion-button fill="clear" (click)="closeModal()" [attr.aria-label]="'core.close' | translate"> | ||||||
|  |                 <ion-icon name="fas-times" slot="icon-only" aria-hidden="true"></ion-icon> | ||||||
|  |             </ion-button> | ||||||
|  |         </ion-col> | ||||||
|  |         <ion-col></ion-col> | ||||||
|  |         <ion-col size="auto" *ngIf="slidesSwiper"> | ||||||
|  |             <ion-button fill="clear" [attr.aria-label]="'core.zoomout' | translate" (click)="zoom(false)"> | ||||||
|  |                 <ion-icon name="fas-search-minus" slot="icon-only" aria-hidden="true"></ion-icon> | ||||||
|  |             </ion-button> | ||||||
|  |         </ion-col> | ||||||
|  |         <ion-col size="auto" *ngIf="slidesSwiper"> | ||||||
|  |             <ion-button fill="clear" [attr.aria-label]="'core.zoomin' | translate" (click)="zoom(true)"> | ||||||
|  |                 <ion-icon name="fas-search-plus" slot="icon-only" aria-hidden="true"></ion-icon> | ||||||
|  |             </ion-button> | ||||||
|  |         </ion-col> | ||||||
|  |     </ion-row> | ||||||
|  | </ion-footer> | ||||||
|  | |||||||
| @ -1,9 +1,11 @@ | |||||||
| :host { | ion-slides { | ||||||
|     .core-zoom-pane { |     height: 100%; | ||||||
|         height: 100%; | } | ||||||
| 
 | 
 | ||||||
|         img { | img { | ||||||
|             max-width: none; |     max-width: 100%; | ||||||
|         } | } | ||||||
|     } | 
 | ||||||
|  | ion-footer { | ||||||
|  |     background: var(--contrast-background); | ||||||
| } | } | ||||||
|  | |||||||
| @ -12,9 +12,10 @@ | |||||||
| // See the License for the specific language governing permissions and
 | // See the License for the specific language governing permissions and
 | ||||||
| // limitations under the License.
 | // limitations under the License.
 | ||||||
| 
 | 
 | ||||||
| import { Component, Input, OnInit } from '@angular/core'; | import { AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; | ||||||
| 
 | import { IonSlides } from '@ionic/angular'; | ||||||
| import { ModalController, Translate } from '@singletons'; | import { ModalController, Translate } from '@singletons'; | ||||||
|  | import { CoreMath } from '@singletons/math'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Modal component to view an image. |  * Modal component to view an image. | ||||||
| @ -24,17 +25,46 @@ import { ModalController, Translate } from '@singletons'; | |||||||
|     templateUrl: 'image.html', |     templateUrl: 'image.html', | ||||||
|     styleUrls: ['image.scss'], |     styleUrls: ['image.scss'], | ||||||
| }) | }) | ||||||
| export class CoreViewerImageComponent implements OnInit { | export class CoreViewerImageComponent implements OnInit, AfterViewInit { | ||||||
| 
 | 
 | ||||||
|     @Input() title?: string; // Modal title.
 |     @ViewChild(IonSlides) protected slides?: IonSlides; | ||||||
|     @Input() image?: string; // Image URL.
 | 
 | ||||||
|  |     @Input() title = ''; // Modal title.
 | ||||||
|  |     @Input() image = ''; // Image URL.
 | ||||||
|     @Input() component?: string; // Component to use in external-content.
 |     @Input() component?: string; // Component to use in external-content.
 | ||||||
|     @Input() componentId?: string | number; // Component ID to use in external-content.
 |     @Input() componentId?: string | number; // Component ID to use in external-content.
 | ||||||
| 
 | 
 | ||||||
|  |     slidesOpts = { | ||||||
|  |         slidesPerView: 1, | ||||||
|  |         centerInsufficientSlides: true, | ||||||
|  |         centerSlides: true, | ||||||
|  |         zoom: { | ||||||
|  |             maxRatio: 8, | ||||||
|  |             minRatio: 0.5, // User can zoom out to 0.5 only using pinch gesture.
 | ||||||
|  |         }, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     protected zoomRatio = 1; | ||||||
|  | 
 | ||||||
|  |     slidesSwiper: any; // eslint-disable-line @typescript-eslint/no-explicit-any
 | ||||||
|  | 
 | ||||||
|  |     constructor(protected element: ElementRef<HTMLElement>) { | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @inheritdoc | ||||||
|  |      */ | ||||||
|     ngOnInit(): void { |     ngOnInit(): void { | ||||||
|         this.title = this.title || Translate.instant('core.imageviewer'); |         this.title = this.title || Translate.instant('core.imageviewer'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * @inheritdoc | ||||||
|  |      */ | ||||||
|  |     async ngAfterViewInit(): Promise<void> { | ||||||
|  |         this.slidesSwiper = await this.slides?.getSwiper(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Close modal. |      * Close modal. | ||||||
|      */ |      */ | ||||||
| @ -42,4 +72,33 @@ export class CoreViewerImageComponent implements OnInit { | |||||||
|         ModalController.dismiss(); |         ModalController.dismiss(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Zoom In or Out. | ||||||
|  |      * | ||||||
|  |      * @param zoomIn: True to zoom in, false to zoom out. | ||||||
|  |      */ | ||||||
|  |     zoom(zoomIn = true): void { | ||||||
|  |         const imageElement = this.element.nativeElement.querySelector('img'); | ||||||
|  | 
 | ||||||
|  |         if (!this.slidesSwiper || !imageElement) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         zoomIn | ||||||
|  |             ? this.zoomRatio *= 2 | ||||||
|  |             : this.zoomRatio /= 2; | ||||||
|  | 
 | ||||||
|  |         // Using 1 as minimum for manual zoom.
 | ||||||
|  |         this.zoomRatio = CoreMath.clamp(this.zoomRatio, 1, this.slidesOpts.zoom.maxRatio); | ||||||
|  | 
 | ||||||
|  |         if (this.zoomRatio > 1) { | ||||||
|  |             this.slidesSwiper.zoom.in(); | ||||||
|  | 
 | ||||||
|  |             imageElement.style.transform = | ||||||
|  |                 'translate3d(0px, 0px, 0px) scale(' + this.zoomRatio + ')'; | ||||||
|  |         } else { | ||||||
|  |             this.slidesSwiper.zoom.out(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -347,5 +347,7 @@ | |||||||
|     "years": "years", |     "years": "years", | ||||||
|     "yes": "Yes", |     "yes": "Yes", | ||||||
|     "youreoffline": "You are offline", |     "youreoffline": "You are offline", | ||||||
|     "youreonline": "You are back online" |     "youreonline": "You are back online", | ||||||
|  |     "zoomin": "Zoom In", | ||||||
|  |     "zoomout": "Zoom Out" | ||||||
| } | } | ||||||
|  | |||||||
| @ -1703,6 +1703,9 @@ export class CoreDomUtilsProvider { | |||||||
| 
 | 
 | ||||||
|         let navSubscription: Subscription | undefined; |         let navSubscription: Subscription | undefined; | ||||||
| 
 | 
 | ||||||
|  |         // Get the promise before presenting to get result if modal is suddenly hidden.
 | ||||||
|  |         const resultPromise = waitForDismissCompleted ? modal.onDidDismiss<T>() : modal.onWillDismiss<T>(); | ||||||
|  | 
 | ||||||
|         if (!this.displayedModals[modalId]) { |         if (!this.displayedModals[modalId]) { | ||||||
|             // Store the modal and remove it when dismissed.
 |             // Store the modal and remove it when dismissed.
 | ||||||
|             this.displayedModals[modalId] = modal; |             this.displayedModals[modalId] = modal; | ||||||
| @ -1719,7 +1722,8 @@ export class CoreDomUtilsProvider { | |||||||
|             await modal.present(); |             await modal.present(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const result = waitForDismissCompleted ? await modal.onDidDismiss<T>() : await modal.onWillDismiss<T>(); |         const result = await resultPromise; | ||||||
|  | 
 | ||||||
|         navSubscription?.unsubscribe(); |         navSubscription?.unsubscribe(); | ||||||
|         delete this.displayedModals[modalId]; |         delete this.displayedModals[modalId]; | ||||||
| 
 | 
 | ||||||
| @ -1787,14 +1791,12 @@ export class CoreDomUtilsProvider { | |||||||
|      * @param title Title of the page or modal. |      * @param title Title of the page or modal. | ||||||
|      * @param component Component to link the image to if needed. |      * @param component Component to link the image to if needed. | ||||||
|      * @param componentId An ID to use in conjunction with the component. |      * @param componentId An ID to use in conjunction with the component. | ||||||
|      * @param fullScreen Whether the modal should be full screen. |  | ||||||
|      */ |      */ | ||||||
|     async viewImage( |     async viewImage( | ||||||
|         image: string, |         image: string, | ||||||
|         title?: string | null, |         title?: string | null, | ||||||
|         component?: string, |         component?: string, | ||||||
|         componentId?: string | number, |         componentId?: string | number, | ||||||
|         fullScreen?: boolean, |  | ||||||
|     ): Promise<void> { |     ): Promise<void> { | ||||||
|         if (!image) { |         if (!image) { | ||||||
|             return; |             return; | ||||||
| @ -1808,7 +1810,7 @@ export class CoreDomUtilsProvider { | |||||||
|                 component, |                 component, | ||||||
|                 componentId, |                 componentId, | ||||||
|             }, |             }, | ||||||
|             cssClass: fullScreen ? 'core-modal-fullscreen' : '', |             cssClass: 'core-modal-transparent', | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -510,6 +510,27 @@ body.core-iframe-fullscreen ion-router-outlet { | |||||||
|     height: 100% !important; |     height: 100% !important; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .core-modal-transparent { | ||||||
|  | 
 | ||||||
|  |     ion-backdrop { | ||||||
|  |         backdrop-filter: blur(8px); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     .modal-wrapper { | ||||||
|  |         backdrop-filter: blur(12px); | ||||||
|  |         --background: rgba(10, 10, 10, 0.2); | ||||||
|  | 
 | ||||||
|  |         ion-content { | ||||||
|  |             --background: transparent !important; | ||||||
|  |         } | ||||||
|  |         position: absolute; | ||||||
|  |         @include position(0 !important, null, null, 0 !important); | ||||||
|  |         display: block; | ||||||
|  |         width: 100% !important; | ||||||
|  |         height: 100% !important; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .core-modal-force-on-top { | .core-modal-force-on-top { | ||||||
|     z-index: 100000 !important; |     z-index: 100000 !important; | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user