@maro-tech/card 0.1.12 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,11 @@
1
1
  import * as i1$1 from '@angular/common';
2
2
  import { CommonModule } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
- import { input, inject, DestroyRef, signal, Component, computed, viewChildren, output, effect, HostListener, Input, InjectionToken, forwardRef, Injectable, ViewChild, untracked } from '@angular/core';
4
+ import { input, inject, DestroyRef, signal, Component, computed, viewChild, viewChildren, output, effect, HostListener, Input, InjectionToken, forwardRef, Injectable, ViewChild, untracked } from '@angular/core';
5
5
  import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
6
6
  import { EventBus, UiButtonComponent, UiToastService, UiModalComponent, UiConfirmService } from '@maro-tech/core';
7
7
  import { DomSanitizer } from '@angular/platform-browser';
8
+ import Panzoom from '@panzoom/panzoom';
8
9
  import * as i1 from 'lucide-angular';
9
10
  import { Download, LucideAngularModule, Eye, FileText, Plus, Sparkles, Trash } from 'lucide-angular';
10
11
  import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
@@ -278,11 +279,14 @@ class ImagesViewerComponent {
278
279
  initialIndex = input(0, ...(ngDevMode ? [{ debugName: "initialIndex" }] : /* istanbul ignore next */ []));
279
280
  currentIndex = signal(0, ...(ngDevMode ? [{ debugName: "currentIndex" }] : /* istanbul ignore next */ []));
280
281
  isOriginalVisible = signal(false, ...(ngDevMode ? [{ debugName: "isOriginalVisible" }] : /* istanbul ignore next */ []));
282
+ originalPreviewUnavailable = signal(false, ...(ngDevMode ? [{ debugName: "originalPreviewUnavailable" }] : /* istanbul ignore next */ []));
283
+ currentImage = viewChild('currentImage', ...(ngDevMode ? [{ debugName: "currentImage" }] : /* istanbul ignore next */ []));
281
284
  thumbnailButtons = viewChildren('thumbnailButton', ...(ngDevMode ? [{ debugName: "thumbnailButtons" }] : /* istanbul ignore next */ []));
282
285
  closed = output();
283
286
  touchStartX = 0;
284
287
  touchStartY = 0;
285
288
  lastImagesKey = '';
289
+ panzoomInstance = null;
286
290
  constructor() {
287
291
  effect(() => {
288
292
  const urls = this.urls();
@@ -293,17 +297,41 @@ class ImagesViewerComponent {
293
297
  if (!urls.length) {
294
298
  this.currentIndex.set(0);
295
299
  this.isOriginalVisible.set(false);
300
+ this.originalPreviewUnavailable.set(false);
296
301
  return;
297
302
  }
298
303
  const maxIndex = urls.length - 1;
299
304
  const startIndex = Math.max(0, Math.min(this.initialIndex(), maxIndex));
300
305
  this.currentIndex.set(startIndex);
301
306
  this.isOriginalVisible.set(false);
307
+ this.originalPreviewUnavailable.set(false);
302
308
  });
303
309
  effect(() => {
304
310
  const thumbnail = this.thumbnailButtons()[this.currentIndex()];
305
311
  thumbnail?.nativeElement.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
306
312
  });
313
+ effect((onCleanup) => {
314
+ const image = this.currentImage()?.nativeElement;
315
+ this.currentIndex();
316
+ this.isOriginalVisible();
317
+ if (!image)
318
+ return;
319
+ const panzoom = Panzoom(image, {
320
+ maxScale: 4,
321
+ minScale: 1,
322
+ step: 0.5,
323
+ panOnlyWhenZoomed: true,
324
+ pinchAndPan: true,
325
+ touchAction: 'none',
326
+ animate: true,
327
+ });
328
+ this.panzoomInstance = panzoom;
329
+ onCleanup(() => {
330
+ panzoom.destroy();
331
+ if (this.panzoomInstance === panzoom)
332
+ this.panzoomInstance = null;
333
+ });
334
+ });
307
335
  }
308
336
  onEscape() {
309
337
  this.closed.emit();
@@ -328,6 +356,8 @@ class ImagesViewerComponent {
328
356
  const touch = event.changedTouches[0];
329
357
  if (!touch)
330
358
  return;
359
+ if (this.panzoomInstance?.getScale() && this.panzoomInstance.getScale() > 1.01)
360
+ return;
331
361
  const deltaX = touch.clientX - this.touchStartX;
332
362
  const deltaY = touch.clientY - this.touchStartY;
333
363
  if (Math.abs(deltaX) < 50 || Math.abs(deltaX) <= Math.abs(deltaY))
@@ -354,12 +384,51 @@ class ImagesViewerComponent {
354
384
  this.blurActiveElement();
355
385
  }
356
386
  toggleOriginal() {
387
+ this.originalPreviewUnavailable.set(false);
357
388
  this.isOriginalVisible.update((visible) => !visible);
358
389
  }
390
+ zoomIn() {
391
+ this.panzoomInstance?.zoomIn({ animate: true });
392
+ }
393
+ zoomOut() {
394
+ this.panzoomInstance?.zoomOut({ animate: true });
395
+ }
396
+ resetZoom() {
397
+ this.panzoomInstance?.reset({ animate: true });
398
+ }
399
+ onWheel(event) {
400
+ event.preventDefault();
401
+ const panzoom = this.panzoomInstance;
402
+ if (!panzoom)
403
+ return;
404
+ const direction = event.deltaY < 0 ? 1 : -1;
405
+ const scale = panzoom.getScale() * Math.exp((direction * 0.5) / 3);
406
+ panzoom.zoomToPoint(scale, { clientX: event.clientX, clientY: event.clientY }, { animate: false });
407
+ }
408
+ onImageLoad() {
409
+ this.panzoomInstance?.reset({ animate: false });
410
+ }
411
+ toggleZoom() {
412
+ const panzoom = this.panzoomInstance;
413
+ if (!panzoom)
414
+ return;
415
+ if (panzoom.getScale() > 1.01) {
416
+ panzoom.reset({ animate: true });
417
+ }
418
+ else {
419
+ panzoom.zoomIn({ animate: true });
420
+ }
421
+ }
359
422
  selectImage(index, event) {
360
423
  this.currentIndex.set(index);
424
+ this.originalPreviewUnavailable.set(false);
361
425
  event.currentTarget?.blur();
362
426
  }
427
+ onImageError(index) {
428
+ if (this.isOriginalVisible() && index === this.currentIndex()) {
429
+ this.originalPreviewUnavailable.set(true);
430
+ }
431
+ }
363
432
  blurActiveElement() {
364
433
  if (typeof document === 'undefined')
365
434
  return;
@@ -369,7 +438,7 @@ class ImagesViewerComponent {
369
438
  return this.urls()[this.currentIndex()] || '';
370
439
  }
371
440
  get currentImageUrl() {
372
- return this.isOriginalVisible() ? this.currentUrl : this.getThumbnailUrl(this.currentUrl);
441
+ return this.isOriginalVisible() ? this.getOriginalUrl(this.currentUrl) : this.getThumbnailUrl(this.currentUrl);
373
442
  }
374
443
  isPreloadIndex(index) {
375
444
  const total = this.urls().length;
@@ -382,7 +451,7 @@ class ImagesViewerComponent {
382
451
  if (index === this.currentIndex()) {
383
452
  return this.currentImageUrl;
384
453
  }
385
- return this.isOriginalVisible() ? url : this.getThumbnailUrl(url);
454
+ return this.isOriginalVisible() ? this.getOriginalUrl(url) : this.getThumbnailUrl(url);
386
455
  }
387
456
  get imageViewerTransform() {
388
457
  return `translate3d(-${this.currentIndex() * 100}%, 0, 0)`;
@@ -394,13 +463,16 @@ class ImagesViewerComponent {
394
463
  const match = String(url || '').match(/^(.*\/api\/files\/[^/?#]+)(?:\?.*)?$/i);
395
464
  return match ? `${match[1]}/preview?scale=25` : url;
396
465
  }
466
+ getOriginalUrl(url) {
467
+ return String(url || '').replace(/\/preview(?:\?[^#]*)?$/i, '');
468
+ }
397
469
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ImagesViewerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
398
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: ImagesViewerComponent, isStandalone: true, selector: "app-images-viewer", inputs: { urls: { classPropertyName: "urls", publicName: "urls", isSignal: true, isRequired: false, transformFunction: null }, initialIndex: { classPropertyName: "initialIndex", publicName: "initialIndex", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, host: { listeners: { "document:keydown.escape": "onEscape()", "document:keydown.arrowleft": "onArrowLeft()", "document:keydown.arrowright": "onArrowRight()" } }, viewQueries: [{ propertyName: "thumbnailButtons", predicate: ["thumbnailButton"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"fixed inset-0 z-[2300] flex items-center justify-center bg-black/80 p-3 md:p-4\" role=\"dialog\" aria-modal=\"true\" aria-label=\"\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u0434 \u0444\u043E\u0442\u043E\">\n <button type=\"button\" class=\"absolute inset-0\" aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438 \u043F\u0435\u0440\u0435\u0433\u043B\u044F\u0434\" (click)=\"closeViewer()\"></button>\n <div class=\"relative z-[1] flex h-full w-full max-w-[1500px] flex-col items-center justify-center gap-4\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"absolute right-2 top-2 z-20 inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white md:right-3 md:top-3\"\n aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438\"\n (click)=\"closeViewer()\">\n \u2715\n </button>\n\n <div\n class=\"relative flex min-h-0 w-full flex-1 items-center justify-center overflow-hidden rounded-[18px] touch-pan-y\"\n (touchstart)=\"onTouchStart($event)\"\n (touchend)=\"onTouchEnd($event)\">\n <div class=\"h-full w-full overflow-hidden rounded-[18px]\">\n <div\n class=\"flex h-full w-full\"\n style=\"will-change: transform; backface-visibility: hidden;\"\n [style.transition]=\"imageViewerTransitionStyle\"\n [style.transform]=\"imageViewerTransform\">\n @for (url of urls(); track $index; let idx = $index) {\n <div class=\"flex h-full w-full min-w-full shrink-0 items-center justify-center overflow-hidden\">\n @if (isPreloadIndex(idx)) {\n <img\n [src]=\"getImageUrl(url, idx)\"\n [attr.alt]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n [attr.loading]=\"idx === currentIndex() ? 'eager' : 'lazy'\"\n decoding=\"async\"\n class=\"block max-h-[74vh] max-w-[96vw] object-contain md:max-h-[78vh]\">\n }\n </div>\n }\n </div>\n </div>\n\n @if (urls().length > 1) {\n <button type=\"button\" class=\"absolute left-0 top-0 z-10 flex h-full w-1/4 min-w-12 items-center justify-start pl-2 md:pl-3\" aria-label=\"\u041F\u043E\u043F\u0435\u0440\u0435\u0434\u043D\u0454 \u0444\u043E\u0442\u043E\" (click)=\"showPreviousImage()\">\n <span class=\"inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white\">\u2039</span>\n </button>\n <button type=\"button\" class=\"absolute right-0 top-0 z-10 flex h-full w-1/4 min-w-12 items-center justify-end pr-2 md:pr-3\" aria-label=\"\u041D\u0430\u0441\u0442\u0443\u043F\u043D\u0435 \u0444\u043E\u0442\u043E\" (click)=\"showNextImage()\">\n <span class=\"inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white\">\u203A</span>\n </button>\n }\n\n @if (currentUrl) {\n <div class=\"absolute bottom-3 right-3 z-20 inline-flex max-w-[calc(100%-1.5rem)] items-center gap-1.5 rounded-2xl bg-black/55 p-1.5 text-white shadow-[0_12px_28px_rgba(15,23,42,0.24)] backdrop-blur-md md:bottom-4 md:right-4\">\n <button\n type=\"button\"\n class=\"inline-flex h-9 items-center gap-2 rounded-xl px-2.5 text-xs font-semibold transition hover:bg-white/10\"\n [class.bg-emerald-500\\/20]=\"isOriginalVisible()\"\n [class.text-emerald-100]=\"isOriginalVisible()\"\n [attr.aria-pressed]=\"isOriginalVisible()\"\n aria-label=\"\u041F\u0435\u0440\u0435\u043C\u043A\u043D\u0443\u0442\u0438 \u043C\u0456\u0436 \u043F\u0440\u0435\u0432\u2019\u044E \u0442\u0430 \u043E\u0440\u0438\u0433\u0456\u043D\u0430\u043B\u043E\u043C\"\n (click)=\"toggleOriginal()\">\n <span>\u041E\u0440\u0438\u0433\u0456\u043D\u0430\u043B</span>\n <span\n class=\"relative inline-flex h-5 w-9 items-center rounded-full border p-0.5 transition-colors\"\n [class.bg-emerald-500]=\"isOriginalVisible()\"\n [class.bg-white\\/15]=\"!isOriginalVisible()\"\n [class.border-emerald-200]=\"isOriginalVisible()\"\n [class.border-white\\/45]=\"!isOriginalVisible()\"\n [class.ring-2]=\"isOriginalVisible()\"\n [class.ring-emerald-300\\/70]=\"isOriginalVisible()\">\n <span\n class=\"h-4 w-4 rounded-full bg-white shadow-sm transition-transform duration-200\"\n [class.translate-x-4]=\"isOriginalVisible()\">\n </span>\n </span>\n </button>\n <a\n class=\"inline-flex h-9 w-9 items-center justify-center rounded-xl transition hover:bg-white/10\"\n [href]=\"currentUrl\"\n download\n aria-label=\"\u0421\u043A\u0430\u0447\u0430\u0442\u0438 \u043E\u0440\u0438\u0433\u0456\u043D\u0430\u043B \u0444\u043E\u0442\u043E\">\n <lucide-icon [img]=\"downloadIcon\" [size]=\"19\" [strokeWidth]=\"2.25\"></lucide-icon>\n </a>\n </div>\n }\n </div>\n\n @if (urls().length > 1) {\n <div class=\"flex w-full flex-col items-center gap-3 pb-1 md:pb-2\">\n <div class=\"flex w-full max-w-[92vw] flex-nowrap justify-start gap-2 overflow-x-auto overflow-y-hidden overscroll-x-contain px-2 py-3 md:max-w-[80vw]\">\n @for (url of urls(); track url; let idx = $index) {\n <button\n #thumbnailButton\n type=\"button\"\n class=\"h-14 w-14 shrink-0 overflow-hidden rounded-xl border-2 border-transparent bg-white p-1 transition-transform duration-200\"\n [class.border-slate-900]=\"idx === currentIndex()\"\n [class.mx-2]=\"idx === currentIndex()\"\n [style.transform]=\"idx === currentIndex() ? 'scale(1.4)' : 'scale(1)'\"\n [class.origin-center]=\"idx === currentIndex()\"\n [class.relative]=\"idx === currentIndex()\"\n [class.z-10]=\"idx === currentIndex()\"\n [attr.aria-current]=\"idx === currentIndex() ? 'true' : null\"\n [attr.aria-label]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n (click)=\"selectImage(idx, $event)\">\n <img class=\"block aspect-square w-full rounded-lg object-contain\" [src]=\"getThumbnailUrl(url)\" [attr.alt]=\"'\u041C\u0456\u043D\u0456\u0430\u0442\u044E\u0440\u0430 ' + (idx + 1)\" loading=\"lazy\" decoding=\"async\">\n </button>\n }\n </div>\n <div class=\"rounded-full bg-white/95 px-3 py-1.5 text-xs font-semibold text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.2)]\">\n {{ currentIndex() + 1 }} / {{ urls().length }}\n </div>\n </div>\n }\n </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }] });
470
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: ImagesViewerComponent, isStandalone: true, selector: "app-images-viewer", inputs: { urls: { classPropertyName: "urls", publicName: "urls", isSignal: true, isRequired: false, transformFunction: null }, initialIndex: { classPropertyName: "initialIndex", publicName: "initialIndex", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, host: { listeners: { "document:keydown.escape": "onEscape()", "document:keydown.arrowleft": "onArrowLeft()", "document:keydown.arrowright": "onArrowRight()" } }, viewQueries: [{ propertyName: "currentImage", first: true, predicate: ["currentImage"], descendants: true, isSignal: true }, { propertyName: "thumbnailButtons", predicate: ["thumbnailButton"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"fixed inset-0 z-[2300] flex items-center justify-center bg-black/80 p-3 md:p-4\" role=\"dialog\" aria-modal=\"true\" aria-label=\"\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u0434 \u0444\u043E\u0442\u043E\">\n <button type=\"button\" class=\"absolute inset-0\" aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438 \u043F\u0435\u0440\u0435\u0433\u043B\u044F\u0434\" (click)=\"closeViewer()\"></button>\n <div class=\"relative z-[1] flex h-full w-full max-w-[1500px] flex-col items-center justify-center gap-4\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"absolute right-2 top-2 z-20 inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white md:right-3 md:top-3\"\n aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438\"\n (click)=\"closeViewer()\">\n \u2715\n </button>\n\n <div\n class=\"relative flex min-h-0 w-full flex-1 items-center justify-center overflow-hidden rounded-[18px]\"\n (touchstart)=\"onTouchStart($event)\"\n (touchend)=\"onTouchEnd($event)\">\n <div class=\"h-full w-full overflow-hidden rounded-[18px]\">\n <div\n class=\"flex h-full w-full\"\n style=\"will-change: transform; backface-visibility: hidden;\"\n [style.transition]=\"imageViewerTransitionStyle\"\n [style.transform]=\"imageViewerTransform\">\n @for (url of urls(); track $index; let idx = $index) {\n <div class=\"flex h-full w-full min-w-full shrink-0 items-center justify-center overflow-hidden\" (wheel)=\"onWheel($event)\">\n @if (isPreloadIndex(idx)) {\n @if (idx === currentIndex() && isOriginalVisible() && originalPreviewUnavailable()) {\n <div class=\"max-w-md rounded-2xl bg-white/95 px-6 py-5 text-center text-sm text-slate-700 shadow-xl\">\n <p class=\"font-semibold text-slate-900\">\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u0434 \u043E\u0440\u0438\u0433\u0456\u043D\u0430\u043B\u0443 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0438\u0439</p>\n <p class=\"mt-1\">\u0426\u0435\u0439 \u0444\u0430\u0439\u043B \u043C\u043E\u0436\u043D\u0430 \u043B\u0438\u0448\u0435 \u0437\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0438\u0442\u0438.</p>\n </div>\n } @else {\n @if (idx === currentIndex()) {\n <img\n #currentImage\n [src]=\"getImageUrl(url, idx)\"\n [attr.alt]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n loading=\"eager\"\n decoding=\"async\"\n (load)=\"onImageLoad()\"\n (error)=\"onImageError(idx)\"\n (dblclick)=\"toggleZoom()\"\n class=\"block max-h-[74vh] max-w-[96vw] object-contain md:max-h-[78vh]\">\n } @else {\n <img\n [src]=\"getImageUrl(url, idx)\"\n [attr.alt]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n loading=\"lazy\"\n decoding=\"async\"\n (error)=\"onImageError(idx)\"\n class=\"block max-h-[74vh] max-w-[96vw] object-contain md:max-h-[78vh]\">\n }\n }\n }\n </div>\n }\n </div>\n </div>\n\n @if (urls().length > 1) {\n <button type=\"button\" class=\"absolute left-2 top-1/2 z-10 inline-flex h-11 w-11 -translate-y-1/2 items-center justify-center rounded-full md:left-3\" aria-label=\"\u041F\u043E\u043F\u0435\u0440\u0435\u0434\u043D\u0454 \u0444\u043E\u0442\u043E\" (click)=\"showPreviousImage()\">\n <span class=\"inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white\">\u2039</span>\n </button>\n <button type=\"button\" class=\"absolute right-2 top-1/2 z-10 inline-flex h-11 w-11 -translate-y-1/2 items-center justify-center rounded-full md:right-3\" aria-label=\"\u041D\u0430\u0441\u0442\u0443\u043F\u043D\u0435 \u0444\u043E\u0442\u043E\" (click)=\"showNextImage()\">\n <span class=\"inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white\">\u203A</span>\n </button>\n }\n\n @if (currentUrl) {\n <div class=\"absolute bottom-3 right-3 z-20 inline-flex max-w-[calc(100%-1.5rem)] items-center gap-1.5 rounded-2xl bg-black/55 p-1.5 text-white shadow-[0_12px_28px_rgba(15,23,42,0.24)] backdrop-blur-md md:bottom-4 md:right-4\">\n <button type=\"button\" class=\"inline-flex h-9 w-9 items-center justify-center rounded-xl text-lg transition hover:bg-white/10\" aria-label=\"\u0417\u0431\u0456\u043B\u044C\u0448\u0438\u0442\u0438 \u0444\u043E\u0442\u043E\" (click)=\"zoomIn()\">+</button>\n <button type=\"button\" class=\"inline-flex h-9 w-9 items-center justify-center rounded-xl text-lg transition hover:bg-white/10\" aria-label=\"\u0417\u043C\u0435\u043D\u0448\u0438\u0442\u0438 \u0444\u043E\u0442\u043E\" (click)=\"zoomOut()\">\u2212</button>\n <button type=\"button\" class=\"inline-flex h-9 items-center justify-center rounded-xl px-2 text-xs font-semibold transition hover:bg-white/10\" aria-label=\"\u0421\u043A\u0438\u043D\u0443\u0442\u0438 \u043C\u0430\u0441\u0448\u0442\u0430\u0431\" (click)=\"resetZoom()\">100%</button>\n <button\n type=\"button\"\n class=\"inline-flex h-9 items-center gap-2 rounded-xl px-2.5 text-xs font-semibold transition hover:bg-white/10\"\n [class.bg-emerald-500\\/20]=\"isOriginalVisible()\"\n [class.text-emerald-100]=\"isOriginalVisible()\"\n [attr.aria-pressed]=\"isOriginalVisible()\"\n aria-label=\"\u041F\u0435\u0440\u0435\u043C\u043A\u043D\u0443\u0442\u0438 \u043C\u0456\u0436 \u043F\u0440\u0435\u0432\u2019\u044E \u0442\u0430 \u043E\u0440\u0438\u0433\u0456\u043D\u0430\u043B\u043E\u043C\"\n (click)=\"toggleOriginal()\">\n <span>\u041E\u0440\u0438\u0433\u0456\u043D\u0430\u043B</span>\n <span\n class=\"relative inline-flex h-5 w-9 items-center rounded-full border p-0.5 transition-colors\"\n [class.bg-emerald-500]=\"isOriginalVisible()\"\n [class.bg-white\\/15]=\"!isOriginalVisible()\"\n [class.border-emerald-200]=\"isOriginalVisible()\"\n [class.border-white\\/45]=\"!isOriginalVisible()\"\n [class.ring-2]=\"isOriginalVisible()\"\n [class.ring-emerald-300\\/70]=\"isOriginalVisible()\">\n <span\n class=\"h-4 w-4 rounded-full bg-white shadow-sm transition-transform duration-200\"\n [class.translate-x-4]=\"isOriginalVisible()\">\n </span>\n </span>\n </button>\n <a\n class=\"inline-flex h-9 w-9 items-center justify-center rounded-xl transition hover:bg-white/10\"\n [href]=\"getOriginalUrl(currentUrl)\"\n download\n aria-label=\"\u0421\u043A\u0430\u0447\u0430\u0442\u0438 \u043E\u0440\u0438\u0433\u0456\u043D\u0430\u043B \u0444\u043E\u0442\u043E\">\n <lucide-icon [img]=\"downloadIcon\" [size]=\"19\" [strokeWidth]=\"2.25\"></lucide-icon>\n </a>\n </div>\n }\n </div>\n\n @if (urls().length > 1) {\n <div class=\"flex w-full flex-col items-center gap-3 pb-1 md:pb-2\">\n <div class=\"flex w-full max-w-[92vw] flex-nowrap justify-start gap-2 overflow-x-auto overflow-y-hidden overscroll-x-contain px-2 py-3 md:max-w-[80vw]\">\n @for (url of urls(); track url; let idx = $index) {\n <button\n #thumbnailButton\n type=\"button\"\n class=\"h-14 w-14 shrink-0 overflow-hidden rounded-xl border-2 border-transparent bg-white p-1 transition-transform duration-200\"\n [class.border-slate-900]=\"idx === currentIndex()\"\n [class.mx-2]=\"idx === currentIndex()\"\n [style.transform]=\"idx === currentIndex() ? 'scale(1.4)' : 'scale(1)'\"\n [class.origin-center]=\"idx === currentIndex()\"\n [class.relative]=\"idx === currentIndex()\"\n [class.z-10]=\"idx === currentIndex()\"\n [attr.aria-current]=\"idx === currentIndex() ? 'true' : null\"\n [attr.aria-label]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n (click)=\"selectImage(idx, $event)\">\n <img class=\"block aspect-square w-full rounded-lg object-contain\" [src]=\"getThumbnailUrl(url)\" [attr.alt]=\"'\u041C\u0456\u043D\u0456\u0430\u0442\u044E\u0440\u0430 ' + (idx + 1)\" loading=\"lazy\" decoding=\"async\">\n </button>\n }\n </div>\n <div class=\"rounded-full bg-white/95 px-3 py-1.5 text-xs font-semibold text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.2)]\">\n {{ currentIndex() + 1 }} / {{ urls().length }}\n </div>\n </div>\n }\n </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: LucideAngularModule }, { kind: "component", type: i1.LucideAngularComponent, selector: "lucide-angular, lucide-icon, i-lucide, span-lucide", inputs: ["class", "name", "img", "color", "absoluteStrokeWidth", "size", "strokeWidth"] }] });
399
471
  }
400
472
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ImagesViewerComponent, decorators: [{
401
473
  type: Component,
402
- args: [{ selector: 'app-images-viewer', standalone: true, imports: [CommonModule, LucideAngularModule], template: "<div class=\"fixed inset-0 z-[2300] flex items-center justify-center bg-black/80 p-3 md:p-4\" role=\"dialog\" aria-modal=\"true\" aria-label=\"\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u0434 \u0444\u043E\u0442\u043E\">\n <button type=\"button\" class=\"absolute inset-0\" aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438 \u043F\u0435\u0440\u0435\u0433\u043B\u044F\u0434\" (click)=\"closeViewer()\"></button>\n <div class=\"relative z-[1] flex h-full w-full max-w-[1500px] flex-col items-center justify-center gap-4\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"absolute right-2 top-2 z-20 inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white md:right-3 md:top-3\"\n aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438\"\n (click)=\"closeViewer()\">\n \u2715\n </button>\n\n <div\n class=\"relative flex min-h-0 w-full flex-1 items-center justify-center overflow-hidden rounded-[18px] touch-pan-y\"\n (touchstart)=\"onTouchStart($event)\"\n (touchend)=\"onTouchEnd($event)\">\n <div class=\"h-full w-full overflow-hidden rounded-[18px]\">\n <div\n class=\"flex h-full w-full\"\n style=\"will-change: transform; backface-visibility: hidden;\"\n [style.transition]=\"imageViewerTransitionStyle\"\n [style.transform]=\"imageViewerTransform\">\n @for (url of urls(); track $index; let idx = $index) {\n <div class=\"flex h-full w-full min-w-full shrink-0 items-center justify-center overflow-hidden\">\n @if (isPreloadIndex(idx)) {\n <img\n [src]=\"getImageUrl(url, idx)\"\n [attr.alt]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n [attr.loading]=\"idx === currentIndex() ? 'eager' : 'lazy'\"\n decoding=\"async\"\n class=\"block max-h-[74vh] max-w-[96vw] object-contain md:max-h-[78vh]\">\n }\n </div>\n }\n </div>\n </div>\n\n @if (urls().length > 1) {\n <button type=\"button\" class=\"absolute left-0 top-0 z-10 flex h-full w-1/4 min-w-12 items-center justify-start pl-2 md:pl-3\" aria-label=\"\u041F\u043E\u043F\u0435\u0440\u0435\u0434\u043D\u0454 \u0444\u043E\u0442\u043E\" (click)=\"showPreviousImage()\">\n <span class=\"inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white\">\u2039</span>\n </button>\n <button type=\"button\" class=\"absolute right-0 top-0 z-10 flex h-full w-1/4 min-w-12 items-center justify-end pr-2 md:pr-3\" aria-label=\"\u041D\u0430\u0441\u0442\u0443\u043F\u043D\u0435 \u0444\u043E\u0442\u043E\" (click)=\"showNextImage()\">\n <span class=\"inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white\">\u203A</span>\n </button>\n }\n\n @if (currentUrl) {\n <div class=\"absolute bottom-3 right-3 z-20 inline-flex max-w-[calc(100%-1.5rem)] items-center gap-1.5 rounded-2xl bg-black/55 p-1.5 text-white shadow-[0_12px_28px_rgba(15,23,42,0.24)] backdrop-blur-md md:bottom-4 md:right-4\">\n <button\n type=\"button\"\n class=\"inline-flex h-9 items-center gap-2 rounded-xl px-2.5 text-xs font-semibold transition hover:bg-white/10\"\n [class.bg-emerald-500\\/20]=\"isOriginalVisible()\"\n [class.text-emerald-100]=\"isOriginalVisible()\"\n [attr.aria-pressed]=\"isOriginalVisible()\"\n aria-label=\"\u041F\u0435\u0440\u0435\u043C\u043A\u043D\u0443\u0442\u0438 \u043C\u0456\u0436 \u043F\u0440\u0435\u0432\u2019\u044E \u0442\u0430 \u043E\u0440\u0438\u0433\u0456\u043D\u0430\u043B\u043E\u043C\"\n (click)=\"toggleOriginal()\">\n <span>\u041E\u0440\u0438\u0433\u0456\u043D\u0430\u043B</span>\n <span\n class=\"relative inline-flex h-5 w-9 items-center rounded-full border p-0.5 transition-colors\"\n [class.bg-emerald-500]=\"isOriginalVisible()\"\n [class.bg-white\\/15]=\"!isOriginalVisible()\"\n [class.border-emerald-200]=\"isOriginalVisible()\"\n [class.border-white\\/45]=\"!isOriginalVisible()\"\n [class.ring-2]=\"isOriginalVisible()\"\n [class.ring-emerald-300\\/70]=\"isOriginalVisible()\">\n <span\n class=\"h-4 w-4 rounded-full bg-white shadow-sm transition-transform duration-200\"\n [class.translate-x-4]=\"isOriginalVisible()\">\n </span>\n </span>\n </button>\n <a\n class=\"inline-flex h-9 w-9 items-center justify-center rounded-xl transition hover:bg-white/10\"\n [href]=\"currentUrl\"\n download\n aria-label=\"\u0421\u043A\u0430\u0447\u0430\u0442\u0438 \u043E\u0440\u0438\u0433\u0456\u043D\u0430\u043B \u0444\u043E\u0442\u043E\">\n <lucide-icon [img]=\"downloadIcon\" [size]=\"19\" [strokeWidth]=\"2.25\"></lucide-icon>\n </a>\n </div>\n }\n </div>\n\n @if (urls().length > 1) {\n <div class=\"flex w-full flex-col items-center gap-3 pb-1 md:pb-2\">\n <div class=\"flex w-full max-w-[92vw] flex-nowrap justify-start gap-2 overflow-x-auto overflow-y-hidden overscroll-x-contain px-2 py-3 md:max-w-[80vw]\">\n @for (url of urls(); track url; let idx = $index) {\n <button\n #thumbnailButton\n type=\"button\"\n class=\"h-14 w-14 shrink-0 overflow-hidden rounded-xl border-2 border-transparent bg-white p-1 transition-transform duration-200\"\n [class.border-slate-900]=\"idx === currentIndex()\"\n [class.mx-2]=\"idx === currentIndex()\"\n [style.transform]=\"idx === currentIndex() ? 'scale(1.4)' : 'scale(1)'\"\n [class.origin-center]=\"idx === currentIndex()\"\n [class.relative]=\"idx === currentIndex()\"\n [class.z-10]=\"idx === currentIndex()\"\n [attr.aria-current]=\"idx === currentIndex() ? 'true' : null\"\n [attr.aria-label]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n (click)=\"selectImage(idx, $event)\">\n <img class=\"block aspect-square w-full rounded-lg object-contain\" [src]=\"getThumbnailUrl(url)\" [attr.alt]=\"'\u041C\u0456\u043D\u0456\u0430\u0442\u044E\u0440\u0430 ' + (idx + 1)\" loading=\"lazy\" decoding=\"async\">\n </button>\n }\n </div>\n <div class=\"rounded-full bg-white/95 px-3 py-1.5 text-xs font-semibold text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.2)]\">\n {{ currentIndex() + 1 }} / {{ urls().length }}\n </div>\n </div>\n }\n </div>\n</div>\n" }]
403
- }], ctorParameters: () => [], propDecorators: { urls: [{ type: i0.Input, args: [{ isSignal: true, alias: "urls", required: false }] }], initialIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialIndex", required: false }] }], thumbnailButtons: [{ type: i0.ViewChildren, args: ['thumbnailButton', { isSignal: true }] }], closed: [{ type: i0.Output, args: ["closed"] }], onEscape: [{
474
+ args: [{ selector: 'app-images-viewer', standalone: true, imports: [CommonModule, LucideAngularModule], template: "<div class=\"fixed inset-0 z-[2300] flex items-center justify-center bg-black/80 p-3 md:p-4\" role=\"dialog\" aria-modal=\"true\" aria-label=\"\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u0434 \u0444\u043E\u0442\u043E\">\n <button type=\"button\" class=\"absolute inset-0\" aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438 \u043F\u0435\u0440\u0435\u0433\u043B\u044F\u0434\" (click)=\"closeViewer()\"></button>\n <div class=\"relative z-[1] flex h-full w-full max-w-[1500px] flex-col items-center justify-center gap-4\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"absolute right-2 top-2 z-20 inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white md:right-3 md:top-3\"\n aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438\"\n (click)=\"closeViewer()\">\n \u2715\n </button>\n\n <div\n class=\"relative flex min-h-0 w-full flex-1 items-center justify-center overflow-hidden rounded-[18px]\"\n (touchstart)=\"onTouchStart($event)\"\n (touchend)=\"onTouchEnd($event)\">\n <div class=\"h-full w-full overflow-hidden rounded-[18px]\">\n <div\n class=\"flex h-full w-full\"\n style=\"will-change: transform; backface-visibility: hidden;\"\n [style.transition]=\"imageViewerTransitionStyle\"\n [style.transform]=\"imageViewerTransform\">\n @for (url of urls(); track $index; let idx = $index) {\n <div class=\"flex h-full w-full min-w-full shrink-0 items-center justify-center overflow-hidden\" (wheel)=\"onWheel($event)\">\n @if (isPreloadIndex(idx)) {\n @if (idx === currentIndex() && isOriginalVisible() && originalPreviewUnavailable()) {\n <div class=\"max-w-md rounded-2xl bg-white/95 px-6 py-5 text-center text-sm text-slate-700 shadow-xl\">\n <p class=\"font-semibold text-slate-900\">\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u0434 \u043E\u0440\u0438\u0433\u0456\u043D\u0430\u043B\u0443 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u0438\u0439</p>\n <p class=\"mt-1\">\u0426\u0435\u0439 \u0444\u0430\u0439\u043B \u043C\u043E\u0436\u043D\u0430 \u043B\u0438\u0448\u0435 \u0437\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0438\u0442\u0438.</p>\n </div>\n } @else {\n @if (idx === currentIndex()) {\n <img\n #currentImage\n [src]=\"getImageUrl(url, idx)\"\n [attr.alt]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n loading=\"eager\"\n decoding=\"async\"\n (load)=\"onImageLoad()\"\n (error)=\"onImageError(idx)\"\n (dblclick)=\"toggleZoom()\"\n class=\"block max-h-[74vh] max-w-[96vw] object-contain md:max-h-[78vh]\">\n } @else {\n <img\n [src]=\"getImageUrl(url, idx)\"\n [attr.alt]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n loading=\"lazy\"\n decoding=\"async\"\n (error)=\"onImageError(idx)\"\n class=\"block max-h-[74vh] max-w-[96vw] object-contain md:max-h-[78vh]\">\n }\n }\n }\n </div>\n }\n </div>\n </div>\n\n @if (urls().length > 1) {\n <button type=\"button\" class=\"absolute left-2 top-1/2 z-10 inline-flex h-11 w-11 -translate-y-1/2 items-center justify-center rounded-full md:left-3\" aria-label=\"\u041F\u043E\u043F\u0435\u0440\u0435\u0434\u043D\u0454 \u0444\u043E\u0442\u043E\" (click)=\"showPreviousImage()\">\n <span class=\"inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white\">\u2039</span>\n </button>\n <button type=\"button\" class=\"absolute right-2 top-1/2 z-10 inline-flex h-11 w-11 -translate-y-1/2 items-center justify-center rounded-full md:right-3\" aria-label=\"\u041D\u0430\u0441\u0442\u0443\u043F\u043D\u0435 \u0444\u043E\u0442\u043E\" (click)=\"showNextImage()\">\n <span class=\"inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white\">\u203A</span>\n </button>\n }\n\n @if (currentUrl) {\n <div class=\"absolute bottom-3 right-3 z-20 inline-flex max-w-[calc(100%-1.5rem)] items-center gap-1.5 rounded-2xl bg-black/55 p-1.5 text-white shadow-[0_12px_28px_rgba(15,23,42,0.24)] backdrop-blur-md md:bottom-4 md:right-4\">\n <button type=\"button\" class=\"inline-flex h-9 w-9 items-center justify-center rounded-xl text-lg transition hover:bg-white/10\" aria-label=\"\u0417\u0431\u0456\u043B\u044C\u0448\u0438\u0442\u0438 \u0444\u043E\u0442\u043E\" (click)=\"zoomIn()\">+</button>\n <button type=\"button\" class=\"inline-flex h-9 w-9 items-center justify-center rounded-xl text-lg transition hover:bg-white/10\" aria-label=\"\u0417\u043C\u0435\u043D\u0448\u0438\u0442\u0438 \u0444\u043E\u0442\u043E\" (click)=\"zoomOut()\">\u2212</button>\n <button type=\"button\" class=\"inline-flex h-9 items-center justify-center rounded-xl px-2 text-xs font-semibold transition hover:bg-white/10\" aria-label=\"\u0421\u043A\u0438\u043D\u0443\u0442\u0438 \u043C\u0430\u0441\u0448\u0442\u0430\u0431\" (click)=\"resetZoom()\">100%</button>\n <button\n type=\"button\"\n class=\"inline-flex h-9 items-center gap-2 rounded-xl px-2.5 text-xs font-semibold transition hover:bg-white/10\"\n [class.bg-emerald-500\\/20]=\"isOriginalVisible()\"\n [class.text-emerald-100]=\"isOriginalVisible()\"\n [attr.aria-pressed]=\"isOriginalVisible()\"\n aria-label=\"\u041F\u0435\u0440\u0435\u043C\u043A\u043D\u0443\u0442\u0438 \u043C\u0456\u0436 \u043F\u0440\u0435\u0432\u2019\u044E \u0442\u0430 \u043E\u0440\u0438\u0433\u0456\u043D\u0430\u043B\u043E\u043C\"\n (click)=\"toggleOriginal()\">\n <span>\u041E\u0440\u0438\u0433\u0456\u043D\u0430\u043B</span>\n <span\n class=\"relative inline-flex h-5 w-9 items-center rounded-full border p-0.5 transition-colors\"\n [class.bg-emerald-500]=\"isOriginalVisible()\"\n [class.bg-white\\/15]=\"!isOriginalVisible()\"\n [class.border-emerald-200]=\"isOriginalVisible()\"\n [class.border-white\\/45]=\"!isOriginalVisible()\"\n [class.ring-2]=\"isOriginalVisible()\"\n [class.ring-emerald-300\\/70]=\"isOriginalVisible()\">\n <span\n class=\"h-4 w-4 rounded-full bg-white shadow-sm transition-transform duration-200\"\n [class.translate-x-4]=\"isOriginalVisible()\">\n </span>\n </span>\n </button>\n <a\n class=\"inline-flex h-9 w-9 items-center justify-center rounded-xl transition hover:bg-white/10\"\n [href]=\"getOriginalUrl(currentUrl)\"\n download\n aria-label=\"\u0421\u043A\u0430\u0447\u0430\u0442\u0438 \u043E\u0440\u0438\u0433\u0456\u043D\u0430\u043B \u0444\u043E\u0442\u043E\">\n <lucide-icon [img]=\"downloadIcon\" [size]=\"19\" [strokeWidth]=\"2.25\"></lucide-icon>\n </a>\n </div>\n }\n </div>\n\n @if (urls().length > 1) {\n <div class=\"flex w-full flex-col items-center gap-3 pb-1 md:pb-2\">\n <div class=\"flex w-full max-w-[92vw] flex-nowrap justify-start gap-2 overflow-x-auto overflow-y-hidden overscroll-x-contain px-2 py-3 md:max-w-[80vw]\">\n @for (url of urls(); track url; let idx = $index) {\n <button\n #thumbnailButton\n type=\"button\"\n class=\"h-14 w-14 shrink-0 overflow-hidden rounded-xl border-2 border-transparent bg-white p-1 transition-transform duration-200\"\n [class.border-slate-900]=\"idx === currentIndex()\"\n [class.mx-2]=\"idx === currentIndex()\"\n [style.transform]=\"idx === currentIndex() ? 'scale(1.4)' : 'scale(1)'\"\n [class.origin-center]=\"idx === currentIndex()\"\n [class.relative]=\"idx === currentIndex()\"\n [class.z-10]=\"idx === currentIndex()\"\n [attr.aria-current]=\"idx === currentIndex() ? 'true' : null\"\n [attr.aria-label]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n (click)=\"selectImage(idx, $event)\">\n <img class=\"block aspect-square w-full rounded-lg object-contain\" [src]=\"getThumbnailUrl(url)\" [attr.alt]=\"'\u041C\u0456\u043D\u0456\u0430\u0442\u044E\u0440\u0430 ' + (idx + 1)\" loading=\"lazy\" decoding=\"async\">\n </button>\n }\n </div>\n <div class=\"rounded-full bg-white/95 px-3 py-1.5 text-xs font-semibold text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.2)]\">\n {{ currentIndex() + 1 }} / {{ urls().length }}\n </div>\n </div>\n }\n </div>\n</div>\n" }]
475
+ }], ctorParameters: () => [], propDecorators: { urls: [{ type: i0.Input, args: [{ isSignal: true, alias: "urls", required: false }] }], initialIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialIndex", required: false }] }], currentImage: [{ type: i0.ViewChild, args: ['currentImage', { isSignal: true }] }], thumbnailButtons: [{ type: i0.ViewChildren, args: ['thumbnailButton', { isSignal: true }] }], closed: [{ type: i0.Output, args: ["closed"] }], onEscape: [{
404
476
  type: HostListener,
405
477
  args: ['document:keydown.escape']
406
478
  }], onArrowLeft: [{
@@ -502,7 +574,7 @@ class DetailsGridComponent {
502
574
  const raw = String(value).trim();
503
575
  if (!raw)
504
576
  return null;
505
- return `/api/files/${encodeURIComponent(raw)}`;
577
+ return `/api/files/${encodeURIComponent(raw)}/preview?scale=25`;
506
578
  }
507
579
  formatEnum(raw, enumMap) {
508
580
  if (enumMap?.[raw]) {