@eternalheart/angular-file-preview 1.6.5

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.
Files changed (119) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +879 -0
  3. package/README.zh-CN.md +876 -0
  4. package/lib/LICENSE +22 -0
  5. package/lib/README.md +879 -0
  6. package/lib/components/preview/file-preview-toolbar.d.ts +19 -0
  7. package/lib/components/preview/nav-arrows.d.ts +25 -0
  8. package/lib/components/preview/renderer-outlet.d.ts +23 -0
  9. package/lib/components/preview/toolbar-button.d.ts +12 -0
  10. package/lib/components/resizable-split.d.ts +32 -0
  11. package/lib/di/locale.service.d.ts +12 -0
  12. package/lib/di/request.service.d.ts +20 -0
  13. package/lib/di/theme-mode.d.ts +6 -0
  14. package/lib/di/theme.service.d.ts +9 -0
  15. package/lib/fesm2022/eternalheart-angular-file-preview-avifLoader-BP3fgcMm-CZsJ9svm.mjs +55 -0
  16. package/lib/fesm2022/eternalheart-angular-file-preview-avifLoader-BP3fgcMm-CZsJ9svm.mjs.map +1 -0
  17. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-RendererError-DtNihCIX-DtNihCIX.mjs +58 -0
  18. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-RendererError-DtNihCIX-DtNihCIX.mjs.map +1 -0
  19. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-UobTf36G.mjs +5835 -0
  20. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-UobTf36G.mjs.map +1 -0
  21. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-base.types-D5x7QPey-D5x7QPey.mjs +17 -0
  22. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-base.types-D5x7QPey-D5x7QPey.mjs.map +1 -0
  23. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index--7d8Hgi0-D-sg24Qs.mjs +284 -0
  24. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index--7d8Hgi0-D-sg24Qs.mjs.map +1 -0
  25. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-6dZS_Cfs-B1itLV5R.mjs +703 -0
  26. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-6dZS_Cfs-B1itLV5R.mjs.map +1 -0
  27. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-B1ANUCTy-BRs7TMLn.mjs +231 -0
  28. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-B1ANUCTy-BRs7TMLn.mjs.map +1 -0
  29. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-BEaqpRV8-BidVIVlO.mjs +552 -0
  30. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-BEaqpRV8-BidVIVlO.mjs.map +1 -0
  31. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-BGqTRZbm-CAOLRw20.mjs +688 -0
  32. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-BGqTRZbm-CAOLRw20.mjs.map +1 -0
  33. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Bv3JRXKn-B4Wewc-4.mjs +273 -0
  34. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Bv3JRXKn-B4Wewc-4.mjs.map +1 -0
  35. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-C0McUoqT-DJ35bhIz.mjs +288 -0
  36. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-C0McUoqT-DJ35bhIz.mjs.map +1 -0
  37. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-C518mdUd-B5SCzrzU.mjs +550 -0
  38. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-C518mdUd-B5SCzrzU.mjs.map +1 -0
  39. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CDjKPqyW-CmX5sdkL.mjs +102 -0
  40. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CDjKPqyW-CmX5sdkL.mjs.map +1 -0
  41. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CHi9tOe0-NOeQYAnM.mjs +516 -0
  42. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CHi9tOe0-NOeQYAnM.mjs.map +1 -0
  43. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CQwhffym-DAGw4Xy_.mjs +823 -0
  44. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CQwhffym-DAGw4Xy_.mjs.map +1 -0
  45. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CSCTj7ft-BfqruUyY.mjs +164 -0
  46. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-CSCTj7ft-BfqruUyY.mjs.map +1 -0
  47. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Cb1RLXeO-Bj9gjS1Z.mjs +655 -0
  48. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Cb1RLXeO-Bj9gjS1Z.mjs.map +1 -0
  49. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Cw90Kdyg-BzrW_cyx.mjs +207 -0
  50. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Cw90Kdyg-BzrW_cyx.mjs.map +1 -0
  51. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-D35x1nwM-CugEEXjg.mjs +199 -0
  52. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-D35x1nwM-CugEEXjg.mjs.map +1 -0
  53. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-D9jJNzeM-mWX59Aj2.mjs +194 -0
  54. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-D9jJNzeM-mWX59Aj2.mjs.map +1 -0
  55. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DbmuvdvN-C29uLyt2.mjs +94 -0
  56. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DbmuvdvN-C29uLyt2.mjs.map +1 -0
  57. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DsNNZ59Q-C8MaSME6.mjs +468 -0
  58. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DsNNZ59Q-C8MaSME6.mjs.map +1 -0
  59. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DufW2Fuc-CTosCut6.mjs +193 -0
  60. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-DufW2Fuc-CTosCut6.mjs.map +1 -0
  61. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-TSTa_Q7E-DBhkL6pV.mjs +184 -0
  62. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-TSTa_Q7E-DBhkL6pV.mjs.map +1 -0
  63. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-pG0Ko5mI-DthQJg-X.mjs +492 -0
  64. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-pG0Ko5mI-DthQJg-X.mjs.map +1 -0
  65. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-shiki-highlight-CL9cG481-BKfwdOpN.mjs +68 -0
  66. package/lib/fesm2022/eternalheart-angular-file-preview-eternalheart-angular-file-preview-shiki-highlight-CL9cG481-BKfwdOpN.mjs.map +1 -0
  67. package/lib/fesm2022/eternalheart-angular-file-preview-heicLoader-CH_raQNn-BSXHd_oi.mjs +36 -0
  68. package/lib/fesm2022/eternalheart-angular-file-preview-heicLoader-CH_raQNn-BSXHd_oi.mjs.map +1 -0
  69. package/lib/fesm2022/eternalheart-angular-file-preview-jp2Loader-ldvCSsvm-DJEhfimG.mjs +121 -0
  70. package/lib/fesm2022/eternalheart-angular-file-preview-jp2Loader-ldvCSsvm-DJEhfimG.mjs.map +1 -0
  71. package/lib/fesm2022/eternalheart-angular-file-preview-psdLoader-D5CkupyY-D5Gegcdg.mjs +78 -0
  72. package/lib/fesm2022/eternalheart-angular-file-preview-psdLoader-D5CkupyY-D5Gegcdg.mjs.map +1 -0
  73. package/lib/fesm2022/eternalheart-angular-file-preview-rawLoader-0mLvxCNp-CCXn9Rdp.mjs +81 -0
  74. package/lib/fesm2022/eternalheart-angular-file-preview-rawLoader-0mLvxCNp-CCXn9Rdp.mjs.map +1 -0
  75. package/lib/fesm2022/eternalheart-angular-file-preview-tiffLoader-CmUAD1vO-0JqIws9t.mjs +59 -0
  76. package/lib/fesm2022/eternalheart-angular-file-preview-tiffLoader-CmUAD1vO-0JqIws9t.mjs.map +1 -0
  77. package/lib/fesm2022/eternalheart-angular-file-preview.mjs +2 -0
  78. package/lib/fesm2022/eternalheart-angular-file-preview.mjs.map +1 -0
  79. package/lib/file-preview-content.d.ts +65 -0
  80. package/lib/file-preview-embed.d.ts +30 -0
  81. package/lib/file-preview-modal.d.ts +34 -0
  82. package/lib/fp-core.d.ts +1 -0
  83. package/lib/index.css +2119 -0
  84. package/lib/index.d.ts +11 -0
  85. package/lib/renderers/Audio/index.d.ts +74 -0
  86. package/lib/renderers/Cad/index.d.ts +45 -0
  87. package/lib/renderers/Cad/toolbar.d.ts +15 -0
  88. package/lib/renderers/Csv/index.d.ts +26 -0
  89. package/lib/renderers/Doc/index.d.ts +19 -0
  90. package/lib/renderers/Docx/index.d.ts +42 -0
  91. package/lib/renderers/Epub/index.d.ts +57 -0
  92. package/lib/renderers/Font/font-preview-line.d.ts +26 -0
  93. package/lib/renderers/Font/index.d.ts +52 -0
  94. package/lib/renderers/Image/index.d.ts +79 -0
  95. package/lib/renderers/Json/index.d.ts +61 -0
  96. package/lib/renderers/Markdown/index.d.ts +35 -0
  97. package/lib/renderers/Mobi/index.d.ts +51 -0
  98. package/lib/renderers/Msg/index.d.ts +47 -0
  99. package/lib/renderers/Pdf/index.d.ts +55 -0
  100. package/lib/renderers/Ppt/index.d.ts +19 -0
  101. package/lib/renderers/Pptx/index.d.ts +29 -0
  102. package/lib/renderers/RendererError.d.ts +9 -0
  103. package/lib/renderers/RendererLoading.d.ts +7 -0
  104. package/lib/renderers/Subtitle/index.d.ts +25 -0
  105. package/lib/renderers/Text/index.d.ts +29 -0
  106. package/lib/renderers/Unsupported/index.d.ts +15 -0
  107. package/lib/renderers/Video/index.d.ts +28 -0
  108. package/lib/renderers/Xlsx/index.d.ts +25 -0
  109. package/lib/renderers/Xml/index.d.ts +26 -0
  110. package/lib/renderers/Zip/index.d.ts +58 -0
  111. package/lib/renderers/Zip/tree-item.d.ts +29 -0
  112. package/lib/renderers/base.types.d.ts +10 -0
  113. package/lib/renderers/lazy.d.ts +23 -0
  114. package/lib/renderers/registry.d.ts +13 -0
  115. package/lib/renderers/toolbar.types.d.ts +19 -0
  116. package/lib/types.d.ts +26 -0
  117. package/lib/utils/audio-player.d.ts +20 -0
  118. package/lib/utils/shiki-highlight.d.ts +7 -0
  119. package/package.json +119 -0
@@ -0,0 +1,655 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, inject, DestroyRef, signal, viewChild, computed, effect, untracked, afterNextRender, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import * as i1 from 'lucide-angular';
4
+ import { Loader2, ZoomOut, ZoomIn, Scan, Maximize2, RotateCcw, RotateCw, RefreshCw, LucideAngularModule } from 'lucide-angular';
5
+ import { L as LocaleService, R as RequestService, g as getFallbackTranslator, a as R0, i as ig, Z as Zg, W as Wg, p as pg } from './eternalheart-angular-file-preview-eternalheart-angular-file-preview-UobTf36G.mjs';
6
+ import { R as RendererError } from './eternalheart-angular-file-preview-eternalheart-angular-file-preview-RendererError-DtNihCIX-DtNihCIX.mjs';
7
+ import { T as ToolbarEventEmitter } from './eternalheart-angular-file-preview-eternalheart-angular-file-preview-base.types-D5x7QPey-D5x7QPey.mjs';
8
+
9
+ class ImageRenderer {
10
+ constructor() {
11
+ this.url = input.required();
12
+ this.fileSize = input(undefined);
13
+ this.file = input(undefined);
14
+ this.emitter = new ToolbarEventEmitter();
15
+ this.locale = inject(LocaleService, { optional: true });
16
+ this.request = inject(RequestService, { optional: true });
17
+ this.destroyRef = inject(DestroyRef);
18
+ this.t = this.locale?.t() ?? getFallbackTranslator();
19
+ this.loader2Icon = Loader2;
20
+ this.Math = Math;
21
+ this.zoom = signal(1);
22
+ this.rotation = signal(0);
23
+ this.loaded = signal(false);
24
+ this.error = signal(null);
25
+ this.decoding = signal(false);
26
+ this.decodeProgress = signal(0);
27
+ this.decodeError = signal(null);
28
+ this.imageSrc = signal('');
29
+ this.currentPage = signal(1);
30
+ this.totalPages = signal(1);
31
+ this.position = signal({ x: 0, y: 0 });
32
+ this.isDragging = signal(false);
33
+ this.naturalSize = signal({ width: 0, height: 0 });
34
+ this.dragStart = { x: 0, y: 0 };
35
+ this.blobUrl = null;
36
+ this.fileBlobCache = null;
37
+ this.loaderCache = null;
38
+ this.pageCache = new Map();
39
+ this.isTouchDevice = false;
40
+ this.touchStartDistance = 0;
41
+ this.touchStartZoom = 1;
42
+ this.touchStartPos = { x: 0, y: 0 };
43
+ this.lastTapTime = 0;
44
+ this.listenersEl = null;
45
+ this.containerRef = viewChild('containerRef');
46
+ this.sizeText = computed(() => {
47
+ const size = this.fileSize();
48
+ if (size == null)
49
+ return '';
50
+ return ` · ${R0(size)}`;
51
+ });
52
+ this.transformCss = computed(() => `translate(${this.position().x}px, ${this.position().y}px) scale(${this.zoom()}) rotate(${this.rotation()}deg)`);
53
+ this.getToolbarGroups = () => [
54
+ {
55
+ items: [
56
+ {
57
+ type: 'button',
58
+ icon: ZoomOut,
59
+ tooltip: this.t('toolbar.zoom_out'),
60
+ action: () => this.handleZoomOut(),
61
+ disabled: this.zoom() <= 0.01,
62
+ },
63
+ {
64
+ type: 'text',
65
+ content: `${Math.round(this.zoom() * 100)}%`,
66
+ minWidth: '3rem',
67
+ },
68
+ {
69
+ type: 'button',
70
+ icon: ZoomIn,
71
+ tooltip: this.t('toolbar.zoom_in'),
72
+ action: () => this.handleZoomIn(),
73
+ disabled: this.zoom() >= 10,
74
+ },
75
+ ],
76
+ },
77
+ {
78
+ items: [
79
+ {
80
+ type: 'button',
81
+ icon: Scan,
82
+ tooltip: this.t('toolbar.fit_to_window'),
83
+ action: () => this.handleFitToWidth(),
84
+ },
85
+ {
86
+ type: 'button',
87
+ icon: Maximize2,
88
+ tooltip: this.t('toolbar.original_size'),
89
+ action: () => this.handleOriginalSize(),
90
+ },
91
+ ],
92
+ },
93
+ {
94
+ items: [
95
+ {
96
+ type: 'button',
97
+ icon: RotateCcw,
98
+ tooltip: this.t('toolbar.rotate_left'),
99
+ action: () => this.handleRotateLeft(),
100
+ },
101
+ {
102
+ type: 'button',
103
+ icon: RotateCw,
104
+ tooltip: this.t('toolbar.rotate_right'),
105
+ action: () => this.handleRotateRight(),
106
+ },
107
+ ],
108
+ },
109
+ {
110
+ items: [
111
+ {
112
+ type: 'button',
113
+ icon: RefreshCw,
114
+ tooltip: this.t('toolbar.reset'),
115
+ action: () => this.handleReset(),
116
+ },
117
+ ],
118
+ },
119
+ ];
120
+ this.onToolbarChange = (listener) => this.emitter.subscribe(listener);
121
+ this.handleWheelNative = (e) => {
122
+ e.preventDefault();
123
+ e.stopPropagation();
124
+ const container = this.containerRef()?.nativeElement;
125
+ if (!container)
126
+ return;
127
+ const rect = container.getBoundingClientRect();
128
+ const mouseX = e.clientX - rect.left - rect.width / 2;
129
+ const mouseY = e.clientY - rect.top - rect.height / 2;
130
+ const delta = e.deltaY > 0 ? -0.05 : 0.05;
131
+ const prev = this.zoom();
132
+ const newZoom = Math.max(0.01, Math.min(10, prev + delta));
133
+ const scale = newZoom / prev;
134
+ const pos = this.position();
135
+ this.position.set(this.clampPosition({
136
+ x: mouseX - scale * (mouseX - pos.x),
137
+ y: mouseY - scale * (mouseY - pos.y),
138
+ }, newZoom));
139
+ this.zoom.set(newZoom);
140
+ };
141
+ this.handleTouchStart = (e) => {
142
+ this.isTouchDevice = true;
143
+ e.preventDefault();
144
+ const touches = e.touches;
145
+ if (touches.length === 1) {
146
+ this.isDragging.set(true);
147
+ const pos = this.position();
148
+ this.dragStart = {
149
+ x: touches[0].clientX - pos.x,
150
+ y: touches[0].clientY - pos.y,
151
+ };
152
+ const now = Date.now();
153
+ if (now - this.lastTapTime < 300) {
154
+ this.position.set({ x: 0, y: 0 });
155
+ this.zoom.set(1);
156
+ }
157
+ this.lastTapTime = now;
158
+ }
159
+ else if (touches.length === 2) {
160
+ this.isDragging.set(false);
161
+ const distance = Math.hypot(touches[1].clientX - touches[0].clientX, touches[1].clientY - touches[0].clientY);
162
+ this.touchStartDistance = distance;
163
+ this.touchStartZoom = this.zoom();
164
+ this.touchStartPos = { ...this.position() };
165
+ }
166
+ };
167
+ this.handleTouchMove = (e) => {
168
+ e.preventDefault();
169
+ const touches = e.touches;
170
+ if (touches.length === 1 && this.isDragging()) {
171
+ this.position.set(this.clampPosition({
172
+ x: touches[0].clientX - this.dragStart.x,
173
+ y: touches[0].clientY - this.dragStart.y,
174
+ }, this.zoom()));
175
+ }
176
+ else if (touches.length === 2) {
177
+ const container = this.containerRef()?.nativeElement;
178
+ if (!container)
179
+ return;
180
+ const distance = Math.hypot(touches[1].clientX - touches[0].clientX, touches[1].clientY - touches[0].clientY);
181
+ if (Math.abs(distance - this.touchStartDistance) < 5)
182
+ return;
183
+ const scale = distance / this.touchStartDistance;
184
+ const newZoom = Math.max(0.01, Math.min(10, this.touchStartZoom * scale));
185
+ const rect = container.getBoundingClientRect();
186
+ const centerX = (touches[0].clientX + touches[1].clientX) / 2 - rect.left - rect.width / 2;
187
+ const centerY = (touches[0].clientY + touches[1].clientY) / 2 - rect.top - rect.height / 2;
188
+ const zoomScale = newZoom / this.zoom();
189
+ this.position.set(this.clampPosition({
190
+ x: centerX - zoomScale * (centerX - this.touchStartPos.x),
191
+ y: centerY - zoomScale * (centerY - this.touchStartPos.y),
192
+ }, newZoom));
193
+ this.zoom.set(newZoom);
194
+ }
195
+ };
196
+ this.handleTouchEnd = () => {
197
+ this.isDragging.set(false);
198
+ this.touchStartDistance = 0;
199
+ };
200
+ effect(() => {
201
+ this.url();
202
+ this.file();
203
+ untracked(() => {
204
+ void this.load();
205
+ });
206
+ });
207
+ effect(() => {
208
+ this.zoom();
209
+ this.rotation();
210
+ this.emitter.notify();
211
+ });
212
+ afterNextRender(() => this.attachListeners());
213
+ this.destroyRef.onDestroy(() => this.cleanup());
214
+ }
215
+ handleZoomIn() {
216
+ this.zoom.set(Math.min(this.zoom() + 0.1, 10));
217
+ }
218
+ handleZoomOut() {
219
+ this.zoom.set(Math.max(this.zoom() - 0.1, 0.01));
220
+ }
221
+ handleRotateRight() {
222
+ this.rotation.set((this.rotation() + 90) % 360);
223
+ }
224
+ handleRotateLeft() {
225
+ this.rotation.set((this.rotation() - 90 + 360) % 360);
226
+ }
227
+ handleOriginalSize() {
228
+ this.zoom.set(1);
229
+ this.rotation.set(0);
230
+ this.position.set({ x: 0, y: 0 });
231
+ }
232
+ handleReset() {
233
+ this.handleFitToWidth();
234
+ }
235
+ handleFitToWidth() {
236
+ const container = this.containerRef()?.nativeElement;
237
+ const size = this.naturalSize();
238
+ if (container && size.width > 0 && size.height > 0) {
239
+ const scaleX = container.clientWidth / size.width;
240
+ const scaleY = container.clientHeight / size.height;
241
+ this.zoom.set(Math.max(0.01, Math.min(10, Math.min(scaleX, scaleY))));
242
+ this.position.set({ x: 0, y: 0 });
243
+ }
244
+ }
245
+ revokeBlobs() {
246
+ if (this.blobUrl) {
247
+ URL.revokeObjectURL(this.blobUrl);
248
+ this.blobUrl = null;
249
+ }
250
+ this.pageCache.forEach((url) => URL.revokeObjectURL(url));
251
+ this.pageCache.clear();
252
+ this.fileBlobCache = null;
253
+ this.loaderCache = null;
254
+ }
255
+ async load() {
256
+ this.imageSrc.set('');
257
+ this.loaded.set(false);
258
+ this.error.set(null);
259
+ this.decoding.set(false);
260
+ this.decodeError.set(null);
261
+ this.decodeProgress.set(0);
262
+ this.position.set({ x: 0, y: 0 });
263
+ this.zoom.set(1);
264
+ this.currentPage.set(1);
265
+ this.totalPages.set(1);
266
+ this.revokeBlobs();
267
+ const file = this.file();
268
+ if (!file) {
269
+ this.imageSrc.set(this.url());
270
+ return;
271
+ }
272
+ try {
273
+ const mimeType = await ig(file);
274
+ const loader = await Zg(mimeType);
275
+ if (!loader || !(await loader.needsDecode(mimeType))) {
276
+ this.imageSrc.set(this.url());
277
+ return;
278
+ }
279
+ this.decoding.set(true);
280
+ let fileBlob;
281
+ if (file instanceof Blob) {
282
+ fileBlob = file;
283
+ }
284
+ else if (file.file instanceof Blob) {
285
+ fileBlob = file.file;
286
+ }
287
+ else {
288
+ const fetcher = this.request?.fetcher() ?? ((u, init) => fetch(u, init));
289
+ const response = await fetcher(this.url());
290
+ if (!response.ok)
291
+ throw new Error('Failed to fetch file');
292
+ fileBlob = await response.blob();
293
+ }
294
+ this.fileBlobCache = fileBlob;
295
+ this.loaderCache = loader;
296
+ if (loader.getMetadata) {
297
+ try {
298
+ const metadata = await loader.getMetadata(fileBlob);
299
+ if (metadata.pageCount && metadata.pageCount > 1) {
300
+ this.totalPages.set(metadata.pageCount);
301
+ }
302
+ }
303
+ catch {
304
+ // 忽略元数据获取失败
305
+ }
306
+ }
307
+ const decodeOptions = {
308
+ page: 1,
309
+ fullQuality: false,
310
+ onProgress: (percent) => {
311
+ this.decodeProgress.set(percent);
312
+ },
313
+ };
314
+ let decodedBlob;
315
+ if (Wg(mimeType)) {
316
+ try {
317
+ decodedBlob = await pg(mimeType, await fileBlob.arrayBuffer(), { page: 1, fullQuality: false });
318
+ }
319
+ catch {
320
+ decodedBlob = await loader.decode(fileBlob, decodeOptions);
321
+ }
322
+ }
323
+ else {
324
+ decodedBlob = await loader.decode(fileBlob, decodeOptions);
325
+ }
326
+ const url = typeof decodedBlob === 'string'
327
+ ? decodedBlob
328
+ : URL.createObjectURL(decodedBlob);
329
+ this.blobUrl = url;
330
+ this.pageCache.set(1, url);
331
+ this.imageSrc.set(url);
332
+ this.decoding.set(false);
333
+ }
334
+ catch (err) {
335
+ const message = err instanceof Error ? err.message : this.t('image.decode_failed');
336
+ this.decodeError.set(message || this.t('image.decode_failed'));
337
+ this.decoding.set(false);
338
+ }
339
+ }
340
+ async handlePageChange(page) {
341
+ if (!this.fileBlobCache || !this.loaderCache)
342
+ return;
343
+ if (page < 1 || page > this.totalPages())
344
+ return;
345
+ const cached = this.pageCache.get(page);
346
+ if (cached) {
347
+ this.currentPage.set(page);
348
+ this.imageSrc.set(cached);
349
+ return;
350
+ }
351
+ this.decoding.set(true);
352
+ try {
353
+ const decodedBlob = await this.loaderCache.decode(this.fileBlobCache, { page });
354
+ const url = typeof decodedBlob === 'string'
355
+ ? decodedBlob
356
+ : URL.createObjectURL(decodedBlob);
357
+ if (this.pageCache.size >= 10) {
358
+ const firstKey = this.pageCache.keys().next().value;
359
+ if (firstKey !== undefined) {
360
+ const oldUrl = this.pageCache.get(firstKey);
361
+ if (oldUrl)
362
+ URL.revokeObjectURL(oldUrl);
363
+ this.pageCache.delete(firstKey);
364
+ }
365
+ }
366
+ this.pageCache.set(page, url);
367
+ this.currentPage.set(page);
368
+ this.imageSrc.set(url);
369
+ this.decoding.set(false);
370
+ }
371
+ catch (err) {
372
+ const message = err instanceof Error ? err.message : this.t('image.decode_failed');
373
+ this.decodeError.set(message || this.t('image.decode_failed'));
374
+ this.decoding.set(false);
375
+ }
376
+ }
377
+ clampPosition(pos, currentZoom) {
378
+ const container = this.containerRef()?.nativeElement;
379
+ const size = this.naturalSize();
380
+ if (!container || size.width === 0)
381
+ return pos;
382
+ const containerW = container.clientWidth;
383
+ const containerH = container.clientHeight;
384
+ const imgW = size.width * currentZoom;
385
+ const imgH = size.height * currentZoom;
386
+ const margin = Math.min(80, containerW * 0.15, containerH * 0.15);
387
+ const rangeX = (containerW + imgW) / 2 - margin;
388
+ const rangeY = (containerH + imgH) / 2 - margin;
389
+ return {
390
+ x: rangeX > 0 ? Math.max(-rangeX, Math.min(rangeX, pos.x)) : 0,
391
+ y: rangeY > 0 ? Math.max(-rangeY, Math.min(rangeY, pos.y)) : 0,
392
+ };
393
+ }
394
+ handleLoad(e) {
395
+ this.loaded.set(true);
396
+ const img = e.currentTarget;
397
+ this.naturalSize.set({ width: img.naturalWidth, height: img.naturalHeight });
398
+ const container = this.containerRef()?.nativeElement;
399
+ const size = this.naturalSize();
400
+ if (container && size.width > 0 && size.height > 0) {
401
+ const scaleX = container.clientWidth / size.width;
402
+ const scaleY = container.clientHeight / size.height;
403
+ const newZoom = Math.min(scaleX, scaleY);
404
+ this.zoom.set(Math.max(0.01, Math.min(10, newZoom)));
405
+ this.position.set({ x: 0, y: 0 });
406
+ }
407
+ }
408
+ handleError() {
409
+ this.error.set(this.t('image.load_failed'));
410
+ this.loaded.set(true);
411
+ }
412
+ handleDoubleClick() {
413
+ this.position.set({ x: 0, y: 0 });
414
+ this.zoom.set(1);
415
+ }
416
+ handleMouseDown(e) {
417
+ if (this.isTouchDevice)
418
+ return;
419
+ if (e.button !== 0)
420
+ return;
421
+ this.isDragging.set(true);
422
+ const pos = this.position();
423
+ this.dragStart = {
424
+ x: e.clientX - pos.x,
425
+ y: e.clientY - pos.y,
426
+ };
427
+ }
428
+ handleMouseMove(e) {
429
+ if (this.isTouchDevice)
430
+ return;
431
+ if (!this.isDragging())
432
+ return;
433
+ this.position.set(this.clampPosition({
434
+ x: e.clientX - this.dragStart.x,
435
+ y: e.clientY - this.dragStart.y,
436
+ }, this.zoom()));
437
+ }
438
+ handleMouseUp() {
439
+ if (this.isTouchDevice)
440
+ return;
441
+ this.isDragging.set(false);
442
+ }
443
+ attachListeners() {
444
+ const container = this.containerRef()?.nativeElement;
445
+ if (!container)
446
+ return;
447
+ this.listenersEl = container;
448
+ container.addEventListener('wheel', this.handleWheelNative, { passive: false });
449
+ container.addEventListener('touchstart', this.handleTouchStart, { passive: false });
450
+ container.addEventListener('touchmove', this.handleTouchMove, { passive: false });
451
+ container.addEventListener('touchend', this.handleTouchEnd);
452
+ container.addEventListener('touchcancel', this.handleTouchEnd);
453
+ }
454
+ detachListeners() {
455
+ const container = this.listenersEl;
456
+ if (!container)
457
+ return;
458
+ container.removeEventListener('wheel', this.handleWheelNative);
459
+ container.removeEventListener('touchstart', this.handleTouchStart);
460
+ container.removeEventListener('touchmove', this.handleTouchMove);
461
+ container.removeEventListener('touchend', this.handleTouchEnd);
462
+ container.removeEventListener('touchcancel', this.handleTouchEnd);
463
+ this.listenersEl = null;
464
+ }
465
+ cleanup() {
466
+ this.revokeBlobs();
467
+ this.detachListeners();
468
+ }
469
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: ImageRenderer, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
470
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.25", type: ImageRenderer, isStandalone: true, selector: "afp-image-renderer", inputs: { url: { classPropertyName: "url", publicName: "url", isSignal: true, isRequired: true, transformFunction: null }, fileSize: { classPropertyName: "fileSize", publicName: "fileSize", isSignal: true, isRequired: false, transformFunction: null }, file: { classPropertyName: "file", publicName: "file", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "afp-block afp-w-full afp-h-full" }, viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["containerRef"], descendants: true, isSignal: true }], ngImport: i0, template: `
471
+ <div
472
+ #containerRef
473
+ class="afp-relative afp-flex afp-items-center afp-justify-center afp-w-full afp-h-full afp-overflow-hidden"
474
+ [style.cursor]="isDragging() ? 'grabbing' : 'grab'"
475
+ [style.touch-action]="'none'"
476
+ (mousedown)="handleMouseDown($event)"
477
+ (mousemove)="handleMouseMove($event)"
478
+ (mouseup)="handleMouseUp()"
479
+ (mouseleave)="handleMouseUp()"
480
+ >
481
+ @if (decoding()) {
482
+ <div class="afp-absolute afp-inset-0 afp-flex afp-flex-col afp-items-center afp-justify-center afp-bg-surface-1/80 afp-z-10">
483
+ <i-lucide [img]="loader2Icon" class="afp-w-12 afp-h-12 afp-text-fg-primary afp-animate-spin" />
484
+ <p class="afp-mt-4 afp-text-fg-secondary">
485
+ {{ t('common.loading') }}
486
+ @if (decodeProgress() > 0) {
487
+ <span>{{ Math.round(decodeProgress()) }}%</span>
488
+ }
489
+ </p>
490
+ </div>
491
+ }
492
+
493
+ @if (decodeError(); as detail) {
494
+ <afp-renderer-error [message]="t('image.decode_failed')" [detail]="detail" />
495
+ }
496
+
497
+ @if (!loaded() && !error() && !decoding() && !decodeError()) {
498
+ <div class="afp-flex afp-items-center afp-justify-center">
499
+ <div class="afp-w-12 afp-h-12 afp-border-4 afp-border-line-strong afp-border-t-spinner-head afp-rounded-full afp-animate-spin"></div>
500
+ </div>
501
+ }
502
+
503
+ @if (error(); as err) {
504
+ <afp-renderer-error [message]="err" />
505
+ }
506
+
507
+ @if (imageSrc()) {
508
+ <img
509
+ #imgRef
510
+ [src]="imageSrc()"
511
+ alt=""
512
+ class="afp-max-w-none afp-select-none"
513
+ [class.afp-hidden]="!loaded() || !!error() || !!decodeError()"
514
+ [style.transform]="transformCss()"
515
+ [style.transform-origin]="'center'"
516
+ [style.transition]="isDragging() ? 'none' : 'transform 0.3s ease-out'"
517
+ [style.opacity]="loaded() && !error() && !decodeError() ? 1 : 0"
518
+ [draggable]="false"
519
+ (load)="handleLoad($event)"
520
+ (error)="handleError()"
521
+ (dblclick)="handleDoubleClick()"
522
+ />
523
+ }
524
+
525
+ @if (loaded() && !error() && naturalSize().width > 0) {
526
+ <div class="afp-absolute afp-bottom-2 afp-right-3 afp-text-[10px] afp-text-fg-disabled hover:afp-text-fg-secondary afp-transition-colors afp-pointer-events-auto afp-select-none afp-cursor-default">
527
+ {{ naturalSize().width }} × {{ naturalSize().height }}{{ sizeText() }}
528
+ </div>
529
+ }
530
+
531
+ @if (totalPages() > 1) {
532
+ <div class="afp-absolute afp-bottom-2 afp-left-1/2 -afp-translate-x-1/2 afp-flex afp-items-center afp-gap-2 afp-px-3 afp-py-1.5 afp-bg-surface-toolbar afp-border afp-border-line afp-rounded-lg afp-text-sm afp-text-fg-primary afp-shadow-md">
533
+ <button
534
+ type="button"
535
+ [disabled]="currentPage() <= 1 || decoding()"
536
+ class="afp-px-2 afp-py-0.5 afp-rounded hover:afp-bg-surface-nav-hover disabled:afp-opacity-40 disabled:afp-cursor-not-allowed"
537
+ (click)="handlePageChange(currentPage() - 1)"
538
+ >
539
+ {{ t('toolbar.prev_page') }}
540
+ </button>
541
+ <span class="afp-text-fg-secondary afp-tabular-nums">
542
+ {{ currentPage() }} / {{ totalPages() }}
543
+ </span>
544
+ <button
545
+ type="button"
546
+ [disabled]="currentPage() >= totalPages() || decoding()"
547
+ class="afp-px-2 afp-py-0.5 afp-rounded hover:afp-bg-surface-nav-hover disabled:afp-opacity-40 disabled:afp-cursor-not-allowed"
548
+ (click)="handlePageChange(currentPage() + 1)"
549
+ >
550
+ {{ t('toolbar.next_page') }}
551
+ </button>
552
+ </div>
553
+ }
554
+ </div>
555
+ `, isInline: true, dependencies: [{ 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"] }, { kind: "component", type: RendererError, selector: "afp-renderer-error", inputs: ["message", "detail", "showIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
556
+ }
557
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.25", ngImport: i0, type: ImageRenderer, decorators: [{
558
+ type: Component,
559
+ args: [{
560
+ selector: 'afp-image-renderer',
561
+ standalone: true,
562
+ imports: [LucideAngularModule, RendererError],
563
+ changeDetection: ChangeDetectionStrategy.OnPush,
564
+ host: { class: 'afp-block afp-w-full afp-h-full' },
565
+ template: `
566
+ <div
567
+ #containerRef
568
+ class="afp-relative afp-flex afp-items-center afp-justify-center afp-w-full afp-h-full afp-overflow-hidden"
569
+ [style.cursor]="isDragging() ? 'grabbing' : 'grab'"
570
+ [style.touch-action]="'none'"
571
+ (mousedown)="handleMouseDown($event)"
572
+ (mousemove)="handleMouseMove($event)"
573
+ (mouseup)="handleMouseUp()"
574
+ (mouseleave)="handleMouseUp()"
575
+ >
576
+ @if (decoding()) {
577
+ <div class="afp-absolute afp-inset-0 afp-flex afp-flex-col afp-items-center afp-justify-center afp-bg-surface-1/80 afp-z-10">
578
+ <i-lucide [img]="loader2Icon" class="afp-w-12 afp-h-12 afp-text-fg-primary afp-animate-spin" />
579
+ <p class="afp-mt-4 afp-text-fg-secondary">
580
+ {{ t('common.loading') }}
581
+ @if (decodeProgress() > 0) {
582
+ <span>{{ Math.round(decodeProgress()) }}%</span>
583
+ }
584
+ </p>
585
+ </div>
586
+ }
587
+
588
+ @if (decodeError(); as detail) {
589
+ <afp-renderer-error [message]="t('image.decode_failed')" [detail]="detail" />
590
+ }
591
+
592
+ @if (!loaded() && !error() && !decoding() && !decodeError()) {
593
+ <div class="afp-flex afp-items-center afp-justify-center">
594
+ <div class="afp-w-12 afp-h-12 afp-border-4 afp-border-line-strong afp-border-t-spinner-head afp-rounded-full afp-animate-spin"></div>
595
+ </div>
596
+ }
597
+
598
+ @if (error(); as err) {
599
+ <afp-renderer-error [message]="err" />
600
+ }
601
+
602
+ @if (imageSrc()) {
603
+ <img
604
+ #imgRef
605
+ [src]="imageSrc()"
606
+ alt=""
607
+ class="afp-max-w-none afp-select-none"
608
+ [class.afp-hidden]="!loaded() || !!error() || !!decodeError()"
609
+ [style.transform]="transformCss()"
610
+ [style.transform-origin]="'center'"
611
+ [style.transition]="isDragging() ? 'none' : 'transform 0.3s ease-out'"
612
+ [style.opacity]="loaded() && !error() && !decodeError() ? 1 : 0"
613
+ [draggable]="false"
614
+ (load)="handleLoad($event)"
615
+ (error)="handleError()"
616
+ (dblclick)="handleDoubleClick()"
617
+ />
618
+ }
619
+
620
+ @if (loaded() && !error() && naturalSize().width > 0) {
621
+ <div class="afp-absolute afp-bottom-2 afp-right-3 afp-text-[10px] afp-text-fg-disabled hover:afp-text-fg-secondary afp-transition-colors afp-pointer-events-auto afp-select-none afp-cursor-default">
622
+ {{ naturalSize().width }} × {{ naturalSize().height }}{{ sizeText() }}
623
+ </div>
624
+ }
625
+
626
+ @if (totalPages() > 1) {
627
+ <div class="afp-absolute afp-bottom-2 afp-left-1/2 -afp-translate-x-1/2 afp-flex afp-items-center afp-gap-2 afp-px-3 afp-py-1.5 afp-bg-surface-toolbar afp-border afp-border-line afp-rounded-lg afp-text-sm afp-text-fg-primary afp-shadow-md">
628
+ <button
629
+ type="button"
630
+ [disabled]="currentPage() <= 1 || decoding()"
631
+ class="afp-px-2 afp-py-0.5 afp-rounded hover:afp-bg-surface-nav-hover disabled:afp-opacity-40 disabled:afp-cursor-not-allowed"
632
+ (click)="handlePageChange(currentPage() - 1)"
633
+ >
634
+ {{ t('toolbar.prev_page') }}
635
+ </button>
636
+ <span class="afp-text-fg-secondary afp-tabular-nums">
637
+ {{ currentPage() }} / {{ totalPages() }}
638
+ </span>
639
+ <button
640
+ type="button"
641
+ [disabled]="currentPage() >= totalPages() || decoding()"
642
+ class="afp-px-2 afp-py-0.5 afp-rounded hover:afp-bg-surface-nav-hover disabled:afp-opacity-40 disabled:afp-cursor-not-allowed"
643
+ (click)="handlePageChange(currentPage() + 1)"
644
+ >
645
+ {{ t('toolbar.next_page') }}
646
+ </button>
647
+ </div>
648
+ }
649
+ </div>
650
+ `,
651
+ }]
652
+ }], ctorParameters: () => [] });
653
+
654
+ export { ImageRenderer };
655
+ //# sourceMappingURL=eternalheart-angular-file-preview-eternalheart-angular-file-preview-index-Cb1RLXeO-Bj9gjS1Z.mjs.map