diff --git a/scripts/langindex.json b/scripts/langindex.json
index 5a800908f..26f2815f3 100644
--- a/scripts/langindex.json
+++ b/scripts/langindex.json
@@ -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"
}
diff --git a/src/core/components/swipe-navigation/swipe-navigation.scss b/src/core/components/swipe-navigation/swipe-navigation.scss
index 046438a5a..d0dbcaffd 100644
--- a/src/core/components/swipe-navigation/swipe-navigation.scss
+++ b/src/core/components/swipe-navigation/swipe-navigation.scss
@@ -9,7 +9,7 @@ ion-slide {
::ng-deep {
core-loading .core-loading-content {
- display: block;
+ display: block !important;
width: 100%;
}
diff --git a/src/core/directives/format-text.ts b/src/core/directives/format-text.ts
index 10c220e72..fdb95c34a 100644
--- a/src/core/directives/format-text.ts
+++ b/src/core/directives/format-text.ts
@@ -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);
diff --git a/src/core/features/viewer/components/image/image.html b/src/core/features/viewer/components/image/image.html
index c55c50d93..c1096c614 100644
--- a/src/core/features/viewer/components/image/image.html
+++ b/src/core/features/viewer/components/image/image.html
@@ -1,16 +1,29 @@
-
-
-
- {{ title }}
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/core/features/viewer/components/image/image.scss b/src/core/features/viewer/components/image/image.scss
index c8b0d2f62..9d8036854 100644
--- a/src/core/features/viewer/components/image/image.scss
+++ b/src/core/features/viewer/components/image/image.scss
@@ -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);
}
diff --git a/src/core/features/viewer/components/image/image.ts b/src/core/features/viewer/components/image/image.ts
index ddb52fe3d..09fe88b40 100644
--- a/src/core/features/viewer/components/image/image.ts
+++ b/src/core/features/viewer/components/image/image.ts
@@ -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) {
+ }
+
+ /**
+ * @inheritdoc
+ */
ngOnInit(): void {
this.title = this.title || Translate.instant('core.imageviewer');
}
+ /**
+ * @inheritdoc
+ */
+ async ngAfterViewInit(): Promise {
+ 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();
+ }
+ }
+
}
diff --git a/src/core/lang.json b/src/core/lang.json
index 793e0da74..ce0aebea4 100644
--- a/src/core/lang.json
+++ b/src/core/lang.json
@@ -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"
}
diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts
index 77a664817..d097ee912 100644
--- a/src/core/services/utils/dom.ts
+++ b/src/core/services/utils/dom.ts
@@ -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 {
if (!image) {
return;
@@ -1808,7 +1806,7 @@ export class CoreDomUtilsProvider {
component,
componentId,
},
- cssClass: fullScreen ? 'core-modal-fullscreen' : '',
+ cssClass: 'core-modal-transparent',
});
}
diff --git a/src/theme/theme.base.scss b/src/theme/theme.base.scss
index d13635f92..0500be86e 100644
--- a/src/theme/theme.base.scss
+++ b/src/theme/theme.base.scss
@@ -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;
}