import { ChangeDetectionStrategy, Component, computed, DestroyRef, inject, } from '@angular/core'; import { AfricaniesIconComponent } from '@africanies/africanies-icons'; import { ButtonComponent } from '../../button/button.component'; import { AfricaniesOverlayRef } from '../../overlay/africanies-overlay-ref'; import { OVERLAY_DATA } from '../../overlay/overlay-data.token'; /** Payload for {@link FilePreviewDialogComponent}. */ export interface FilePreviewData { /** * Local pick when previewing a fresh upload. Null for server-backed * {@link FileUploadExistingFile} rows (preview uses {@link previewUrl} only). */ file: File | null; /** Display name in the dialog title. */ name: string; previewUrl: string | null; isImage: boolean; /** Byte size when known; omit for remote existing files. */ size?: number | null; } type PreviewKind = 'image' | 'unavailable'; /** * Large-file preview modal for {@link FileUploadComponent}. * * Local image picks and existing server image URLs render inline. Other types * show a clear “can’t view” message. Object URLs created here are revoked on * destroy; list-owned `previewUrl` values are left alone. */ @Component({ selector: 'africanies-file-preview-dialog', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [AfricaniesIconComponent, ButtonComponent], host: { class: 'flex min-h-0 w-full flex-col overflow-hidden', }, template: ` `, }) /** * Large-file preview modal for {@link FileUploadComponent}. */ export class FilePreviewDialogComponent { protected readonly data = inject(OVERLAY_DATA); private readonly ref = inject(AfricaniesOverlayRef); private readonly destroyRef = inject(DestroyRef); protected readonly titleId = `africanies-file-preview-${Math.random().toString(36).slice(2, 10)}`; private readonly ownedUrl: string | null; constructor() { this.ownedUrl = this.data.isImage && !this.data.previewUrl && this.data.file ? URL.createObjectURL(this.data.file) : null; this.destroyRef.onDestroy(() => { if (this.ownedUrl) { URL.revokeObjectURL(this.ownedUrl); } }); } protected readonly src = computed( () => this.data.previewUrl ?? this.ownedUrl ?? '', ); protected readonly kind = computed((): PreviewKind => { const type = (this.data.file?.type || '').toLowerCase(); if (this.data.isImage || type.startsWith('image/')) { return 'image'; } return 'unavailable'; }); protected typeLabel(): string | null { const type = this.data.file?.type?.trim(); return type || null; } protected sizeLabel(): string { const bytes = this.data.size ?? this.data.file?.size; if (bytes == null) { return 'On file'; } if (bytes < 1024) { return `${bytes} B`; } if (bytes < 1024 * 1024) { return `${(bytes / 1024).toFixed(1)} KB`; } return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } protected close(): void { this.ref.close(); } }