@libs-ui/components-preview-file 0.2.356-42 → 0.2.356-43

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.
package/README.md CHANGED
@@ -1,35 +1,33 @@
1
1
  # @libs-ui/components-preview-file
2
2
 
3
- > Component xem trước tệp tin hỗ trợ nhiều định dạng từ hình ảnh đến tài liệu văn phòng, PDF thông qua các bộ Viewer phổ biến.
3
+ > Component modal xem trước tệp tin hỗ trợ đa định dạng: hình ảnh, PDF, tài liệu Office tự động chọn viewer phù hợp (Google Viewer, Microsoft Online Viewer, hoặc iframe trực tiếp).
4
4
 
5
5
  ## Giới thiệu
6
6
 
7
- `LibsUiComponentsPreviewFileComponent` là một component cung cấp giao diện modal để xem nhanh nội dung của tệp tin. hỗ trợ điều hướng giữa một danh sách tệp, các nút hành động tùy chỉnh (như download, share) tự động nhận diện định dạng để sử dụng viewer phù hợp (Google Viewer cho PDF/Images, Microsoft Viewer cho Office documents).
7
+ `LibsUiComponentsPreviewFileComponent` cung cấp giao diện modal để xem nhanh nội dung tệp tin không cần tải về. Component tự động nhận diện MIME type để định tuyến sang viewer tối ưu: hình ảnh hiển thị trực tiếp, tài liệu Office dùng Microsoft Online Viewer, PDF các loại khác dùng Google Docs Viewer. Hỗ trợ điều hướng qua danh sách nhiều tệp bằng nút Prev/Next cho phép thêm các nút hành động tùy chỉnh trên thanh tiêu đề.
8
8
 
9
- ### Tính năng
9
+ ## Tính năng
10
10
 
11
- - ✅ **Hỗ trợ đa định dạng**: Image (JPEG, PNG, SVG), PDF, Office (DOCX, XLSX, PPTX).
12
- - ✅ **Viewer tích hợp**: Sử dụng Google Microsoft Online Viewers cho các tài liệu phức tạp.
13
- - ✅ **Điều hướng**: Hỗ trợ xem danh sách nhiều tệp với nút Prev/Next.
14
- - ✅ **Tùy biến hành động**: Cho phép thêm các nút điều khiển tùy chỉnh trên header.
15
- - ✅ **Lazy Loading**: Chỉ load viewer khi tệp được chọn.
16
- - ✅ **OnPush Change Detection**
17
- - ✅ **Angular Signals**
11
+ - ✅ **Hỗ trợ đa định dạng**: Hình ảnh (JPEG, PNG, GIF, SVG), PDF, Word (DOC/DOCX), Excel (XLS/XLSX), PowerPoint (PPT/PPTX)
12
+ - ✅ **Tự động chọn Viewer**: Microsoft Online Viewer cho Office, Google Docs Viewer cho PDF/các định dạng khác
13
+ - ✅ **Chế độ Iframe trực tiếp**: Bỏ qua map viewer dùng URL gốc làm src iframe (cho Google Drive, Dropbox share link)
14
+ - ✅ **Điều hướng Prev/Next**: Duyệt qua danh sách nhiều tệp với two-way binding index
15
+ - ✅ **Nút hành động tùy chỉnh**: Thêm Download, Share hoặc bất kỳ action nào lên header modal
16
+ - ✅ **Loading state**: Spinner cho hình ảnh, Skeleton cho iframe — reset tự động khi chuyển tệp
17
+ - ✅ **Fallback ảnh lỗi**: Hỗ trợ injection token `LINK_IMAGE_ERROR_TOKEN_INJECT` cho ảnh placeholder khi lỗi load
18
+ - ✅ **OnPush Change Detection** + **Angular Signals**
18
19
 
19
20
  ## Khi nào sử dụng
20
21
 
21
- - Khi cần xem nhanh nội dung của một hoặc nhiều tệp đính kèm.
22
- - Hỗ trợ duyệt album ảnh hoặc danh sách tài liệu PDF, Office.
23
- - Tránh việc phải tải tệp về máy chỉ để xem nội dung bản.
22
+ - Xem nhanh nội dung tệp đính kèm (hợp đồng, báo cáo, ảnh sản phẩm) mà không cần tải về máy
23
+ - Duyệt gallery ảnh hoặc danh sách tài liệu PDF/Office trong workflow phê duyệt
24
+ - Nhúng preview tệp từ Google Drive, Dropbox hoặc CDN nội bộ vào trong ứng dụng
25
+ - Cho phép người dùng thực hiện hành động (download, share) ngay từ giao diện xem trước
24
26
 
25
27
  ## Cài đặt
26
28
 
27
29
  ```bash
28
- # npm
29
30
  npm install @libs-ui/components-preview-file
30
-
31
- # yarn
32
- yarn add @libs-ui/components-preview-file
33
31
  ```
34
32
 
35
33
  ## Import
@@ -45,108 +43,361 @@ import { LibsUiComponentsPreviewFileComponent } from '@libs-ui/components-previe
45
43
  export class YourComponent {}
46
44
  ```
47
45
 
48
- ## Ví dụ
46
+ ## Ví dụ sử dụng
47
+
48
+ ### Ví dụ 1 — Cơ bản (xem danh sách tệp)
49
+
50
+ **TypeScript:**
51
+
52
+ ```typescript
53
+ import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
54
+ import { LibsUiComponentsPreviewFileComponent } from '@libs-ui/components-preview-file';
55
+ import { IFile } from '@libs-ui/interfaces-types';
56
+
57
+ @Component({
58
+ selector: 'app-document-list',
59
+ templateUrl: './document-list.component.html',
60
+ standalone: true,
61
+ imports: [LibsUiComponentsPreviewFileComponent],
62
+ changeDetection: ChangeDetectionStrategy.OnPush,
63
+ })
64
+ export class DocumentListComponent {
65
+ protected showPreview = signal(false);
66
+ protected currentIndex = signal(0);
67
+
68
+ protected fileList: IFile[] = [
69
+ {
70
+ name: 'Bao cao thang 6.pdf',
71
+ url: 'https://example.com/files/report-june.pdf',
72
+ mimetype: 'application/pdf',
73
+ },
74
+ {
75
+ name: 'Anh san pham.jpg',
76
+ url: 'https://example.com/images/product.jpg',
77
+ mimetype: 'image/jpeg',
78
+ },
79
+ {
80
+ name: 'Du lieu Excel.xlsx',
81
+ url: 'https://example.com/files/data.xlsx',
82
+ mimetype: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
83
+ },
84
+ ];
85
+
86
+ protected handlerOpenPreview(index: number): void {
87
+ this.currentIndex.set(index);
88
+ this.showPreview.set(true);
89
+ }
90
+
91
+ protected handlerClosePreview(): void {
92
+ this.showPreview.set(false);
93
+ }
94
+ }
95
+ ```
49
96
 
50
- ### Cơ bản
97
+ **HTML:**
51
98
 
52
99
  ```html
53
- <libs_ui-components-preview_file
54
- [data]="fileList"
55
- [(index)]="currentIndex"
56
- (outClose)="onClose()" />
100
+ <button (click)="handlerOpenPreview(0)">Xem tài liệu</button>
101
+
102
+ @if (showPreview()) {
103
+ <libs_ui-components-preview_file
104
+ [data]="fileList"
105
+ [(index)]="currentIndex"
106
+ (outClose)="handlerClosePreview()" />
107
+ }
108
+ ```
109
+
110
+ ---
111
+
112
+ ### Ví dụ 2 — Với nút hành động tùy chỉnh (Download, Share)
113
+
114
+ **TypeScript:**
115
+
116
+ ```typescript
117
+ import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
118
+ import { IButton, LibsUiComponentsButtonsButtonComponent } from '@libs-ui/components-buttons-button';
119
+ import { LibsUiComponentsPreviewFileComponent } from '@libs-ui/components-preview-file';
120
+ import { IFile } from '@libs-ui/interfaces-types';
121
+
122
+ @Component({
123
+ selector: 'app-file-gallery',
124
+ templateUrl: './file-gallery.component.html',
125
+ standalone: true,
126
+ imports: [LibsUiComponentsPreviewFileComponent, LibsUiComponentsButtonsButtonComponent],
127
+ changeDetection: ChangeDetectionStrategy.OnPush,
128
+ })
129
+ export class FileGalleryComponent {
130
+ protected showPreview = signal(false);
131
+ protected currentIndex = signal(0);
132
+
133
+ protected fileList: IFile[] = [
134
+ {
135
+ name: 'Hop dong dich vu.docx',
136
+ url: 'https://example.com/files/contract.docx',
137
+ mimetype: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
138
+ },
139
+ {
140
+ name: 'Thuyet trinh Q2.pptx',
141
+ url: 'https://example.com/files/presentation-q2.pptx',
142
+ mimetype: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
143
+ },
144
+ ];
145
+
146
+ protected customButtons: IButton[] = [
147
+ {
148
+ key: 'download',
149
+ label: 'Tai xuong',
150
+ type: 'button-primary',
151
+ classIconLeft: 'libs-ui-icon-download',
152
+ action: async (index: number) => {
153
+ const file = this.fileList[index];
154
+ window.open(file.url, '_blank');
155
+ },
156
+ },
157
+ {
158
+ key: 'share',
159
+ label: '',
160
+ type: 'button-outline',
161
+ classIconLeft: 'libs-ui-icon-share',
162
+ iconOnlyType: true,
163
+ action: async (index: number) => {
164
+ const file = this.fileList[index];
165
+ await navigator.clipboard.writeText(file.url || '');
166
+ },
167
+ },
168
+ ];
169
+
170
+ protected handlerOpenPreview(index: number): void {
171
+ this.currentIndex.set(index);
172
+ this.showPreview.set(true);
173
+ }
174
+
175
+ protected handlerClosePreview(): void {
176
+ this.showPreview.set(false);
177
+ }
178
+
179
+ protected handlerChangeIndex(newIndex: number): void {
180
+ // Cập nhật nút theo tệp hiện tại nếu cần
181
+ console.log('Chuyển sang tệp index:', newIndex);
182
+ }
183
+ }
184
+ ```
185
+
186
+ **HTML:**
187
+
188
+ ```html
189
+ <div class="flex flex-col gap-2">
190
+ @for (file of fileList; track file.url) {
191
+ <div class="flex items-center justify-between p-3 border rounded-lg">
192
+ <span>{{ file.name }}</span>
193
+ <button (click)="handlerOpenPreview($index)">Xem truoc</button>
194
+ </div>
195
+ }
196
+ </div>
197
+
198
+ @if (showPreview()) {
199
+ <libs_ui-components-preview_file
200
+ [data]="fileList"
201
+ [(index)]="currentIndex"
202
+ [buttons]="customButtons"
203
+ (outClose)="handlerClosePreview()"
204
+ (outChangeIndex)="handlerChangeIndex($event)" />
205
+ }
206
+ ```
207
+
208
+ ---
209
+
210
+ ### Ví dụ 3 — Chế độ Iframe trực tiếp (Google Drive, Dropbox)
211
+
212
+ Dùng khi URL đã là embed link sẵn (ví dụ Google Drive `/preview`), không cần qua Google Docs Viewer hay Microsoft Viewer.
213
+
214
+ **TypeScript:**
215
+
216
+ ```typescript
217
+ import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
218
+ import { LibsUiComponentsPreviewFileComponent } from '@libs-ui/components-preview-file';
219
+ import { IFile } from '@libs-ui/interfaces-types';
220
+
221
+ @Component({
222
+ selector: 'app-drive-preview',
223
+ templateUrl: './drive-preview.component.html',
224
+ standalone: true,
225
+ imports: [LibsUiComponentsPreviewFileComponent],
226
+ changeDetection: ChangeDetectionStrategy.OnPush,
227
+ })
228
+ export class DrivePreviewComponent {
229
+ protected showPreview = signal(false);
230
+ protected currentIndex = signal(0);
231
+
232
+ protected driveFiles: IFile[] = [
233
+ {
234
+ name: 'Tai lieu Google Drive',
235
+ url: 'https://drive.google.com/file/d/1B9Ke75VYIznY4cjyZTnXRsRYPkmJ1AgK/preview',
236
+ mimetype: 'application/pdf',
237
+ },
238
+ {
239
+ name: 'Dropbox Shared File',
240
+ url: 'https://www.dropbox.com/s/abc123/file.pdf?dl=0&raw=1',
241
+ mimetype: 'application/pdf',
242
+ },
243
+ ];
244
+
245
+ protected handlerOpen(): void {
246
+ this.currentIndex.set(0);
247
+ this.showPreview.set(true);
248
+ }
249
+
250
+ protected handlerClose(): void {
251
+ this.showPreview.set(false);
252
+ }
253
+ }
57
254
  ```
58
255
 
59
- ### Với nút bấm tùy chỉnh
256
+ **HTML:**
60
257
 
61
258
  ```html
62
- <libs_ui-components-preview_file
63
- [data]="fileList"
64
- [(index)]="currentIndex"
65
- [buttons]="customButtons"
66
- (outClose)="onClose()" />
259
+ <button (click)="handlerOpen()">Mo Google Drive Preview</button>
260
+
261
+ @if (showPreview()) {
262
+ <libs_ui-components-preview_file
263
+ [data]="driveFiles"
264
+ [(index)]="currentIndex"
265
+ [isSourceIframe]="true"
266
+ (outClose)="handlerClose()" />
267
+ }
67
268
  ```
68
269
 
69
- ## API
270
+ ---
271
+
272
+ ### Ví dụ 4 — Với `LINK_IMAGE_ERROR_TOKEN_INJECT` (ảnh placeholder khi lỗi)
273
+
274
+ ```typescript
275
+ import { Component } from '@angular/core';
276
+ import { LibsUiComponentsPreviewFileComponent } from '@libs-ui/components-preview-file';
277
+ import { LINK_IMAGE_ERROR_TOKEN_INJECT } from '@libs-ui/utils';
70
278
 
71
- ### libs_ui-components-preview_file
279
+ @Component({
280
+ // ...
281
+ imports: [LibsUiComponentsPreviewFileComponent],
282
+ providers: [
283
+ {
284
+ provide: LINK_IMAGE_ERROR_TOKEN_INJECT,
285
+ useValue: 'https://example.com/assets/image-error-placeholder.png',
286
+ },
287
+ ],
288
+ })
289
+ export class AppComponent {}
290
+ ```
72
291
 
73
- #### Inputs
292
+ ## @Input()
74
293
 
75
- | Property | Type | Default | Description |
76
- | ----------- | ----------- | ------------ | ------------------------------------------------------------- |
77
- | `[data]` | `IFile[]` | **Bắt buộc** | Danh sách các đối tượng tệp tin cần xem trước. |
78
- | `[(index)]` | `number` | **Bắt buộc** | Vị trí tệp hiện tại trong danh sách (hỗ trợ Two-way binding). |
79
- | `[buttons]` | `IButton[]` | `[]` | Danh sách các nút hành động bổ sung hiển thị trên header. |
80
- | `[zIndex]` | `number` | `undefined` | Thiết lập z-index cho modal preview. |
294
+ | Input | Type | Default | Mô tả | Ví dụ |
295
+ |---|---|---|---|---|
296
+ | `[data]` | `IFile[]` | **Bắt buộc** | Danh sách tệp cần xem trước. Phải là mảng `IFile[]`. | `[data]="fileList"` |
297
+ | `[(index)]` | `number` | **Bắt buộc** | Index tệp hiện tại trong mảng `data`. Hỗ trợ two-way binding (model). | `[(index)]="currentIndex"` |
298
+ | `[buttons]` | `IButton[]` | `undefined` | Danh sách nút hành động tùy chỉnh hiển thị trên header modal (Download, Share...). | `[buttons]="customButtons"` |
299
+ | `[zIndex]` | `number` | `undefined` | Z-index CSS cho modal. Dùng khi cần kiểm soát lớp hiển thị. | `[zIndex]="1050"` |
300
+ | `[isSourceIframe]` | `boolean` | `undefined` (falsy) | Bỏ qua logic map viewer — dùng `url` hoặc `origin_url` trực tiếp làm `src` của iframe. Dùng cho Google Drive share link, Dropbox, v.v. | `[isSourceIframe]="true"` |
81
301
 
82
- #### Outputs
302
+ ## @Output()
83
303
 
84
- | Property | Type | Description |
85
- | ------------ | ------ | --------------------------------------------------------------- |
86
- | `(outClose)` | `void` | Phát ra khi người dùng nhấn nút đóng hoặc click ra ngoài modal. |
304
+ | Output | Type | Mô tả | Handler TS | Binding HTML |
305
+ |---|---|---|---|---|
306
+ | `(outClose)` | `void` | Phát ra khi người dùng đóng modal (nhấn nút X hoặc click backdrop). | `handlerClosePreview(): void { this.showPreview.set(false); }` | `(outClose)="handlerClosePreview()"` |
307
+ | `(outChangeIndex)` | `number` | Phát ra index mới sau khi nhấn Prev/Next để chuyển tệp. | `handlerChangeIndex(index: number): void { console.log(index); }` | `(outChangeIndex)="handlerChangeIndex($event)"` |
87
308
 
88
309
  ## Types & Interfaces
89
310
 
311
+ Các type được import từ `@libs-ui/interfaces-types`:
312
+
313
+ ```typescript
314
+ import { IFile } from '@libs-ui/interfaces-types';
315
+ import { IButton } from '@libs-ui/components-buttons-button';
316
+ ```
317
+
90
318
  ```typescript
91
319
  export interface IFile {
92
320
  id?: string;
93
- name?: string;
94
- file?: File;
95
- size?: string;
96
- url?: string;
97
- origin_url?: string;
98
- mimetype?: string;
321
+ name?: string; // Tên tệp hiển thị trên header modal
322
+ file?: File; // Native File object (dùng khi upload)
323
+ size?: string; // Kích thước tệp (chuỗi, vd: "2.5MB")
324
+ isUploading?: boolean;
325
+ percentUploading?: number;
326
+ isUpdate?: boolean;
327
+ url?: string; // URL công khai để hiển thị tệp
328
+ origin_url?: string; // URL gốc (fallback khi url không có)
329
+ mimetype?: string; // MIME type — dùng để chọn viewer phù hợp
99
330
  type?: 'document' | 'image' | 'video' | 'audio';
100
- extension?: string;
331
+ error?: string;
332
+ isAvatar?: boolean;
101
333
  }
102
334
 
103
335
  export interface IButton {
104
- key: string;
105
- label?: string;
106
- type?: string;
107
- classIconLeft?: string;
108
- iconOnlyType?: boolean;
109
- action?: (index: number) => Promise<void>;
336
+ key: string; // ID duy nhất cho button
337
+ label?: string; // Text hiển thị
338
+ type?: string; // Kiểu button (button-primary, button-outline...)
339
+ classIconLeft?: string; // Class icon bên trái
340
+ classIconRight?: string; // Class icon bên phải
341
+ iconOnlyType?: boolean; // Chỉ hiện icon, ẩn label
342
+ classInclude?: string; // Class CSS bổ sung
343
+ action?: (index: number) => Promise<void>; // Callback nhận index tệp hiện tại
110
344
  }
111
345
  ```
112
346
 
113
- ## Styling
347
+ ## Logic ẩn quan trọng
114
348
 
115
- ### CSS Classes
349
+ ### 1. Tự động chọn Viewer theo MIME Type
116
350
 
117
- | Class | Description |
118
- | ------------------------- | -------------------------------------------------- |
119
- | `.preview-file-container` | Container chính của modal preview. |
120
- | `.preview-header` | Thanh tiêu đề chứa tên file và các nút điều hướng. |
121
- | `.preview-body` | Vùng hiển thị nội dung file (iframe/img). |
351
+ Component ưu tiên theo thứ tự:
122
352
 
123
- ## Công nghệ
353
+ 1. Nếu `isSourceIframe = true` → dùng `url` / `origin_url` làm src trực tiếp (bỏ qua tất cả logic map)
354
+ 2. Nếu MIME type thuộc nhóm Microsoft Office → dùng `https://view.officeapps.live.com/op/embed.aspx?src=...`
355
+ 3. Mọi trường hợp còn lại (PDF, ảnh embed, v.v.) → dùng `https://docs.google.com/viewer?url=...`
124
356
 
125
- | Technology | Version | Purpose |
126
- | ----------------------- | ------- | ----------------------- |
127
- | Angular | 18+ | Framework |
128
- | Google Viewer | - | PDF & Image Viewing |
129
- | Microsoft Office Online | - | Office Document Viewing |
130
- | Angular Signals | - | State management |
357
+ **Các MIME type được nhận diện là Office (dùng Microsoft Viewer):**
131
358
 
132
- ## Demo
359
+ | MIME Type | Loại tệp |
360
+ |---|---|
361
+ | `application/msword` | Word DOC |
362
+ | `application/vnd.openxmlformats-officedocument.wordprocessingml.document` | Word DOCX |
363
+ | `application/vnd.openxmlformats-officedocument.wordprocessingml.template` | Word DOTX |
364
+ | `application/vnd.ms-excel` | Excel XLS |
365
+ | `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` | Excel XLSX |
366
+ | `application/vnd.openxmlformats-officedocument.spreadsheetml.template` | Excel XLTX |
367
+ | `application/vnd.ms-excel.sheet.macroEnabled.12` | Excel XLSM |
368
+ | `application/vnd.ms-powerpoint` | PowerPoint PPT |
369
+ | `application/vnd.openxmlformats-officedocument.presentationml.presentation` | PowerPoint PPTX |
370
+ | `application/vnd.ms-powerpoint.presentation.macroEnabled.12` | PowerPoint PPTM |
371
+ | `application/vnd.openxmlformats-officedocument.presentationml.slideshow` | PowerPoint PPSX |
133
372
 
134
- ```bash
135
- npx nx serve core-ui
136
- ```
373
+ ### 2. Kiểm tra hình ảnh bằng Pipe
137
374
 
138
- Truy cập: `http://localhost:4200/preview-file`
375
+ Hình ảnh được render bằng thẻ `<img>` (không qua iframe) khi `LibsUiPipesCheckFileExtensionPipe` trả về `true` cho loại `'image'`. Pipe này kiểm tra cả `mimetype` lẫn extension từ tên tệp.
139
376
 
140
- ## Unit Tests
377
+ ### 3. Reset loading khi chuyển tệp
141
378
 
142
- ```bash
143
- # Chạy tests
144
- npx nx test components-preview-file
379
+ Khi `index` thay đổi, `computed()` tính lại `fileView` và dùng `untracked()` để set `loading = true` mà không tạo vòng lặp reactive. Trạng thái loading chỉ về `false` sau khi `(load)` event của `<img>` hoặc `<iframe>` kích hoạt.
145
380
 
146
- # Coverage
147
- npx nx test components-preview-file --coverage
148
- ```
381
+ ### 4. Bảo vệ điều hướng biên
382
+
383
+ Nút Prev chỉ hiển thị khi `index() > 0`. Nút Next chỉ hiển thị khi `index() < data().length - 1`. Component không tự động wrap vòng (không nhảy từ cuối về đầu).
384
+
385
+ ## Lưu ý quan trọng
386
+
387
+ ⚠️ **Yêu cầu kết nối Internet**: Google Viewer và Microsoft Online Viewer là dịch vụ bên ngoài. Tệp phải có URL công khai (public URL) để các viewer này truy cập được. Không hoạt động với URL nội bộ hoặc localhost.
149
388
 
150
- ## License
389
+ ⚠️ **CORS và embed permissions**: Một số máy chủ cấu hình header `X-Frame-Options: DENY` hoặc `Content-Security-Policy` chặn nhúng iframe. Kiểm tra cấu hình server nếu tệp không hiển thị được.
390
+
391
+ ⚠️ **Chỉ dùng HTTPS**: Trình duyệt sẽ chặn nội dung Mixed Content nếu trang web dùng HTTPS nhưng URL tệp là HTTP. Luôn dùng link HTTPS cho tệp cần preview.
392
+
393
+ ⚠️ **`isSourceIframe` cho Google Drive**: Khi chia sẻ file từ Google Drive, dùng link dạng `https://drive.google.com/file/d/{fileId}/preview` kết hợp với `[isSourceIframe]="true"`. Không dùng link `/view` thông thường vì Google Drive chặn nhúng iframe.
394
+
395
+ ⚠️ **Two-way binding bắt buộc cho `index`**: Input `index` là `model.required()`, phải truyền bằng `[(index)]` hoặc cả `[index]` lẫn `(indexChange)`. Không có giá trị mặc định.
396
+
397
+ ## Demo
398
+
399
+ ```bash
400
+ npx nx serve core-ui
401
+ ```
151
402
 
152
- MIT
403
+ Truy cập: `http://localhost:4500/components/preview-file`
@@ -1,6 +1,6 @@
1
1
  import { AsyncPipe } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { signal, computed, untracked, input, model, output, Optional, Inject, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { signal, computed, untracked, input, model, output, Component, ChangeDetectionStrategy, Optional, Inject } from '@angular/core';
4
4
  import { LibsUiComponentsButtonsButtonComponent } from '@libs-ui/components-buttons-button';
5
5
  import { LibsUiComponentsModalComponent } from '@libs-ui/components-modal';
6
6
  import { LibsUiComponentsPopoverComponent } from '@libs-ui/components-popover';
@@ -1 +1 @@
1
- {"version":3,"file":"libs-ui-components-preview-file.mjs","sources":["../../../../../libs-ui/components/preview-file/src/preview-file.component.ts","../../../../../libs-ui/components/preview-file/src/preview-file.component.html","../../../../../libs-ui/components/preview-file/src/libs-ui-components-preview-file.ts"],"sourcesContent":["import { AsyncPipe } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, computed, Inject, input, model, Optional, output, signal, untracked } from '@angular/core';\nimport { IButton, LibsUiComponentsButtonsButtonComponent } from '@libs-ui/components-buttons-button';\nimport { LibsUiComponentsModalComponent, TYPE_MODAL_EVENT } from '@libs-ui/components-modal';\nimport { LibsUiComponentsPopoverComponent } from '@libs-ui/components-popover';\nimport { LibsUiComponentsSkeletonComponent } from '@libs-ui/components-skeleton';\nimport { LibsUiComponentsSpinnerComponent } from '@libs-ui/components-spinner';\nimport { IEvent, IFile } from '@libs-ui/interfaces-types';\nimport { LibsUiPipesCheckFileExtensionPipe } from '@libs-ui/pipes-check-file-extension';\nimport { LibsUiPipesSecurityTrustPipe } from '@libs-ui/pipes-security-trust';\nimport { LINK_IMAGE_ERROR_TOKEN_INJECT } from '@libs-ui/utils';\n\n@Component({\n // eslint-disable-next-line @angular-eslint/component-selector\n selector: 'libs_ui-components-preview_file',\n templateUrl: './preview-file.component.html',\n standalone: true,\n imports: [\n AsyncPipe,\n LibsUiComponentsModalComponent,\n LibsUiPipesSecurityTrustPipe,\n LibsUiComponentsSkeletonComponent,\n LibsUiComponentsPopoverComponent,\n LibsUiComponentsButtonsButtonComponent,\n LibsUiPipesCheckFileExtensionPipe,\n LibsUiComponentsSpinnerComponent,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class LibsUiComponentsPreviewFileComponent {\n protected loading = signal(true);\n protected fileView = computed(() => {\n const file = this.data()[this.index()];\n\n if (!file) {\n return null;\n }\n untracked(() => this.loading.set(true));\n const mimeType = file.mimetype || file.file?.type;\n const url = file.url || file.origin_url;\n\n if (this.isSourceIframe()) {\n return { ...file, urlFile: url };\n }\n\n if (mimeType && this.mineTypeMicroSoftSupport().includes(mimeType)) {\n return { ...file, urlFile: `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(url || '')}` };\n }\n\n return { ...file, urlFile: `https://docs.google.com/a/umd.edu/viewer?url=${encodeURIComponent(url || '')}&embedded=true` };\n });\n\n readonly zIndex = input<number>();\n readonly index = model.required<number>();\n readonly data = input.required<IFile[]>();\n readonly buttons = input<IButton[]>();\n readonly isSourceIframe = input<boolean>();\n\n readonly outClose = output<void>();\n readonly outChangeIndex = output<number>();\n\n constructor(@Optional() @Inject(LINK_IMAGE_ERROR_TOKEN_INJECT) private readonly linkImageError: string) {}\n\n protected async handlerOnLoad() {\n this.loading.set(false);\n }\n\n protected async handlerClose(type: TYPE_MODAL_EVENT) {\n if (type === 'close') {\n this.outClose.emit();\n }\n }\n\n private mineTypeMicroSoftSupport() {\n return [\n 'application/msword',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n 'application/vnd.ms-excel.sheet.macroEnabled.12',\n 'application/vnd.ms-excel',\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',\n 'application/vnd.ms-powerpoint',\n 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',\n 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',\n ];\n }\n\n protected handlerPrev() {\n this.index.update((value) => value - 1);\n this.outChangeIndex.emit(this.index());\n }\n\n protected handlerNext() {\n this.index.update((value) => value + 1);\n this.outChangeIndex.emit(this.index());\n }\n\n protected async handlerImageError(event: Event) {\n if (!this.linkImageError) {\n return;\n }\n (event as IEvent).target.src = this.linkImageError;\n }\n}\n","<libs_ui-components-modal\n [minWidth]=\"'1200px'\"\n [headerConfig]=\"{ ignoreHeaderTheme: true, classButtonCloseInclude: '!mr-[24px]' }\"\n [bodyConfig]=\"{ classInclude: '!p-0', scrollOverlayOptions: { scrollX: 'hidden', scrollY: 'hidden' } }\"\n [footerConfig]=\"{ hidden: true }\"\n [zIndex]=\"zIndex()\"\n [buttonsFooter]=\"[]\"\n [mode]=\"'center'\"\n (outEvent)=\"handlerClose($event)\">\n <div class=\"libs-ui-modal-header-custom w-full flex items-center justify-between min-w-0\">\n <div class=\"w-full ml-[24px] mr-[8px] flex justify-between items-center min-w-0 gap-[16px]\">\n <div class=\"w-2/8 flex-1\"></div>\n <div class=\"flex libs-ui-font-h4s min-w-0 w-4/8 justify-center flex-1\">\n <libs_ui-components-popover\n [type]=\"'text'\"\n [config]=\"{ zIndex: zIndex() }\">\n {{ fileView()?.name }}\n </libs_ui-components-popover>\n </div>\n @if (buttons()?.length) {\n <div class=\"flex gap-[8px] items-center w-2/8 justify-end flex-1\">\n @for (button of buttons(); track button.key) {\n <libs_ui-components-buttons-button\n [type]=\"button.type || 'button-third'\"\n [label]=\"button.label || ''\"\n [classIconLeft]=\"button.classIconLeft || ''\"\n [classIconRight]=\"button.classIconRight || ''\"\n [iconOnlyType]=\"button.iconOnlyType || false\"\n [classInclude]=\"button.classInclude || '!py-[4px] !px-[12px]'\"\n (outClick)=\"button.action?.(index())\" />\n }\n <div class=\"h-[12px] libs-ui-border-left-general\"></div>\n </div>\n }\n </div>\n </div>\n <div class=\"libs-ui-modal-body-custom h-full w-full flex rounded-b-[8px] overflow-hidden relative\">\n @if (index() > 0) {\n <div class=\"absolute top-[50%] left-[24px] transform -translate-y-[50%] z-[1]\">\n <libs_ui-components-buttons-button\n [type]=\"'button-outline'\"\n [iconOnlyType]=\"true\"\n [classInclude]=\"'!p-[11px] !rounded-[8px]'\"\n [classIconLeft]=\"'libs-ui-icon-chevron-right rotate-180 before:!text-[20px]'\"\n (outClick)=\"handlerPrev()\" />\n </div>\n }\n @if (fileView(); as fileView) {\n @if (fileView | LibsUiPipesCheckFileExtensionPipe: 'image') {\n <div class=\"w-full h-full relative flex items-center justify-center\">\n @if (fileView.url || fileView.origin_url; as url) {\n <img\n [src]=\"url\"\n alt=\"\"\n class=\"max-w-full max-h-full object-contain\"\n (load)=\"handlerOnLoad()\"\n (error)=\"handlerImageError($event)\" />\n @if (loading()) {\n <libs_ui-components-spinner [size]=\"'medium'\" />\n }\n }\n </div>\n } @else {\n <iframe\n id=\"google\"\n title=\"File preview\"\n [class.hidden]=\"loading()\"\n [src]=\"fileView.urlFile ?? '' | LibsUiPipesSecurityTrustPipe: 'resourceUrl' | async\"\n width=\"100%\"\n height=\"100%\"\n class=\"border-0\"\n allowfullscreen=\"allowfullscreen\"\n (load)=\"handlerOnLoad()\"></iframe>\n @if (loading()) {\n <libs_ui-components-skeleton [class]=\"'w-full h-full'\" />\n }\n }\n @if (index() < data().length - 1) {\n <div class=\"absolute top-[50%] right-[24px] transform -translate-y-[50%] z-[1]\">\n <libs_ui-components-buttons-button\n [type]=\"'button-outline'\"\n [iconOnlyType]=\"true\"\n [classInclude]=\"'!p-[11px] !rounded-[8px]'\"\n [classIconLeft]=\"'libs-ui-icon-chevron-right before:!text-[20px]'\"\n (outClick)=\"handlerNext()\" />\n </div>\n }\n }\n </div>\n</libs_ui-components-modal>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;;;;;;MA6Ba,oCAAoC,CAAA;AAgCiC,IAAA,cAAA;AA/BtE,IAAA,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC;AACtB,IAAA,QAAQ,GAAG,QAAQ,CAAC,MAAK;AACjC,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QAEtC,IAAI,CAAC,IAAI,EAAE;AACT,YAAA,OAAO,IAAI;QACb;AACA,QAAA,SAAS,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,IAAI;QACjD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,UAAU;AAEvC,QAAA,IAAI,IAAI,CAAC,cAAc,EAAE,EAAE;YACzB,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE;QAClC;AAEA,QAAA,IAAI,QAAQ,IAAI,IAAI,CAAC,wBAAwB,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE;AAClE,YAAA,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,CAAA,mDAAA,EAAsD,kBAAkB,CAAC,GAAG,IAAI,EAAE,CAAC,CAAA,CAAE,EAAE;QACpH;AAEA,QAAA,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,CAAA,6CAAA,EAAgD,kBAAkB,CAAC,GAAG,IAAI,EAAE,CAAC,CAAA,cAAA,CAAgB,EAAE;AAC5H,IAAA,CAAC,CAAC;IAEO,MAAM,GAAG,KAAK,EAAU;AACxB,IAAA,KAAK,GAAG,KAAK,CAAC,QAAQ,EAAU;AAChC,IAAA,IAAI,GAAG,KAAK,CAAC,QAAQ,EAAW;IAChC,OAAO,GAAG,KAAK,EAAa;IAC5B,cAAc,GAAG,KAAK,EAAW;IAEjC,QAAQ,GAAG,MAAM,EAAQ;IACzB,cAAc,GAAG,MAAM,EAAU;AAE1C,IAAA,WAAA,CAAgF,cAAsB,EAAA;QAAtB,IAAA,CAAA,cAAc,GAAd,cAAc;IAAW;AAE/F,IAAA,MAAM,aAAa,GAAA;AAC3B,QAAA,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;IACzB;IAEU,MAAM,YAAY,CAAC,IAAsB,EAAA;AACjD,QAAA,IAAI,IAAI,KAAK,OAAO,EAAE;AACpB,YAAA,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE;QACtB;IACF;IAEQ,wBAAwB,GAAA;QAC9B,OAAO;YACL,oBAAoB;YACpB,yEAAyE;YACzE,yEAAyE;YACzE,mEAAmE;YACnE,gDAAgD;YAChD,0BAA0B;YAC1B,sEAAsE;YACtE,+BAA+B;YAC/B,2EAA2E;YAC3E,4DAA4D;YAC5D,wEAAwE;SACzE;IACH;IAEU,WAAW,GAAA;AACnB,QAAA,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,KAAK,KAAK,GAAG,CAAC,CAAC;QACvC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;IACxC;IAEU,WAAW,GAAA;AACnB,QAAA,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,KAAK,KAAK,GAAG,CAAC,CAAC;QACvC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;IACxC;IAEU,MAAM,iBAAiB,CAAC,KAAY,EAAA;AAC5C,QAAA,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE;YACxB;QACF;QACC,KAAgB,CAAC,MAAM,CAAC,GAAG,GAAG,IAAI,CAAC,cAAc;IACpD;AA3EW,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,kBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,oCAAoC,kBAgCf,6BAA6B,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAhClD,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,oCAAoC,yzBC7BjD,20HA0FA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EDxEI,SAAS,EAAA,IAAA,EAAA,OAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EACT,8BAA8B,EAAA,QAAA,EAAA,0BAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,MAAA,EAAA,uBAAA,EAAA,8BAAA,EAAA,kCAAA,EAAA,2CAAA,EAAA,0BAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,UAAA,EAAA,WAAA,EAAA,UAAA,EAAA,cAAA,EAAA,SAAA,EAAA,6BAAA,EAAA,0BAAA,EAAA,qBAAA,EAAA,cAAA,EAAA,YAAA,EAAA,cAAA,EAAA,eAAA,EAAA,OAAA,EAAA,mBAAA,EAAA,iBAAA,EAAA,mBAAA,CAAA,EAAA,OAAA,EAAA,CAAA,YAAA,EAAA,aAAA,EAAA,cAAA,EAAA,gBAAA,EAAA,iBAAA,EAAA,gBAAA,EAAA,eAAA,EAAA,qBAAA,EAAA,kBAAA,EAAA,UAAA,EAAA,oBAAA,EAAA,UAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EAC9B,4BAA4B,EAAA,IAAA,EAAA,8BAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAC5B,iCAAiC,4FACjC,gCAAgC,EAAA,QAAA,EAAA,+DAAA,EAAA,MAAA,EAAA,CAAA,SAAA,EAAA,WAAA,EAAA,MAAA,EAAA,MAAA,EAAA,QAAA,EAAA,mBAAA,EAAA,kBAAA,EAAA,6BAAA,EAAA,cAAA,EAAA,0CAAA,EAAA,4BAAA,EAAA,kCAAA,EAAA,8BAAA,EAAA,oBAAA,CAAA,EAAA,OAAA,EAAA,CAAA,UAAA,EAAA,wBAAA,EAAA,wBAAA,EAAA,qBAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAChC,sCAAsC,EAAA,QAAA,EAAA,mCAAA,EAAA,MAAA,EAAA,CAAA,WAAA,EAAA,MAAA,EAAA,cAAA,EAAA,YAAA,EAAA,OAAA,EAAA,SAAA,EAAA,WAAA,EAAA,WAAA,EAAA,cAAA,EAAA,eAAA,EAAA,gBAAA,EAAA,YAAA,EAAA,cAAA,EAAA,SAAA,EAAA,4BAAA,EAAA,QAAA,EAAA,mBAAA,EAAA,eAAA,EAAA,aAAA,EAAA,yBAAA,EAAA,+BAAA,EAAA,oBAAA,EAAA,UAAA,EAAA,mCAAA,CAAA,EAAA,OAAA,EAAA,CAAA,UAAA,EAAA,iBAAA,EAAA,qBAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EACtC,iCAAiC,0EACjC,gCAAgC,EAAA,QAAA,EAAA,4BAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,MAAA,CAAA,EAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;4FAIvB,oCAAoC,EAAA,UAAA,EAAA,CAAA;kBAjBhD,SAAS;+BAEE,iCAAiC,EAAA,UAAA,EAE/B,IAAI,EAAA,OAAA,EACP;wBACP,SAAS;wBACT,8BAA8B;wBAC9B,4BAA4B;wBAC5B,iCAAiC;wBACjC,gCAAgC;wBAChC,sCAAsC;wBACtC,iCAAiC;wBACjC,gCAAgC;qBACjC,EAAA,eAAA,EACgB,uBAAuB,CAAC,MAAM,EAAA,QAAA,EAAA,20HAAA,EAAA;;0BAkClC;;0BAAY,MAAM;2BAAC,6BAA6B;;;AE7D/D;;AAEG;;;;"}
1
+ {"version":3,"file":"libs-ui-components-preview-file.mjs","sources":["../../../../../libs-ui/components/preview-file/src/preview-file.component.ts","../../../../../libs-ui/components/preview-file/src/preview-file.component.html","../../../../../libs-ui/components/preview-file/src/libs-ui-components-preview-file.ts"],"sourcesContent":["import { AsyncPipe } from '@angular/common';\nimport { ChangeDetectionStrategy, Component, computed, Inject, input, model, Optional, output, signal, untracked } from '@angular/core';\nimport { IButton, LibsUiComponentsButtonsButtonComponent } from '@libs-ui/components-buttons-button';\nimport { LibsUiComponentsModalComponent, TYPE_MODAL_EVENT } from '@libs-ui/components-modal';\nimport { LibsUiComponentsPopoverComponent } from '@libs-ui/components-popover';\nimport { LibsUiComponentsSkeletonComponent } from '@libs-ui/components-skeleton';\nimport { LibsUiComponentsSpinnerComponent } from '@libs-ui/components-spinner';\nimport { IEvent, IFile } from '@libs-ui/interfaces-types';\nimport { LibsUiPipesCheckFileExtensionPipe } from '@libs-ui/pipes-check-file-extension';\nimport { LibsUiPipesSecurityTrustPipe } from '@libs-ui/pipes-security-trust';\nimport { LINK_IMAGE_ERROR_TOKEN_INJECT } from '@libs-ui/utils';\n\n@Component({\n // eslint-disable-next-line @angular-eslint/component-selector\n selector: 'libs_ui-components-preview_file',\n templateUrl: './preview-file.component.html',\n standalone: true,\n imports: [\n AsyncPipe,\n LibsUiComponentsModalComponent,\n LibsUiPipesSecurityTrustPipe,\n LibsUiComponentsSkeletonComponent,\n LibsUiComponentsPopoverComponent,\n LibsUiComponentsButtonsButtonComponent,\n LibsUiPipesCheckFileExtensionPipe,\n LibsUiComponentsSpinnerComponent,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class LibsUiComponentsPreviewFileComponent {\n protected loading = signal(true);\n protected fileView = computed(() => {\n const file = this.data()[this.index()];\n\n if (!file) {\n return null;\n }\n untracked(() => this.loading.set(true));\n const mimeType = file.mimetype || file.file?.type;\n const url = file.url || file.origin_url;\n\n if (this.isSourceIframe()) {\n return { ...file, urlFile: url };\n }\n\n if (mimeType && this.mineTypeMicroSoftSupport().includes(mimeType)) {\n return { ...file, urlFile: `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(url || '')}` };\n }\n\n return { ...file, urlFile: `https://docs.google.com/a/umd.edu/viewer?url=${encodeURIComponent(url || '')}&embedded=true` };\n });\n\n readonly zIndex = input<number>();\n readonly index = model.required<number>();\n readonly data = input.required<IFile[]>();\n readonly buttons = input<IButton[]>();\n readonly isSourceIframe = input<boolean>();\n\n readonly outClose = output<void>();\n readonly outChangeIndex = output<number>();\n\n constructor(@Optional() @Inject(LINK_IMAGE_ERROR_TOKEN_INJECT) private readonly linkImageError: string) {}\n\n protected async handlerOnLoad() {\n this.loading.set(false);\n }\n\n protected async handlerClose(type: TYPE_MODAL_EVENT) {\n if (type === 'close') {\n this.outClose.emit();\n }\n }\n\n private mineTypeMicroSoftSupport() {\n return [\n 'application/msword',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n 'application/vnd.ms-excel.sheet.macroEnabled.12',\n 'application/vnd.ms-excel',\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',\n 'application/vnd.ms-powerpoint',\n 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',\n 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',\n ];\n }\n\n protected handlerPrev() {\n this.index.update((value) => value - 1);\n this.outChangeIndex.emit(this.index());\n }\n\n protected handlerNext() {\n this.index.update((value) => value + 1);\n this.outChangeIndex.emit(this.index());\n }\n\n protected async handlerImageError(event: Event) {\n if (!this.linkImageError) {\n return;\n }\n (event as IEvent).target.src = this.linkImageError;\n }\n}\n","<libs_ui-components-modal\n [minWidth]=\"'1200px'\"\n [headerConfig]=\"{ ignoreHeaderTheme: true, classButtonCloseInclude: '!mr-[24px]' }\"\n [bodyConfig]=\"{ classInclude: '!p-0', scrollOverlayOptions: { scrollX: 'hidden', scrollY: 'hidden' } }\"\n [footerConfig]=\"{ hidden: true }\"\n [zIndex]=\"zIndex()\"\n [buttonsFooter]=\"[]\"\n [mode]=\"'center'\"\n (outEvent)=\"handlerClose($event)\">\n <div class=\"libs-ui-modal-header-custom w-full flex items-center justify-between min-w-0\">\n <div class=\"w-full ml-[24px] mr-[8px] flex justify-between items-center min-w-0 gap-[16px]\">\n <div class=\"w-2/8 flex-1\"></div>\n <div class=\"flex libs-ui-font-h4s min-w-0 w-4/8 justify-center flex-1\">\n <libs_ui-components-popover\n [type]=\"'text'\"\n [config]=\"{ zIndex: zIndex() }\">\n {{ fileView()?.name }}\n </libs_ui-components-popover>\n </div>\n @if (buttons()?.length) {\n <div class=\"flex gap-[8px] items-center w-2/8 justify-end flex-1\">\n @for (button of buttons(); track button.key) {\n <libs_ui-components-buttons-button\n [type]=\"button.type || 'button-third'\"\n [label]=\"button.label || ''\"\n [classIconLeft]=\"button.classIconLeft || ''\"\n [classIconRight]=\"button.classIconRight || ''\"\n [iconOnlyType]=\"button.iconOnlyType || false\"\n [classInclude]=\"button.classInclude || '!py-[4px] !px-[12px]'\"\n (outClick)=\"button.action?.(index())\" />\n }\n <div class=\"h-[12px] libs-ui-border-left-general\"></div>\n </div>\n }\n </div>\n </div>\n <div class=\"libs-ui-modal-body-custom h-full w-full flex rounded-b-[8px] overflow-hidden relative\">\n @if (index() > 0) {\n <div class=\"absolute top-[50%] left-[24px] transform -translate-y-[50%] z-[1]\">\n <libs_ui-components-buttons-button\n [type]=\"'button-outline'\"\n [iconOnlyType]=\"true\"\n [classInclude]=\"'!p-[11px] !rounded-[8px]'\"\n [classIconLeft]=\"'libs-ui-icon-chevron-right rotate-180 before:!text-[20px]'\"\n (outClick)=\"handlerPrev()\" />\n </div>\n }\n @if (fileView(); as fileView) {\n @if (fileView | LibsUiPipesCheckFileExtensionPipe: 'image') {\n <div class=\"w-full h-full relative flex items-center justify-center\">\n @if (fileView.url || fileView.origin_url; as url) {\n <img\n [src]=\"url\"\n alt=\"\"\n class=\"max-w-full max-h-full object-contain\"\n (load)=\"handlerOnLoad()\"\n (error)=\"handlerImageError($event)\" />\n @if (loading()) {\n <libs_ui-components-spinner [size]=\"'medium'\" />\n }\n }\n </div>\n } @else {\n <iframe\n id=\"google\"\n title=\"File preview\"\n [class.hidden]=\"loading()\"\n [src]=\"fileView.urlFile ?? '' | LibsUiPipesSecurityTrustPipe: 'resourceUrl' | async\"\n width=\"100%\"\n height=\"100%\"\n class=\"border-0\"\n allowfullscreen=\"allowfullscreen\"\n (load)=\"handlerOnLoad()\"></iframe>\n @if (loading()) {\n <libs_ui-components-skeleton [class]=\"'w-full h-full'\" />\n }\n }\n @if (index() < data().length - 1) {\n <div class=\"absolute top-[50%] right-[24px] transform -translate-y-[50%] z-[1]\">\n <libs_ui-components-buttons-button\n [type]=\"'button-outline'\"\n [iconOnlyType]=\"true\"\n [classInclude]=\"'!p-[11px] !rounded-[8px]'\"\n [classIconLeft]=\"'libs-ui-icon-chevron-right before:!text-[20px]'\"\n (outClick)=\"handlerNext()\" />\n </div>\n }\n }\n </div>\n</libs_ui-components-modal>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@libs-ui/components-preview-file",
3
- "version": "0.2.356-42",
3
+ "version": "0.2.356-43",
4
4
  "peerDependencies": {
5
5
  "@angular/common": ">=18.0.0",
6
6
  "@angular/core": ">=18.0.0",
7
- "@libs-ui/components-modal": "0.2.356-42",
8
- "@libs-ui/components-skeleton": "0.2.356-42",
9
- "@libs-ui/interfaces-types": "0.2.356-42",
10
- "@libs-ui/pipes-security-trust": "0.2.356-42",
11
- "@libs-ui/components-buttons-button": "0.2.356-42",
12
- "@libs-ui/components-popover": "0.2.356-42",
13
- "@libs-ui/components-spinner": "0.2.356-42",
14
- "@libs-ui/pipes-check-file-extension": "0.2.356-42",
15
- "@libs-ui/utils": "0.2.356-42"
7
+ "@libs-ui/components-modal": "0.2.356-43",
8
+ "@libs-ui/components-skeleton": "0.2.356-43",
9
+ "@libs-ui/interfaces-types": "0.2.356-43",
10
+ "@libs-ui/pipes-security-trust": "0.2.356-43",
11
+ "@libs-ui/components-buttons-button": "0.2.356-43",
12
+ "@libs-ui/components-popover": "0.2.356-43",
13
+ "@libs-ui/components-spinner": "0.2.356-43",
14
+ "@libs-ui/pipes-check-file-extension": "0.2.356-43",
15
+ "@libs-ui/utils": "0.2.356-43"
16
16
  },
17
17
  "sideEffects": false,
18
18
  "module": "fesm2022/libs-ui-components-preview-file.mjs",