@libs-ui/components-inputs-upload 0.2.356-41 → 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,130 +1,443 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @libs-ui/components-inputs-upload
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Component upload file toàn diện hỗ trợ kéo thả, xem trước, chỉnh sửa ảnh và quản lý nhiều loại tệp khác nhau.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Giới thiệu
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
- 🖱️ **Drag & Drop**: Hỗ trợ kéo thả tệp tin trực tiếp vào vùng tải lên.
|
|
9
|
-
- 🖼️ **Image Editor**: Tích hợp trình chỉnh sửa ảnh (cắt, xoay) trước khi lưu.
|
|
10
|
-
- 🔍 **File Preview**: Xem trước hình ảnh qua Gallery Viewer và các tài liệu khác qua Preview File component.
|
|
11
|
-
- 👤 **Avatar Mode**: Hỗ trợ chế độ chọn một hình ảnh làm ảnh đại diện (isAvatar).
|
|
12
|
-
- 📏 **Size & Limit Control**: Kiểm soát kích thước tệp tối đa (theo từng loại), tổng kích thước và số lượng tệp tối đa.
|
|
13
|
-
- 🎨 **Display Modes**: Hỗ trợ hai chế độ hiển thị danh sách tệp: `full` (chi tiết) và `short` (gọn nhẹ).
|
|
14
|
-
- 🔗 **Sample Download**: Hỗ trợ nút tải tệp mẫu đi kèm.
|
|
15
|
-
- 🛡️ **Signal Based**: Sử dụng Angular Signals để quản lý trạng thái tệp và cấu hình mượt mà.
|
|
7
|
+
`@libs-ui/components-inputs-upload` cung cấp giao diện tải tệp mạnh mẽ cho Angular, tích hợp sẵn kéo thả (drag & drop), xem trước hình ảnh/tài liệu qua Gallery Viewer và Preview File, chỉnh sửa ảnh (cắt/xoay) bằng Image Editor động. Component sử dụng Angular Signals để quản lý trạng thái tệp theo thời gian thực, hỗ trợ kiểm soát kích thước, số lượng, định dạng và tích hợp luồng upload có tiến trình phần trăm.
|
|
16
8
|
|
|
17
|
-
##
|
|
9
|
+
## Tính năng
|
|
10
|
+
|
|
11
|
+
- ✅ Hỗ trợ đa định dạng: `image`, `video`, `audio`, `document` và các tổ hợp
|
|
12
|
+
- ✅ Kéo thả tệp trực tiếp vào vùng upload (Drag & Drop)
|
|
13
|
+
- ✅ Tích hợp Image Editor (cắt, xoay) với tỉ lệ khung hình tùy chỉnh
|
|
14
|
+
- ✅ Xem trước hình ảnh qua Gallery Viewer và tài liệu qua Preview File
|
|
15
|
+
- ✅ Chế độ Avatar — tự động gán ảnh đầu tiên làm ảnh đại diện
|
|
16
|
+
- ✅ Kiểm soát kích thước tối đa theo từng loại file (image / video / audio / document)
|
|
17
|
+
- ✅ Kiểm soát tổng dung lượng và số lượng tệp tối đa
|
|
18
|
+
- ✅ Hai chế độ hiển thị: `full` (chi tiết) và `short` (gọn nhẹ)
|
|
19
|
+
- ✅ Nút tải tệp mẫu đi kèm (download sample file)
|
|
20
|
+
- ✅ Hiển thị tiến trình upload phần trăm qua `IUploadFunctionControlEvent`
|
|
21
|
+
- ✅ Validate bắt buộc tích hợp (`validRequired`)
|
|
22
|
+
- ✅ Chế độ readonly và disable riêng biệt
|
|
23
|
+
|
|
24
|
+
## Khi nào sử dụng
|
|
18
25
|
|
|
19
|
-
|
|
26
|
+
- Tải lên ảnh đại diện người dùng với crop tỉ lệ cố định
|
|
27
|
+
- Đính kèm tài liệu (PDF, Excel, Word) trong biểu mẫu
|
|
28
|
+
- Quản lý thư viện hình ảnh hoặc video với tính năng xem trước
|
|
29
|
+
- Cần giới hạn nghiêm ngặt kích thước và định dạng tệp được phép
|
|
30
|
+
- Form có yêu cầu upload bắt buộc với validate tích hợp
|
|
31
|
+
- Cần kiểm soát tiến trình upload qua API bên ngoài
|
|
32
|
+
|
|
33
|
+
## Cài đặt
|
|
20
34
|
|
|
21
35
|
```bash
|
|
22
36
|
npm install @libs-ui/components-inputs-upload
|
|
23
37
|
```
|
|
24
38
|
|
|
25
|
-
##
|
|
26
|
-
|
|
27
|
-
### Import Module
|
|
39
|
+
## Import
|
|
28
40
|
|
|
29
41
|
```typescript
|
|
30
42
|
import { LibsUiComponentsInputsUploadComponent } from '@libs-ui/components-inputs-upload';
|
|
31
43
|
|
|
44
|
+
// Các directives độc lập (nếu cần dùng riêng)
|
|
45
|
+
import { LibsUiComponentsInputsUploadDirective } from '@libs-ui/components-inputs-upload';
|
|
46
|
+
import { LibsUiComponentsInputsUploadDropFileDirective } from '@libs-ui/components-inputs-upload';
|
|
47
|
+
|
|
48
|
+
// Sub-component Avatar
|
|
49
|
+
import { LibsUiComponentsInputsUploadAvatarComponent } from '@libs-ui/components-inputs-upload';
|
|
50
|
+
|
|
51
|
+
// Types & Interfaces
|
|
52
|
+
import {
|
|
53
|
+
TYPE_FILE_UPLOAD,
|
|
54
|
+
TYPE_MODE_FILE_DISPLAY,
|
|
55
|
+
IUploadFunctionControlEvent,
|
|
56
|
+
IUploadConfigDownloadSampleFile,
|
|
57
|
+
IUploadDescriptionFormatAndSizeFile,
|
|
58
|
+
} from '@libs-ui/components-inputs-upload';
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Đăng ký vào component standalone:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
32
64
|
@Component({
|
|
33
65
|
standalone: true,
|
|
34
66
|
imports: [LibsUiComponentsInputsUploadComponent],
|
|
35
|
-
// ...
|
|
36
67
|
})
|
|
37
|
-
export class
|
|
68
|
+
export class MyComponent {}
|
|
38
69
|
```
|
|
39
70
|
|
|
40
|
-
|
|
71
|
+
## Ví dụ sử dụng
|
|
72
|
+
|
|
73
|
+
### Ví dụ 1 — Upload nhiều ảnh cơ bản
|
|
41
74
|
|
|
42
75
|
```html
|
|
43
76
|
<libs_ui-components-inputs-upload
|
|
44
77
|
[fileType]="'image'"
|
|
45
78
|
[multiple]="true"
|
|
46
79
|
[limitFile]="5"
|
|
47
|
-
|
|
80
|
+
[maxImageSize]="5 * 1024 * 1024"
|
|
81
|
+
(outFileChanged)="handlerFileChanged($event)"
|
|
82
|
+
/>
|
|
48
83
|
```
|
|
49
84
|
|
|
50
|
-
|
|
85
|
+
```typescript
|
|
86
|
+
import { Component, signal, WritableSignal } from '@angular/core';
|
|
87
|
+
import { LibsUiComponentsInputsUploadComponent } from '@libs-ui/components-inputs-upload';
|
|
88
|
+
import { IFile } from '@libs-ui/interfaces-types';
|
|
89
|
+
|
|
90
|
+
@Component({
|
|
91
|
+
standalone: true,
|
|
92
|
+
imports: [LibsUiComponentsInputsUploadComponent],
|
|
93
|
+
templateUrl: './my.component.html',
|
|
94
|
+
})
|
|
95
|
+
export class MyComponent {
|
|
96
|
+
protected files = signal<Array<WritableSignal<IFile>>>([]);
|
|
97
|
+
|
|
98
|
+
protected handlerFileChanged(files: Array<WritableSignal<IFile>>): void {
|
|
99
|
+
this.files.set(files);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Ví dụ 2 — Chế độ Avatar với cắt ảnh tỉ lệ 1:1
|
|
51
105
|
|
|
52
106
|
```html
|
|
53
107
|
<libs_ui-components-inputs-upload
|
|
54
108
|
[fileType]="'image'"
|
|
109
|
+
[multiple]="false"
|
|
55
110
|
[canSetAvatar]="true"
|
|
56
111
|
[aspectRatio]="{ width: 1, height: 1 }"
|
|
57
|
-
[
|
|
58
|
-
(outFileChanged)="
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
112
|
+
[modeDisplayFile]="'short'"
|
|
113
|
+
(outFileChanged)="handlerAvatarChanged($event)"
|
|
114
|
+
/>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { Component, signal, WritableSignal } from '@angular/core';
|
|
119
|
+
import { LibsUiComponentsInputsUploadComponent } from '@libs-ui/components-inputs-upload';
|
|
120
|
+
import { IAspectRatio, IFile } from '@libs-ui/interfaces-types';
|
|
121
|
+
|
|
122
|
+
@Component({
|
|
123
|
+
standalone: true,
|
|
124
|
+
imports: [LibsUiComponentsInputsUploadComponent],
|
|
125
|
+
templateUrl: './my.component.html',
|
|
126
|
+
})
|
|
127
|
+
export class MyComponent {
|
|
128
|
+
protected avatar = signal<WritableSignal<IFile> | undefined>(undefined);
|
|
129
|
+
|
|
130
|
+
protected handlerAvatarChanged(files: Array<WritableSignal<IFile>>): void {
|
|
131
|
+
const avatarFile = files.find((file) => file().isAvatar);
|
|
132
|
+
this.avatar.set(avatarFile);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Ví dụ 3 — Đa định dạng với validate bắt buộc và tải tệp mẫu
|
|
138
|
+
|
|
139
|
+
```html
|
|
140
|
+
<libs_ui-components-inputs-upload
|
|
141
|
+
[fileType]="'image_video_document'"
|
|
142
|
+
[multiple]="true"
|
|
143
|
+
[limitFile]="10"
|
|
144
|
+
[maxTotalSize]="50 * 1024 * 1024"
|
|
145
|
+
[validRequired]="{ isRequired: true, message: 'Vui lòng tải lên ít nhất 1 tệp' }"
|
|
146
|
+
[configDownloadSampleFile]="{
|
|
147
|
+
title: 'Tải file mẫu',
|
|
148
|
+
url: 'assets/sample-template.xlsx',
|
|
149
|
+
position: 'top'
|
|
150
|
+
}"
|
|
151
|
+
(outFunctionsControl)="handlerFunctionsControl($event)"
|
|
152
|
+
(outFileChanged)="handlerFileChanged($event)"
|
|
153
|
+
(outFileRemoved)="handlerFileRemoved($event)"
|
|
154
|
+
/>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { Component, signal, WritableSignal } from '@angular/core';
|
|
159
|
+
import { LibsUiComponentsInputsUploadComponent, IUploadFunctionControlEvent } from '@libs-ui/components-inputs-upload';
|
|
160
|
+
import { IFile } from '@libs-ui/interfaces-types';
|
|
161
|
+
|
|
162
|
+
@Component({
|
|
163
|
+
standalone: true,
|
|
164
|
+
imports: [LibsUiComponentsInputsUploadComponent],
|
|
165
|
+
templateUrl: './my.component.html',
|
|
166
|
+
})
|
|
167
|
+
export class MyComponent {
|
|
168
|
+
private uploadControl: IUploadFunctionControlEvent | undefined;
|
|
169
|
+
protected files = signal<Array<WritableSignal<IFile>>>([]);
|
|
170
|
+
|
|
171
|
+
protected handlerFunctionsControl(control: IUploadFunctionControlEvent): void {
|
|
172
|
+
this.uploadControl = control;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
protected handlerFileChanged(files: Array<WritableSignal<IFile>>): void {
|
|
176
|
+
this.files.set(files);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
protected handlerFileRemoved(file: WritableSignal<IFile>): void {
|
|
180
|
+
console.log('File removed:', file().name);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
protected async handlerSubmit(): Promise<void> {
|
|
184
|
+
const isValid = await this.uploadControl?.checkIsValid();
|
|
185
|
+
if (!isValid) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
// tiến hành submit
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
protected async handlerClear(): Promise<void> {
|
|
192
|
+
await this.uploadControl?.removeAll();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Ví dụ 4 — Upload file với hiển thị tiến trình
|
|
198
|
+
|
|
199
|
+
```html
|
|
200
|
+
<libs_ui-components-inputs-upload
|
|
201
|
+
[fileType]="'image_document'"
|
|
202
|
+
[multiple]="true"
|
|
203
|
+
[originFiles]="existingFiles()"
|
|
204
|
+
(outFunctionsControl)="handlerFunctionsControl($event)"
|
|
205
|
+
(outFileChanged)="handlerFileChanged($event)"
|
|
206
|
+
/>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
import { Component, signal, WritableSignal } from '@angular/core';
|
|
211
|
+
import { LibsUiComponentsInputsUploadComponent, IUploadFunctionControlEvent } from '@libs-ui/components-inputs-upload';
|
|
212
|
+
import { IFile } from '@libs-ui/interfaces-types';
|
|
213
|
+
|
|
214
|
+
@Component({
|
|
215
|
+
standalone: true,
|
|
216
|
+
imports: [LibsUiComponentsInputsUploadComponent],
|
|
217
|
+
templateUrl: './my.component.html',
|
|
218
|
+
})
|
|
219
|
+
export class MyComponent {
|
|
220
|
+
private uploadControl: IUploadFunctionControlEvent | undefined;
|
|
221
|
+
protected files = signal<Array<WritableSignal<IFile>>>([]);
|
|
222
|
+
protected existingFiles = signal<Array<WritableSignal<IFile>>>([]);
|
|
223
|
+
|
|
224
|
+
protected handlerFunctionsControl(control: IUploadFunctionControlEvent): void {
|
|
225
|
+
this.uploadControl = control;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
protected handlerFileChanged(files: Array<WritableSignal<IFile>>): void {
|
|
229
|
+
this.files.set(files);
|
|
230
|
+
// Giả lập tiến trình upload
|
|
231
|
+
files.forEach((file) => {
|
|
232
|
+
const id = file().id;
|
|
233
|
+
if (!id) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
this.uploadControl?.uploading({ percent: 50 }, id);
|
|
237
|
+
setTimeout(() => {
|
|
238
|
+
this.uploadControl?.uploading({ percent: 100 }, id);
|
|
239
|
+
}, 2000);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## @Input()
|
|
246
|
+
|
|
247
|
+
| Input | Type | Default | Mô tả | Ví dụ |
|
|
248
|
+
|---|---|---|---|---|
|
|
249
|
+
| `allowShowPushMessageMaxSizeError` | `boolean` | `false` | Hiển thị notification toast khi file vượt quá kích thước — chỉ áp dụng chế độ single | `[allowShowPushMessageMaxSizeError]="true"` |
|
|
250
|
+
| `aspectRatio` | `IAspectRatio` | `undefined` | Tỉ lệ khung hình cố định khi mở Image Editor để crop ảnh | `[aspectRatio]="{ width: 16, height: 9 }"` |
|
|
251
|
+
| `audioExtList` | `string[]` | `AudioExtList` | Danh sách đuôi file âm thanh được phép tải lên | `[audioExtList]="['mp3', 'wav']"` |
|
|
252
|
+
| `canSetAvatar` | `boolean` | `false` | Bật chế độ chọn ảnh đại diện — file ảnh đầu tiên tự động được đặt làm avatar | `[canSetAvatar]="true"` |
|
|
253
|
+
| `canUploadIfHasExistFile` | `boolean` | `false` | Cho phép tải lên tiếp khi đã có file (áp dụng chế độ single — thay thế file cũ) | `[canUploadIfHasExistFile]="true"` |
|
|
254
|
+
| `classIncludeAvatar` | `string` | `''` | Class CSS bổ sung cho vùng hiển thị avatar | `[classIncludeAvatar]="'w-[80px] h-[80px]'"` |
|
|
255
|
+
| `classIncludeFileContent` | `string` | `''` | Class CSS bổ sung cho vùng nội dung danh sách file | `[classIncludeFileContent]="'mt-2'"` |
|
|
256
|
+
| `classIncludeListItem` | `string` | `undefined` | Class CSS bổ sung cho từng item file trong danh sách | `[classIncludeListItem]="'border-dashed'"` |
|
|
257
|
+
| `configDescriptionInputUpload` | `IMessageTranslate` | `{ message: 'i18n_drag_file_here_to_upload_or_select_file' }` | Cấu hình văn bản mô tả hiển thị trong vùng kéo thả | `[configDescriptionInputUpload]="{ message: 'i18n_drop_files_here' }"` |
|
|
258
|
+
| `configDownloadSampleFile` | `IUploadConfigDownloadSampleFile` | `undefined` | Cấu hình nút tải file mẫu — hỗ trợ URL hoặc callback tùy chỉnh | `[configDownloadSampleFile]="{ title: 'Tải mẫu', url: '/assets/sample.xlsx' }"` |
|
|
259
|
+
| `descriptionFormatAndSizeFile` | `Array<IUploadDescriptionFormatAndSizeFile>` | `undefined` | Ghi đè mô tả định dạng và kích thước file — mặc định tự sinh từ fileType + maxSize | `[descriptionFormatAndSizeFile]="customDesc"` |
|
|
260
|
+
| `disable` | `boolean` | `false` | Vô hiệu hóa toàn bộ vùng upload | `[disable]="isDisabled()"` |
|
|
261
|
+
| `documentExtList` | `string[]` | `DocumentExtList` | Danh sách đuôi file tài liệu được phép | `[documentExtList]="['pdf', 'docx', 'xlsx']"` |
|
|
262
|
+
| `fileType` | `TYPE_FILE_UPLOAD` | `'image_video_document'` | Nhóm định dạng file được phép tải lên — ảnh hưởng đến accept filter | `[fileType]="'image'"` |
|
|
263
|
+
| `ignoreIconEdit` | `boolean` | `false` | Ẩn nút chỉnh sửa ảnh (Image Editor) | `[ignoreIconEdit]="true"` |
|
|
264
|
+
| `ignoreIconPreview` | `boolean` | `true` | Ẩn nút xem trước file — mặc định đã ẩn | `[ignoreIconPreview]="false"` |
|
|
265
|
+
| `ignoreIconRemove` | `boolean` | `false` | Ẩn nút xóa file | `[ignoreIconRemove]="true"` |
|
|
266
|
+
| `ignoreShowSizeFile` | `boolean` | `false` | Ẩn thông tin kích thước file trong danh sách | `[ignoreShowSizeFile]="true"` |
|
|
267
|
+
| `imageExtList` | `string[]` | `ImageExtList` | Danh sách đuôi file ảnh được phép | `[imageExtList]="['jpg', 'png', 'webp']"` |
|
|
268
|
+
| `labelConfig` | `ILabel` | `undefined` | Cấu hình label hiển thị phía trên vùng upload | `[labelConfig]="{ label: 'Tệp đính kèm', required: true }"` |
|
|
269
|
+
| `limitFile` | `number` | `10` | Số lượng file tối đa được phép tải lên | `[limitFile]="3"` |
|
|
270
|
+
| `maxAudioSize` | `number` | `10485760` (10MB) | Kích thước tối đa (bytes) cho mỗi file âm thanh | `[maxAudioSize]="20 * 1024 * 1024"` |
|
|
271
|
+
| `maxDocumentSize` | `number` | `10485760` (10MB) | Kích thước tối đa (bytes) cho mỗi file tài liệu | `[maxDocumentSize]="5 * 1024 * 1024"` |
|
|
272
|
+
| `maxImageSize` | `number` | `5242880` (5MB) | Kích thước tối đa (bytes) cho mỗi file ảnh | `[maxImageSize]="2 * 1024 * 1024"` |
|
|
273
|
+
| `maxTotalSize` | `number` | `10485760` (10MB) | Tổng dung lượng tối đa (bytes) của toàn bộ tệp được upload | `[maxTotalSize]="50 * 1024 * 1024"` |
|
|
274
|
+
| `maxVideoSize` | `number` | `10485760` (10MB) | Kích thước tối đa (bytes) cho mỗi file video | `[maxVideoSize]="100 * 1024 * 1024"` |
|
|
275
|
+
| `messageFileUploadError` | `string` | `'i18n_invalid_file_upload'` | Key i18n thông báo lỗi khi file không hợp lệ (chế độ multiple) | `[messageFileUploadError]="'i18n_custom_error'"` |
|
|
276
|
+
| `messageMaxSizeError` | `string` | `'i18n_file_size_exceeds_the_allowed_limit'` | Key i18n thông báo lỗi khi file vượt kích thước giới hạn | `[messageMaxSizeError]="'i18n_file_too_large'"` |
|
|
277
|
+
| `messageTotalFileExceedsError` | `string` | `'i18n_file_number_exceeds_the_allowed_limit_please_delete_the_file'` | Key i18n thông báo lỗi khi số lượng file vượt limitFile | `[messageTotalFileExceedsError]="'i18n_too_many_files'"` |
|
|
278
|
+
| `messageTypeFileError` | `string` | `'i18n_the_file_support_format_is_not_correct'` | Key i18n thông báo lỗi khi định dạng file không được hỗ trợ | `[messageTypeFileError]="'i18n_format_not_supported'"` |
|
|
279
|
+
| `modeDisplayFile` | `'full' \| 'short'` | `'full'` | Chế độ hiển thị danh sách file: `full` (tên, kích thước, actions) hoặc `short` (ảnh thumbnail nhỏ) | `[modeDisplayFile]="'short'"` |
|
|
280
|
+
| `multiple` | `boolean` | `undefined` | Cho phép chọn nhiều file cùng lúc | `[multiple]="true"` |
|
|
281
|
+
| `originFiles` | `Array<WritableSignal<IFile>>` | `[]` | Danh sách file khởi tạo ban đầu (từ API hoặc edit form) | `[originFiles]="existingFiles()"` |
|
|
282
|
+
| `readonly` | `boolean` | `false` | Chế độ chỉ đọc — hiển thị file nhưng không cho phép thao tác | `[readonly]="isReadonly()"` |
|
|
283
|
+
| `showBorderErrorAllItemWhenError` | `boolean` | `false` | Hiển thị viền đỏ toàn bộ item khi có bất kỳ lỗi nào | `[showBorderErrorAllItemWhenError]="true"` |
|
|
284
|
+
| `showPopupUploadWhenHasFileAndModeSingle` | `boolean` | `false` | Tự động mở dialog chọn file khi click vào file đã có (chế độ single) | `[showPopupUploadWhenHasFileAndModeSingle]="true"` |
|
|
285
|
+
| `showVideoDuration` | `boolean` | `false` | Hiển thị thời lượng video trong thumbnail | `[showVideoDuration]="true"` |
|
|
286
|
+
| `validRequired` | `IIsValidRequired` | `undefined` | Cấu hình validate bắt buộc — hiển thị lỗi khi chưa có file | `[validRequired]="{ isRequired: true, message: 'Vui lòng tải lên ít nhất 1 tệp' }"` |
|
|
287
|
+
| `videoExtList` | `string[]` | `VideoExtList` | Danh sách đuôi file video được phép | `[videoExtList]="['mp4', 'mov', 'avi']"` |
|
|
288
|
+
| `zIndex` | `number` | `1200` | Z-index cho các popup động (Gallery Viewer, Image Editor, Preview File) | `[zIndex]="1500"` |
|
|
289
|
+
|
|
290
|
+
## @Output()
|
|
291
|
+
|
|
292
|
+
| Output | Type | Mô tả | Handler TS | Binding HTML |
|
|
293
|
+
|---|---|---|---|---|
|
|
294
|
+
| `(outClose)` | `boolean` | Phát ra khi đóng popup upload | `handlerClose(val: boolean): void { event.stopPropagation(); ... }` | `(outClose)="handlerClose($event)"` |
|
|
295
|
+
| `(outFileChanged)` | `Array<WritableSignal<IFile>>` | Phát ra danh sách file hiện tại mỗi khi có thay đổi (thêm, xóa, chỉnh sửa) | `handlerFileChanged(files: Array<WritableSignal<IFile>>): void { this.files.set(files); }` | `(outFileChanged)="handlerFileChanged($event)"` |
|
|
296
|
+
| `(outFileRemoved)` | `WritableSignal<IFile>` | Phát ra file vừa bị xóa — chỉ phát ra khi xóa file có trong `originFiles` | `handlerFileRemoved(file: WritableSignal<IFile>): void { console.log(file().name); }` | `(outFileRemoved)="handlerFileRemoved($event)"` |
|
|
297
|
+
| `(outFunctionsControl)` | `IUploadFunctionControlEvent` | Phát ra object chứa các hàm điều khiển component từ bên ngoài — phát ngay khi `ngOnInit` | `handlerFunctionsControl(ctrl: IUploadFunctionControlEvent): void { this.uploadControl = ctrl; }` | `(outFunctionsControl)="handlerFunctionsControl($event)"` |
|
|
298
|
+
|
|
299
|
+
## FunctionsControl — Điều khiển từ bên ngoài
|
|
300
|
+
|
|
301
|
+
`outFunctionsControl` cung cấp `IUploadFunctionControlEvent` ngay khi component khởi tạo. Lưu tham chiếu để gọi các hàm điều khiển:
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
private uploadControl: IUploadFunctionControlEvent | undefined;
|
|
305
|
+
|
|
306
|
+
protected handlerFunctionsControl(ctrl: IUploadFunctionControlEvent): void {
|
|
307
|
+
this.uploadControl = ctrl;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Validate — dùng trước khi submit form
|
|
311
|
+
protected async handlerSubmit(): Promise<void> {
|
|
312
|
+
const isValid = await this.uploadControl?.checkIsValid();
|
|
313
|
+
if (!isValid) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
// proceed with form submit
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Xóa tất cả file
|
|
320
|
+
protected async handlerReset(): Promise<void> {
|
|
321
|
+
await this.uploadControl?.removeAll();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Cập nhật tiến trình upload file theo id
|
|
325
|
+
protected updateProgress(fileId: string, percent: number): void {
|
|
326
|
+
this.uploadControl?.uploading({ percent }, fileId);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Set thông báo lỗi — toàn component hoặc file cụ thể theo id
|
|
330
|
+
protected setError(message: string, fileId?: string): void {
|
|
331
|
+
this.uploadControl?.setMessageError(message, fileId);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Kích hoạt dialog chọn file (tương đương click vào nút upload)
|
|
335
|
+
protected handlerTriggerUpload(): void {
|
|
336
|
+
this.uploadControl?.handlerUploadFile();
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Sub-components
|
|
341
|
+
|
|
342
|
+
### LibsUiComponentsInputsUploadAvatarComponent
|
|
343
|
+
|
|
344
|
+
Component con hiển thị thumbnail cho từng file trong danh sách. Dùng độc lập khi cần custom UI danh sách file.
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
import { LibsUiComponentsInputsUploadAvatarComponent } from '@libs-ui/components-inputs-upload';
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Selector: `libs_ui-components-inputs-upload-avatar`
|
|
351
|
+
|
|
352
|
+
| Input | Type | Default | Mô tả | Ví dụ |
|
|
353
|
+
|---|---|---|---|---|
|
|
354
|
+
| `item` | `IFile` (model) | bắt buộc | File cần hiển thị thumbnail — là `model()` hai chiều | `[(item)]="fileSignal()"` |
|
|
355
|
+
| `classInclude` | `string` | `''` | Class CSS bổ sung cho container | `[classInclude]="'w-[60px] h-[60px]'"` |
|
|
356
|
+
| `disable` | `boolean` | `undefined` | Vô hiệu hóa tương tác | `[disable]="true"` |
|
|
357
|
+
| `showVideoDuration` | `boolean` | `undefined` | Hiển thị thời lượng video | `[showVideoDuration]="true"` |
|
|
358
|
+
| `size` | `number` | `undefined` | Kích thước ảnh thumbnail (px) | `[size]="80"` |
|
|
359
|
+
|
|
360
|
+
| Output | Type | Mô tả | Handler TS | Binding HTML |
|
|
361
|
+
|---|---|---|---|---|
|
|
362
|
+
| `(outOpenPreview)` | `IFile` | Phát ra khi click vào thumbnail để xem trước | `handlerOpenPreview(file: IFile): void { event.stopPropagation(); ... }` | `(outOpenPreview)="handlerOpenPreview($event)"` |
|
|
363
|
+
|
|
364
|
+
### LibsUiComponentsInputsUploadDirective
|
|
365
|
+
|
|
366
|
+
Directive gắn vào element bất kỳ để biến nó thành trigger mở dialog chọn file.
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import { LibsUiComponentsInputsUploadDirective } from '@libs-ui/components-inputs-upload';
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Selector: `[LibsUiComponentsInputsUploadDirective]`
|
|
373
|
+
|
|
374
|
+
```html
|
|
375
|
+
<button LibsUiComponentsInputsUploadDirective
|
|
376
|
+
[accessFiles]="'.jpg,.png,.pdf'"
|
|
377
|
+
[multiple]="true"
|
|
378
|
+
(outUploadFiles)="handlerUploadFiles($event)"
|
|
379
|
+
(outUploadFile)="handlerUploadFile($event)">
|
|
380
|
+
Chọn file
|
|
381
|
+
</button>
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
| Input | Type | Default | Mô tả | Ví dụ |
|
|
385
|
+
|---|---|---|---|---|
|
|
386
|
+
| `accessFiles` | `string` | `'image/x-png,image/jpg,image/jpeg,...'` | Chuỗi MIME types hoặc extensions được chấp nhận | `[accessFiles]="'.jpg,.png,.pdf'"` |
|
|
387
|
+
| `multiple` | `boolean` | `true` | Cho phép chọn nhiều file | `[multiple]="false"` |
|
|
388
|
+
|
|
389
|
+
| Output | Type | Mô tả | Handler TS | Binding HTML |
|
|
390
|
+
|---|---|---|---|---|
|
|
391
|
+
| `(outUploadFiles)` | `File[]` | Phát ra khi chọn nhiều file (multiple = true) | `handlerUploadFiles(files: File[]): void { ... }` | `(outUploadFiles)="handlerUploadFiles($event)"` |
|
|
392
|
+
| `(outUploadFile)` | `File` | Phát ra khi chọn một file (multiple = false) | `handlerUploadFile(file: File): void { ... }` | `(outUploadFile)="handlerUploadFile($event)"` |
|
|
393
|
+
|
|
394
|
+
### LibsUiComponentsInputsUploadDropFileDirective
|
|
395
|
+
|
|
396
|
+
Directive gắn vào container để nhận file kéo thả vào.
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
import { LibsUiComponentsInputsUploadDropFileDirective } from '@libs-ui/components-inputs-upload';
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Selector: `[LibsUiComponentsInputsUploadDropFileDirective]`
|
|
403
|
+
|
|
404
|
+
```html
|
|
405
|
+
<div LibsUiComponentsInputsUploadDropFileDirective
|
|
406
|
+
[multiple]="true"
|
|
407
|
+
(outDragOver)="handlerDragOver($event)"
|
|
408
|
+
(outDragLeave)="handlerDragLeave($event)"
|
|
409
|
+
(outDropFiles)="handlerDropFiles($event)">
|
|
410
|
+
Kéo thả file vào đây
|
|
411
|
+
</div>
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
| Input | Type | Default | Mô tả | Ví dụ |
|
|
415
|
+
|---|---|---|---|---|
|
|
416
|
+
| `multiple` | `boolean` | `true` | Cho phép thả nhiều file | `[multiple]="false"` |
|
|
417
|
+
|
|
418
|
+
| Output | Type | Mô tả | Handler TS | Binding HTML |
|
|
419
|
+
|---|---|---|---|---|
|
|
420
|
+
| `(outDragOver)` | `IEvent` | Phát ra khi file được kéo vào vùng drop | `handlerDragOver(e: IEvent): void { ... }` | `(outDragOver)="handlerDragOver($event)"` |
|
|
421
|
+
| `(outDragLeave)` | `IEvent` | Phát ra khi file rời khỏi vùng drop | `handlerDragLeave(e: IEvent): void { ... }` | `(outDragLeave)="handlerDragLeave($event)"` |
|
|
422
|
+
| `(outDrop)` | `IEvent` | Phát ra khi thả file (raw event) | `handlerDrop(e: IEvent): void { ... }` | `(outDrop)="handlerDrop($event)"` |
|
|
423
|
+
| `(outDropFiles)` | `File[]` | Phát ra danh sách file khi thả nhiều (multiple = true) | `handlerDropFiles(files: File[]): void { ... }` | `(outDropFiles)="handlerDropFiles($event)"` |
|
|
424
|
+
| `(outDropFile)` | `File` | Phát ra file đầu tiên khi thả (multiple = false) | `handlerDropFile(file: File): void { ... }` | `(outDropFile)="handlerDropFile($event)"` |
|
|
122
425
|
|
|
123
426
|
## Types & Interfaces
|
|
124
427
|
|
|
428
|
+
```typescript
|
|
429
|
+
import {
|
|
430
|
+
TYPE_FILE_UPLOAD,
|
|
431
|
+
TYPE_MODE_FILE_DISPLAY,
|
|
432
|
+
IUploadFunctionControlEvent,
|
|
433
|
+
IUploadConfigDownloadSampleFile,
|
|
434
|
+
IUploadDescriptionFormatAndSizeFile,
|
|
435
|
+
} from '@libs-ui/components-inputs-upload';
|
|
436
|
+
```
|
|
437
|
+
|
|
125
438
|
### TYPE_FILE_UPLOAD
|
|
126
439
|
|
|
127
|
-
|
|
440
|
+
Kiểu string xác định nhóm định dạng file được phép — ghép bằng dấu `_`:
|
|
128
441
|
|
|
129
442
|
```typescript
|
|
130
443
|
export type TYPE_FILE_UPLOAD =
|
|
@@ -144,41 +457,124 @@ export type TYPE_FILE_UPLOAD =
|
|
|
144
457
|
| 'image_audio_video_document';
|
|
145
458
|
```
|
|
146
459
|
|
|
460
|
+
### TYPE_MODE_FILE_DISPLAY
|
|
461
|
+
|
|
462
|
+
```typescript
|
|
463
|
+
export type TYPE_MODE_FILE_DISPLAY = 'full' | 'short';
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### IUploadFunctionControlEvent
|
|
467
|
+
|
|
468
|
+
Object chứa các hàm điều khiển component từ bên ngoài, nhận qua `(outFunctionsControl)`:
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
export interface IUploadFunctionControlEvent {
|
|
472
|
+
// Validate danh sách file hiện tại — trả true nếu hợp lệ
|
|
473
|
+
checkIsValid: () => Promise<boolean>;
|
|
474
|
+
// Xóa toàn bộ file trong danh sách
|
|
475
|
+
removeAll: () => Promise<void>;
|
|
476
|
+
// Cập nhật tiến trình upload cho file theo id
|
|
477
|
+
uploading: (process: IHttpProcessUpload, id: string) => Promise<void>;
|
|
478
|
+
// Set thông báo lỗi — toàn component hoặc file cụ thể
|
|
479
|
+
setMessageError: (message: string, id?: string) => Promise<void>;
|
|
480
|
+
// Kích hoạt dialog chọn file
|
|
481
|
+
handlerUploadFile: () => Promise<void>;
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
147
485
|
### IUploadConfigDownloadSampleFile
|
|
148
486
|
|
|
149
|
-
Cấu hình
|
|
487
|
+
Cấu hình nút tải file mẫu:
|
|
150
488
|
|
|
151
489
|
```typescript
|
|
152
490
|
export interface IUploadConfigDownloadSampleFile {
|
|
491
|
+
// Vị trí hiển thị nút — trên hoặc dưới vùng upload
|
|
153
492
|
position?: 'top' | 'bottom';
|
|
493
|
+
// Tiêu đề hiển thị trên nút
|
|
154
494
|
title: string;
|
|
495
|
+
// URL file mẫu để download trực tiếp
|
|
155
496
|
url?: string;
|
|
497
|
+
// Class CSS cho label
|
|
156
498
|
classLabel?: string;
|
|
499
|
+
// Class CSS bổ sung cho nút
|
|
157
500
|
classInclude?: string;
|
|
501
|
+
// Class CSS cho icon bên trái
|
|
158
502
|
classIconLeftInclude?: string;
|
|
503
|
+
// Callback tùy chỉnh — ưu tiên hơn url nếu có
|
|
159
504
|
callBack?: () => void;
|
|
160
505
|
}
|
|
161
506
|
```
|
|
162
507
|
|
|
163
|
-
###
|
|
508
|
+
### IUploadDescriptionFormatAndSizeFile
|
|
164
509
|
|
|
165
|
-
|
|
510
|
+
Cấu hình mô tả định dạng và kích thước file (hiển thị dưới vùng upload):
|
|
166
511
|
|
|
167
512
|
```typescript
|
|
168
|
-
export interface
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
513
|
+
export interface IUploadDescriptionFormatAndSizeFile {
|
|
514
|
+
// Key i18n
|
|
515
|
+
message: string;
|
|
516
|
+
// Tham số interpolation cho i18n
|
|
517
|
+
interpolateParams?: TYPE_OBJECT;
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### Interfaces từ @libs-ui/interfaces-types (dùng chung)
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
import { IFile, IAspectRatio, IIsValidRequired, IMessageTranslate, IHttpProcessUpload } from '@libs-ui/interfaces-types';
|
|
525
|
+
|
|
526
|
+
// IFile — cấu trúc file trong danh sách
|
|
527
|
+
interface IFile {
|
|
528
|
+
id?: string;
|
|
529
|
+
name: string;
|
|
530
|
+
url?: string; // Base64 hoặc Object URL sau khi chọn
|
|
531
|
+
origin_url?: string; // URL gốc từ server
|
|
532
|
+
file?: File; // File object gốc
|
|
533
|
+
size?: string; // Kích thước định dạng text ("1.2 MB")
|
|
534
|
+
type?: 'image' | 'video' | 'audio' | 'document';
|
|
535
|
+
isAvatar?: boolean; // Được chọn làm avatar
|
|
536
|
+
error?: string; // Thông báo lỗi nếu file không hợp lệ
|
|
537
|
+
percentUploading?: number; // Tiến trình upload (0-100)
|
|
538
|
+
isUploading?: boolean;
|
|
539
|
+
isUpdate?: boolean; // File đã được chỉnh sửa qua Image Editor
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// IAspectRatio — tỉ lệ khung hình cho Image Editor
|
|
543
|
+
interface IAspectRatio {
|
|
544
|
+
width: number;
|
|
545
|
+
height: number;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// IIsValidRequired — cấu hình validate bắt buộc
|
|
549
|
+
interface IIsValidRequired {
|
|
550
|
+
isRequired: boolean;
|
|
551
|
+
message?: string;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// IHttpProcessUpload — tiến trình upload
|
|
555
|
+
interface IHttpProcessUpload {
|
|
556
|
+
percent: number; // 0-100
|
|
174
557
|
}
|
|
175
558
|
```
|
|
176
559
|
|
|
177
|
-
##
|
|
560
|
+
## Lưu ý quan trọng
|
|
561
|
+
|
|
562
|
+
⚠️ **Dynamic Component Dependencies**: Component này dùng `LibsUiDynamicComponentService` để khởi tạo Image Editor, Gallery Viewer và Preview File động. Đảm bảo `LibsUiDynamicComponentService` được cung cấp trong provider tree của app.
|
|
563
|
+
|
|
564
|
+
⚠️ **outFunctionsControl phát ngay ngOnInit**: Nhận `IUploadFunctionControlEvent` qua `(outFunctionsControl)` và lưu tham chiếu vào biến class (không phải local variable) để tránh mất tham chiếu. Gọi `checkIsValid()` trước khi submit form.
|
|
565
|
+
|
|
566
|
+
⚠️ **originFiles là signal array**: Input `originFiles` nhận `Array<WritableSignal<IFile>>`. Mỗi phần tử là `WritableSignal<IFile>` — khi chỉnh sửa ảnh qua editor, signal đó được `update()` trực tiếp, không tạo phần tử mới.
|
|
178
567
|
|
|
179
|
-
|
|
180
|
-
- **Dependencies**: `@libs-ui/components-buttons-button`, `@libs-ui/components-gallery`, `@libs-ui/components-image-editor`, `@libs-ui/components-label`, `@libs-ui/components-preview-file`, `@libs-ui/services-dynamic-component`.
|
|
568
|
+
⚠️ **maxTotalSize tính bằng bytes**: Tất cả input `maxXxxSize` và `maxTotalSize` nhận giá trị bytes. Ví dụ 5MB = `5 * 1024 * 1024`. Nếu truyền `5` sẽ bị giới hạn ở 5 bytes.
|
|
181
569
|
|
|
182
|
-
|
|
570
|
+
⚠️ **fileType quyết định danh sách extension**: `fileType` xác định nhóm định dạng và tự động sinh chuỗi `accept` cho input file. Các input `imageExtList`, `videoExtList`, `documentExtList`, `audioExtList` cho phép override danh sách extension mặc định trong từng nhóm.
|
|
571
|
+
|
|
572
|
+
⚠️ **multiple vs canUploadIfHasExistFile**: Khi `multiple = false` và đã có file, upload file mới sẽ không thay thế file cũ trừ khi bật `canUploadIfHasExistFile = true`.
|
|
573
|
+
|
|
574
|
+
## Demo
|
|
575
|
+
|
|
576
|
+
```bash
|
|
577
|
+
npx nx serve core-ui
|
|
578
|
+
```
|
|
183
579
|
|
|
184
|
-
|
|
580
|
+
Truy cập: http://localhost:4500/components/inputs/upload
|