diff --git a/src/app/core/services/modal.service.spec.ts b/src/app/core/services/modal.service.spec.ts index b25464e..c6ece6b 100644 --- a/src/app/core/services/modal.service.spec.ts +++ b/src/app/core/services/modal.service.spec.ts @@ -7,6 +7,7 @@ import { firstValueFrom } from 'rxjs'; import { ConfirmDeleteModalComponent } from '../../shared/components/confirm-delete-modal/confirm-delete-modal.component'; import { ConfirmModalComponent } from '../../shared/components/confirm-modal/confirm-modal.component'; +import { ImageModalComponent } from '../../shared/components/image-modal/image-modal.component'; import { QrModalComponent } from '../../shared/components/qr-modal/qr-modal.component'; import { SimpleModalComponent } from '../../shared/components/simple-modal/simple-modal.component'; import { MODAL_DATA, ModalRef, ModalService } from './modal.service'; @@ -258,6 +259,36 @@ describe('ModalService', () => { showCloseButton: true, }); }); + + it('opens the image viewer fullscreen with normalized zoom limits', () => { + service.openImage({ + title: 'Producto', + src: '/images/producto.webp', + alt: 'Producto visto de frente', + initialZoom: 8, + minZoom: 0, + maxZoom: 3, + }); + + const activeModal = service.activeModal(); + + expect(activeModal?.component).toBe(ImageModalComponent); + expect(activeModal?.config).toEqual({ + title: 'Producto', + size: 'full', + presentation: 'fullscreen-media', + data: { + src: '/images/producto.webp', + alt: 'Producto visto de frente', + initialZoom: 3, + minZoom: 0.1, + maxZoom: 3, + }, + closeOnBackdrop: true, + closeOnEscape: true, + showCloseButton: true, + }); + }); }); @Component({ diff --git a/src/app/core/services/modal.service.ts b/src/app/core/services/modal.service.ts index 3dfd1ff..54d5915 100644 --- a/src/app/core/services/modal.service.ts +++ b/src/app/core/services/modal.service.ts @@ -2,16 +2,19 @@ import { InjectionToken, Injectable, Type, signal } from '@angular/core'; import { Observable, Subject, map } from 'rxjs'; import { ConfirmDeleteModalComponent } from '../../shared/components/confirm-delete-modal/confirm-delete-modal.component'; import { ConfirmModalComponent } from '../../shared/components/confirm-modal/confirm-modal.component'; +import { ImageModalComponent } from '../../shared/components/image-modal/image-modal.component'; import { QrModalComponent } from '../../shared/components/qr-modal/qr-modal.component'; import { SimpleModalComponent } from '../../shared/components/simple-modal/simple-modal.component'; export type ModalSize = 'qr' | 'sm' | 'md' | 'lg' | 'xl' | 'full'; -export type ModalDismissReason = 'backdrop' | 'escape' | 'programmatic' | 'replaced'; +export type ModalDismissReason = 'backdrop' | 'escape' | 'programmatic' | 'replaced' | 'swipe'; +export type ModalPresentation = 'dialog' | 'fullscreen-media'; export interface ModalConfig { title?: string; data?: TData; size?: ModalSize; + presentation?: ModalPresentation; closeOnBackdrop?: boolean; closeOnEscape?: boolean; showCloseButton?: boolean; @@ -62,6 +65,25 @@ export interface QrModalConfig extends Omit, 'data' | ' ticket: string; } +export interface ImageModalData { + src: string; + alt: string; + initialZoom: number; + minZoom: number; + maxZoom: number; +} + +export interface ImageModalConfig extends Omit< + ModalConfig, + 'data' | 'presentation' | 'size' +> { + src: string; + alt: string; + initialZoom?: number; + minZoom?: number; + maxZoom?: number; +} + export interface ActiveModalState { component: Type; config: NormalizedModalConfig; @@ -195,6 +217,33 @@ export class ModalService { }); } + openImage(config: ImageModalConfig): Observable { + return this.openImageRef(config).afterClosed$.pipe(map(() => undefined)); + } + + openImageRef(config: ImageModalConfig): ModalRef { + const { src, alt, initialZoom = 1, minZoom = 1, maxZoom = 4, ...modalConfig } = config; + const normalizedMinZoom = Math.max(0.1, minZoom); + const normalizedMaxZoom = Math.max(normalizedMinZoom, maxZoom); + const normalizedInitialZoom = Math.min( + normalizedMaxZoom, + Math.max(normalizedMinZoom, initialZoom), + ); + + return this.open(ImageModalComponent, { + ...modalConfig, + size: 'full', + presentation: 'fullscreen-media', + data: { + src, + alt, + initialZoom: normalizedInitialZoom, + minZoom: normalizedMinZoom, + maxZoom: normalizedMaxZoom, + }, + }); + } + private close(ref: ModalRef, result?: TResult): void { if (this.activeModalState()?.ref !== ref) { return; diff --git a/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.html b/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.html index b36a8a7..d3d5d8c 100644 --- a/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.html +++ b/src/app/features/componentes-test/pages/reutilizables-test-page/reutilizables-test-page.component.html @@ -83,6 +83,9 @@ Abrir modal ancho Abrir simple modal + + Abrir visor de imagen +