Merge pull request #3026 from crazyserver/MOBILE-3950

Mobile 3950
main
Dani Palou 2021-12-16 10:51:10 +01:00 committed by GitHub
commit 4ec6096482
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 137 additions and 36 deletions

View File

@ -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"
} }

View File

@ -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%;
} }

View File

@ -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);

View File

@ -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>

View File

@ -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);
} }

View File

@ -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();
}
}
} }

View File

@ -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"
} }

View File

@ -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',
}); });
} }

View File

@ -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;
} }