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…
Reference in New Issue