MOBILE-3950 image: Restore zoom on image viewer

main
Pau Ferrer Ocaña 2021-12-14 15:13:55 +01:00
parent 8a97b0251d
commit 2ae55d04fb
9 changed files with 132 additions and 35 deletions

View File

@ -2327,5 +2327,7 @@
"core.years": "moodle",
"core.yes": "moodle",
"core.youreoffline": "local_moodlemobileapp",
"core.youreonline": "local_moodlemobileapp"
"core.youreonline": "local_moodlemobileapp",
"core.zoomin": "local_moodlemobileapp",
"core.zoomout": "local_moodlemobileapp"
}

View File

@ -9,7 +9,7 @@ ion-slide {
::ng-deep {
core-loading .core-loading-content {
display: block;
display: block !important;
width: 100%;
}

View File

@ -235,7 +235,7 @@ export class CoreFormatTextDirective implements OnChanges {
button.addEventListener('click', (e: Event) => {
e.preventDefault();
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);

View File

@ -1,16 +1,29 @@
<ion-header>
<ion-toolbar>
<ion-title>
<h2>{{ title }}</h2>
</ion-title>
<ion-buttons slot="end">
<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-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-slides [options]="slidesOpts">
<ion-slide>
<div class="swiper-zoom-container">
<img [src]="image" [alt]="title" core-external-content [component]="component" [componentId]="componentId">
</div>
</ion-slide>
</ion-slides>
</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>

View File

@ -1,9 +1,11 @@
:host {
.core-zoom-pane {
height: 100%;
img {
max-width: none;
}
}
ion-slides {
height: 100%;
}
img {
max-width: 100%;
}
ion-footer {
background: var(--contrast-background);
}

View File

@ -12,9 +12,10 @@
// See the License for the specific language governing permissions and
// 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 { CoreMath } from '@singletons/math';
/**
* Modal component to view an image.
@ -24,17 +25,46 @@ import { ModalController, Translate } from '@singletons';
templateUrl: 'image.html',
styleUrls: ['image.scss'],
})
export class CoreViewerImageComponent implements OnInit {
export class CoreViewerImageComponent implements OnInit, AfterViewInit {
@Input() title?: string; // Modal title.
@Input() image?: string; // Image URL.
@ViewChild(IonSlides) protected slides?: IonSlides;
@Input() title = ''; // Modal title.
@Input() image = ''; // Image URL.
@Input() component?: string; // Component 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 {
this.title = this.title || Translate.instant('core.imageviewer');
}
/**
* @inheritdoc
*/
async ngAfterViewInit(): Promise<void> {
this.slidesSwiper = await this.slides?.getSwiper();
}
/**
* Close modal.
*/
@ -42,4 +72,33 @@ export class CoreViewerImageComponent implements OnInit {
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();
}
}
}

View File

@ -347,5 +347,7 @@
"years": "years",
"yes": "Yes",
"youreoffline": "You are offline",
"youreonline": "You are back online"
"youreonline": "You are back online",
"zoomin": "Zoom In",
"zoomout": "Zoom Out"
}

View File

@ -1787,14 +1787,12 @@ export class CoreDomUtilsProvider {
* @param title Title of the page or modal.
* @param component Component to link the image to if needed.
* @param componentId An ID to use in conjunction with the component.
* @param fullScreen Whether the modal should be full screen.
*/
async viewImage(
image: string,
title?: string | null,
component?: string,
componentId?: string | number,
fullScreen?: boolean,
): Promise<void> {
if (!image) {
return;
@ -1808,7 +1806,7 @@ export class CoreDomUtilsProvider {
component,
componentId,
},
cssClass: fullScreen ? 'core-modal-fullscreen' : '',
cssClass: 'core-modal-transparent',
});
}

View File

@ -510,6 +510,27 @@ body.core-iframe-fullscreen ion-router-outlet {
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 {
z-index: 100000 !important;
}