MOBILE-3950 image: Restore zoom on image viewer
parent
8a97b0251d
commit
2ae55d04fb
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ ion-slide {
|
|||
::ng-deep {
|
||||
|
||||
core-loading .core-loading-content {
|
||||
display: block;
|
||||
display: block !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue