@flusys/ng-shared 3.0.0 → 4.0.0-rc
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 +435 -359
- package/fesm2022/flusys-ng-shared.mjs +1648 -614
- package/fesm2022/flusys-ng-shared.mjs.map +1 -1
- package/package.json +2 -2
- package/types/flusys-ng-shared.d.ts +286 -308
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, PLATFORM_ID, Injectable, DOCUMENT, REQUEST, signal, computed, ElementRef, input, effect, Directive, TemplateRef, ViewContainerRef, output,
|
|
3
|
-
import
|
|
4
|
-
import { isPlatformServer, CommonModule, NgOptimizedImage, NgComponentOutlet, DatePipe } from '@angular/common';
|
|
2
|
+
import { inject, PLATFORM_ID, Injectable, DOCUMENT, REQUEST, signal, computed, ElementRef, input, effect, Directive, TemplateRef, ViewContainerRef, output, Pipe, Injector, isDevMode, NgModule, runInInjectionContext, resource, model, untracked, forwardRef, Component, ApplicationRef, viewChild, afterNextRender, ViewEncapsulation, DestroyRef, InjectionToken } from '@angular/core';
|
|
3
|
+
import { isPlatformServer, CommonModule, NgOptimizedImage, NgComponentOutlet, DatePipe, DOCUMENT as DOCUMENT$1 } from '@angular/common';
|
|
5
4
|
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
|
|
6
|
-
import { APP_CONFIG, getServiceUrl } from '@flusys/ng-core';
|
|
5
|
+
import { APP_CONFIG, getServiceUrl, TRANSLATE_ADAPTER, FALLBACK_MESSAGES_REGISTRY } from '@flusys/ng-core';
|
|
7
6
|
import { of, firstValueFrom, map as map$1 } from 'rxjs';
|
|
8
7
|
import { map, tap, catchError } from 'rxjs/operators';
|
|
9
8
|
import * as i1$1 from '@angular/forms';
|
|
@@ -18,12 +17,14 @@ import * as i1 from 'primeng/checkbox';
|
|
|
18
17
|
import { CheckboxModule } from 'primeng/checkbox';
|
|
19
18
|
import { ConfirmDialogModule } from 'primeng/confirmdialog';
|
|
20
19
|
import { DatePickerModule } from 'primeng/datepicker';
|
|
21
|
-
import * as
|
|
20
|
+
import * as i5 from 'primeng/dialog';
|
|
22
21
|
import { DialogModule } from 'primeng/dialog';
|
|
23
22
|
import { DividerModule } from 'primeng/divider';
|
|
24
23
|
import { FileUploadModule } from 'primeng/fileupload';
|
|
24
|
+
import * as i6 from 'primeng/iconfield';
|
|
25
25
|
import { IconFieldModule } from 'primeng/iconfield';
|
|
26
26
|
import { ImageModule } from 'primeng/image';
|
|
27
|
+
import * as i7 from 'primeng/inputicon';
|
|
27
28
|
import { InputIconModule } from 'primeng/inputicon';
|
|
28
29
|
import { InputNumberModule } from 'primeng/inputnumber';
|
|
29
30
|
import * as i2 from 'primeng/inputtext';
|
|
@@ -38,7 +39,7 @@ import * as i2$1 from 'primeng/progressbar';
|
|
|
38
39
|
import { ProgressBarModule } from 'primeng/progressbar';
|
|
39
40
|
import { RadioButtonModule } from 'primeng/radiobutton';
|
|
40
41
|
import { RippleModule } from 'primeng/ripple';
|
|
41
|
-
import * as i3
|
|
42
|
+
import * as i3 from 'primeng/select';
|
|
42
43
|
import { SelectModule } from 'primeng/select';
|
|
43
44
|
import { SelectButtonModule } from 'primeng/selectbutton';
|
|
44
45
|
import { SkeletonModule } from 'primeng/skeleton';
|
|
@@ -49,23 +50,16 @@ import { TabsModule } from 'primeng/tabs';
|
|
|
49
50
|
import { TagModule } from 'primeng/tag';
|
|
50
51
|
import { TextareaModule } from 'primeng/textarea';
|
|
51
52
|
import { ToastModule } from 'primeng/toast';
|
|
53
|
+
import { ToggleButtonModule } from 'primeng/togglebutton';
|
|
52
54
|
import { ToggleSwitchModule } from 'primeng/toggleswitch';
|
|
53
55
|
import { TooltipModule } from 'primeng/tooltip';
|
|
54
56
|
import { TreeTableModule } from 'primeng/treetable';
|
|
57
|
+
import { ProgressSpinnerModule } from 'primeng/progressspinner';
|
|
58
|
+
import { ColorPickerModule } from 'primeng/colorpicker';
|
|
59
|
+
import * as i3$1 from 'primeng/api';
|
|
55
60
|
import { MessageService, ConfirmationService } from 'primeng/api';
|
|
56
61
|
import { toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
57
62
|
|
|
58
|
-
/**
|
|
59
|
-
* Centralized Permission Codes
|
|
60
|
-
*
|
|
61
|
-
* Single source of truth for all permission codes used across the application.
|
|
62
|
-
* Use these constants instead of hardcoded strings to prevent typos and enable easy refactoring.
|
|
63
|
-
*
|
|
64
|
-
* Naming Convention: <entity>.<action>
|
|
65
|
-
* - entity: The resource being accessed (e.g., user, role, company)
|
|
66
|
-
* - action: The operation being performed (create, read, update, delete, assign)
|
|
67
|
-
*/
|
|
68
|
-
// ==================== AUTH MODULE ====================
|
|
69
63
|
const USER_PERMISSIONS = {
|
|
70
64
|
CREATE: 'user.create',
|
|
71
65
|
READ: 'user.read',
|
|
@@ -84,7 +78,6 @@ const BRANCH_PERMISSIONS = {
|
|
|
84
78
|
UPDATE: 'branch.update',
|
|
85
79
|
DELETE: 'branch.delete',
|
|
86
80
|
};
|
|
87
|
-
// ==================== IAM MODULE ====================
|
|
88
81
|
const ACTION_PERMISSIONS = {
|
|
89
82
|
CREATE: 'action.create',
|
|
90
83
|
READ: 'action.read',
|
|
@@ -113,7 +106,6 @@ const COMPANY_ACTION_PERMISSIONS = {
|
|
|
113
106
|
READ: 'company-action.read',
|
|
114
107
|
ASSIGN: 'company-action.assign',
|
|
115
108
|
};
|
|
116
|
-
// ==================== STORAGE MODULE ====================
|
|
117
109
|
const FILE_PERMISSIONS = {
|
|
118
110
|
CREATE: 'file.create',
|
|
119
111
|
READ: 'file.read',
|
|
@@ -132,7 +124,6 @@ const STORAGE_CONFIG_PERMISSIONS = {
|
|
|
132
124
|
UPDATE: 'storage-config.update',
|
|
133
125
|
DELETE: 'storage-config.delete',
|
|
134
126
|
};
|
|
135
|
-
// ==================== EMAIL MODULE ====================
|
|
136
127
|
const EMAIL_CONFIG_PERMISSIONS = {
|
|
137
128
|
CREATE: 'email-config.create',
|
|
138
129
|
READ: 'email-config.read',
|
|
@@ -145,43 +136,444 @@ const EMAIL_TEMPLATE_PERMISSIONS = {
|
|
|
145
136
|
UPDATE: 'email-template.update',
|
|
146
137
|
DELETE: 'email-template.delete',
|
|
147
138
|
};
|
|
148
|
-
// ==================== FORM BUILDER MODULE ====================
|
|
149
139
|
const FORM_PERMISSIONS = {
|
|
150
140
|
CREATE: 'form.create',
|
|
151
141
|
READ: 'form.read',
|
|
152
142
|
UPDATE: 'form.update',
|
|
153
143
|
DELETE: 'form.delete',
|
|
154
144
|
};
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
145
|
+
const EVENT_PERMISSIONS = {
|
|
146
|
+
CREATE: 'event.create',
|
|
147
|
+
READ: 'event.read',
|
|
148
|
+
UPDATE: 'event.update',
|
|
149
|
+
DELETE: 'event.delete',
|
|
150
|
+
};
|
|
151
|
+
const EVENT_PARTICIPANT_PERMISSIONS = {
|
|
152
|
+
CREATE: 'event-participant.create',
|
|
153
|
+
READ: 'event-participant.read',
|
|
154
|
+
UPDATE: 'event-participant.update',
|
|
155
|
+
DELETE: 'event-participant.delete',
|
|
156
|
+
};
|
|
157
|
+
const NOTIFICATION_PERMISSIONS = {
|
|
158
|
+
CREATE: 'notification.create',
|
|
159
|
+
READ: 'notification.read',
|
|
160
|
+
UPDATE: 'notification.update',
|
|
161
|
+
DELETE: 'notification.delete',
|
|
162
|
+
};
|
|
163
|
+
const LANGUAGE_PERMISSIONS = {
|
|
164
|
+
CREATE: 'language.create',
|
|
165
|
+
READ: 'language.read',
|
|
166
|
+
UPDATE: 'language.update',
|
|
167
|
+
DELETE: 'language.delete',
|
|
168
|
+
};
|
|
169
|
+
const TRANSLATION_KEY_PERMISSIONS = {
|
|
170
|
+
CREATE: 'translation-key.create',
|
|
171
|
+
READ: 'translation-key.read',
|
|
172
|
+
UPDATE: 'translation-key.update',
|
|
173
|
+
DELETE: 'translation-key.delete',
|
|
174
|
+
};
|
|
175
|
+
const TRANSLATION_PERMISSIONS = {
|
|
176
|
+
CREATE: 'translation.create',
|
|
177
|
+
READ: 'translation.read',
|
|
178
|
+
UPDATE: 'translation.update',
|
|
179
|
+
DELETE: 'translation.delete',
|
|
180
|
+
};
|
|
159
181
|
const PERMISSIONS = {
|
|
160
|
-
// Auth
|
|
161
182
|
USER: USER_PERMISSIONS,
|
|
162
183
|
COMPANY: COMPANY_PERMISSIONS,
|
|
163
184
|
BRANCH: BRANCH_PERMISSIONS,
|
|
164
|
-
// IAM
|
|
165
185
|
ACTION: ACTION_PERMISSIONS,
|
|
166
186
|
ROLE: ROLE_PERMISSIONS,
|
|
167
187
|
ROLE_ACTION: ROLE_ACTION_PERMISSIONS,
|
|
168
188
|
USER_ROLE: USER_ROLE_PERMISSIONS,
|
|
169
189
|
USER_ACTION: USER_ACTION_PERMISSIONS,
|
|
170
190
|
COMPANY_ACTION: COMPANY_ACTION_PERMISSIONS,
|
|
171
|
-
// Storage
|
|
172
191
|
FILE: FILE_PERMISSIONS,
|
|
173
192
|
FOLDER: FOLDER_PERMISSIONS,
|
|
174
193
|
STORAGE_CONFIG: STORAGE_CONFIG_PERMISSIONS,
|
|
175
|
-
// Email
|
|
176
194
|
EMAIL_CONFIG: EMAIL_CONFIG_PERMISSIONS,
|
|
177
195
|
EMAIL_TEMPLATE: EMAIL_TEMPLATE_PERMISSIONS,
|
|
178
|
-
// Form Builder
|
|
179
196
|
FORM: FORM_PERMISSIONS,
|
|
197
|
+
EVENT: EVENT_PERMISSIONS,
|
|
198
|
+
EVENT_PARTICIPANT: EVENT_PARTICIPANT_PERMISSIONS,
|
|
199
|
+
NOTIFICATION: NOTIFICATION_PERMISSIONS,
|
|
200
|
+
LANGUAGE: LANGUAGE_PERMISSIONS,
|
|
201
|
+
TRANSLATION_KEY: TRANSLATION_KEY_PERMISSIONS,
|
|
202
|
+
TRANSLATION: TRANSLATION_PERMISSIONS,
|
|
180
203
|
};
|
|
181
204
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
205
|
+
const SHARED_MESSAGES = {
|
|
206
|
+
// Common actions
|
|
207
|
+
'shared.save': 'Save',
|
|
208
|
+
'shared.cancel': 'Cancel',
|
|
209
|
+
'shared.delete': 'Delete',
|
|
210
|
+
'shared.edit': 'Edit',
|
|
211
|
+
'shared.create': 'Create',
|
|
212
|
+
'shared.update': 'Update',
|
|
213
|
+
'shared.add': 'Add',
|
|
214
|
+
'shared.remove': 'Remove',
|
|
215
|
+
'shared.close': 'Close',
|
|
216
|
+
'shared.confirm': 'Confirm',
|
|
217
|
+
'shared.back': 'Back',
|
|
218
|
+
'shared.next': 'Next',
|
|
219
|
+
'shared.previous': 'Previous',
|
|
220
|
+
'shared.submit': 'Submit',
|
|
221
|
+
'shared.reset': 'Reset',
|
|
222
|
+
'shared.clear': 'Clear',
|
|
223
|
+
'shared.search': 'Search',
|
|
224
|
+
'shared.filter': 'Filter',
|
|
225
|
+
'shared.refresh': 'Refresh',
|
|
226
|
+
'shared.export': 'Export',
|
|
227
|
+
'shared.import': 'Import',
|
|
228
|
+
'shared.download': 'Download',
|
|
229
|
+
'shared.upload': 'Upload',
|
|
230
|
+
'shared.view': 'View',
|
|
231
|
+
'shared.details': 'Details',
|
|
232
|
+
'shared.actions': 'Actions',
|
|
233
|
+
'shared.options': 'Options',
|
|
234
|
+
'shared.settings': 'Settings',
|
|
235
|
+
'shared.yes': 'Yes',
|
|
236
|
+
'shared.no': 'No',
|
|
237
|
+
'shared.continue': 'Continue',
|
|
238
|
+
'shared.view.details': 'View Details',
|
|
239
|
+
// Common labels
|
|
240
|
+
'shared.name': 'Name',
|
|
241
|
+
'shared.description': 'Description',
|
|
242
|
+
'shared.status': 'Status',
|
|
243
|
+
'shared.active': 'Active',
|
|
244
|
+
'shared.inactive': 'Inactive',
|
|
245
|
+
'shared.enabled': 'Enabled',
|
|
246
|
+
'shared.disabled': 'Disabled',
|
|
247
|
+
'shared.verified': 'Verified',
|
|
248
|
+
'shared.unverified': 'Unverified',
|
|
249
|
+
'shared.read.only': 'Read Only',
|
|
250
|
+
'shared.assigned': 'Assigned',
|
|
251
|
+
'shared.not.assigned': 'Not Assigned',
|
|
252
|
+
'shared.unknown': 'Unknown',
|
|
253
|
+
'shared.na': 'N/A',
|
|
254
|
+
'shared.no.company': 'No Company',
|
|
255
|
+
'shared.company': 'Company',
|
|
256
|
+
'shared.no.branch': 'No Branch',
|
|
257
|
+
'shared.display.order': 'Display Order',
|
|
258
|
+
'shared.display.order.placeholder': 'Enter display order',
|
|
259
|
+
'shared.created.at': 'Created At',
|
|
260
|
+
'shared.updated.at': 'Updated At',
|
|
261
|
+
'shared.created.by': 'Created By',
|
|
262
|
+
'shared.updated.by': 'Updated By',
|
|
263
|
+
'shared.date': 'Date',
|
|
264
|
+
'shared.time': 'Time',
|
|
265
|
+
'shared.email': 'Email',
|
|
266
|
+
'shared.phone': 'Phone',
|
|
267
|
+
'shared.address': 'Address',
|
|
268
|
+
'shared.type': 'Type',
|
|
269
|
+
'shared.category': 'Category',
|
|
270
|
+
'shared.code': 'Code',
|
|
271
|
+
'shared.serial': 'Serial',
|
|
272
|
+
'shared.order': 'Order',
|
|
273
|
+
'shared.priority': 'Priority',
|
|
274
|
+
'shared.notes': 'Notes',
|
|
275
|
+
'shared.comments': 'Comments',
|
|
276
|
+
// Toast summaries
|
|
277
|
+
'shared.info': 'Info',
|
|
278
|
+
'shared.warning': 'Warning',
|
|
279
|
+
'shared.confirm.delete.header': 'Confirm Delete',
|
|
280
|
+
// Status messages
|
|
281
|
+
'shared.loading': 'Loading...',
|
|
282
|
+
'shared.saving': 'Saving...',
|
|
283
|
+
'shared.deleting': 'Deleting...',
|
|
284
|
+
'shared.processing': 'Processing...',
|
|
285
|
+
'shared.please.wait': 'Please wait...',
|
|
286
|
+
// Success messages
|
|
287
|
+
'shared.success': 'Success',
|
|
288
|
+
'shared.operation.success': 'Operation completed successfully',
|
|
289
|
+
'shared.save.success': 'Saved successfully',
|
|
290
|
+
'shared.delete.success': 'Deleted successfully',
|
|
291
|
+
'shared.update.success': 'Updated successfully',
|
|
292
|
+
'shared.create.success': 'Created successfully',
|
|
293
|
+
// Error messages
|
|
294
|
+
'shared.error': 'Error',
|
|
295
|
+
'shared.operation.failed': 'Operation failed',
|
|
296
|
+
'shared.save.failed': 'Failed to save',
|
|
297
|
+
'shared.delete.failed': 'Failed to delete',
|
|
298
|
+
'shared.update.failed': 'Failed to update',
|
|
299
|
+
'shared.create.failed': 'Failed to create',
|
|
300
|
+
'shared.load.failed': 'Failed to load data',
|
|
301
|
+
'shared.unexpected.error': 'An unexpected error occurred',
|
|
302
|
+
'shared.network.error': 'Network error. Please check your connection.',
|
|
303
|
+
'shared.server.error': 'Server error. Please try again later.',
|
|
304
|
+
// Validation messages
|
|
305
|
+
'shared.required': 'This field is required',
|
|
306
|
+
'shared.invalid.email': 'Please enter a valid email address',
|
|
307
|
+
'shared.invalid.phone': 'Please enter a valid phone number',
|
|
308
|
+
'shared.min.length': 'Minimum {{min}} characters required',
|
|
309
|
+
'shared.max.length': 'Maximum {{max}} characters allowed',
|
|
310
|
+
'shared.min.value': 'Minimum value is {{min}}',
|
|
311
|
+
'shared.max.value': 'Maximum value is {{max}}',
|
|
312
|
+
'shared.invalid.format': 'Invalid format',
|
|
313
|
+
'shared.password.mismatch': 'Passwords do not match',
|
|
314
|
+
// Confirmation messages
|
|
315
|
+
'shared.confirm.delete': 'Are you sure you want to delete this item?',
|
|
316
|
+
'shared.confirm.delete.item': 'Are you sure you want to delete "{{name}}"?',
|
|
317
|
+
'shared.confirm.delete.multiple': 'Are you sure you want to delete {{count}} items?',
|
|
318
|
+
'shared.confirm.cancel': 'Are you sure you want to cancel? Unsaved changes will be lost.',
|
|
319
|
+
'shared.confirm.action': 'Are you sure you want to proceed?',
|
|
320
|
+
'shared.unsaved.changes': 'You have unsaved changes.',
|
|
321
|
+
// Empty states
|
|
322
|
+
'shared.no.data': 'No data available',
|
|
323
|
+
'shared.no.results': 'No results found',
|
|
324
|
+
'shared.empty.list': 'The list is empty',
|
|
325
|
+
'shared.no.items.selected': 'No items selected',
|
|
326
|
+
// Pagination
|
|
327
|
+
'shared.showing': 'Showing',
|
|
328
|
+
'shared.of': 'of',
|
|
329
|
+
'shared.items': 'items',
|
|
330
|
+
'shared.page': 'Page',
|
|
331
|
+
'shared.rows.per.page': 'Rows per page',
|
|
332
|
+
'shared.first': 'First',
|
|
333
|
+
'shared.last': 'Last',
|
|
334
|
+
// CRUD operation messages (used by createApiController)
|
|
335
|
+
'item.create.success': 'Item created successfully',
|
|
336
|
+
'item.create.many.success': '{{count}} items created successfully',
|
|
337
|
+
'item.get.success': 'Item retrieved successfully',
|
|
338
|
+
'item.get.all.success': 'Items retrieved successfully',
|
|
339
|
+
'item.update.success': 'Item updated successfully',
|
|
340
|
+
'item.update.many.success': '{{count}} items updated successfully',
|
|
341
|
+
'item.delete.success': '{{count}} item(s) deleted successfully',
|
|
342
|
+
'item.restore.success': '{{count}} item(s) restored successfully',
|
|
343
|
+
// HTTP error messages (for error interceptor)
|
|
344
|
+
'shared.error.bad.request': 'Bad Request',
|
|
345
|
+
'shared.error.not.found': 'Not Found',
|
|
346
|
+
'shared.error.conflict': 'Conflict',
|
|
347
|
+
'shared.error.validation.error': 'Validation Error',
|
|
348
|
+
'shared.error.server.error': 'Server Error',
|
|
349
|
+
'shared.error.service.unavailable': 'Service Unavailable',
|
|
350
|
+
// Validation
|
|
351
|
+
'shared.validation.error': 'Validation Error',
|
|
352
|
+
'shared.fill.all.fields': 'Please fill in all required fields correctly.',
|
|
353
|
+
'shared.fill.required.fields': 'Please fill in all required fields',
|
|
354
|
+
'shared.name.required': 'Name is required',
|
|
355
|
+
'shared.email.required': 'Email is required',
|
|
356
|
+
'shared.select.all': 'Select All',
|
|
357
|
+
'shared.deselect.all': 'Deselect All',
|
|
358
|
+
'shared.save.changes': 'Save Changes',
|
|
359
|
+
'shared.validation.required': '{{field}} is required',
|
|
360
|
+
'shared.validation.email': 'Please enter a valid email address',
|
|
361
|
+
'shared.validation.min.length': '{{field}} must be at least {{min}} characters',
|
|
362
|
+
'shared.validation.max.length': '{{field}} must be at most {{max}} characters',
|
|
363
|
+
'shared.validation.min': '{{field}} must be at least {{min}}',
|
|
364
|
+
'shared.validation.max': '{{field}} must be at most {{max}}',
|
|
365
|
+
'shared.validation.pattern': '{{field}} format is invalid',
|
|
366
|
+
'shared.password.required': 'Password is required',
|
|
367
|
+
'shared.confirm.password.required': 'Please confirm your password',
|
|
368
|
+
'shared.min.characters': 'Min. {{count}} characters',
|
|
369
|
+
'shared.select': 'Select...',
|
|
370
|
+
'shared.select.option': 'Select an option',
|
|
371
|
+
'shared.placeholder.current.password': 'Enter current password',
|
|
372
|
+
'shared.placeholder.new.password': 'Enter new password',
|
|
373
|
+
'shared.placeholder.confirm.password': 'Confirm new password',
|
|
374
|
+
// File uploader
|
|
375
|
+
'shared.upload.drop.multiple': 'Drop files here or click to upload',
|
|
376
|
+
'shared.upload.drop.single': 'Drop file here or click to upload',
|
|
377
|
+
'shared.upload.allowed.types': 'Allowed:',
|
|
378
|
+
'shared.upload.all.types.allowed': 'All file types allowed',
|
|
379
|
+
'shared.upload.uploading': 'Uploading {{fileName}}...',
|
|
380
|
+
'shared.upload.max.size': '(Max {{size}}MB)',
|
|
381
|
+
'shared.upload.invalid.type': 'Invalid File Type',
|
|
382
|
+
'shared.upload.file.too.large': 'File Too Large',
|
|
383
|
+
'shared.upload.complete': 'Upload Complete',
|
|
384
|
+
'shared.upload.files': 'files',
|
|
385
|
+
'shared.upload.files.uploaded': '{{count}} file(s) uploaded successfully',
|
|
386
|
+
'shared.upload.failed': 'Upload failed',
|
|
387
|
+
'shared.upload.provider.not.configured': 'File upload not configured. Add {{provider}} to your app config.',
|
|
388
|
+
// File type categories
|
|
389
|
+
'shared.file.type.images': 'Images',
|
|
390
|
+
'shared.file.type.documents': 'Documents',
|
|
391
|
+
'shared.file.type.videos': 'Videos',
|
|
392
|
+
'shared.file.type.audio': 'Audio',
|
|
393
|
+
// Size units
|
|
394
|
+
'shared.units.kb': 'KB',
|
|
395
|
+
'shared.units.mb': 'MB',
|
|
396
|
+
'shared.units.gb': 'GB',
|
|
397
|
+
'shared.units.tb': 'TB',
|
|
398
|
+
'shared.units.bytes': 'Bytes',
|
|
399
|
+
// File selector
|
|
400
|
+
'shared.file.selector.search.placeholder': 'Search files...',
|
|
401
|
+
'shared.file.selector.no.files': 'No files found',
|
|
402
|
+
'shared.file.selector.selected': '{{count}} selected',
|
|
403
|
+
'shared.file.selector.select.multiple': 'Select ({{count}})',
|
|
404
|
+
'shared.file.selector.select': 'Select',
|
|
405
|
+
'shared.file.selector.default.header': 'Select File',
|
|
406
|
+
'shared.file.selector.select.file': 'Select File',
|
|
407
|
+
'shared.file.selector.select.files': 'Select Files',
|
|
408
|
+
'shared.file.selector.all.folders': 'All Folders',
|
|
409
|
+
'shared.file.selector.all.storage': 'All Storage',
|
|
410
|
+
'shared.file.selector.provider.not.configured': 'File selection not configured.',
|
|
411
|
+
'shared.file.selector.add.provider': 'Add',
|
|
412
|
+
'shared.default': 'Default',
|
|
413
|
+
// User/option select
|
|
414
|
+
'shared.user.select.placeholder': 'Select User',
|
|
415
|
+
'shared.select.placeholder': 'Select Option',
|
|
416
|
+
'shared.multi.select.placeholder': 'Select Options',
|
|
417
|
+
'shared.multi.select.items.selected': '{{count}} Items Selected',
|
|
418
|
+
'shared.select.deselect.all': 'Select/Deselect All',
|
|
419
|
+
'shared.loading.actions': 'Loading actions...',
|
|
420
|
+
'shared.loading.roles': 'Loading roles...',
|
|
421
|
+
'shared.pending.changes': 'Pending Changes',
|
|
422
|
+
'shared.to.add': 'To Add',
|
|
423
|
+
'shared.to.remove': 'To Remove',
|
|
424
|
+
'shared.to.assign': 'To Assign',
|
|
425
|
+
'shared.to.whitelist': 'To Whitelist',
|
|
426
|
+
'shared.description.placeholder': 'Enter description',
|
|
427
|
+
// Backend error keys (used by exception filters)
|
|
428
|
+
'error.generic': 'An error occurred',
|
|
429
|
+
'error.not.found': 'Resource not found',
|
|
430
|
+
'error.validation': 'Validation failed',
|
|
431
|
+
'error.unauthorized': 'Unauthorized access',
|
|
432
|
+
'error.forbidden': 'Access forbidden',
|
|
433
|
+
'error.conflict': 'Resource conflict',
|
|
434
|
+
'error.internal': 'Internal server error',
|
|
435
|
+
'error.service.unavailable': 'Service temporarily unavailable',
|
|
436
|
+
'error.http': 'HTTP error',
|
|
437
|
+
'error.unknown': 'Unknown error occurred',
|
|
438
|
+
'error.permission.system.unavailable': 'Permission system temporarily unavailable. Please try again later.',
|
|
439
|
+
'error.insufficient.permissions': 'Missing required permissions: {{permissions}}',
|
|
440
|
+
'error.insufficient.permissions.or': 'Requires at least one of: {{permissions}}',
|
|
441
|
+
'error.no.permissions.found': 'No permissions found. Please contact administrator.',
|
|
442
|
+
// Backend system keys (infrastructure errors)
|
|
443
|
+
'system.repository.not.available': '{{entity}} repository not available',
|
|
444
|
+
'system.datasource.not.available': 'Data source not available',
|
|
445
|
+
'system.database.config.not.available': 'Database configuration not available',
|
|
446
|
+
'system.service.not.available': 'Service "{{provider}}" not available. Available: {{available}}',
|
|
447
|
+
'system.config.required': 'Configuration required',
|
|
448
|
+
'system.internal.error': 'Failed to initialize "{{provider}}": {{error}}',
|
|
449
|
+
'system.not.found': 'System resource not found',
|
|
450
|
+
'system.duplicate.request': 'Duplicate request detected',
|
|
451
|
+
'system.invalid.tenant.id': 'Invalid tenant ID',
|
|
452
|
+
'system.tenant.not.found': 'Tenant "{{tenantId}}" not found',
|
|
453
|
+
'system.tenant.header.required': 'Tenant not found. Ensure "{{header}}" header is set.',
|
|
454
|
+
'system.missing.parameter': 'Missing required parameter: {{key}}',
|
|
455
|
+
'system.sdk.not.installed': 'Required SDK "{{sdk}}" not installed. Run: npm install {{sdk}}',
|
|
456
|
+
'system.path.traversal.detected': 'Path traversal detected',
|
|
457
|
+
'system.invalid.file.key': 'Invalid file key',
|
|
458
|
+
// Aliases for commonly used keys (dot-separated)
|
|
459
|
+
'shared.all': 'All',
|
|
460
|
+
// PrimeNG component messages (synced to PrimeNGConfig)
|
|
461
|
+
'primeng.empty.message': 'No results found',
|
|
462
|
+
'primeng.empty.filter.message': 'No results found',
|
|
463
|
+
'primeng.empty.search.message': 'No results found',
|
|
464
|
+
'primeng.selection.message': '{0} items selected',
|
|
465
|
+
'primeng.empty.selection.message': 'No selected item',
|
|
466
|
+
'primeng.choose': 'Choose',
|
|
467
|
+
'primeng.upload': 'Upload',
|
|
468
|
+
'primeng.cancel': 'Cancel',
|
|
469
|
+
'primeng.pending': 'Pending',
|
|
470
|
+
'primeng.choose.date': 'Choose Date',
|
|
471
|
+
'primeng.today': 'Today',
|
|
472
|
+
'primeng.clear': 'Clear',
|
|
473
|
+
'primeng.week.header': 'Wk',
|
|
474
|
+
'primeng.first.day.of.week': '0',
|
|
475
|
+
'primeng.accept': 'Yes',
|
|
476
|
+
'primeng.reject': 'No',
|
|
477
|
+
'primeng.lt': 'Less than',
|
|
478
|
+
'primeng.lte': 'Less than or equal to',
|
|
479
|
+
'primeng.gt': 'Greater than',
|
|
480
|
+
'primeng.gte': 'Greater than or equal to',
|
|
481
|
+
'primeng.date.is': 'Date is',
|
|
482
|
+
'primeng.date.is.not': 'Date is not',
|
|
483
|
+
'primeng.date.before': 'Date is before',
|
|
484
|
+
'primeng.date.after': 'Date is after',
|
|
485
|
+
'primeng.contains': 'Contains',
|
|
486
|
+
'primeng.not.contains': 'Not contains',
|
|
487
|
+
'primeng.starts.with': 'Starts with',
|
|
488
|
+
'primeng.ends.with': 'Ends with',
|
|
489
|
+
'primeng.equals': 'Equals',
|
|
490
|
+
'primeng.not.equals': 'Not equals',
|
|
491
|
+
'primeng.no.filter': 'No Filter',
|
|
492
|
+
'primeng.match.all': 'Match All',
|
|
493
|
+
'primeng.match.any': 'Match Any',
|
|
494
|
+
'primeng.add.rule': 'Add Rule',
|
|
495
|
+
'primeng.remove.rule': 'Remove Rule',
|
|
496
|
+
'primeng.apply': 'Apply',
|
|
497
|
+
'primeng.aria.true.label': 'True',
|
|
498
|
+
'primeng.aria.false.label': 'False',
|
|
499
|
+
'primeng.aria.null.label': 'Not Selected',
|
|
500
|
+
'primeng.aria.star': '1 star',
|
|
501
|
+
'primeng.aria.stars': '{star} stars',
|
|
502
|
+
'primeng.aria.select.all': 'All items selected',
|
|
503
|
+
'primeng.aria.unselect.all': 'All items unselected',
|
|
504
|
+
'primeng.aria.close': 'Close',
|
|
505
|
+
'primeng.aria.previous': 'Previous',
|
|
506
|
+
'primeng.aria.next': 'Next',
|
|
507
|
+
'primeng.aria.navigation': 'Navigation',
|
|
508
|
+
'primeng.aria.scroll.top': 'Scroll Top',
|
|
509
|
+
'primeng.aria.move.top': 'Move Top',
|
|
510
|
+
'primeng.aria.move.up': 'Move Up',
|
|
511
|
+
'primeng.aria.move.down': 'Move Down',
|
|
512
|
+
'primeng.aria.move.bottom': 'Move Bottom',
|
|
513
|
+
'primeng.aria.move.to.target': 'Move to Target',
|
|
514
|
+
'primeng.aria.move.to.source': 'Move to Source',
|
|
515
|
+
'primeng.aria.move.all.to.target': 'Move All to Target',
|
|
516
|
+
'primeng.aria.move.all.to.source': 'Move All to Source',
|
|
517
|
+
'primeng.aria.page.label': 'Page {page}',
|
|
518
|
+
'primeng.aria.first.page.label': 'First Page',
|
|
519
|
+
'primeng.aria.last.page.label': 'Last Page',
|
|
520
|
+
'primeng.aria.next.page.label': 'Next Page',
|
|
521
|
+
'primeng.aria.prev.page.label': 'Previous Page',
|
|
522
|
+
'primeng.aria.rows.per.page.label': 'Rows per page',
|
|
523
|
+
'primeng.aria.jump.to.page.dropdown.label': 'Jump to Page Dropdown',
|
|
524
|
+
'primeng.aria.jump.to.page.input.label': 'Jump to Page Input',
|
|
525
|
+
'primeng.aria.select.row': 'Row Selected',
|
|
526
|
+
'primeng.aria.unselect.row': 'Row Unselected',
|
|
527
|
+
'primeng.aria.expand.row': 'Row Expanded',
|
|
528
|
+
'primeng.aria.collapse.row': 'Row Collapsed',
|
|
529
|
+
'primeng.aria.show.filter.menu': 'Show Filter Menu',
|
|
530
|
+
'primeng.aria.hide.filter.menu': 'Hide Filter Menu',
|
|
531
|
+
'primeng.aria.filter.operator': 'Filter Operator',
|
|
532
|
+
'primeng.aria.filter.constraint': 'Filter Constraint',
|
|
533
|
+
'primeng.aria.edit.row': 'Edit Row',
|
|
534
|
+
'primeng.aria.save.edit': 'Save Edit',
|
|
535
|
+
'primeng.aria.cancel.edit': 'Cancel Edit',
|
|
536
|
+
'primeng.aria.list.view': 'List View',
|
|
537
|
+
'primeng.aria.grid.view': 'Grid View',
|
|
538
|
+
'primeng.aria.slide': 'Slide',
|
|
539
|
+
'primeng.aria.slide.number': '{slideNumber}',
|
|
540
|
+
'primeng.aria.zoom.image': 'Zoom Image',
|
|
541
|
+
'primeng.aria.zoom.in': 'Zoom In',
|
|
542
|
+
'primeng.aria.zoom.out': 'Zoom Out',
|
|
543
|
+
'primeng.aria.rotate.right': 'Rotate Right',
|
|
544
|
+
'primeng.aria.rotate.left': 'Rotate Left',
|
|
545
|
+
// Common aliases (backward compatibility for modules using common.* prefix)
|
|
546
|
+
'common.save': 'Save',
|
|
547
|
+
'common.cancel': 'Cancel',
|
|
548
|
+
'common.delete': 'Delete',
|
|
549
|
+
'common.edit': 'Edit',
|
|
550
|
+
'common.create': 'Create',
|
|
551
|
+
'common.update': 'Update',
|
|
552
|
+
'common.add': 'Add',
|
|
553
|
+
'common.remove': 'Remove',
|
|
554
|
+
'common.close': 'Close',
|
|
555
|
+
'common.confirm': 'Confirm',
|
|
556
|
+
'common.test': 'Test',
|
|
557
|
+
'common.name': 'Name',
|
|
558
|
+
'common.description': 'Description',
|
|
559
|
+
'common.status': 'Status',
|
|
560
|
+
'common.active': 'Active',
|
|
561
|
+
'common.inactive': 'Inactive',
|
|
562
|
+
'common.default': 'Default',
|
|
563
|
+
'common.type': 'Type',
|
|
564
|
+
'common.created': 'Created',
|
|
565
|
+
'common.updated': 'Updated',
|
|
566
|
+
'common.actions': 'Actions',
|
|
567
|
+
'common.company': 'Company',
|
|
568
|
+
'common.success': 'Success',
|
|
569
|
+
'common.error': 'Error',
|
|
570
|
+
'common.validation': 'Validation',
|
|
571
|
+
'common.fill.required.fields': 'Please fill in all required fields',
|
|
572
|
+
// File uploader
|
|
573
|
+
'shared.file.uploader.no.upload.function': 'No upload function available. Configure FILE_PROVIDER or provide uploadFile input.',
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
185
577
|
const FILE_TYPE_FILTERS = {
|
|
186
578
|
IMAGES: ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml'],
|
|
187
579
|
DOCUMENTS: [
|
|
@@ -195,30 +587,15 @@ const FILE_TYPE_FILTERS = {
|
|
|
195
587
|
AUDIO: ['audio/mpeg', 'audio/wav', 'audio/ogg', 'audio/webm'],
|
|
196
588
|
ALL: [],
|
|
197
589
|
};
|
|
198
|
-
|
|
199
|
-
* Get accept string for file input from content types
|
|
200
|
-
*/
|
|
590
|
+
// ─── Utility Functions ────────────────────────────────────────────────────────
|
|
201
591
|
function getAcceptString(contentTypes) {
|
|
202
|
-
|
|
203
|
-
return '*/*';
|
|
204
|
-
return contentTypes.join(',');
|
|
592
|
+
return contentTypes.length ? contentTypes.join(',') : '*/*';
|
|
205
593
|
}
|
|
206
|
-
/**
|
|
207
|
-
* Check if file matches allowed content types
|
|
208
|
-
*/
|
|
209
594
|
function isFileTypeAllowed(file, allowedTypes) {
|
|
210
595
|
if (!allowedTypes.length)
|
|
211
596
|
return true;
|
|
212
|
-
return allowedTypes.some((type) =>
|
|
213
|
-
if (type.endsWith('/*')) {
|
|
214
|
-
return file.type.startsWith(type.replace('/*', '/'));
|
|
215
|
-
}
|
|
216
|
-
return file.type === type;
|
|
217
|
-
});
|
|
597
|
+
return allowedTypes.some((type) => type.endsWith('/*') ? file.type.startsWith(type.replace('/*', '/')) : file.type === type);
|
|
218
598
|
}
|
|
219
|
-
/**
|
|
220
|
-
* Get file icon class based on content type
|
|
221
|
-
*/
|
|
222
599
|
function getFileIconClass(contentType) {
|
|
223
600
|
if (contentType.startsWith('image/'))
|
|
224
601
|
return 'pi pi-image';
|
|
@@ -234,9 +611,6 @@ function getFileIconClass(contentType) {
|
|
|
234
611
|
return 'pi pi-file-excel';
|
|
235
612
|
return 'pi pi-file';
|
|
236
613
|
}
|
|
237
|
-
/**
|
|
238
|
-
* Format file size for display
|
|
239
|
-
*/
|
|
240
614
|
function formatFileSize(sizeInKb) {
|
|
241
615
|
const kb = typeof sizeInKb === 'string' ? parseFloat(sizeInKb) : sizeInKb;
|
|
242
616
|
if (kb < 1024)
|
|
@@ -244,8 +618,7 @@ function formatFileSize(sizeInKb) {
|
|
|
244
618
|
const mb = kb / 1024;
|
|
245
619
|
if (mb < 1024)
|
|
246
620
|
return `${mb.toFixed(1)} MB`;
|
|
247
|
-
|
|
248
|
-
return `${gb.toFixed(2)} GB`;
|
|
621
|
+
return `${(mb / 1024).toFixed(2)} GB`;
|
|
249
622
|
}
|
|
250
623
|
|
|
251
624
|
var ContactTypeEnum;
|
|
@@ -720,6 +1093,156 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
720
1093
|
}]
|
|
721
1094
|
}], propDecorators: { eventType: [{ type: i0.Input, args: [{ isSignal: true, alias: "eventType", required: false }] }], preventKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "preventKey", required: false }] }], action: [{ type: i0.Output, args: ["action"] }] } });
|
|
722
1095
|
|
|
1096
|
+
class TranslatePipe {
|
|
1097
|
+
translateAdapter = inject(TRANSLATE_ADAPTER, { optional: true });
|
|
1098
|
+
fallbackMessages = inject(FALLBACK_MESSAGES_REGISTRY, { optional: true });
|
|
1099
|
+
transform(key, params) {
|
|
1100
|
+
if (!this.translateAdapter) {
|
|
1101
|
+
return this.getFromFallback(key, params);
|
|
1102
|
+
}
|
|
1103
|
+
// Read languageCode signal to create reactive dependency for zoneless mode
|
|
1104
|
+
// This ensures the pipe re-evaluates when language changes
|
|
1105
|
+
this.translateAdapter.languageCode();
|
|
1106
|
+
return this.translateAdapter.translate(key, params);
|
|
1107
|
+
}
|
|
1108
|
+
getFromFallback(template, params) {
|
|
1109
|
+
// Try to get from fallback messages registry first
|
|
1110
|
+
if (this.fallbackMessages) {
|
|
1111
|
+
const value = this.fallbackMessages[template];
|
|
1112
|
+
if (value) {
|
|
1113
|
+
return this.interpolate(value, params);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
// Fall back to interpolating the key itself
|
|
1117
|
+
return this.interpolate(template, params);
|
|
1118
|
+
}
|
|
1119
|
+
interpolate(template, params) {
|
|
1120
|
+
if (!params)
|
|
1121
|
+
return template;
|
|
1122
|
+
return template.replace(/\{\{(\w+)\}\}/g, (match, paramKey) => {
|
|
1123
|
+
const value = params[paramKey];
|
|
1124
|
+
return value !== undefined ? String(value) : match;
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: TranslatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
1128
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.5", ngImport: i0, type: TranslatePipe, isStandalone: true, name: "translate", pure: false });
|
|
1129
|
+
}
|
|
1130
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: TranslatePipe, decorators: [{
|
|
1131
|
+
type: Pipe,
|
|
1132
|
+
args: [{
|
|
1133
|
+
name: 'translate',
|
|
1134
|
+
pure: false,
|
|
1135
|
+
}]
|
|
1136
|
+
}] });
|
|
1137
|
+
|
|
1138
|
+
async function tryLoadLocalizationServices(injector) {
|
|
1139
|
+
try {
|
|
1140
|
+
// @ts-ignore - ng-localization is optional and loaded dynamically at runtime
|
|
1141
|
+
const locModule = await import('@flusys/ng-localization');
|
|
1142
|
+
const config = injector.get(locModule.LOCALIZATION_CONFIG, null);
|
|
1143
|
+
if (!config)
|
|
1144
|
+
return null;
|
|
1145
|
+
return {
|
|
1146
|
+
stateService: injector.get(locModule.LocalizationStateService),
|
|
1147
|
+
apiService: injector.get(locModule.LocalizationApiService),
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
catch {
|
|
1151
|
+
return null;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
function resolveTranslationModule(config) {
|
|
1155
|
+
return async () => {
|
|
1156
|
+
const injector = inject(Injector);
|
|
1157
|
+
const { modules, fallbackMessages } = config;
|
|
1158
|
+
// Try to load localization services if available
|
|
1159
|
+
const locServices = await tryLoadLocalizationServices(injector);
|
|
1160
|
+
// Register fallback messages
|
|
1161
|
+
if (fallbackMessages) {
|
|
1162
|
+
if (locServices) {
|
|
1163
|
+
modules.forEach((module) => {
|
|
1164
|
+
if (!locServices.stateService.hasModuleFallbacks(module)) {
|
|
1165
|
+
locServices.stateService.registerModuleFallbacks(module, fallbackMessages);
|
|
1166
|
+
}
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
else {
|
|
1170
|
+
const registry = injector.get(FALLBACK_MESSAGES_REGISTRY, null);
|
|
1171
|
+
if (registry) {
|
|
1172
|
+
Object.assign(registry, fallbackMessages);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
if (!locServices)
|
|
1177
|
+
return true;
|
|
1178
|
+
// Load translations from API
|
|
1179
|
+
const languageCode = locServices.stateService.currentLanguageCode();
|
|
1180
|
+
const modulesToLoad = modules.filter((m) => !locServices.stateService.isModuleLoaded(m));
|
|
1181
|
+
if (modulesToLoad.length === 0)
|
|
1182
|
+
return true;
|
|
1183
|
+
try {
|
|
1184
|
+
const response = (await firstValueFrom(locServices.apiService.getTranslationsByLanguage(languageCode, modulesToLoad)));
|
|
1185
|
+
if (response?.success && response?.data) {
|
|
1186
|
+
locServices.stateService.mergeTranslations(response.data);
|
|
1187
|
+
modulesToLoad.forEach((m) => locServices.stateService.markModuleLoaded(m));
|
|
1188
|
+
}
|
|
1189
|
+
else if (modulesToLoad.length > 0 && isDevMode()) {
|
|
1190
|
+
console.warn(`No translation data received for modules: ${modulesToLoad.join(', ')}`);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
catch (error) {
|
|
1194
|
+
if (isDevMode()) {
|
|
1195
|
+
console.error(`Failed to load translations for modules: ${modules.join(', ')}`, error);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
return true;
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
/** Fallback translate adapter for when localization provider is not used */
|
|
1203
|
+
const createFallbackTranslateAdapter = (fallbackRegistry) => ({
|
|
1204
|
+
languageCode: signal('en'),
|
|
1205
|
+
translate: (key, params) => {
|
|
1206
|
+
// Check fallback messages registry first
|
|
1207
|
+
const value = fallbackRegistry[key];
|
|
1208
|
+
if (value) {
|
|
1209
|
+
// Simple parameter interpolation
|
|
1210
|
+
if (params) {
|
|
1211
|
+
return value.replace(/\{\{(\w+)\}\}/g, (match, paramKey) => {
|
|
1212
|
+
const paramValue = params[paramKey];
|
|
1213
|
+
return paramValue !== undefined ? String(paramValue) : match;
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
return value;
|
|
1217
|
+
}
|
|
1218
|
+
// Fall back to returning the key
|
|
1219
|
+
return key;
|
|
1220
|
+
},
|
|
1221
|
+
});
|
|
1222
|
+
/**
|
|
1223
|
+
* Provide fallback localization when @flusys/ng-localization is not used.
|
|
1224
|
+
* Uses hardcoded fallback messages registered by route resolvers.
|
|
1225
|
+
*
|
|
1226
|
+
* @example
|
|
1227
|
+
* ```typescript
|
|
1228
|
+
* providers: [
|
|
1229
|
+
* ...provideFallbackLocalization(),
|
|
1230
|
+
* ]
|
|
1231
|
+
* ```
|
|
1232
|
+
*/
|
|
1233
|
+
function provideFallbackLocalization() {
|
|
1234
|
+
return [
|
|
1235
|
+
// Fallback messages registry (populated by route resolvers)
|
|
1236
|
+
{ provide: FALLBACK_MESSAGES_REGISTRY, useValue: {} },
|
|
1237
|
+
// Fallback translate adapter (reads from registry)
|
|
1238
|
+
{
|
|
1239
|
+
provide: TRANSLATE_ADAPTER,
|
|
1240
|
+
useFactory: createFallbackTranslateAdapter,
|
|
1241
|
+
deps: [FALLBACK_MESSAGES_REGISTRY],
|
|
1242
|
+
},
|
|
1243
|
+
];
|
|
1244
|
+
}
|
|
1245
|
+
|
|
723
1246
|
class AngularModule {
|
|
724
1247
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: AngularModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
725
1248
|
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.1.5", ngImport: i0, type: AngularModule, imports: [CommonModule,
|
|
@@ -814,9 +1337,12 @@ class PrimeModule {
|
|
|
814
1337
|
TagModule,
|
|
815
1338
|
TextareaModule,
|
|
816
1339
|
ToastModule,
|
|
1340
|
+
ToggleButtonModule,
|
|
817
1341
|
ToggleSwitchModule,
|
|
818
1342
|
TooltipModule,
|
|
819
|
-
TreeTableModule
|
|
1343
|
+
TreeTableModule,
|
|
1344
|
+
ProgressSpinnerModule,
|
|
1345
|
+
ColorPickerModule] });
|
|
820
1346
|
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: PrimeModule, imports: [AutoCompleteModule,
|
|
821
1347
|
AvatarModule,
|
|
822
1348
|
ButtonModule,
|
|
@@ -851,9 +1377,12 @@ class PrimeModule {
|
|
|
851
1377
|
TagModule,
|
|
852
1378
|
TextareaModule,
|
|
853
1379
|
ToastModule,
|
|
1380
|
+
ToggleButtonModule,
|
|
854
1381
|
ToggleSwitchModule,
|
|
855
1382
|
TooltipModule,
|
|
856
|
-
TreeTableModule
|
|
1383
|
+
TreeTableModule,
|
|
1384
|
+
ProgressSpinnerModule,
|
|
1385
|
+
ColorPickerModule] });
|
|
857
1386
|
}
|
|
858
1387
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: PrimeModule, decorators: [{
|
|
859
1388
|
type: NgModule,
|
|
@@ -893,64 +1422,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
893
1422
|
TagModule,
|
|
894
1423
|
TextareaModule,
|
|
895
1424
|
ToastModule,
|
|
1425
|
+
ToggleButtonModule,
|
|
896
1426
|
ToggleSwitchModule,
|
|
897
1427
|
TooltipModule,
|
|
898
1428
|
TreeTableModule,
|
|
1429
|
+
ProgressSpinnerModule,
|
|
1430
|
+
ColorPickerModule,
|
|
899
1431
|
],
|
|
900
1432
|
}]
|
|
901
1433
|
}] });
|
|
902
1434
|
|
|
903
|
-
// =============================================================================
|
|
904
|
-
// API Resource Service - Signal-based CRUD with Angular Resource API
|
|
905
|
-
// =============================================================================
|
|
906
1435
|
/**
|
|
907
1436
|
* Abstract base class for API services using Angular 21 resource() API.
|
|
908
1437
|
* Provides signal-based reactive data fetching with automatic loading states.
|
|
909
|
-
* Response types match FLUSYS_NEST backend DTOs.
|
|
910
|
-
*
|
|
911
|
-
* ## Endpoint Mapping
|
|
912
|
-
*
|
|
913
|
-
* All endpoints use POST method (RPC-style API):
|
|
914
|
-
* - `POST /{resource}/insert` - Create single item
|
|
915
|
-
* - `POST /{resource}/insert-many` - Create multiple items
|
|
916
|
-
* - `POST /{resource}/get/:id` - Get single item by ID
|
|
917
|
-
* - `POST /{resource}/get-all?q=` - List with pagination/filter
|
|
918
|
-
* - `POST /{resource}/update` - Update single item
|
|
919
|
-
* - `POST /{resource}/update-many` - Update multiple items
|
|
920
|
-
* - `POST /{resource}/delete` - Delete/restore/permanent delete
|
|
921
1438
|
*
|
|
922
1439
|
* @example
|
|
923
1440
|
* ```typescript
|
|
924
|
-
* // Define service with global apiBaseUrl
|
|
925
1441
|
* @Injectable({ providedIn: 'root' })
|
|
926
1442
|
* export class UserService extends ApiResourceService<UserDto, User> {
|
|
927
1443
|
* constructor() {
|
|
928
1444
|
* super('auth/users', inject(HttpClient));
|
|
929
1445
|
* }
|
|
930
1446
|
* }
|
|
931
|
-
*
|
|
932
|
-
* // Define service with feature-specific baseUrl
|
|
933
|
-
* @Injectable({ providedIn: 'root' })
|
|
934
|
-
* export class FormService extends ApiResourceService<FormDto, Form> {
|
|
935
|
-
* constructor() {
|
|
936
|
-
* super('form', inject(HttpClient), 'formBuilder');
|
|
937
|
-
* // URL: services.formBuilder.baseUrl + '/form'
|
|
938
|
-
* }
|
|
939
|
-
* }
|
|
940
|
-
*
|
|
941
|
-
* // In component - use signals
|
|
942
|
-
* userService = inject(UserService);
|
|
943
|
-
* users = this.userService.data; // Signal<User[]>
|
|
944
|
-
* isLoading = this.userService.isLoading; // Signal<boolean>
|
|
945
|
-
* total = this.userService.total; // Signal<number>
|
|
946
|
-
*
|
|
947
|
-
* // Trigger fetch
|
|
948
|
-
* this.userService.fetchList('search', { pagination: { currentPage: 0, pageSize: 10 } });
|
|
949
|
-
*
|
|
950
|
-
* // CRUD operations
|
|
951
|
-
* await this.userService.insertAsync({ name: 'John', email: 'john@example.com' });
|
|
952
|
-
* await this.userService.updateAsync({ id: '123', name: 'John Updated' });
|
|
953
|
-
* await this.userService.deleteAsync({ id: '123', type: 'delete' });
|
|
954
1447
|
* ```
|
|
955
1448
|
*/
|
|
956
1449
|
class ApiResourceService {
|
|
@@ -958,43 +1451,22 @@ class ApiResourceService {
|
|
|
958
1451
|
injector = inject(Injector);
|
|
959
1452
|
http;
|
|
960
1453
|
moduleApiName;
|
|
961
|
-
// ==========================================================================
|
|
962
|
-
// State Signals for List Queries
|
|
963
|
-
// ==========================================================================
|
|
964
|
-
/** Current search term */
|
|
965
1454
|
searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
|
|
966
|
-
/** Current filter and pagination state */
|
|
967
1455
|
filterData = signal({
|
|
968
1456
|
pagination: { currentPage: 0, pageSize: 10 },
|
|
969
1457
|
filter: {},
|
|
970
1458
|
select: [],
|
|
971
1459
|
sort: {},
|
|
972
1460
|
}, ...(ngDevMode ? [{ debugName: "filterData" }] : []));
|
|
973
|
-
/**
|
|
974
|
-
* Resource for list data - lazy initialized to prevent auto-fetch on service injection.
|
|
975
|
-
* Call initListResource() or any list method (fetchList, reload, etc.) to initialize.
|
|
976
|
-
*/
|
|
977
1461
|
_listResource = null;
|
|
978
|
-
/** Whether the list resource has been initialized */
|
|
979
1462
|
_resourceInitialized = false;
|
|
980
|
-
/**
|
|
981
|
-
* Signal to track resource initialization for computed signals.
|
|
982
|
-
* This allows computed signals to re-evaluate when the resource is created.
|
|
983
|
-
* Without this, computed signals would not detect when _listResource changes from null.
|
|
984
|
-
*/
|
|
985
1463
|
_resourceInitSignal = signal(false, ...(ngDevMode ? [{ debugName: "_resourceInitSignal" }] : []));
|
|
986
|
-
/** Get or create the list resource (lazy initialization) */
|
|
987
1464
|
get listResource() {
|
|
988
1465
|
if (!this._listResource) {
|
|
989
1466
|
this.initListResource();
|
|
990
1467
|
}
|
|
991
1468
|
return this._listResource;
|
|
992
1469
|
}
|
|
993
|
-
/**
|
|
994
|
-
* Initialize the list resource. Called automatically when accessing listResource
|
|
995
|
-
* or any list-related computed signals/methods.
|
|
996
|
-
* Uses runInInjectionContext to support lazy initialization outside constructor.
|
|
997
|
-
*/
|
|
998
1470
|
initListResource() {
|
|
999
1471
|
if (this._resourceInitialized)
|
|
1000
1472
|
return;
|
|
@@ -1004,88 +1476,49 @@ class ApiResourceService {
|
|
|
1004
1476
|
search: this.searchTerm(),
|
|
1005
1477
|
filter: this.filterData(),
|
|
1006
1478
|
}),
|
|
1007
|
-
loader: async ({ params }) =>
|
|
1008
|
-
const { search, filter } = params;
|
|
1009
|
-
return this.fetchAllAsync(search, filter);
|
|
1010
|
-
} });
|
|
1479
|
+
loader: async ({ params }) => this.getAllAsync(params.filter, params.search) });
|
|
1011
1480
|
});
|
|
1012
|
-
// Signal that resource is now initialized - triggers computed re-evaluation
|
|
1013
1481
|
this._resourceInitSignal.set(true);
|
|
1014
1482
|
}
|
|
1015
|
-
// ==========================================================================
|
|
1016
|
-
// Computed State Accessors
|
|
1017
|
-
// ==========================================================================
|
|
1018
|
-
/**
|
|
1019
|
-
* Whether data is currently loading.
|
|
1020
|
-
* Tracks _resourceInitSignal to re-evaluate when resource is created.
|
|
1021
|
-
*/
|
|
1022
1483
|
isLoading = computed(() => {
|
|
1023
|
-
this._resourceInitSignal();
|
|
1484
|
+
this._resourceInitSignal();
|
|
1024
1485
|
return this._listResource?.isLoading() ?? false;
|
|
1025
1486
|
}, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
1026
|
-
/**
|
|
1027
|
-
* List data array.
|
|
1028
|
-
* Tracks _resourceInitSignal to re-evaluate when resource is created.
|
|
1029
|
-
*/
|
|
1030
1487
|
data = computed(() => {
|
|
1031
|
-
this._resourceInitSignal();
|
|
1488
|
+
this._resourceInitSignal();
|
|
1032
1489
|
return this._listResource?.value()?.data ?? [];
|
|
1033
1490
|
}, ...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
1034
|
-
/**
|
|
1035
|
-
* Total count of items.
|
|
1036
|
-
* Tracks _resourceInitSignal to re-evaluate when resource is created.
|
|
1037
|
-
*/
|
|
1038
1491
|
total = computed(() => {
|
|
1039
|
-
this._resourceInitSignal();
|
|
1492
|
+
this._resourceInitSignal();
|
|
1040
1493
|
return this._listResource?.value()?.meta?.total ?? 0;
|
|
1041
1494
|
}, ...(ngDevMode ? [{ debugName: "total" }] : []));
|
|
1042
|
-
/**
|
|
1043
|
-
* Pagination metadata.
|
|
1044
|
-
* Tracks _resourceInitSignal to re-evaluate when resource is created.
|
|
1045
|
-
*/
|
|
1046
1495
|
pageInfo = computed(() => {
|
|
1047
|
-
this._resourceInitSignal();
|
|
1496
|
+
this._resourceInitSignal();
|
|
1048
1497
|
return this._listResource?.value()?.meta;
|
|
1049
1498
|
}, ...(ngDevMode ? [{ debugName: "pageInfo" }] : []));
|
|
1050
|
-
/**
|
|
1051
|
-
* Whether there are more pages.
|
|
1052
|
-
* Tracks _resourceInitSignal to re-evaluate when resource is created.
|
|
1053
|
-
*/
|
|
1054
1499
|
hasMore = computed(() => {
|
|
1055
|
-
this._resourceInitSignal();
|
|
1500
|
+
this._resourceInitSignal();
|
|
1056
1501
|
const meta = this._listResource?.value()?.meta;
|
|
1057
1502
|
if (!meta)
|
|
1058
1503
|
return false;
|
|
1059
1504
|
return meta.hasMore ?? (meta.page + 1) * meta.pageSize < meta.total;
|
|
1060
1505
|
}, ...(ngDevMode ? [{ debugName: "hasMore" }] : []));
|
|
1061
|
-
/**
|
|
1062
|
-
* @param moduleApiName - The API resource path (e.g., 'form' for /form-builder/form)
|
|
1063
|
-
* @param http - HttpClient instance
|
|
1064
|
-
* @param serviceName - Optional service name for feature-specific base URL (e.g., 'formBuilder')
|
|
1065
|
-
*/
|
|
1066
1506
|
constructor(moduleApiName, http, serviceName) {
|
|
1067
|
-
this.moduleApiName = moduleApiName;
|
|
1507
|
+
this.moduleApiName = moduleApiName || serviceName || 'api';
|
|
1068
1508
|
const config = inject(APP_CONFIG);
|
|
1069
|
-
// Use service-specific URL if provided, otherwise fallback to global apiBaseUrl
|
|
1070
1509
|
const serviceBaseUrl = serviceName ? getServiceUrl(config, serviceName) : config.apiBaseUrl;
|
|
1071
|
-
this.baseUrl = `${serviceBaseUrl}/${moduleApiName}
|
|
1510
|
+
this.baseUrl = moduleApiName ? `${serviceBaseUrl}/${moduleApiName}` : serviceBaseUrl;
|
|
1072
1511
|
this.http = http;
|
|
1073
|
-
// Resource is now lazy-initialized, not created in constructor
|
|
1074
1512
|
}
|
|
1075
1513
|
getHttpOptions(endpoint, params) {
|
|
1076
1514
|
return {
|
|
1077
|
-
headers: new HttpHeaders({
|
|
1078
|
-
'x-loader-tag': `${this.moduleApiName}/${endpoint}`,
|
|
1079
|
-
}),
|
|
1515
|
+
headers: new HttpHeaders({ 'x-loader-tag': `${this.moduleApiName}/${endpoint}` }),
|
|
1080
1516
|
...(params ? { params } : {}),
|
|
1081
1517
|
};
|
|
1082
1518
|
}
|
|
1083
1519
|
// ==========================================================================
|
|
1084
|
-
// List Management
|
|
1520
|
+
// List Management
|
|
1085
1521
|
// ==========================================================================
|
|
1086
|
-
/**
|
|
1087
|
-
* Fetch list data (triggers resource initialization and reload)
|
|
1088
|
-
*/
|
|
1089
1522
|
fetchList(search = '', filter) {
|
|
1090
1523
|
this.initListResource();
|
|
1091
1524
|
this.searchTerm.set(search);
|
|
@@ -1093,16 +1526,10 @@ class ApiResourceService {
|
|
|
1093
1526
|
this.filterData.update((prev) => ({ ...prev, ...filter }));
|
|
1094
1527
|
}
|
|
1095
1528
|
}
|
|
1096
|
-
/**
|
|
1097
|
-
* Update pagination
|
|
1098
|
-
*/
|
|
1099
1529
|
setPagination(pagination) {
|
|
1100
1530
|
this.initListResource();
|
|
1101
1531
|
this.filterData.update((prev) => ({ ...prev, pagination }));
|
|
1102
1532
|
}
|
|
1103
|
-
/**
|
|
1104
|
-
* Go to next page
|
|
1105
|
-
*/
|
|
1106
1533
|
nextPage() {
|
|
1107
1534
|
this.initListResource();
|
|
1108
1535
|
this.filterData.update((prev) => ({
|
|
@@ -1113,9 +1540,6 @@ class ApiResourceService {
|
|
|
1113
1540
|
},
|
|
1114
1541
|
}));
|
|
1115
1542
|
}
|
|
1116
|
-
/**
|
|
1117
|
-
* Reset to first page
|
|
1118
|
-
*/
|
|
1119
1543
|
resetPagination() {
|
|
1120
1544
|
this.initListResource();
|
|
1121
1545
|
this.filterData.update((prev) => ({
|
|
@@ -1123,42 +1547,21 @@ class ApiResourceService {
|
|
|
1123
1547
|
pagination: { currentPage: 0, pageSize: prev.pagination?.pageSize ?? 10 },
|
|
1124
1548
|
}));
|
|
1125
1549
|
}
|
|
1126
|
-
/**
|
|
1127
|
-
* Reload current data
|
|
1128
|
-
*/
|
|
1129
1550
|
reload() {
|
|
1130
|
-
|
|
1131
|
-
this._listResource.reload();
|
|
1132
|
-
}
|
|
1551
|
+
this._listResource?.reload();
|
|
1133
1552
|
}
|
|
1134
1553
|
// ==========================================================================
|
|
1135
|
-
// Observable
|
|
1554
|
+
// Observable API (IApiService interface)
|
|
1136
1555
|
// ==========================================================================
|
|
1137
|
-
/**
|
|
1138
|
-
* Insert single item (Observable)
|
|
1139
|
-
* POST /{resource}/insert
|
|
1140
|
-
*/
|
|
1141
1556
|
insert(dto) {
|
|
1142
1557
|
return this.http.post(`${this.baseUrl}/insert`, dto, this.getHttpOptions('insert'));
|
|
1143
1558
|
}
|
|
1144
|
-
/**
|
|
1145
|
-
* Insert multiple items (Observable)
|
|
1146
|
-
* POST /{resource}/insert-many
|
|
1147
|
-
*/
|
|
1148
1559
|
insertMany(dtos) {
|
|
1149
1560
|
return this.http.post(`${this.baseUrl}/insert-many`, dtos, this.getHttpOptions('insert-many'));
|
|
1150
1561
|
}
|
|
1151
|
-
/**
|
|
1152
|
-
* Find single item by ID (Observable)
|
|
1153
|
-
* POST /{resource}/get/:id
|
|
1154
|
-
*/
|
|
1155
1562
|
findById(id, select) {
|
|
1156
1563
|
return this.http.post(`${this.baseUrl}/get/${id}`, { select }, this.getHttpOptions(`get/${id}`));
|
|
1157
1564
|
}
|
|
1158
|
-
/**
|
|
1159
|
-
* Get all items with pagination (Observable)
|
|
1160
|
-
* POST /{resource}/get-all?q=search
|
|
1161
|
-
*/
|
|
1162
1565
|
getAll(search, filter) {
|
|
1163
1566
|
let params = new HttpParams();
|
|
1164
1567
|
if (search) {
|
|
@@ -1166,70 +1569,36 @@ class ApiResourceService {
|
|
|
1166
1569
|
}
|
|
1167
1570
|
return this.http.post(`${this.baseUrl}/get-all`, filter, this.getHttpOptions('get-all', params));
|
|
1168
1571
|
}
|
|
1169
|
-
/**
|
|
1170
|
-
* Update single item (Observable)
|
|
1171
|
-
* POST /{resource}/update
|
|
1172
|
-
*/
|
|
1173
1572
|
update(dto) {
|
|
1174
1573
|
return this.http.post(`${this.baseUrl}/update`, dto, this.getHttpOptions('update'));
|
|
1175
1574
|
}
|
|
1176
|
-
/**
|
|
1177
|
-
* Update multiple items (Observable)
|
|
1178
|
-
* POST /{resource}/update-many
|
|
1179
|
-
*/
|
|
1180
1575
|
updateMany(dtos) {
|
|
1181
1576
|
return this.http.post(`${this.baseUrl}/update-many`, dtos, this.getHttpOptions('update-many'));
|
|
1182
1577
|
}
|
|
1183
|
-
/**
|
|
1184
|
-
* Delete items (Observable)
|
|
1185
|
-
* POST /{resource}/delete
|
|
1186
|
-
* @param deleteDto - { id: string | string[], type: 'delete' | 'restore' | 'permanent' }
|
|
1187
|
-
*/
|
|
1188
1578
|
delete(deleteDto) {
|
|
1189
1579
|
return this.http.post(`${this.baseUrl}/delete`, deleteDto, this.getHttpOptions('delete'));
|
|
1190
1580
|
}
|
|
1191
1581
|
// ==========================================================================
|
|
1192
|
-
//
|
|
1582
|
+
// Async API
|
|
1193
1583
|
// ==========================================================================
|
|
1194
|
-
|
|
1195
|
-
* Fetch paginated list (async)
|
|
1196
|
-
*/
|
|
1197
|
-
async fetchAllAsync(search, filter) {
|
|
1584
|
+
async getAllAsync(filter, search = '') {
|
|
1198
1585
|
return firstValueFrom(this.getAll(search, filter));
|
|
1199
1586
|
}
|
|
1200
|
-
/**
|
|
1201
|
-
* Find single item by ID (async)
|
|
1202
|
-
*/
|
|
1203
1587
|
async findByIdAsync(id, select) {
|
|
1204
1588
|
return firstValueFrom(this.findById(id, select));
|
|
1205
1589
|
}
|
|
1206
|
-
/**
|
|
1207
|
-
* Insert single item (async)
|
|
1208
|
-
*/
|
|
1209
1590
|
async insertAsync(dto) {
|
|
1210
1591
|
return firstValueFrom(this.insert(dto));
|
|
1211
1592
|
}
|
|
1212
|
-
/**
|
|
1213
|
-
* Insert multiple items (async)
|
|
1214
|
-
*/
|
|
1215
1593
|
async insertManyAsync(dtos) {
|
|
1216
1594
|
return firstValueFrom(this.insertMany(dtos));
|
|
1217
1595
|
}
|
|
1218
|
-
/**
|
|
1219
|
-
* Update single item (async)
|
|
1220
|
-
*/
|
|
1221
1596
|
async updateAsync(dto) {
|
|
1222
1597
|
return firstValueFrom(this.update(dto));
|
|
1223
1598
|
}
|
|
1224
|
-
/**
|
|
1225
|
-
* Update multiple items (async)
|
|
1226
|
-
*/
|
|
1227
1599
|
async updateManyAsync(dtos) {
|
|
1228
1600
|
return firstValueFrom(this.updateMany(dtos));
|
|
1229
1601
|
}
|
|
1230
|
-
/**
|
|
1231
|
-
* Delete items (async)
|
|
1232
|
-
*/
|
|
1233
1602
|
async deleteAsync(deleteDto) {
|
|
1234
1603
|
return firstValueFrom(this.delete(deleteDto));
|
|
1235
1604
|
}
|
|
@@ -1373,14 +1742,13 @@ class IconComponent {
|
|
|
1373
1742
|
<i class="pi pi-question"></i>
|
|
1374
1743
|
}
|
|
1375
1744
|
}
|
|
1376
|
-
`, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: IsEmptyImageDirective, selector: "img", inputs: ["src"] }]
|
|
1745
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: IsEmptyImageDirective, selector: "img", inputs: ["src"] }] });
|
|
1377
1746
|
}
|
|
1378
1747
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: IconComponent, decorators: [{
|
|
1379
1748
|
type: Component,
|
|
1380
1749
|
args: [{
|
|
1381
1750
|
selector: 'lib-icon',
|
|
1382
1751
|
imports: [AngularModule],
|
|
1383
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1384
1752
|
template: `
|
|
1385
1753
|
@if (icon()) {
|
|
1386
1754
|
@if (iconType() === IconTypeEnum.PRIMENG_ICON) {
|
|
@@ -1438,13 +1806,24 @@ function checkScrollPagination(event, config) {
|
|
|
1438
1806
|
* - Signal forms: `[formField]="formTree.field"`
|
|
1439
1807
|
*/
|
|
1440
1808
|
class LazyMultiSelectComponent extends BaseFormControl {
|
|
1441
|
-
|
|
1809
|
+
document = inject(DOCUMENT$1);
|
|
1810
|
+
appRef = inject(ApplicationRef);
|
|
1811
|
+
translateAdapter = inject(TRANSLATE_ADAPTER, { optional: true });
|
|
1442
1812
|
onDocumentClickBound = this.handleDocumentClick.bind(this);
|
|
1443
|
-
isDestroyed = false;
|
|
1444
1813
|
// View references
|
|
1445
1814
|
pSelectRef = viewChild.required('pSelect');
|
|
1815
|
+
overlayTemplate = viewChild.required('overlayTpl');
|
|
1816
|
+
// Portal state
|
|
1817
|
+
overlayViewRef = null;
|
|
1446
1818
|
// Inputs
|
|
1447
|
-
placeHolder = input('
|
|
1819
|
+
placeHolder = input('', ...(ngDevMode ? [{ debugName: "placeHolder" }] : []));
|
|
1820
|
+
// Computed placeholder with translation fallback
|
|
1821
|
+
displayPlaceholder = computed(() => {
|
|
1822
|
+
const customPlaceholder = this.placeHolder();
|
|
1823
|
+
if (customPlaceholder)
|
|
1824
|
+
return customPlaceholder;
|
|
1825
|
+
return this.t('shared.multi.select.placeholder');
|
|
1826
|
+
}, ...(ngDevMode ? [{ debugName: "displayPlaceholder" }] : []));
|
|
1448
1827
|
isEditMode = input.required(...(ngDevMode ? [{ debugName: "isEditMode" }] : []));
|
|
1449
1828
|
isLoading = input.required(...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
1450
1829
|
total = input.required(...(ngDevMode ? [{ debugName: "total" }] : []));
|
|
@@ -1464,7 +1843,7 @@ class LazyMultiSelectComponent extends BaseFormControl {
|
|
|
1464
1843
|
if (selectedValues.length === 0)
|
|
1465
1844
|
return '';
|
|
1466
1845
|
if (selectedValues.length > 3) {
|
|
1467
|
-
return
|
|
1846
|
+
return this.t('shared.multi.select.items.selected', { count: selectedValues.length });
|
|
1468
1847
|
}
|
|
1469
1848
|
return this.selectDataList()
|
|
1470
1849
|
.filter((item) => selectedValues.includes(item.value))
|
|
@@ -1503,16 +1882,16 @@ class LazyMultiSelectComponent extends BaseFormControl {
|
|
|
1503
1882
|
});
|
|
1504
1883
|
});
|
|
1505
1884
|
// Document click listener for closing dropdown
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
afterNextRender(() => {
|
|
1511
|
-
if (!this.isDestroyed) {
|
|
1512
|
-
document.addEventListener('click', this.onDocumentClickBound);
|
|
1513
|
-
}
|
|
1885
|
+
afterNextRender({
|
|
1886
|
+
write: () => {
|
|
1887
|
+
this.document.addEventListener('click', this.onDocumentClickBound);
|
|
1888
|
+
},
|
|
1514
1889
|
});
|
|
1515
1890
|
}
|
|
1891
|
+
ngOnDestroy() {
|
|
1892
|
+
this.document.removeEventListener('click', this.onDocumentClickBound);
|
|
1893
|
+
this.closeOverlay();
|
|
1894
|
+
}
|
|
1516
1895
|
onScroll(event) {
|
|
1517
1896
|
const nextPagination = checkScrollPagination(event, {
|
|
1518
1897
|
pagination: this.pagination(),
|
|
@@ -1523,29 +1902,62 @@ class LazyMultiSelectComponent extends BaseFormControl {
|
|
|
1523
1902
|
this.onPagination.emit(nextPagination);
|
|
1524
1903
|
}
|
|
1525
1904
|
}
|
|
1526
|
-
onSelectClick(
|
|
1527
|
-
if (this.disabled())
|
|
1905
|
+
onSelectClick() {
|
|
1906
|
+
if (this.disabled() || !this.isEditMode())
|
|
1528
1907
|
return;
|
|
1908
|
+
if (this.openOptions()) {
|
|
1909
|
+
this.closeOverlay();
|
|
1910
|
+
}
|
|
1911
|
+
else {
|
|
1912
|
+
this.openOverlay();
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
openOverlay() {
|
|
1529
1916
|
this.pSelectRef()?.nativeElement.classList.add('p-focus');
|
|
1530
|
-
this.openOptions.
|
|
1917
|
+
this.openOptions.set(true);
|
|
1918
|
+
// Create embedded view and append to body (portal pattern)
|
|
1919
|
+
this.overlayViewRef = this.overlayTemplate().createEmbeddedView({});
|
|
1920
|
+
this.appRef.attachView(this.overlayViewRef);
|
|
1921
|
+
const overlayEl = this.overlayViewRef.rootNodes[0];
|
|
1922
|
+
this.document.body.appendChild(overlayEl);
|
|
1923
|
+
// Override positioning for portal (CSS class handles visual styles)
|
|
1924
|
+
const rect = this.pSelectRef()?.nativeElement.getBoundingClientRect();
|
|
1925
|
+
if (rect) {
|
|
1926
|
+
overlayEl.style.cssText = `
|
|
1927
|
+
position: fixed !important;
|
|
1928
|
+
top: ${rect.bottom + 2}px !important;
|
|
1929
|
+
left: ${rect.left}px !important;
|
|
1930
|
+
width: ${rect.width}px !important;
|
|
1931
|
+
max-width: ${rect.width}px !important;
|
|
1932
|
+
min-width: ${rect.width}px !important;
|
|
1933
|
+
z-index: 999999999999 !important;
|
|
1934
|
+
`;
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
closeOverlay() {
|
|
1938
|
+
this.openOptions.set(false);
|
|
1939
|
+
this.pSelectRef()?.nativeElement.classList.remove('p-focus');
|
|
1940
|
+
if (this.overlayViewRef) {
|
|
1941
|
+
this.appRef.detachView(this.overlayViewRef);
|
|
1942
|
+
this.overlayViewRef.destroy();
|
|
1943
|
+
this.overlayViewRef = null;
|
|
1944
|
+
}
|
|
1531
1945
|
}
|
|
1532
1946
|
onOverlayClick(event) {
|
|
1533
1947
|
event.stopPropagation();
|
|
1534
1948
|
}
|
|
1535
1949
|
handleDocumentClick(event) {
|
|
1536
|
-
const
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1950
|
+
const target = event.target;
|
|
1951
|
+
const clickedInSelect = this.pSelectRef()?.nativeElement.contains(target);
|
|
1952
|
+
const clickedInOverlay = this.overlayViewRef?.rootNodes[0]?.contains(target);
|
|
1953
|
+
if (!clickedInSelect && !clickedInOverlay) {
|
|
1954
|
+
this.closeOverlay();
|
|
1540
1955
|
this.markAsTouched();
|
|
1541
1956
|
}
|
|
1542
1957
|
}
|
|
1543
1958
|
isSelected(data) {
|
|
1544
1959
|
return this.value()?.includes(data.value) ?? false;
|
|
1545
1960
|
}
|
|
1546
|
-
key(option) {
|
|
1547
|
-
return option.value;
|
|
1548
|
-
}
|
|
1549
1961
|
selectValue(event, option) {
|
|
1550
1962
|
const previousValue = this.value() ?? [];
|
|
1551
1963
|
if (event.checked) {
|
|
@@ -1569,13 +1981,19 @@ class LazyMultiSelectComponent extends BaseFormControl {
|
|
|
1569
1981
|
event.stopPropagation();
|
|
1570
1982
|
this.value.set([]);
|
|
1571
1983
|
}
|
|
1984
|
+
t(key, variables) {
|
|
1985
|
+
if (this.translateAdapter) {
|
|
1986
|
+
return this.translateAdapter.translate(key, variables);
|
|
1987
|
+
}
|
|
1988
|
+
return key;
|
|
1989
|
+
}
|
|
1572
1990
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: LazyMultiSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1573
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: LazyMultiSelectComponent, isStandalone: true, selector: "lib-lazy-multi-select", inputs: { placeHolder: { classPropertyName: "placeHolder", publicName: "placeHolder", isSignal: true, isRequired: false, transformFunction: null }, isEditMode: { classPropertyName: "isEditMode", publicName: "isEditMode", isSignal: true, isRequired: true, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: true, transformFunction: null }, total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: true, transformFunction: null }, pagination: { classPropertyName: "pagination", publicName: "pagination", isSignal: true, isRequired: true, transformFunction: null }, selectDataList: { classPropertyName: "selectDataList", publicName: "selectDataList", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", onSearch: "onSearch", onPagination: "onPagination" }, providers: [provideValueAccessor(LazyMultiSelectComponent)], viewQueries: [{ propertyName: "pSelectRef", first: true, predicate: ["pSelect"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div
|
|
1991
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: LazyMultiSelectComponent, isStandalone: true, selector: "lib-lazy-multi-select", inputs: { placeHolder: { classPropertyName: "placeHolder", publicName: "placeHolder", isSignal: true, isRequired: false, transformFunction: null }, isEditMode: { classPropertyName: "isEditMode", publicName: "isEditMode", isSignal: true, isRequired: true, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: true, transformFunction: null }, total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: true, transformFunction: null }, pagination: { classPropertyName: "pagination", publicName: "pagination", isSignal: true, isRequired: true, transformFunction: null }, selectDataList: { classPropertyName: "selectDataList", publicName: "selectDataList", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", onSearch: "onSearch", onPagination: "onPagination" }, providers: [provideValueAccessor(LazyMultiSelectComponent)], viewQueries: [{ propertyName: "pSelectRef", first: true, predicate: ["pSelect"], descendants: true, isSignal: true }, { propertyName: "overlayTemplate", first: true, predicate: ["overlayTpl"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div\n class=\"p-select w-full\"\n #pSelect\n (click)=\"onSelectClick()\"\n [class.p-disabled]=\"disabled()\"\n [class.edit-mode-element-css]=\"!isEditMode()\"\n [class.overflow-hidden]=\"!isEditMode()\"\n>\n @if (selectedValueDisplay()) {\n <span class=\"p-select-label\" [class.edit-mode-element-css]=\"!isEditMode()\">\n {{ selectedValueDisplay() }}\n </span>\n } @else {\n <span class=\"p-select-label p-placeholder\">{{ displayPlaceholder() }}</span>\n }\n\n @if (isEditMode()) {\n <span class=\"p-select-clear-icon\" (click)=\"clear($event)\">\n <i class=\"pi pi-times\"></i>\n </span>\n\n <div class=\"p-select-dropdown\">\n <span class=\"p-select-dropdown-icon flex items-center\">\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 14 14\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n class=\"p-multiselect-dropdown-icon p-icon\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M7.01744 10.398C6.91269 10.3985 6.8089 10.378 6.71215 10.3379C6.61541 10.2977 6.52766 10.2386 6.45405 10.1641L1.13907 4.84913C1.03306 4.69404 0.985221 4.5065 1.00399 4.31958C1.02276 4.13266 1.10693 3.95838 1.24166 3.82747C1.37639 3.69655 1.55301 3.61742 1.74039 3.60402C1.92777 3.59062 2.11386 3.64382 2.26584 3.75424L7.01744 8.47394L11.769 3.75424C11.9189 3.65709 12.097 3.61306 12.2748 3.62921C12.4527 3.64535 12.6199 3.72073 12.7498 3.84328C12.8797 3.96582 12.9647 4.12842 12.9912 4.30502C13.0177 4.48162 12.9841 4.662 12.8958 4.81724L7.58083 10.1322C7.50996 10.2125 7.42344 10.2775 7.32656 10.3232C7.22968 10.3689 7.12449 10.3944 7.01744 10.398Z\"\n fill=\"currentColor\"\n />\n </svg>\n </span>\n </div>\n }\n</div>\n\n<ng-template #overlayTpl>\n <div class=\"p-select-overlay\" (click)=\"onOverlayClick($event)\">\n <div class=\"p-select-header flex flex-row gap-2 items-center\">\n <p-checkbox\n [binary]=\"true\"\n [ngModel]=\"isSelectAll()\"\n [disabled]=\"disabled()\"\n (onChange)=\"changeSelectAll($event)\"\n />\n <input\n type=\"text\"\n pInputText\n class=\"w-full\"\n [placeholder]=\"'shared.search' | translate\"\n [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"searchTerm.set($event)\"\n />\n </div>\n <div class=\"p-select-list-container\" (scroll)=\"onScroll($event)\">\n <ul class=\"p-select-list\">\n @for (data of selectDataList(); track data.value) {\n <li\n class=\"p-select-option flex flex-row gap-2 items-center\"\n [class.p-select-option-selected]=\"isSelected(data)\"\n >\n <p-checkbox\n [binary]=\"true\"\n [ngModel]=\"isSelected(data)\"\n [disabled]=\"disabled()\"\n (onChange)=\"selectValue($event, data)\"\n />\n <span>{{ data.label }}</span>\n </li>\n }\n </ul>\n </div>\n </div>\n</ng-template>\n", styles: [".p-select-overlay{display:flex;flex-direction:column;max-height:250px;overflow:hidden}.p-select-header{padding:.75rem;border-bottom:1px solid var(--p-surface-border);background:var(--p-content-hover-background);flex-shrink:0;width:100%;box-sizing:border-box}.p-select-header input{background:var(--p-form-field-background);border-color:var(--p-form-field-border-color);color:var(--p-text-color)}.p-select-header input::placeholder{color:var(--p-text-muted-color)}.p-select-list-container{flex:1;overflow-y:auto;min-height:0;width:100%}.p-select-list{margin:0;padding:.25rem 0;list-style:none;background:var(--p-surface-overlay);color:var(--p-text-color);width:100%}.p-select-option{padding:.5rem .75rem;cursor:pointer;transition:background-color .2s ease;color:var(--p-text-color)}.p-select-option:hover{background:var(--p-content-hover-background)}.p-select-option.p-select-option-selected{background:var(--p-highlight-background);color:var(--p-highlight-color)}.p-select-option.p-select-option-selected:hover{background:var(--p-highlight-focus-background);color:var(--p-highlight-focus-color)}.p-select-clear-icon{display:flex;align-items:center;padding:0 .5rem;color:var(--p-text-muted-color);cursor:pointer;transition:color .2s ease}.p-select-clear-icon:hover{color:var(--p-text-color)}\n"], dependencies: [{ kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i1.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "directive", type: i2.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], encapsulation: i0.ViewEncapsulation.None });
|
|
1574
1992
|
}
|
|
1575
1993
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: LazyMultiSelectComponent, decorators: [{
|
|
1576
1994
|
type: Component,
|
|
1577
|
-
args: [{ selector: 'lib-lazy-multi-select', imports: [PrimeModule, AngularModule],
|
|
1578
|
-
}], ctorParameters: () => [], propDecorators: { pSelectRef: [{ type: i0.ViewChild, args: ['pSelect', { isSignal: true }] }], placeHolder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeHolder", required: false }] }], isEditMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditMode", required: true }] }], isLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoading", required: true }] }], total: [{ type: i0.Input, args: [{ isSignal: true, alias: "total", required: true }] }], pagination: [{ type: i0.Input, args: [{ isSignal: true, alias: "pagination", required: true }] }], selectDataList: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectDataList", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], onSearch: [{ type: i0.Output, args: ["onSearch"] }], onPagination: [{ type: i0.Output, args: ["onPagination"] }] } });
|
|
1995
|
+
args: [{ selector: 'lib-lazy-multi-select', imports: [PrimeModule, AngularModule, TranslatePipe], encapsulation: ViewEncapsulation.None, providers: [provideValueAccessor(LazyMultiSelectComponent)], template: "<div\n class=\"p-select w-full\"\n #pSelect\n (click)=\"onSelectClick()\"\n [class.p-disabled]=\"disabled()\"\n [class.edit-mode-element-css]=\"!isEditMode()\"\n [class.overflow-hidden]=\"!isEditMode()\"\n>\n @if (selectedValueDisplay()) {\n <span class=\"p-select-label\" [class.edit-mode-element-css]=\"!isEditMode()\">\n {{ selectedValueDisplay() }}\n </span>\n } @else {\n <span class=\"p-select-label p-placeholder\">{{ displayPlaceholder() }}</span>\n }\n\n @if (isEditMode()) {\n <span class=\"p-select-clear-icon\" (click)=\"clear($event)\">\n <i class=\"pi pi-times\"></i>\n </span>\n\n <div class=\"p-select-dropdown\">\n <span class=\"p-select-dropdown-icon flex items-center\">\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 14 14\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n class=\"p-multiselect-dropdown-icon p-icon\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M7.01744 10.398C6.91269 10.3985 6.8089 10.378 6.71215 10.3379C6.61541 10.2977 6.52766 10.2386 6.45405 10.1641L1.13907 4.84913C1.03306 4.69404 0.985221 4.5065 1.00399 4.31958C1.02276 4.13266 1.10693 3.95838 1.24166 3.82747C1.37639 3.69655 1.55301 3.61742 1.74039 3.60402C1.92777 3.59062 2.11386 3.64382 2.26584 3.75424L7.01744 8.47394L11.769 3.75424C11.9189 3.65709 12.097 3.61306 12.2748 3.62921C12.4527 3.64535 12.6199 3.72073 12.7498 3.84328C12.8797 3.96582 12.9647 4.12842 12.9912 4.30502C13.0177 4.48162 12.9841 4.662 12.8958 4.81724L7.58083 10.1322C7.50996 10.2125 7.42344 10.2775 7.32656 10.3232C7.22968 10.3689 7.12449 10.3944 7.01744 10.398Z\"\n fill=\"currentColor\"\n />\n </svg>\n </span>\n </div>\n }\n</div>\n\n<ng-template #overlayTpl>\n <div class=\"p-select-overlay\" (click)=\"onOverlayClick($event)\">\n <div class=\"p-select-header flex flex-row gap-2 items-center\">\n <p-checkbox\n [binary]=\"true\"\n [ngModel]=\"isSelectAll()\"\n [disabled]=\"disabled()\"\n (onChange)=\"changeSelectAll($event)\"\n />\n <input\n type=\"text\"\n pInputText\n class=\"w-full\"\n [placeholder]=\"'shared.search' | translate\"\n [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"searchTerm.set($event)\"\n />\n </div>\n <div class=\"p-select-list-container\" (scroll)=\"onScroll($event)\">\n <ul class=\"p-select-list\">\n @for (data of selectDataList(); track data.value) {\n <li\n class=\"p-select-option flex flex-row gap-2 items-center\"\n [class.p-select-option-selected]=\"isSelected(data)\"\n >\n <p-checkbox\n [binary]=\"true\"\n [ngModel]=\"isSelected(data)\"\n [disabled]=\"disabled()\"\n (onChange)=\"selectValue($event, data)\"\n />\n <span>{{ data.label }}</span>\n </li>\n }\n </ul>\n </div>\n </div>\n</ng-template>\n", styles: [".p-select-overlay{display:flex;flex-direction:column;max-height:250px;overflow:hidden}.p-select-header{padding:.75rem;border-bottom:1px solid var(--p-surface-border);background:var(--p-content-hover-background);flex-shrink:0;width:100%;box-sizing:border-box}.p-select-header input{background:var(--p-form-field-background);border-color:var(--p-form-field-border-color);color:var(--p-text-color)}.p-select-header input::placeholder{color:var(--p-text-muted-color)}.p-select-list-container{flex:1;overflow-y:auto;min-height:0;width:100%}.p-select-list{margin:0;padding:.25rem 0;list-style:none;background:var(--p-surface-overlay);color:var(--p-text-color);width:100%}.p-select-option{padding:.5rem .75rem;cursor:pointer;transition:background-color .2s ease;color:var(--p-text-color)}.p-select-option:hover{background:var(--p-content-hover-background)}.p-select-option.p-select-option-selected{background:var(--p-highlight-background);color:var(--p-highlight-color)}.p-select-option.p-select-option-selected:hover{background:var(--p-highlight-focus-background);color:var(--p-highlight-focus-color)}.p-select-clear-icon{display:flex;align-items:center;padding:0 .5rem;color:var(--p-text-muted-color);cursor:pointer;transition:color .2s ease}.p-select-clear-icon:hover{color:var(--p-text-color)}\n"] }]
|
|
1996
|
+
}], ctorParameters: () => [], propDecorators: { pSelectRef: [{ type: i0.ViewChild, args: ['pSelect', { isSignal: true }] }], overlayTemplate: [{ type: i0.ViewChild, args: ['overlayTpl', { isSignal: true }] }], placeHolder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeHolder", required: false }] }], isEditMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditMode", required: true }] }], isLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoading", required: true }] }], total: [{ type: i0.Input, args: [{ isSignal: true, alias: "total", required: true }] }], pagination: [{ type: i0.Input, args: [{ isSignal: true, alias: "pagination", required: true }] }], selectDataList: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectDataList", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], onSearch: [{ type: i0.Output, args: ["onSearch"] }], onPagination: [{ type: i0.Output, args: ["onPagination"] }] } });
|
|
1579
1997
|
|
|
1580
1998
|
/**
|
|
1581
1999
|
* Lazy-loading single select component with search and pagination.
|
|
@@ -1587,13 +2005,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1587
2005
|
*/
|
|
1588
2006
|
class LazySelectComponent extends BaseFormControl {
|
|
1589
2007
|
destroyRef = inject(DestroyRef);
|
|
2008
|
+
translateAdapter = inject(TRANSLATE_ADAPTER, { optional: true });
|
|
1590
2009
|
onScrollBound = this.onScroll.bind(this);
|
|
1591
2010
|
scrollTargetEl = null;
|
|
1592
2011
|
isDestroyed = false;
|
|
1593
2012
|
// View references
|
|
1594
2013
|
scrollContainer = viewChild.required('scrollContainer');
|
|
1595
2014
|
// Inputs
|
|
1596
|
-
placeHolder = input('
|
|
2015
|
+
placeHolder = input('', ...(ngDevMode ? [{ debugName: "placeHolder" }] : []));
|
|
2016
|
+
// Computed placeholder with translation fallback
|
|
2017
|
+
displayPlaceholder = computed(() => {
|
|
2018
|
+
const customPlaceholder = this.placeHolder();
|
|
2019
|
+
if (customPlaceholder)
|
|
2020
|
+
return customPlaceholder;
|
|
2021
|
+
return this.t('shared.select.placeholder');
|
|
2022
|
+
}, ...(ngDevMode ? [{ debugName: "displayPlaceholder" }] : []));
|
|
1597
2023
|
optionLabel = input.required(...(ngDevMode ? [{ debugName: "optionLabel" }] : []));
|
|
1598
2024
|
optionValue = input.required(...(ngDevMode ? [{ debugName: "optionValue" }] : []));
|
|
1599
2025
|
isEditMode = input.required(...(ngDevMode ? [{ debugName: "isEditMode" }] : []));
|
|
@@ -1676,12 +2102,18 @@ class LazySelectComponent extends BaseFormControl {
|
|
|
1676
2102
|
onBlur() {
|
|
1677
2103
|
this.markAsTouched();
|
|
1678
2104
|
}
|
|
2105
|
+
t(key, variables) {
|
|
2106
|
+
if (this.translateAdapter) {
|
|
2107
|
+
return this.translateAdapter.translate(key, variables);
|
|
2108
|
+
}
|
|
2109
|
+
return key;
|
|
2110
|
+
}
|
|
1679
2111
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: LazySelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1680
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.1.5", type: LazySelectComponent, isStandalone: true, selector: "lib-lazy-select", inputs: { placeHolder: { classPropertyName: "placeHolder", publicName: "placeHolder", isSignal: true, isRequired: false, transformFunction: null }, optionLabel: { classPropertyName: "optionLabel", publicName: "optionLabel", isSignal: true, isRequired: true, transformFunction: null }, optionValue: { classPropertyName: "optionValue", publicName: "optionValue", isSignal: true, isRequired: true, transformFunction: null }, isEditMode: { classPropertyName: "isEditMode", publicName: "isEditMode", isSignal: true, isRequired: true, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: true, transformFunction: null }, total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: true, transformFunction: null }, pagination: { classPropertyName: "pagination", publicName: "pagination", isSignal: true, isRequired: true, transformFunction: null }, selectDataList: { classPropertyName: "selectDataList", publicName: "selectDataList", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", onSearch: "onSearch", onPagination: "onPagination" }, providers: [provideValueAccessor(LazySelectComponent)], viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div #scrollContainer class=\"lib-scroll-container\">\n <p-select\n class=\"w-full\"\n [options]=\"selectDataList()\"\n [optionLabel]=\"optionLabel()\"\n [optionValue]=\"optionValue()\"\n [filter]=\"true\"\n [showClear]=\"true\"\n [placeholder]=\"
|
|
2112
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.1.5", type: LazySelectComponent, isStandalone: true, selector: "lib-lazy-select", inputs: { placeHolder: { classPropertyName: "placeHolder", publicName: "placeHolder", isSignal: true, isRequired: false, transformFunction: null }, optionLabel: { classPropertyName: "optionLabel", publicName: "optionLabel", isSignal: true, isRequired: true, transformFunction: null }, optionValue: { classPropertyName: "optionValue", publicName: "optionValue", isSignal: true, isRequired: true, transformFunction: null }, isEditMode: { classPropertyName: "isEditMode", publicName: "isEditMode", isSignal: true, isRequired: true, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: true, transformFunction: null }, total: { classPropertyName: "total", publicName: "total", isSignal: true, isRequired: true, transformFunction: null }, pagination: { classPropertyName: "pagination", publicName: "pagination", isSignal: true, isRequired: true, transformFunction: null }, selectDataList: { classPropertyName: "selectDataList", publicName: "selectDataList", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", onSearch: "onSearch", onPagination: "onPagination" }, providers: [provideValueAccessor(LazySelectComponent)], viewQueries: [{ propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div #scrollContainer class=\"lib-scroll-container\">\n <p-select\n class=\"w-full\"\n [options]=\"selectDataList()\"\n [optionLabel]=\"optionLabel()\"\n [optionValue]=\"optionValue()\"\n [filter]=\"true\"\n [showClear]=\"true\"\n [placeholder]=\"displayPlaceholder()\"\n [disabled]=\"disabled()\"\n [(ngModel)]=\"value\"\n appEditModeElementChanger\n [isEditMode]=\"isEditMode()\"\n (click)=\"showPanel()\"\n (onBlur)=\"onBlur()\"\n >\n <ng-template #filter let-filter>\n <input\n pInputText\n class=\"w-full\"\n [ngModel]=\"searchTerm()\"\n [ngModelOptions]=\"{ standalone: true }\"\n (ngModelChange)=\"searchTerm.set($event)\"\n />\n </ng-template>\n <ng-template #selectedItem let-selectedOption>\n {{ selectedOption[optionLabel()] }}\n </ng-template>\n <ng-template #item let-item>\n {{ item[optionLabel()] }}\n </ng-template>\n </p-select>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "directive", type: i2.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "component", type: i3.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "directive", type: EditModeElementChangerDirective, selector: "[appEditModeElementChanger]", inputs: ["isEditMode"] }] });
|
|
1681
2113
|
}
|
|
1682
2114
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: LazySelectComponent, decorators: [{
|
|
1683
2115
|
type: Component,
|
|
1684
|
-
args: [{ selector: 'lib-lazy-select', imports: [AngularModule, PrimeModule, EditModeElementChangerDirective],
|
|
2116
|
+
args: [{ selector: 'lib-lazy-select', imports: [AngularModule, PrimeModule, EditModeElementChangerDirective], providers: [provideValueAccessor(LazySelectComponent)], template: "<div #scrollContainer class=\"lib-scroll-container\">\n <p-select\n class=\"w-full\"\n [options]=\"selectDataList()\"\n [optionLabel]=\"optionLabel()\"\n [optionValue]=\"optionValue()\"\n [filter]=\"true\"\n [showClear]=\"true\"\n [placeholder]=\"displayPlaceholder()\"\n [disabled]=\"disabled()\"\n [(ngModel)]=\"value\"\n appEditModeElementChanger\n [isEditMode]=\"isEditMode()\"\n (click)=\"showPanel()\"\n (onBlur)=\"onBlur()\"\n >\n <ng-template #filter let-filter>\n <input\n pInputText\n class=\"w-full\"\n [ngModel]=\"searchTerm()\"\n [ngModelOptions]=\"{ standalone: true }\"\n (ngModelChange)=\"searchTerm.set($event)\"\n />\n </ng-template>\n <ng-template #selectedItem let-selectedOption>\n {{ selectedOption[optionLabel()] }}\n </ng-template>\n <ng-template #item let-item>\n {{ item[optionLabel()] }}\n </ng-template>\n </p-select>\n</div>\n" }]
|
|
1685
2117
|
}], ctorParameters: () => [], propDecorators: { scrollContainer: [{ type: i0.ViewChild, args: ['scrollContainer', { isSignal: true }] }], placeHolder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeHolder", required: false }] }], optionLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionLabel", required: true }] }], optionValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionValue", required: true }] }], isEditMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditMode", required: true }] }], isLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoading", required: true }] }], total: [{ type: i0.Input, args: [{ isSignal: true, alias: "total", required: true }] }], pagination: [{ type: i0.Input, args: [{ isSignal: true, alias: "pagination", required: true }] }], selectDataList: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectDataList", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], onSearch: [{ type: i0.Output, args: ["onSearch"] }], onPagination: [{ type: i0.Output, args: ["onPagination"] }] } });
|
|
1686
2118
|
|
|
1687
2119
|
/**
|
|
@@ -1757,14 +2189,6 @@ const AUTH_STATE_PROVIDER = new InjectionToken('AUTH_STATE_PROVIDER', {
|
|
|
1757
2189
|
* Use with `inject(PROFILE_PERMISSION_PROVIDER, { optional: true })`.
|
|
1758
2190
|
*/
|
|
1759
2191
|
const PROFILE_PERMISSION_PROVIDER = new InjectionToken('PROFILE_PERMISSION_PROVIDER');
|
|
1760
|
-
/**
|
|
1761
|
-
* Profile Upload Provider Token
|
|
1762
|
-
*
|
|
1763
|
-
* Provides file upload functionality for profile pictures.
|
|
1764
|
-
* Optional - if not configured or storage not enabled, upload section is hidden.
|
|
1765
|
-
* Use with `inject(PROFILE_UPLOAD_PROVIDER, { optional: true })`.
|
|
1766
|
-
*/
|
|
1767
|
-
const PROFILE_UPLOAD_PROVIDER = new InjectionToken('PROFILE_UPLOAD_PROVIDER');
|
|
1768
2192
|
/**
|
|
1769
2193
|
* User List Provider Token
|
|
1770
2194
|
*
|
|
@@ -1779,6 +2203,20 @@ const PROFILE_UPLOAD_PROVIDER = new InjectionToken('PROFILE_UPLOAD_PROVIDER');
|
|
|
1779
2203
|
* ]
|
|
1780
2204
|
*/
|
|
1781
2205
|
const USER_LIST_PROVIDER = new InjectionToken('USER_LIST_PROVIDER');
|
|
2206
|
+
/**
|
|
2207
|
+
* File Provider Token
|
|
2208
|
+
*
|
|
2209
|
+
* Provides file loading and upload functionality for file selectors.
|
|
2210
|
+
* Optional - if not configured, file selector requires loadFiles/uploadFile inputs.
|
|
2211
|
+
* Use with `inject(FILE_PROVIDER, { optional: true })`.
|
|
2212
|
+
*
|
|
2213
|
+
* @example
|
|
2214
|
+
* // In app.config.ts
|
|
2215
|
+
* providers: [
|
|
2216
|
+
* { provide: FILE_PROVIDER, useClass: StorageFileProvider },
|
|
2217
|
+
* ]
|
|
2218
|
+
*/
|
|
2219
|
+
const FILE_PROVIDER = new InjectionToken('FILE_PROVIDER');
|
|
1782
2220
|
|
|
1783
2221
|
const DEFAULT_PAGE_SIZE$1 = 20;
|
|
1784
2222
|
/**
|
|
@@ -1792,10 +2230,11 @@ class BaseUserSelectComponent {
|
|
|
1792
2230
|
destroyRef = inject(DestroyRef);
|
|
1793
2231
|
injector = inject(Injector);
|
|
1794
2232
|
userProvider = inject(USER_PROVIDER);
|
|
2233
|
+
translateAdapter = inject(TRANSLATE_ADAPTER, { optional: true });
|
|
1795
2234
|
abortController = null;
|
|
1796
2235
|
// Inputs
|
|
1797
2236
|
loadUsers = input(...(ngDevMode ? [undefined, { debugName: "loadUsers" }] : []));
|
|
1798
|
-
placeHolder = input('
|
|
2237
|
+
placeHolder = input('', ...(ngDevMode ? [{ debugName: "placeHolder" }] : []));
|
|
1799
2238
|
isEditMode = input.required(...(ngDevMode ? [{ debugName: "isEditMode" }] : []));
|
|
1800
2239
|
filterActive = input(true, ...(ngDevMode ? [{ debugName: "filterActive" }] : []));
|
|
1801
2240
|
additionalFilters = input({}, ...(ngDevMode ? [{ debugName: "additionalFilters" }] : []));
|
|
@@ -1813,6 +2252,13 @@ class BaseUserSelectComponent {
|
|
|
1813
2252
|
label: user.name || user.email,
|
|
1814
2253
|
value: user.id,
|
|
1815
2254
|
})), ...(ngDevMode ? [{ debugName: "dropdownUsers" }] : []));
|
|
2255
|
+
// Computed placeholder with translation fallback
|
|
2256
|
+
displayPlaceholder = computed(() => {
|
|
2257
|
+
const customPlaceholder = this.placeHolder();
|
|
2258
|
+
if (customPlaceholder)
|
|
2259
|
+
return customPlaceholder;
|
|
2260
|
+
return this.t('shared.user.select.placeholder');
|
|
2261
|
+
}, ...(ngDevMode ? [{ debugName: "displayPlaceholder" }] : []));
|
|
1816
2262
|
constructor() {
|
|
1817
2263
|
// Cleanup on destroy
|
|
1818
2264
|
this.destroyRef.onDestroy(() => {
|
|
@@ -1905,6 +2351,12 @@ class BaseUserSelectComponent {
|
|
|
1905
2351
|
})),
|
|
1906
2352
|
})));
|
|
1907
2353
|
}
|
|
2354
|
+
t(key, variables) {
|
|
2355
|
+
if (this.translateAdapter) {
|
|
2356
|
+
return this.translateAdapter.translate(key, variables);
|
|
2357
|
+
}
|
|
2358
|
+
return key;
|
|
2359
|
+
}
|
|
1908
2360
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: BaseUserSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1909
2361
|
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.5", type: BaseUserSelectComponent, isStandalone: true, inputs: { loadUsers: { classPropertyName: "loadUsers", publicName: "loadUsers", isSignal: true, isRequired: false, transformFunction: null }, placeHolder: { classPropertyName: "placeHolder", publicName: "placeHolder", isSignal: true, isRequired: false, transformFunction: null }, isEditMode: { classPropertyName: "isEditMode", publicName: "isEditMode", isSignal: true, isRequired: true, transformFunction: null }, filterActive: { classPropertyName: "filterActive", publicName: "filterActive", isSignal: true, isRequired: false, transformFunction: null }, additionalFilters: { classPropertyName: "additionalFilters", publicName: "additionalFilters", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onError: "onError" }, ngImport: i0 });
|
|
1910
2362
|
}
|
|
@@ -1964,7 +2416,7 @@ class UserSelectComponent extends BaseUserSelectComponent {
|
|
|
1964
2416
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.5", type: UserSelectComponent, isStandalone: true, selector: "lib-user-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", userSelected: "userSelected" }, usesInheritance: true, ngImport: i0, template: `
|
|
1965
2417
|
<lib-lazy-select
|
|
1966
2418
|
[(value)]="value"
|
|
1967
|
-
[placeHolder]="
|
|
2419
|
+
[placeHolder]="displayPlaceholder()"
|
|
1968
2420
|
[optionLabel]="'label'"
|
|
1969
2421
|
[optionValue]="'value'"
|
|
1970
2422
|
[isEditMode]="isEditMode()"
|
|
@@ -1975,7 +2427,7 @@ class UserSelectComponent extends BaseUserSelectComponent {
|
|
|
1975
2427
|
(onSearch)="handleSearch($event)"
|
|
1976
2428
|
(onPagination)="handlePagination($event)"
|
|
1977
2429
|
/>
|
|
1978
|
-
`, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: LazySelectComponent, selector: "lib-lazy-select", inputs: ["placeHolder", "optionLabel", "optionValue", "isEditMode", "isLoading", "total", "pagination", "selectDataList", "value"], outputs: ["valueChange", "onSearch", "onPagination"] }]
|
|
2430
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: LazySelectComponent, selector: "lib-lazy-select", inputs: ["placeHolder", "optionLabel", "optionValue", "isEditMode", "isLoading", "total", "pagination", "selectDataList", "value"], outputs: ["valueChange", "onSearch", "onPagination"] }] });
|
|
1979
2431
|
}
|
|
1980
2432
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: UserSelectComponent, decorators: [{
|
|
1981
2433
|
type: Component,
|
|
@@ -1985,7 +2437,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1985
2437
|
template: `
|
|
1986
2438
|
<lib-lazy-select
|
|
1987
2439
|
[(value)]="value"
|
|
1988
|
-
[placeHolder]="
|
|
2440
|
+
[placeHolder]="displayPlaceholder()"
|
|
1989
2441
|
[optionLabel]="'label'"
|
|
1990
2442
|
[optionValue]="'value'"
|
|
1991
2443
|
[isEditMode]="isEditMode()"
|
|
@@ -1997,7 +2449,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1997
2449
|
(onPagination)="handlePagination($event)"
|
|
1998
2450
|
/>
|
|
1999
2451
|
`,
|
|
2000
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2001
2452
|
}]
|
|
2002
2453
|
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], userSelected: [{ type: i0.Output, args: ["userSelected"] }] } });
|
|
2003
2454
|
|
|
@@ -2049,7 +2500,7 @@ class UserMultiSelectComponent extends BaseUserSelectComponent {
|
|
|
2049
2500
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.5", type: UserMultiSelectComponent, isStandalone: true, selector: "lib-user-multi-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", usersSelected: "usersSelected" }, usesInheritance: true, ngImport: i0, template: `
|
|
2050
2501
|
<lib-lazy-multi-select
|
|
2051
2502
|
[(value)]="value"
|
|
2052
|
-
[placeHolder]="
|
|
2503
|
+
[placeHolder]="displayPlaceholder()"
|
|
2053
2504
|
[isEditMode]="isEditMode()"
|
|
2054
2505
|
[isLoading]="isLoading()"
|
|
2055
2506
|
[total]="total()"
|
|
@@ -2058,7 +2509,7 @@ class UserMultiSelectComponent extends BaseUserSelectComponent {
|
|
|
2058
2509
|
(onSearch)="handleSearch($event)"
|
|
2059
2510
|
(onPagination)="handlePagination($event)"
|
|
2060
2511
|
/>
|
|
2061
|
-
`, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: LazyMultiSelectComponent, selector: "lib-lazy-multi-select", inputs: ["placeHolder", "isEditMode", "isLoading", "total", "pagination", "selectDataList", "value"], outputs: ["valueChange", "onSearch", "onPagination"] }]
|
|
2512
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: LazyMultiSelectComponent, selector: "lib-lazy-multi-select", inputs: ["placeHolder", "isEditMode", "isLoading", "total", "pagination", "selectDataList", "value"], outputs: ["valueChange", "onSearch", "onPagination"] }] });
|
|
2062
2513
|
}
|
|
2063
2514
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: UserMultiSelectComponent, decorators: [{
|
|
2064
2515
|
type: Component,
|
|
@@ -2068,7 +2519,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
2068
2519
|
template: `
|
|
2069
2520
|
<lib-lazy-multi-select
|
|
2070
2521
|
[(value)]="value"
|
|
2071
|
-
[placeHolder]="
|
|
2522
|
+
[placeHolder]="displayPlaceholder()"
|
|
2072
2523
|
[isEditMode]="isEditMode()"
|
|
2073
2524
|
[isLoading]="isLoading()"
|
|
2074
2525
|
[total]="total()"
|
|
@@ -2078,19 +2529,59 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
2078
2529
|
(onPagination)="handlePagination($event)"
|
|
2079
2530
|
/>
|
|
2080
2531
|
`,
|
|
2081
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2082
2532
|
}]
|
|
2083
2533
|
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], usersSelected: [{ type: i0.Output, args: ["usersSelected"] }] } });
|
|
2084
2534
|
|
|
2085
2535
|
/**
|
|
2086
2536
|
* File Uploader Component - Drag & drop file upload with type filtering.
|
|
2087
2537
|
*
|
|
2088
|
-
*
|
|
2538
|
+
* Uses FILE_PROVIDER when available (no inputs needed).
|
|
2539
|
+
* Optionally pass `uploadFile` to override the provider.
|
|
2540
|
+
*
|
|
2541
|
+
* @example
|
|
2542
|
+
* ```html
|
|
2543
|
+
* <!-- Using FILE_PROVIDER (recommended) -->
|
|
2544
|
+
* <lib-file-uploader
|
|
2545
|
+
* [acceptTypes]="['image/*']"
|
|
2546
|
+
* (fileUploaded)="onFileUploaded($event)"
|
|
2547
|
+
* />
|
|
2548
|
+
*
|
|
2549
|
+
* <!-- Custom upload function -->
|
|
2550
|
+
* <lib-file-uploader
|
|
2551
|
+
* [uploadFile]="customUploadFn"
|
|
2552
|
+
* (fileUploaded)="onFileUploaded($event)"
|
|
2553
|
+
* />
|
|
2554
|
+
* ```
|
|
2089
2555
|
*/
|
|
2090
2556
|
class FileUploaderComponent {
|
|
2091
2557
|
messageService = inject(MessageService);
|
|
2092
|
-
|
|
2093
|
-
|
|
2558
|
+
translateAdapter = inject(TRANSLATE_ADAPTER, { optional: true });
|
|
2559
|
+
fileProvider = inject(FILE_PROVIDER, { optional: true });
|
|
2560
|
+
// Optional: custom upload function (overrides FILE_PROVIDER)
|
|
2561
|
+
uploadFile = input(...(ngDevMode ? [undefined, { debugName: "uploadFile" }] : []));
|
|
2562
|
+
// Optional: function to upload multiple files at once (more efficient)
|
|
2563
|
+
uploadMultipleFiles = input(...(ngDevMode ? [undefined, { debugName: "uploadMultipleFiles" }] : []));
|
|
2564
|
+
// Check if upload capability is available
|
|
2565
|
+
hasUploadCapability = computed(() => !!this.uploadFile() || !!this.fileProvider, ...(ngDevMode ? [{ debugName: "hasUploadCapability" }] : []));
|
|
2566
|
+
// Get effective upload function (input or provider)
|
|
2567
|
+
getUploadFn() {
|
|
2568
|
+
const inputFn = this.uploadFile();
|
|
2569
|
+
if (inputFn)
|
|
2570
|
+
return inputFn;
|
|
2571
|
+
if (this.fileProvider)
|
|
2572
|
+
return (file, opts) => this.fileProvider.uploadFile(file, opts);
|
|
2573
|
+
throw new Error('shared.file.uploader.no.upload.function');
|
|
2574
|
+
}
|
|
2575
|
+
// Get effective multiple upload function
|
|
2576
|
+
getUploadMultipleFn() {
|
|
2577
|
+
const inputFn = this.uploadMultipleFiles();
|
|
2578
|
+
if (inputFn)
|
|
2579
|
+
return inputFn;
|
|
2580
|
+
if (this.fileProvider?.uploadMultipleFiles) {
|
|
2581
|
+
return (files, opts) => this.fileProvider.uploadMultipleFiles(files, opts);
|
|
2582
|
+
}
|
|
2583
|
+
return undefined;
|
|
2584
|
+
}
|
|
2094
2585
|
// Inputs
|
|
2095
2586
|
acceptTypes = input([], ...(ngDevMode ? [{ debugName: "acceptTypes" }] : []));
|
|
2096
2587
|
multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : []));
|
|
@@ -2120,14 +2611,14 @@ class FileUploaderComponent {
|
|
|
2120
2611
|
// Check if types match predefined filters
|
|
2121
2612
|
const typesStr = JSON.stringify(types);
|
|
2122
2613
|
if (typesStr === JSON.stringify(FILE_TYPE_FILTERS.IMAGES))
|
|
2123
|
-
return '
|
|
2614
|
+
return this.t('shared.file.type.images');
|
|
2124
2615
|
if (typesStr === JSON.stringify(FILE_TYPE_FILTERS.DOCUMENTS))
|
|
2125
|
-
return '
|
|
2616
|
+
return this.t('shared.file.type.documents');
|
|
2126
2617
|
if (typesStr === JSON.stringify(FILE_TYPE_FILTERS.VIDEOS))
|
|
2127
|
-
return '
|
|
2618
|
+
return this.t('shared.file.type.videos');
|
|
2128
2619
|
if (typesStr === JSON.stringify(FILE_TYPE_FILTERS.AUDIO))
|
|
2129
|
-
return '
|
|
2130
|
-
return types.map(t => t.split('/')[1] || t).join(', ');
|
|
2620
|
+
return this.t('shared.file.type.audio');
|
|
2621
|
+
return types.map((t) => t.split('/')[1] || t).join(', ');
|
|
2131
2622
|
}, ...(ngDevMode ? [{ debugName: "acceptTypesDisplay" }] : []));
|
|
2132
2623
|
onDragOver(event) {
|
|
2133
2624
|
event.preventDefault();
|
|
@@ -2164,12 +2655,12 @@ class FileUploaderComponent {
|
|
|
2164
2655
|
handleFiles(files) {
|
|
2165
2656
|
// Filter by type
|
|
2166
2657
|
const allowedTypes = this.acceptTypes();
|
|
2167
|
-
const validFiles = files.filter(file => {
|
|
2658
|
+
const validFiles = files.filter((file) => {
|
|
2168
2659
|
if (!isFileTypeAllowed(file, allowedTypes)) {
|
|
2169
2660
|
this.messageService.add({
|
|
2170
2661
|
severity: 'warn',
|
|
2171
|
-
summary: '
|
|
2172
|
-
detail:
|
|
2662
|
+
summary: this.t('shared.upload.invalid.type'),
|
|
2663
|
+
detail: `${file.name}`,
|
|
2173
2664
|
});
|
|
2174
2665
|
return false;
|
|
2175
2666
|
}
|
|
@@ -2177,12 +2668,12 @@ class FileUploaderComponent {
|
|
|
2177
2668
|
});
|
|
2178
2669
|
// Filter by size
|
|
2179
2670
|
const maxSize = this.maxSizeMb() * 1024 * 1024;
|
|
2180
|
-
const sizeValidFiles = validFiles.filter(file => {
|
|
2671
|
+
const sizeValidFiles = validFiles.filter((file) => {
|
|
2181
2672
|
if (file.size > maxSize) {
|
|
2182
2673
|
this.messageService.add({
|
|
2183
2674
|
severity: 'warn',
|
|
2184
|
-
summary: '
|
|
2185
|
-
detail: `${file.name}
|
|
2675
|
+
summary: this.t('shared.upload.file.too.large'),
|
|
2676
|
+
detail: `${file.name} (${this.maxSizeMb()}${this.t('shared.units.mb')})`,
|
|
2186
2677
|
});
|
|
2187
2678
|
return false;
|
|
2188
2679
|
}
|
|
@@ -2201,37 +2692,24 @@ class FileUploaderComponent {
|
|
|
2201
2692
|
}
|
|
2202
2693
|
}
|
|
2203
2694
|
removeFile(file) {
|
|
2204
|
-
this.selectedFiles.update(files => files.filter(f => f !== file));
|
|
2695
|
+
this.selectedFiles.update((files) => files.filter((f) => f !== file));
|
|
2205
2696
|
}
|
|
2206
2697
|
async uploadFiles(files) {
|
|
2207
2698
|
const filesToUpload = files ?? this.selectedFiles();
|
|
2208
2699
|
if (!filesToUpload.length || this.isUploading())
|
|
2209
2700
|
return;
|
|
2210
2701
|
this.isUploading.set(true);
|
|
2211
|
-
const uploadedFiles = [];
|
|
2212
2702
|
try {
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
}
|
|
2221
|
-
else {
|
|
2222
|
-
throw new Error(response.message || 'Upload failed');
|
|
2223
|
-
}
|
|
2703
|
+
const batchUploadFn = this.getUploadMultipleFn();
|
|
2704
|
+
// Use batch upload if available and multiple files
|
|
2705
|
+
if (batchUploadFn && filesToUpload.length > 1) {
|
|
2706
|
+
await this.uploadBatch(filesToUpload, batchUploadFn);
|
|
2707
|
+
}
|
|
2708
|
+
else {
|
|
2709
|
+
await this.uploadSequential(filesToUpload);
|
|
2224
2710
|
}
|
|
2225
|
-
this.filesUploaded.emit(uploadedFiles);
|
|
2226
|
-
this.selectedFiles.set([]);
|
|
2227
|
-
this.messageService.add({
|
|
2228
|
-
severity: 'success',
|
|
2229
|
-
summary: 'Upload Complete',
|
|
2230
|
-
detail: `${uploadedFiles.length} file(s) uploaded successfully`,
|
|
2231
|
-
});
|
|
2232
2711
|
}
|
|
2233
2712
|
catch (error) {
|
|
2234
|
-
// Error toast handled by global interceptor
|
|
2235
2713
|
this.onError.emit(error);
|
|
2236
2714
|
}
|
|
2237
2715
|
finally {
|
|
@@ -2240,54 +2718,113 @@ class FileUploaderComponent {
|
|
|
2240
2718
|
this.uploadProgress.set(0);
|
|
2241
2719
|
}
|
|
2242
2720
|
}
|
|
2721
|
+
async uploadBatch(files, uploadFn) {
|
|
2722
|
+
this.uploadingFileName.set(`${files.length} ${this.t('shared.upload.files')}`);
|
|
2723
|
+
this.uploadProgress.set(0);
|
|
2724
|
+
const response = await firstValueFrom(uploadFn(files, this.uploadOptions()));
|
|
2725
|
+
if (response.success && response.data) {
|
|
2726
|
+
response.data.forEach((file) => this.fileUploaded.emit(file));
|
|
2727
|
+
this.filesUploaded.emit(response.data);
|
|
2728
|
+
this.selectedFiles.set([]);
|
|
2729
|
+
this.messageService.add({
|
|
2730
|
+
severity: 'success',
|
|
2731
|
+
summary: this.t('shared.upload.complete'),
|
|
2732
|
+
detail: this.t('shared.upload.files.uploaded', { count: response.data.length }),
|
|
2733
|
+
});
|
|
2734
|
+
}
|
|
2735
|
+
else {
|
|
2736
|
+
throw new Error(response.message || this.t('shared.upload.failed'));
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
async uploadSequential(files) {
|
|
2740
|
+
const uploadedFiles = [];
|
|
2741
|
+
const uploadFn = this.getUploadFn();
|
|
2742
|
+
for (const file of files) {
|
|
2743
|
+
this.uploadingFileName.set(file.name);
|
|
2744
|
+
this.uploadProgress.set(0);
|
|
2745
|
+
const response = await firstValueFrom(uploadFn(file, this.uploadOptions()));
|
|
2746
|
+
if (response.success && response.data) {
|
|
2747
|
+
uploadedFiles.push(response.data);
|
|
2748
|
+
this.fileUploaded.emit(response.data);
|
|
2749
|
+
}
|
|
2750
|
+
else {
|
|
2751
|
+
throw new Error(response.message || this.t('shared.upload.failed'));
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
this.filesUploaded.emit(uploadedFiles);
|
|
2755
|
+
this.selectedFiles.set([]);
|
|
2756
|
+
this.messageService.add({
|
|
2757
|
+
severity: 'success',
|
|
2758
|
+
summary: this.t('shared.upload.complete'),
|
|
2759
|
+
detail: this.t('shared.upload.files.uploaded', { count: uploadedFiles.length }),
|
|
2760
|
+
});
|
|
2761
|
+
}
|
|
2243
2762
|
getFileIcon(file) {
|
|
2244
2763
|
return getFileIconClass(file.type);
|
|
2245
2764
|
}
|
|
2246
2765
|
formatSize(bytes) {
|
|
2247
2766
|
const kb = bytes / 1024;
|
|
2248
2767
|
if (kb < 1024)
|
|
2249
|
-
return `${kb.toFixed(1)}
|
|
2768
|
+
return `${kb.toFixed(1)} ${this.t('shared.units.kb')}`;
|
|
2250
2769
|
const mb = kb / 1024;
|
|
2251
|
-
return `${mb.toFixed(1)}
|
|
2770
|
+
return `${mb.toFixed(1)} ${this.t('shared.units.mb')}`;
|
|
2771
|
+
}
|
|
2772
|
+
t(key, variables) {
|
|
2773
|
+
if (this.translateAdapter) {
|
|
2774
|
+
return this.translateAdapter.translate(key, variables);
|
|
2775
|
+
}
|
|
2776
|
+
return key;
|
|
2252
2777
|
}
|
|
2253
2778
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: FileUploaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2254
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: FileUploaderComponent, isStandalone: true, selector: "lib-file-uploader", inputs: { uploadFile: { classPropertyName: "uploadFile", publicName: "uploadFile", isSignal: true, isRequired: true, transformFunction: null }, acceptTypes: { classPropertyName: "acceptTypes", publicName: "acceptTypes", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, maxFiles: { classPropertyName: "maxFiles", publicName: "maxFiles", isSignal: true, isRequired: false, transformFunction: null }, maxSizeMb: { classPropertyName: "maxSizeMb", publicName: "maxSizeMb", isSignal: true, isRequired: false, transformFunction: null }, uploadOptions: { classPropertyName: "uploadOptions", publicName: "uploadOptions", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, showPreview: { classPropertyName: "showPreview", publicName: "showPreview", isSignal: true, isRequired: false, transformFunction: null }, autoUpload: { classPropertyName: "autoUpload", publicName: "autoUpload", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fileUploaded: "fileUploaded", filesUploaded: "filesUploaded", onError: "onError", fileSelected: "fileSelected" }, ngImport: i0, template: `
|
|
2255
|
-
|
|
2256
|
-
class="
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2779
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: FileUploaderComponent, isStandalone: true, selector: "lib-file-uploader", inputs: { uploadFile: { classPropertyName: "uploadFile", publicName: "uploadFile", isSignal: true, isRequired: false, transformFunction: null }, uploadMultipleFiles: { classPropertyName: "uploadMultipleFiles", publicName: "uploadMultipleFiles", isSignal: true, isRequired: false, transformFunction: null }, acceptTypes: { classPropertyName: "acceptTypes", publicName: "acceptTypes", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, maxFiles: { classPropertyName: "maxFiles", publicName: "maxFiles", isSignal: true, isRequired: false, transformFunction: null }, maxSizeMb: { classPropertyName: "maxSizeMb", publicName: "maxSizeMb", isSignal: true, isRequired: false, transformFunction: null }, uploadOptions: { classPropertyName: "uploadOptions", publicName: "uploadOptions", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, showPreview: { classPropertyName: "showPreview", publicName: "showPreview", isSignal: true, isRequired: false, transformFunction: null }, autoUpload: { classPropertyName: "autoUpload", publicName: "autoUpload", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fileUploaded: "fileUploaded", filesUploaded: "filesUploaded", onError: "onError", fileSelected: "fileSelected" }, ngImport: i0, template: `
|
|
2780
|
+
@if (!hasUploadCapability()) {
|
|
2781
|
+
<div class="p-4 border border-dashed border-orange-300 bg-orange-50 dark:bg-orange-900/20 rounded-lg text-center">
|
|
2782
|
+
<i class="pi pi-exclamation-triangle text-2xl text-orange-500 mb-2"></i>
|
|
2783
|
+
<p class="text-sm text-orange-700 dark:text-orange-300">
|
|
2784
|
+
{{ 'shared.upload.provider.not.configured' | translate: { provider: 'provideStorageProviders()' } }}
|
|
2785
|
+
</p>
|
|
2786
|
+
</div>
|
|
2787
|
+
} @else {
|
|
2263
2788
|
<div
|
|
2264
|
-
class="
|
|
2265
|
-
[class.
|
|
2266
|
-
|
|
2267
|
-
(
|
|
2789
|
+
class="w-full"
|
|
2790
|
+
[class.opacity-60]="disabled()"
|
|
2791
|
+
(dragover)="onDragOver($event)"
|
|
2792
|
+
(dragleave)="onDragLeave($event)"
|
|
2793
|
+
(drop)="onDrop($event)"
|
|
2268
2794
|
>
|
|
2269
|
-
|
|
2795
|
+
<!-- Upload Area - Responsive padding -->
|
|
2796
|
+
<div
|
|
2797
|
+
class="upload-zone border-2 border-dashed rounded-lg p-4 sm:p-6 md:p-8 cursor-pointer transition-all duration-200 text-center"
|
|
2798
|
+
[class.drag-over]="isDragOver()"
|
|
2799
|
+
[class.cursor-not-allowed]="disabled()"
|
|
2800
|
+
(click)="fileInput.click()"
|
|
2801
|
+
>
|
|
2802
|
+
@if (isUploading()) {
|
|
2270
2803
|
<div class="flex flex-col items-center">
|
|
2271
2804
|
<i class="pi pi-spin pi-spinner text-3xl sm:text-4xl text-primary"></i>
|
|
2272
|
-
<p class="mt-2 text-sm sm:text-base break-all px-2">
|
|
2805
|
+
<p class="mt-2 text-sm sm:text-base break-all px-2">{{ 'shared.upload.uploading' | translate: { fileName: uploadingFileName() } }}</p>
|
|
2273
2806
|
@if (uploadProgress() > 0) {
|
|
2274
|
-
<p-progressBar
|
|
2807
|
+
<p-progressBar
|
|
2808
|
+
[value]="uploadProgress()"
|
|
2809
|
+
[showValue]="true"
|
|
2810
|
+
class="w-full mt-2 max-w-xs"
|
|
2811
|
+
/>
|
|
2275
2812
|
}
|
|
2276
2813
|
</div>
|
|
2277
2814
|
} @else {
|
|
2278
2815
|
<div class="flex flex-col items-center">
|
|
2279
2816
|
<i class="pi pi-cloud-upload text-3xl sm:text-4xl text-primary"></i>
|
|
2280
2817
|
<p class="mt-2 mb-1 font-semibold text-sm sm:text-base">
|
|
2281
|
-
{{ multiple() ? '
|
|
2818
|
+
{{ multiple() ? ('shared.upload.drop.multiple' | translate) : ('shared.upload.drop.single' | translate) }}
|
|
2282
2819
|
</p>
|
|
2283
2820
|
<p class="text-xs sm:text-sm text-color-secondary px-2">
|
|
2284
2821
|
@if (acceptTypesDisplay()) {
|
|
2285
|
-
|
|
2822
|
+
{{ 'shared.upload.allowed.types' | translate }} {{ acceptTypesDisplay() }}
|
|
2286
2823
|
} @else {
|
|
2287
|
-
|
|
2824
|
+
{{ 'shared.upload.all.types.allowed' | translate }}
|
|
2288
2825
|
}
|
|
2289
2826
|
@if (maxSizeMb()) {
|
|
2290
|
-
<span class="whitespace-nowrap">
|
|
2827
|
+
<span class="whitespace-nowrap">{{ 'shared.upload.max.size' | translate: { size: maxSizeMb() } }}</span>
|
|
2291
2828
|
}
|
|
2292
2829
|
</p>
|
|
2293
2830
|
</div>
|
|
@@ -2309,10 +2846,20 @@ class FileUploaderComponent {
|
|
|
2309
2846
|
@if (selectedFiles().length > 0 && showPreview()) {
|
|
2310
2847
|
<div class="mt-3 space-y-2">
|
|
2311
2848
|
@for (file of selectedFiles(); track file.name) {
|
|
2312
|
-
<div
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
<
|
|
2849
|
+
<div
|
|
2850
|
+
class="file-preview-item flex items-center gap-2 p-2 sm:p-3 rounded-lg"
|
|
2851
|
+
>
|
|
2852
|
+
<i
|
|
2853
|
+
[class]="getFileIcon(file)"
|
|
2854
|
+
class="text-lg sm:text-xl flex-shrink-0"
|
|
2855
|
+
></i>
|
|
2856
|
+
<span class="flex-1 truncate text-sm sm:text-base min-w-0">{{
|
|
2857
|
+
file.name
|
|
2858
|
+
}}</span>
|
|
2859
|
+
<span
|
|
2860
|
+
class="text-xs sm:text-sm text-color-secondary whitespace-nowrap"
|
|
2861
|
+
>{{ formatSize(file.size) }}</span
|
|
2862
|
+
>
|
|
2316
2863
|
<p-button
|
|
2317
2864
|
icon="pi pi-times"
|
|
2318
2865
|
[text]="true"
|
|
@@ -2326,48 +2873,61 @@ class FileUploaderComponent {
|
|
|
2326
2873
|
}
|
|
2327
2874
|
</div>
|
|
2328
2875
|
}
|
|
2329
|
-
|
|
2330
|
-
|
|
2876
|
+
</div>
|
|
2877
|
+
}
|
|
2878
|
+
`, isInline: true, styles: [".upload-zone{border-color:var(--p-surface-300);background:var(--p-surface-50)}.upload-zone:hover:not(.cursor-not-allowed){border-color:var(--p-primary-color);background:var(--p-surface-100)}.upload-zone.drag-over{border-color:var(--p-primary-color);background:var(--p-primary-50)}:host-context(.p-dark) .upload-zone,.dark .upload-zone{border-color:var(--p-surface-600);background:var(--p-surface-800)}:host-context(.p-dark) .upload-zone:hover:not(.cursor-not-allowed),.dark .upload-zone:hover:not(.cursor-not-allowed){border-color:var(--p-primary-color);background:var(--p-surface-700)}:host-context(.p-dark) .upload-zone.drag-over,.dark .upload-zone.drag-over{border-color:var(--p-primary-color);background:var(--p-primary-900)}.file-preview-item{border:1px solid var(--p-surface-200);background:var(--p-surface-0)}:host-context(.p-dark) .file-preview-item,.dark .file-preview-item{border-color:var(--p-surface-600);background:var(--p-surface-800)}\n"], dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i1$2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i2$1.ProgressBar, selector: "p-progressBar, p-progressbar, p-progress-bar", inputs: ["value", "showValue", "styleClass", "valueStyleClass", "unit", "mode", "color"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
2331
2879
|
}
|
|
2332
2880
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: FileUploaderComponent, decorators: [{
|
|
2333
2881
|
type: Component,
|
|
2334
|
-
args: [{ selector: 'lib-file-uploader', imports: [AngularModule, PrimeModule], template: `
|
|
2335
|
-
|
|
2336
|
-
class="
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2882
|
+
args: [{ selector: 'lib-file-uploader', imports: [AngularModule, PrimeModule, TranslatePipe], template: `
|
|
2883
|
+
@if (!hasUploadCapability()) {
|
|
2884
|
+
<div class="p-4 border border-dashed border-orange-300 bg-orange-50 dark:bg-orange-900/20 rounded-lg text-center">
|
|
2885
|
+
<i class="pi pi-exclamation-triangle text-2xl text-orange-500 mb-2"></i>
|
|
2886
|
+
<p class="text-sm text-orange-700 dark:text-orange-300">
|
|
2887
|
+
{{ 'shared.upload.provider.not.configured' | translate: { provider: 'provideStorageProviders()' } }}
|
|
2888
|
+
</p>
|
|
2889
|
+
</div>
|
|
2890
|
+
} @else {
|
|
2343
2891
|
<div
|
|
2344
|
-
class="
|
|
2345
|
-
[class.
|
|
2346
|
-
|
|
2347
|
-
(
|
|
2892
|
+
class="w-full"
|
|
2893
|
+
[class.opacity-60]="disabled()"
|
|
2894
|
+
(dragover)="onDragOver($event)"
|
|
2895
|
+
(dragleave)="onDragLeave($event)"
|
|
2896
|
+
(drop)="onDrop($event)"
|
|
2348
2897
|
>
|
|
2349
|
-
|
|
2898
|
+
<!-- Upload Area - Responsive padding -->
|
|
2899
|
+
<div
|
|
2900
|
+
class="upload-zone border-2 border-dashed rounded-lg p-4 sm:p-6 md:p-8 cursor-pointer transition-all duration-200 text-center"
|
|
2901
|
+
[class.drag-over]="isDragOver()"
|
|
2902
|
+
[class.cursor-not-allowed]="disabled()"
|
|
2903
|
+
(click)="fileInput.click()"
|
|
2904
|
+
>
|
|
2905
|
+
@if (isUploading()) {
|
|
2350
2906
|
<div class="flex flex-col items-center">
|
|
2351
2907
|
<i class="pi pi-spin pi-spinner text-3xl sm:text-4xl text-primary"></i>
|
|
2352
|
-
<p class="mt-2 text-sm sm:text-base break-all px-2">
|
|
2908
|
+
<p class="mt-2 text-sm sm:text-base break-all px-2">{{ 'shared.upload.uploading' | translate: { fileName: uploadingFileName() } }}</p>
|
|
2353
2909
|
@if (uploadProgress() > 0) {
|
|
2354
|
-
<p-progressBar
|
|
2910
|
+
<p-progressBar
|
|
2911
|
+
[value]="uploadProgress()"
|
|
2912
|
+
[showValue]="true"
|
|
2913
|
+
class="w-full mt-2 max-w-xs"
|
|
2914
|
+
/>
|
|
2355
2915
|
}
|
|
2356
2916
|
</div>
|
|
2357
2917
|
} @else {
|
|
2358
2918
|
<div class="flex flex-col items-center">
|
|
2359
2919
|
<i class="pi pi-cloud-upload text-3xl sm:text-4xl text-primary"></i>
|
|
2360
2920
|
<p class="mt-2 mb-1 font-semibold text-sm sm:text-base">
|
|
2361
|
-
{{ multiple() ? '
|
|
2921
|
+
{{ multiple() ? ('shared.upload.drop.multiple' | translate) : ('shared.upload.drop.single' | translate) }}
|
|
2362
2922
|
</p>
|
|
2363
2923
|
<p class="text-xs sm:text-sm text-color-secondary px-2">
|
|
2364
2924
|
@if (acceptTypesDisplay()) {
|
|
2365
|
-
|
|
2925
|
+
{{ 'shared.upload.allowed.types' | translate }} {{ acceptTypesDisplay() }}
|
|
2366
2926
|
} @else {
|
|
2367
|
-
|
|
2927
|
+
{{ 'shared.upload.all.types.allowed' | translate }}
|
|
2368
2928
|
}
|
|
2369
2929
|
@if (maxSizeMb()) {
|
|
2370
|
-
<span class="whitespace-nowrap">
|
|
2930
|
+
<span class="whitespace-nowrap">{{ 'shared.upload.max.size' | translate: { size: maxSizeMb() } }}</span>
|
|
2371
2931
|
}
|
|
2372
2932
|
</p>
|
|
2373
2933
|
</div>
|
|
@@ -2389,10 +2949,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
2389
2949
|
@if (selectedFiles().length > 0 && showPreview()) {
|
|
2390
2950
|
<div class="mt-3 space-y-2">
|
|
2391
2951
|
@for (file of selectedFiles(); track file.name) {
|
|
2392
|
-
<div
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
<
|
|
2952
|
+
<div
|
|
2953
|
+
class="file-preview-item flex items-center gap-2 p-2 sm:p-3 rounded-lg"
|
|
2954
|
+
>
|
|
2955
|
+
<i
|
|
2956
|
+
[class]="getFileIcon(file)"
|
|
2957
|
+
class="text-lg sm:text-xl flex-shrink-0"
|
|
2958
|
+
></i>
|
|
2959
|
+
<span class="flex-1 truncate text-sm sm:text-base min-w-0">{{
|
|
2960
|
+
file.name
|
|
2961
|
+
}}</span>
|
|
2962
|
+
<span
|
|
2963
|
+
class="text-xs sm:text-sm text-color-secondary whitespace-nowrap"
|
|
2964
|
+
>{{ formatSize(file.size) }}</span
|
|
2965
|
+
>
|
|
2396
2966
|
<p-button
|
|
2397
2967
|
icon="pi pi-times"
|
|
2398
2968
|
[text]="true"
|
|
@@ -2406,71 +2976,63 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
2406
2976
|
}
|
|
2407
2977
|
</div>
|
|
2408
2978
|
}
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2979
|
+
</div>
|
|
2980
|
+
}
|
|
2981
|
+
`, styles: [".upload-zone{border-color:var(--p-surface-300);background:var(--p-surface-50)}.upload-zone:hover:not(.cursor-not-allowed){border-color:var(--p-primary-color);background:var(--p-surface-100)}.upload-zone.drag-over{border-color:var(--p-primary-color);background:var(--p-primary-50)}:host-context(.p-dark) .upload-zone,.dark .upload-zone{border-color:var(--p-surface-600);background:var(--p-surface-800)}:host-context(.p-dark) .upload-zone:hover:not(.cursor-not-allowed),.dark .upload-zone:hover:not(.cursor-not-allowed){border-color:var(--p-primary-color);background:var(--p-surface-700)}:host-context(.p-dark) .upload-zone.drag-over,.dark .upload-zone.drag-over{border-color:var(--p-primary-color);background:var(--p-primary-900)}.file-preview-item{border:1px solid var(--p-surface-200);background:var(--p-surface-0)}:host-context(.p-dark) .file-preview-item,.dark .file-preview-item{border-color:var(--p-surface-600);background:var(--p-surface-800)}\n"] }]
|
|
2982
|
+
}], propDecorators: { uploadFile: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadFile", required: false }] }], uploadMultipleFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadMultipleFiles", required: false }] }], acceptTypes: [{ type: i0.Input, args: [{ isSignal: true, alias: "acceptTypes", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], maxFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFiles", required: false }] }], maxSizeMb: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSizeMb", required: false }] }], uploadOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadOptions", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], showPreview: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPreview", required: false }] }], autoUpload: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoUpload", required: false }] }], fileUploaded: [{ type: i0.Output, args: ["fileUploaded"] }], filesUploaded: [{ type: i0.Output, args: ["filesUploaded"] }], onError: [{ type: i0.Output, args: ["onError"] }], fileSelected: [{ type: i0.Output, args: ["fileSelected"] }] } });
|
|
2412
2983
|
|
|
2413
2984
|
const DEFAULT_PAGE_SIZE = 20;
|
|
2985
|
+
const DEFAULT_SELECTOR_PAGE_SIZE = 50;
|
|
2414
2986
|
/**
|
|
2415
|
-
* File Selector Dialog -
|
|
2987
|
+
* File Selector Dialog - Self-contained file browser with upload support.
|
|
2416
2988
|
*
|
|
2417
|
-
*
|
|
2989
|
+
* Uses FILE_PROVIDER internally - no external functions needed.
|
|
2990
|
+
* Just configure with inputs and handle selection events.
|
|
2418
2991
|
*
|
|
2419
2992
|
* Features:
|
|
2420
2993
|
* - Search with debouncing
|
|
2421
|
-
* - File type filtering
|
|
2994
|
+
* - File type filtering (acceptTypes)
|
|
2995
|
+
* - Folder filtering
|
|
2422
2996
|
* - Infinite scroll pagination
|
|
2423
2997
|
* - Single or multiple selection
|
|
2998
|
+
* - Built-in file upload (withUploader)
|
|
2424
2999
|
* - File preview with icons
|
|
2425
3000
|
*
|
|
2426
3001
|
* @example
|
|
2427
|
-
* ```typescript
|
|
2428
|
-
* // In component
|
|
2429
|
-
* readonly fileService = inject(FileManagerApiService);
|
|
2430
|
-
*
|
|
2431
|
-
* readonly loadFiles: LoadFilesFn = (filter) =>
|
|
2432
|
-
* this.fileService.getAll(filter.search, {
|
|
2433
|
-
* pagination: { currentPage: filter.page, pageSize: filter.pageSize },
|
|
2434
|
-
* filter: { contentTypes: filter.contentTypes },
|
|
2435
|
-
* }).pipe(
|
|
2436
|
-
* map(res => ({
|
|
2437
|
-
* ...res,
|
|
2438
|
-
* data: res.data?.map(f => ({
|
|
2439
|
-
* id: f.id,
|
|
2440
|
-
* name: f.name,
|
|
2441
|
-
* contentType: f.contentType,
|
|
2442
|
-
* size: f.size,
|
|
2443
|
-
* url: f.url
|
|
2444
|
-
* }))
|
|
2445
|
-
* }))
|
|
2446
|
-
* );
|
|
2447
|
-
* ```
|
|
2448
|
-
*
|
|
2449
3002
|
* ```html
|
|
2450
3003
|
* <lib-file-selector-dialog
|
|
2451
|
-
* [(visible)]="
|
|
2452
|
-
* [loadFiles]="loadFiles"
|
|
3004
|
+
* [(visible)]="showSelector"
|
|
2453
3005
|
* [acceptTypes]="['image/*']"
|
|
2454
|
-
* [multiple]="
|
|
2455
|
-
*
|
|
3006
|
+
* [multiple]="true"
|
|
3007
|
+
* [withUploader]="true"
|
|
3008
|
+
* (filesSelected)="onFilesSelected($event)"
|
|
2456
3009
|
* />
|
|
2457
3010
|
* ```
|
|
2458
3011
|
*/
|
|
2459
3012
|
class FileSelectorDialogComponent {
|
|
2460
3013
|
destroyRef = inject(DestroyRef);
|
|
3014
|
+
translateAdapter = inject(TRANSLATE_ADAPTER, { optional: true });
|
|
3015
|
+
fileProvider = inject(FILE_PROVIDER, { optional: true });
|
|
2461
3016
|
abortController = null;
|
|
2462
3017
|
searchDebounceTimer = null;
|
|
2463
|
-
//
|
|
2464
|
-
|
|
2465
|
-
// Inputs
|
|
2466
|
-
header = input('Select File', ...(ngDevMode ? [{ debugName: "header" }] : []));
|
|
3018
|
+
// Configuration inputs
|
|
3019
|
+
header = input(...(ngDevMode ? [undefined, { debugName: "header" }] : []));
|
|
2467
3020
|
acceptTypes = input([], ...(ngDevMode ? [{ debugName: "acceptTypes" }] : []));
|
|
2468
3021
|
multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : []));
|
|
3022
|
+
// Computed header - shows "Select Files" for multiple, allows custom override
|
|
3023
|
+
dialogHeader = computed(() => {
|
|
3024
|
+
const customHeader = this.header();
|
|
3025
|
+
if (customHeader)
|
|
3026
|
+
return customHeader;
|
|
3027
|
+
return this.t(this.multiple() ? 'shared.file.selector.select.files' : 'shared.file.selector.select.file');
|
|
3028
|
+
}, ...(ngDevMode ? [{ debugName: "dialogHeader" }] : []));
|
|
2469
3029
|
maxSelection = input(10, ...(ngDevMode ? [{ debugName: "maxSelection" }] : []));
|
|
2470
3030
|
pageSize = input(DEFAULT_PAGE_SIZE, ...(ngDevMode ? [{ debugName: "pageSize" }] : []));
|
|
3031
|
+
folderId = input(...(ngDevMode ? [undefined, { debugName: "folderId" }] : [])); // Filter by folder
|
|
3032
|
+
withUploader = input(false, ...(ngDevMode ? [{ debugName: "withUploader" }] : []));
|
|
2471
3033
|
// Two-way visibility binding
|
|
2472
3034
|
visible = model(false, ...(ngDevMode ? [{ debugName: "visible" }] : []));
|
|
2473
|
-
// Outputs
|
|
3035
|
+
// Outputs - return file IDs
|
|
2474
3036
|
fileSelected = output();
|
|
2475
3037
|
filesSelected = output();
|
|
2476
3038
|
closed = output();
|
|
@@ -2480,10 +3042,48 @@ class FileSelectorDialogComponent {
|
|
|
2480
3042
|
files = signal([], ...(ngDevMode ? [{ debugName: "files" }] : []));
|
|
2481
3043
|
selectedFiles = signal([], ...(ngDevMode ? [{ debugName: "selectedFiles" }] : []));
|
|
2482
3044
|
total = signal(undefined, ...(ngDevMode ? [{ debugName: "total" }] : []));
|
|
2483
|
-
pagination = signal({
|
|
3045
|
+
pagination = signal({
|
|
3046
|
+
pageSize: DEFAULT_PAGE_SIZE,
|
|
3047
|
+
currentPage: 0,
|
|
3048
|
+
}, ...(ngDevMode ? [{ debugName: "pagination" }] : []));
|
|
2484
3049
|
searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
|
|
2485
|
-
//
|
|
2486
|
-
|
|
3050
|
+
// Folder selector state
|
|
3051
|
+
folders = signal([], ...(ngDevMode ? [{ debugName: "folders" }] : []));
|
|
3052
|
+
selectedFolderId = signal(null, ...(ngDevMode ? [{ debugName: "selectedFolderId" }] : []));
|
|
3053
|
+
foldersLoading = signal(false, ...(ngDevMode ? [{ debugName: "foldersLoading" }] : []));
|
|
3054
|
+
foldersPagination = signal({
|
|
3055
|
+
pageSize: DEFAULT_SELECTOR_PAGE_SIZE,
|
|
3056
|
+
currentPage: 0,
|
|
3057
|
+
}, ...(ngDevMode ? [{ debugName: "foldersPagination" }] : []));
|
|
3058
|
+
foldersTotal = signal(undefined, ...(ngDevMode ? [{ debugName: "foldersTotal" }] : []));
|
|
3059
|
+
// Storage config selector state
|
|
3060
|
+
storageConfigs = signal([], ...(ngDevMode ? [{ debugName: "storageConfigs" }] : []));
|
|
3061
|
+
selectedStorageConfigId = signal(null, ...(ngDevMode ? [{ debugName: "selectedStorageConfigId" }] : []));
|
|
3062
|
+
storageConfigsLoading = signal(false, ...(ngDevMode ? [{ debugName: "storageConfigsLoading" }] : []));
|
|
3063
|
+
storageConfigsPagination = signal({
|
|
3064
|
+
pageSize: DEFAULT_SELECTOR_PAGE_SIZE,
|
|
3065
|
+
currentPage: 0,
|
|
3066
|
+
}, ...(ngDevMode ? [{ debugName: "storageConfigsPagination" }] : []));
|
|
3067
|
+
storageConfigsTotal = signal(undefined, ...(ngDevMode ? [{ debugName: "storageConfigsTotal" }] : []));
|
|
3068
|
+
// Get effective storage config ID (selected or default)
|
|
3069
|
+
effectiveStorageConfigId = computed(() => {
|
|
3070
|
+
const selected = this.selectedStorageConfigId();
|
|
3071
|
+
if (selected)
|
|
3072
|
+
return selected;
|
|
3073
|
+
// Find default config
|
|
3074
|
+
const defaultConfig = this.storageConfigs().find((c) => c.isDefault);
|
|
3075
|
+
return defaultConfig?.id;
|
|
3076
|
+
}, ...(ngDevMode ? [{ debugName: "effectiveStorageConfigId" }] : []));
|
|
3077
|
+
// Upload function bound to provider
|
|
3078
|
+
uploadFileFn = (file, options) => {
|
|
3079
|
+
if (!this.fileProvider)
|
|
3080
|
+
throw new Error('shared.file.selector.provider.not.configured');
|
|
3081
|
+
return this.fileProvider.uploadFile(file, options);
|
|
3082
|
+
};
|
|
3083
|
+
// Multiple upload function bound to provider (optional)
|
|
3084
|
+
uploadMultipleFilesFn = this.fileProvider?.uploadMultipleFiles
|
|
3085
|
+
? (files, options) => this.fileProvider.uploadMultipleFiles(files, options)
|
|
3086
|
+
: undefined;
|
|
2487
3087
|
constructor() {
|
|
2488
3088
|
this.destroyRef.onDestroy(() => {
|
|
2489
3089
|
this.abortController?.abort();
|
|
@@ -2491,13 +3091,17 @@ class FileSelectorDialogComponent {
|
|
|
2491
3091
|
clearTimeout(this.searchDebounceTimer);
|
|
2492
3092
|
}
|
|
2493
3093
|
});
|
|
2494
|
-
// Load files when dialog becomes visible
|
|
3094
|
+
// Load files and storage configs when dialog becomes visible
|
|
2495
3095
|
effect(() => {
|
|
2496
3096
|
const isVisible = this.visible();
|
|
2497
3097
|
if (isVisible) {
|
|
2498
3098
|
untracked(() => {
|
|
2499
3099
|
this.resetState();
|
|
2500
3100
|
this.fetchFiles();
|
|
3101
|
+
// Preload storage configs for upload default
|
|
3102
|
+
if (this.fileProvider?.loadStorageConfigs) {
|
|
3103
|
+
this.loadStorageConfigs();
|
|
3104
|
+
}
|
|
2501
3105
|
});
|
|
2502
3106
|
}
|
|
2503
3107
|
});
|
|
@@ -2510,7 +3114,6 @@ class FileSelectorDialogComponent {
|
|
|
2510
3114
|
});
|
|
2511
3115
|
}
|
|
2512
3116
|
onSearchChange(value) {
|
|
2513
|
-
// Debounce search
|
|
2514
3117
|
if (this.searchDebounceTimer) {
|
|
2515
3118
|
clearTimeout(this.searchDebounceTimer);
|
|
2516
3119
|
}
|
|
@@ -2590,15 +3193,131 @@ class FileSelectorDialogComponent {
|
|
|
2590
3193
|
onDialogHide() {
|
|
2591
3194
|
this.closed.emit();
|
|
2592
3195
|
}
|
|
3196
|
+
// Folder selector methods
|
|
3197
|
+
onFolderChange(folderId) {
|
|
3198
|
+
this.selectedFolderId.set(folderId);
|
|
3199
|
+
this.pagination.update((p) => ({ ...p, currentPage: 0 }));
|
|
3200
|
+
this.files.set([]);
|
|
3201
|
+
this.fetchFiles();
|
|
3202
|
+
}
|
|
3203
|
+
onFolderSearch(event) {
|
|
3204
|
+
this.foldersPagination.update((p) => ({ ...p, currentPage: 0 }));
|
|
3205
|
+
this.loadFolders(event.filter);
|
|
3206
|
+
}
|
|
3207
|
+
async loadFolders(search = '') {
|
|
3208
|
+
if (!this.fileProvider?.loadFolders || this.foldersLoading())
|
|
3209
|
+
return;
|
|
3210
|
+
this.foldersLoading.set(true);
|
|
3211
|
+
try {
|
|
3212
|
+
const pag = this.foldersPagination();
|
|
3213
|
+
const filter = {
|
|
3214
|
+
page: pag.currentPage,
|
|
3215
|
+
pageSize: pag.pageSize,
|
|
3216
|
+
search,
|
|
3217
|
+
};
|
|
3218
|
+
const response = await firstValueFrom(this.fileProvider.loadFolders(filter));
|
|
3219
|
+
if (response.success && response.data) {
|
|
3220
|
+
this.folders.set(response.data);
|
|
3221
|
+
this.foldersTotal.set(response.meta?.total);
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
catch (error) {
|
|
3225
|
+
this.onError.emit(error);
|
|
3226
|
+
}
|
|
3227
|
+
finally {
|
|
3228
|
+
this.foldersLoading.set(false);
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
// Storage config selector methods
|
|
3232
|
+
onStorageConfigChange(configId) {
|
|
3233
|
+
this.selectedStorageConfigId.set(configId);
|
|
3234
|
+
this.pagination.update((p) => ({ ...p, currentPage: 0 }));
|
|
3235
|
+
this.files.set([]);
|
|
3236
|
+
this.fetchFiles();
|
|
3237
|
+
}
|
|
3238
|
+
onStorageConfigSearch(event) {
|
|
3239
|
+
this.storageConfigsPagination.update((p) => ({ ...p, currentPage: 0 }));
|
|
3240
|
+
this.loadStorageConfigs(event.filter);
|
|
3241
|
+
}
|
|
3242
|
+
async loadStorageConfigs(search = '') {
|
|
3243
|
+
if (!this.fileProvider?.loadStorageConfigs || this.storageConfigsLoading())
|
|
3244
|
+
return;
|
|
3245
|
+
this.storageConfigsLoading.set(true);
|
|
3246
|
+
try {
|
|
3247
|
+
const pag = this.storageConfigsPagination();
|
|
3248
|
+
const filter = {
|
|
3249
|
+
page: pag.currentPage,
|
|
3250
|
+
pageSize: pag.pageSize,
|
|
3251
|
+
search,
|
|
3252
|
+
};
|
|
3253
|
+
const response = await firstValueFrom(this.fileProvider.loadStorageConfigs(filter));
|
|
3254
|
+
if (response.success && response.data) {
|
|
3255
|
+
this.storageConfigs.set(response.data);
|
|
3256
|
+
this.storageConfigsTotal.set(response.meta?.total);
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
catch (error) {
|
|
3260
|
+
this.onError.emit(error);
|
|
3261
|
+
}
|
|
3262
|
+
finally {
|
|
3263
|
+
this.storageConfigsLoading.set(false);
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
onFileUploaded(uploadedFile) {
|
|
3267
|
+
if (!uploadedFile.id)
|
|
3268
|
+
return;
|
|
3269
|
+
// Convert uploaded file to IFileBasicInfo
|
|
3270
|
+
const fileInfo = {
|
|
3271
|
+
id: uploadedFile.id,
|
|
3272
|
+
name: uploadedFile.name,
|
|
3273
|
+
contentType: uploadedFile.contentType,
|
|
3274
|
+
size: String(uploadedFile.size / 1024),
|
|
3275
|
+
url: null,
|
|
3276
|
+
};
|
|
3277
|
+
// Add to the beginning of files list
|
|
3278
|
+
this.files.update((current) => [fileInfo, ...current]);
|
|
3279
|
+
// Auto-select the uploaded file
|
|
3280
|
+
if (this.multiple()) {
|
|
3281
|
+
const selected = this.selectedFiles();
|
|
3282
|
+
if (selected.length < this.maxSelection()) {
|
|
3283
|
+
this.selectedFiles.update((files) => [...files, fileInfo]);
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
else {
|
|
3287
|
+
this.selectedFiles.set([fileInfo]);
|
|
3288
|
+
}
|
|
3289
|
+
// Refresh files to get proper URLs
|
|
3290
|
+
this.fetchFiles();
|
|
3291
|
+
}
|
|
2593
3292
|
resetState() {
|
|
3293
|
+
// Cancel any pending request and reset loading state
|
|
3294
|
+
this.abortController?.abort();
|
|
3295
|
+
this.abortController = null;
|
|
3296
|
+
this.isLoading.set(false);
|
|
2594
3297
|
this.files.set([]);
|
|
2595
3298
|
this.selectedFiles.set([]);
|
|
2596
3299
|
this.searchTerm.set('');
|
|
2597
3300
|
this.pagination.set({ pageSize: this.pageSize(), currentPage: 0 });
|
|
2598
3301
|
this.total.set(undefined);
|
|
3302
|
+
// Reset folder selector
|
|
3303
|
+
this.selectedFolderId.set(null);
|
|
3304
|
+
this.folders.set([]);
|
|
3305
|
+
this.foldersPagination.set({
|
|
3306
|
+
pageSize: DEFAULT_SELECTOR_PAGE_SIZE,
|
|
3307
|
+
currentPage: 0,
|
|
3308
|
+
});
|
|
3309
|
+
this.foldersTotal.set(undefined);
|
|
3310
|
+
// Reset storage config selector
|
|
3311
|
+
this.selectedStorageConfigId.set(null);
|
|
3312
|
+
this.storageConfigs.set([]);
|
|
3313
|
+
this.storageConfigsPagination.set({
|
|
3314
|
+
pageSize: DEFAULT_SELECTOR_PAGE_SIZE,
|
|
3315
|
+
currentPage: 0,
|
|
3316
|
+
});
|
|
3317
|
+
this.storageConfigsTotal.set(undefined);
|
|
2599
3318
|
}
|
|
2600
3319
|
async fetchFiles(append = false) {
|
|
2601
|
-
if (this.isLoading())
|
|
3320
|
+
if (!this.fileProvider || this.isLoading())
|
|
2602
3321
|
return;
|
|
2603
3322
|
this.abortController?.abort();
|
|
2604
3323
|
this.abortController = new AbortController();
|
|
@@ -2609,15 +3328,33 @@ class FileSelectorDialogComponent {
|
|
|
2609
3328
|
page: pag.currentPage,
|
|
2610
3329
|
pageSize: pag.pageSize,
|
|
2611
3330
|
search: this.searchTerm(),
|
|
2612
|
-
contentTypes: this.acceptTypes().length
|
|
3331
|
+
contentTypes: this.acceptTypes().length
|
|
3332
|
+
? this.acceptTypes()
|
|
3333
|
+
: undefined,
|
|
3334
|
+
folderId: this.selectedFolderId() || this.folderId(),
|
|
3335
|
+
storageConfigId: this.selectedStorageConfigId() || undefined,
|
|
2613
3336
|
};
|
|
2614
|
-
const response = await firstValueFrom(this.loadFiles(
|
|
3337
|
+
const response = await firstValueFrom(this.fileProvider.loadFiles(filter));
|
|
2615
3338
|
if (response.success && response.data) {
|
|
3339
|
+
let files = response.data;
|
|
3340
|
+
// Always fetch URLs for all files to get fresh presigned URLs
|
|
3341
|
+
if (this.fileProvider.getFileUrls && files.length > 0) {
|
|
3342
|
+
const fileIds = files.map((f) => f.id);
|
|
3343
|
+
const urlResponse = await firstValueFrom(this.fileProvider.getFileUrls(fileIds));
|
|
3344
|
+
if (urlResponse.success && urlResponse.data) {
|
|
3345
|
+
// Merge URLs into files
|
|
3346
|
+
const urlMap = new Map(urlResponse.data.map((f) => [f.id, f.url]));
|
|
3347
|
+
files = files.map((f) => ({
|
|
3348
|
+
...f,
|
|
3349
|
+
url: urlMap.get(f.id) ?? f.url,
|
|
3350
|
+
}));
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
2616
3353
|
if (append) {
|
|
2617
|
-
this.files.update((current) => [...current, ...
|
|
3354
|
+
this.files.update((current) => [...current, ...files]);
|
|
2618
3355
|
}
|
|
2619
3356
|
else {
|
|
2620
|
-
this.files.set(
|
|
3357
|
+
this.files.set(files);
|
|
2621
3358
|
}
|
|
2622
3359
|
this.total.set(response.meta?.total);
|
|
2623
3360
|
}
|
|
@@ -2631,10 +3368,16 @@ class FileSelectorDialogComponent {
|
|
|
2631
3368
|
this.isLoading.set(false);
|
|
2632
3369
|
}
|
|
2633
3370
|
}
|
|
3371
|
+
t(key, variables) {
|
|
3372
|
+
if (this.translateAdapter) {
|
|
3373
|
+
return this.translateAdapter.translate(key, variables);
|
|
3374
|
+
}
|
|
3375
|
+
return key;
|
|
3376
|
+
}
|
|
2634
3377
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: FileSelectorDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2635
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: FileSelectorDialogComponent, isStandalone: true, selector: "lib-file-selector-dialog", inputs: {
|
|
3378
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: FileSelectorDialogComponent, isStandalone: true, selector: "lib-file-selector-dialog", inputs: { header: { classPropertyName: "header", publicName: "header", isSignal: true, isRequired: false, transformFunction: null }, acceptTypes: { classPropertyName: "acceptTypes", publicName: "acceptTypes", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, maxSelection: { classPropertyName: "maxSelection", publicName: "maxSelection", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, folderId: { classPropertyName: "folderId", publicName: "folderId", isSignal: true, isRequired: false, transformFunction: null }, withUploader: { classPropertyName: "withUploader", publicName: "withUploader", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { visible: "visibleChange", fileSelected: "fileSelected", filesSelected: "filesSelected", closed: "closed", onError: "onError" }, ngImport: i0, template: `
|
|
2636
3379
|
<p-dialog
|
|
2637
|
-
[header]="
|
|
3380
|
+
[header]="dialogHeader()"
|
|
2638
3381
|
[(visible)]="visible"
|
|
2639
3382
|
[modal]="true"
|
|
2640
3383
|
[closable]="true"
|
|
@@ -2645,107 +3388,246 @@ class FileSelectorDialogComponent {
|
|
|
2645
3388
|
styleClass="file-selector-dialog"
|
|
2646
3389
|
(onHide)="onDialogHide()"
|
|
2647
3390
|
>
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
class="
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
3391
|
+
@if (!fileProvider) {
|
|
3392
|
+
<div class="p-6 text-center">
|
|
3393
|
+
<i
|
|
3394
|
+
class="pi pi-exclamation-triangle text-4xl text-orange-500 mb-3"
|
|
3395
|
+
></i>
|
|
3396
|
+
<p class="text-lg font-medium text-color mb-2">
|
|
3397
|
+
{{ 'shared.file.selector.provider.not.configured' | translate }}
|
|
3398
|
+
</p>
|
|
3399
|
+
<p class="text-sm text-color-secondary mb-4">
|
|
3400
|
+
{{ 'shared.file.selector.add.provider' | translate }}
|
|
3401
|
+
<code class="inline-block bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 px-2 py-1 rounded font-mono text-xs">provideStorageProviders()</code>
|
|
3402
|
+
</p>
|
|
3403
|
+
<button
|
|
3404
|
+
pButton
|
|
3405
|
+
[label]="'shared.close' | translate"
|
|
3406
|
+
class="p-button-text"
|
|
3407
|
+
(click)="onCancel()"
|
|
3408
|
+
></button>
|
|
3409
|
+
</div>
|
|
3410
|
+
} @else {
|
|
3411
|
+
<!-- Upload Section (when withUploader is enabled) -->
|
|
3412
|
+
@if (withUploader()) {
|
|
3413
|
+
<div class="mb-4">
|
|
3414
|
+
<lib-file-uploader
|
|
3415
|
+
[uploadFile]="uploadFileFn"
|
|
3416
|
+
[uploadMultipleFiles]="uploadMultipleFilesFn"
|
|
3417
|
+
[uploadOptions]="{ storageConfigId: effectiveStorageConfigId() }"
|
|
3418
|
+
[acceptTypes]="acceptTypes()"
|
|
3419
|
+
[multiple]="multiple()"
|
|
3420
|
+
[maxFiles]="maxSelection()"
|
|
3421
|
+
[maxSizeMb]="10"
|
|
3422
|
+
[showPreview]="false"
|
|
3423
|
+
(fileUploaded)="onFileUploaded($event)"
|
|
3424
|
+
/>
|
|
3425
|
+
</div>
|
|
2665
3426
|
}
|
|
2666
|
-
</div>
|
|
2667
3427
|
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
[
|
|
2689
|
-
|
|
3428
|
+
<!-- Filters Row - All on same line on desktop -->
|
|
3429
|
+
<div
|
|
3430
|
+
class="flex flex-col sm:flex-row gap-2 mb-3 items-stretch sm:items-center"
|
|
3431
|
+
>
|
|
3432
|
+
<!-- Search -->
|
|
3433
|
+
<p-iconfield class="flex-1">
|
|
3434
|
+
<p-inputicon class="pi pi-search" />
|
|
3435
|
+
<input
|
|
3436
|
+
pInputText
|
|
3437
|
+
type="text"
|
|
3438
|
+
[ngModel]="searchTerm()"
|
|
3439
|
+
(ngModelChange)="onSearchChange($event)"
|
|
3440
|
+
[placeholder]="'shared.file.selector.search.placeholder' | translate"
|
|
3441
|
+
class="w-full"
|
|
3442
|
+
/>
|
|
3443
|
+
</p-iconfield>
|
|
3444
|
+
|
|
3445
|
+
<!-- Folder Selector -->
|
|
3446
|
+
@if (fileProvider.loadFolders) {
|
|
3447
|
+
<p-select
|
|
3448
|
+
[options]="folders()"
|
|
3449
|
+
[ngModel]="selectedFolderId()"
|
|
3450
|
+
(ngModelChange)="onFolderChange($event)"
|
|
3451
|
+
optionLabel="name"
|
|
3452
|
+
optionValue="id"
|
|
3453
|
+
[placeholder]="'shared.file.selector.all.folders' | translate"
|
|
3454
|
+
[showClear]="true"
|
|
3455
|
+
[filter]="true"
|
|
3456
|
+
filterBy="name"
|
|
3457
|
+
(onFilter)="onFolderSearch($event)"
|
|
3458
|
+
[loading]="foldersLoading()"
|
|
3459
|
+
class="w-full sm:w-40"
|
|
3460
|
+
(onShow)="loadFolders()"
|
|
2690
3461
|
>
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
<
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
<
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
3462
|
+
<ng-template pTemplate="selectedItem" let-folder>
|
|
3463
|
+
<div class="flex items-center gap-2">
|
|
3464
|
+
<i class="pi pi-folder text-color-secondary"></i>
|
|
3465
|
+
<span>{{ folder?.name }}</span>
|
|
3466
|
+
</div>
|
|
3467
|
+
</ng-template>
|
|
3468
|
+
<ng-template pTemplate="item" let-folder>
|
|
3469
|
+
<div class="flex items-center gap-2">
|
|
3470
|
+
<i class="pi pi-folder text-color-secondary"></i>
|
|
3471
|
+
<span>{{ folder.name }}</span>
|
|
3472
|
+
</div>
|
|
3473
|
+
</ng-template>
|
|
3474
|
+
</p-select>
|
|
3475
|
+
}
|
|
2704
3476
|
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
3477
|
+
<!-- Storage Config Selector -->
|
|
3478
|
+
@if (fileProvider.loadStorageConfigs) {
|
|
3479
|
+
<p-select
|
|
3480
|
+
[options]="storageConfigs()"
|
|
3481
|
+
[ngModel]="selectedStorageConfigId()"
|
|
3482
|
+
(ngModelChange)="onStorageConfigChange($event)"
|
|
3483
|
+
optionLabel="name"
|
|
3484
|
+
optionValue="id"
|
|
3485
|
+
[placeholder]="'shared.file.selector.all.storage' | translate"
|
|
3486
|
+
[showClear]="true"
|
|
3487
|
+
[filter]="true"
|
|
3488
|
+
filterBy="name"
|
|
3489
|
+
(onFilter)="onStorageConfigSearch($event)"
|
|
3490
|
+
[loading]="storageConfigsLoading()"
|
|
3491
|
+
class="w-full sm:w-40"
|
|
3492
|
+
(onShow)="loadStorageConfigs()"
|
|
3493
|
+
>
|
|
3494
|
+
<ng-template pTemplate="selectedItem" let-config>
|
|
3495
|
+
<div class="flex items-center gap-2">
|
|
3496
|
+
<i class="pi pi-database text-color-secondary"></i>
|
|
3497
|
+
<span>{{ config?.name }}</span>
|
|
3498
|
+
@if (config?.isDefault) {
|
|
3499
|
+
<span
|
|
3500
|
+
class="text-xs bg-primary text-primary-contrast px-1 rounded"
|
|
3501
|
+
>{{ 'shared.default' | translate }}</span
|
|
3502
|
+
>
|
|
3503
|
+
}
|
|
3504
|
+
</div>
|
|
3505
|
+
</ng-template>
|
|
3506
|
+
<ng-template pTemplate="item" let-config>
|
|
3507
|
+
<div class="flex items-center gap-2">
|
|
3508
|
+
<i class="pi pi-database text-color-secondary"></i>
|
|
3509
|
+
<span>{{ config.name }}</span>
|
|
3510
|
+
@if (config.isDefault) {
|
|
3511
|
+
<span
|
|
3512
|
+
class="text-xs bg-primary text-primary-contrast px-1 rounded"
|
|
3513
|
+
>{{ 'shared.default' | translate }}</span
|
|
3514
|
+
>
|
|
3515
|
+
}
|
|
3516
|
+
</div>
|
|
3517
|
+
</ng-template>
|
|
3518
|
+
</p-select>
|
|
2713
3519
|
}
|
|
2714
3520
|
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
3521
|
+
<!-- Selected count -->
|
|
3522
|
+
@if (multiple()) {
|
|
3523
|
+
<span
|
|
3524
|
+
class="text-sm text-color-secondary self-center whitespace-nowrap"
|
|
3525
|
+
>
|
|
3526
|
+
{{ 'shared.file.selector.selected' | translate: { count: selectedFiles().length } }}
|
|
3527
|
+
</span>
|
|
2719
3528
|
}
|
|
2720
|
-
|
|
2721
|
-
</div>
|
|
3529
|
+
</div>
|
|
2722
3530
|
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
(
|
|
2738
|
-
|
|
3531
|
+
<!-- File Grid - Responsive columns -->
|
|
3532
|
+
<div class="file-grid" #scrollContainer (scroll)="onScroll($event)">
|
|
3533
|
+
@if (isLoading() && files().length === 0) {
|
|
3534
|
+
<div class="col-span-full flex justify-center p-4">
|
|
3535
|
+
<i
|
|
3536
|
+
class="pi pi-spin pi-spinner text-4xl text-color-secondary"
|
|
3537
|
+
></i>
|
|
3538
|
+
</div>
|
|
3539
|
+
} @else if (files().length === 0) {
|
|
3540
|
+
<div class="col-span-full text-center p-4 text-color-secondary">
|
|
3541
|
+
<i class="pi pi-inbox text-4xl mb-2 block"></i>
|
|
3542
|
+
<p>{{ 'shared.file.selector.no.files' | translate }}</p>
|
|
3543
|
+
</div>
|
|
3544
|
+
} @else {
|
|
3545
|
+
@for (file of files(); track file.id) {
|
|
3546
|
+
<div
|
|
3547
|
+
class="file-card"
|
|
3548
|
+
[class.selected]="isSelected(file)"
|
|
3549
|
+
[class.disabled]="!isFileAllowed(file)"
|
|
3550
|
+
(click)="toggleSelection(file)"
|
|
3551
|
+
>
|
|
3552
|
+
<!-- File Preview -->
|
|
3553
|
+
<div class="file-preview">
|
|
3554
|
+
@if (isImage(file) && file.url) {
|
|
3555
|
+
<img
|
|
3556
|
+
[src]="file.url"
|
|
3557
|
+
[alt]="file.name"
|
|
3558
|
+
class="w-full h-full object-cover"
|
|
3559
|
+
/>
|
|
3560
|
+
} @else {
|
|
3561
|
+
<i
|
|
3562
|
+
[class]="getFileIcon(file)"
|
|
3563
|
+
class="text-4xl sm:text-5xl text-color-secondary"
|
|
3564
|
+
></i>
|
|
3565
|
+
}
|
|
3566
|
+
@if (isSelected(file)) {
|
|
3567
|
+
<div class="selected-overlay">
|
|
3568
|
+
<i class="pi pi-check text-xl sm:text-2xl"></i>
|
|
3569
|
+
</div>
|
|
3570
|
+
}
|
|
3571
|
+
</div>
|
|
3572
|
+
|
|
3573
|
+
<!-- File Info -->
|
|
3574
|
+
<div class="p-2 text-center bg-surface-0 dark:bg-surface-900">
|
|
3575
|
+
<span
|
|
3576
|
+
class="block text-xs sm:text-sm whitespace-nowrap overflow-hidden text-ellipsis"
|
|
3577
|
+
[title]="file.name"
|
|
3578
|
+
>
|
|
3579
|
+
{{ file.name }}
|
|
3580
|
+
</span>
|
|
3581
|
+
<span class="block text-xs text-color-secondary">{{
|
|
3582
|
+
formatSize(file.size)
|
|
3583
|
+
}}</span>
|
|
3584
|
+
</div>
|
|
3585
|
+
</div>
|
|
3586
|
+
}
|
|
3587
|
+
|
|
3588
|
+
@if (isLoading()) {
|
|
3589
|
+
<div class="col-span-full flex justify-center p-2">
|
|
3590
|
+
<i class="pi pi-spin pi-spinner text-color-secondary"></i>
|
|
3591
|
+
</div>
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
2739
3594
|
</div>
|
|
2740
|
-
|
|
3595
|
+
|
|
3596
|
+
<!-- Footer -->
|
|
3597
|
+
<ng-template pTemplate="footer">
|
|
3598
|
+
<div
|
|
3599
|
+
class="flex flex-col-reverse sm:flex-row gap-2 w-full sm:w-auto sm:justify-end"
|
|
3600
|
+
>
|
|
3601
|
+
<button
|
|
3602
|
+
pButton
|
|
3603
|
+
type="button"
|
|
3604
|
+
[label]="'shared.cancel' | translate"
|
|
3605
|
+
class="p-button-text w-full sm:w-auto"
|
|
3606
|
+
(click)="onCancel()"
|
|
3607
|
+
></button>
|
|
3608
|
+
<button
|
|
3609
|
+
pButton
|
|
3610
|
+
type="button"
|
|
3611
|
+
[label]="
|
|
3612
|
+
multiple()
|
|
3613
|
+
? ('shared.file.selector.select.multiple' | translate: { count: selectedFiles().length })
|
|
3614
|
+
: ('shared.file.selector.select' | translate)
|
|
3615
|
+
"
|
|
3616
|
+
[disabled]="selectedFiles().length === 0"
|
|
3617
|
+
class="w-full sm:w-auto"
|
|
3618
|
+
(click)="onConfirm()"
|
|
3619
|
+
></button>
|
|
3620
|
+
</div>
|
|
3621
|
+
</ng-template>
|
|
3622
|
+
}
|
|
2741
3623
|
</p-dialog>
|
|
2742
|
-
`, isInline: true, styles: [".file-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:.75rem;max-height:300px;overflow-y:auto;padding:.5rem}@media(min-width:480px){.file-grid{grid-template-columns:repeat(3,1fr);max-height:350px}}@media(min-width:640px){.file-grid{grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:1rem;max-height:400px}}.file-card{border:2px solid var(--p-surface-300);border-radius:var(--p-border-radius);cursor:pointer;transition:all .2s ease;overflow:hidden;background:var(--p-surface-0)}:host-context(.p-dark) .file-card,.dark .file-card{border-color:var(--p-surface-600);background:var(--p-surface-800)}.file-card:hover:not(.disabled){border-color:var(--p-primary-color);transform:translateY(-2px);box-shadow:var(--p-overlay-shadow)}.file-card.selected{border-color:var(--p-primary-color);background:var(--p-primary-50)}:host-context(.p-dark) .file-card.selected,.dark .file-card.selected{background:var(--p-primary-900)}.file-card.disabled{opacity:.5;cursor:not-allowed}.file-preview{position:relative;height:80px;display:flex;align-items:center;justify-content:center;background:var(--p-surface-100)}@media(min-width:640px){.file-preview{height:100px}}:host-context(.p-dark) .file-preview,.dark .file-preview{background:var(--p-surface-700)}.selected-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(var(--p-primary-500-rgb, 59, 130, 246),.3)}.selected-overlay i{color:var(--p-primary-color);background:var(--p-surface-0);border-radius:50%;padding:.5rem}:host-context(.p-dark) .selected-overlay i,.dark .selected-overlay i{background:var(--p-surface-900)}\n"], dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: IsEmptyImageDirective, selector: "img", inputs: ["src"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "directive", type: i1$2.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "pButtonPT", "pButtonUnstyled", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "component", type:
|
|
3624
|
+
`, isInline: true, styles: [".file-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:.75rem;max-height:300px;overflow-y:auto;padding:.5rem}@media(min-width:480px){.file-grid{grid-template-columns:repeat(3,1fr);max-height:350px}}@media(min-width:640px){.file-grid{grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:1rem;max-height:400px}}.file-card{border:2px solid var(--p-surface-300);border-radius:var(--p-border-radius);cursor:pointer;transition:all .2s ease;overflow:hidden;background:var(--p-surface-0)}:host-context(.p-dark) .file-card,.dark .file-card{border-color:var(--p-surface-600);background:var(--p-surface-800)}.file-card:hover:not(.disabled){border-color:var(--p-primary-color);transform:translateY(-2px);box-shadow:var(--p-overlay-shadow)}.file-card.selected{border-color:var(--p-primary-color);background:var(--p-primary-50)}:host-context(.p-dark) .file-card.selected,.dark .file-card.selected{background:var(--p-primary-900)}.file-card.disabled{opacity:.5;cursor:not-allowed}.file-preview{position:relative;height:80px;display:flex;align-items:center;justify-content:center;background:var(--p-surface-100)}@media(min-width:640px){.file-preview{height:100px}}:host-context(.p-dark) .file-preview,.dark .file-preview{background:var(--p-surface-700)}.selected-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(var(--p-primary-500-rgb, 59, 130, 246),.3)}.selected-overlay i{color:var(--p-primary-color);background:var(--p-surface-0);border-radius:50%;padding:.5rem}:host-context(.p-dark) .selected-overlay i,.dark .selected-overlay i{background:var(--p-surface-900)}\n"], dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: IsEmptyImageDirective, selector: "img", inputs: ["src"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "directive", type: i3$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "directive", type: i1$2.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "pButtonPT", "pButtonUnstyled", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "component", type: i5.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "component", type: i6.IconField, selector: "p-iconfield, p-iconField, p-icon-field", inputs: ["hostName", "iconPosition", "styleClass"] }, { kind: "component", type: i7.InputIcon, selector: "p-inputicon, p-inputIcon", inputs: ["hostName", "styleClass"] }, { kind: "directive", type: i2.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "component", type: i3.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: FileUploaderComponent, selector: "lib-file-uploader", inputs: ["uploadFile", "uploadMultipleFiles", "acceptTypes", "multiple", "maxFiles", "maxSizeMb", "uploadOptions", "disabled", "showPreview", "autoUpload"], outputs: ["fileUploaded", "filesUploaded", "onError", "fileSelected"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
2743
3625
|
}
|
|
2744
3626
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: FileSelectorDialogComponent, decorators: [{
|
|
2745
3627
|
type: Component,
|
|
2746
|
-
args: [{ selector: 'lib-file-selector-dialog', imports: [AngularModule, PrimeModule], template: `
|
|
3628
|
+
args: [{ selector: 'lib-file-selector-dialog', imports: [AngularModule, PrimeModule, FileUploaderComponent, TranslatePipe], template: `
|
|
2747
3629
|
<p-dialog
|
|
2748
|
-
[header]="
|
|
3630
|
+
[header]="dialogHeader()"
|
|
2749
3631
|
[(visible)]="visible"
|
|
2750
3632
|
[modal]="true"
|
|
2751
3633
|
[closable]="true"
|
|
@@ -2756,102 +3638,241 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
2756
3638
|
styleClass="file-selector-dialog"
|
|
2757
3639
|
(onHide)="onDialogHide()"
|
|
2758
3640
|
>
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
class="
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
3641
|
+
@if (!fileProvider) {
|
|
3642
|
+
<div class="p-6 text-center">
|
|
3643
|
+
<i
|
|
3644
|
+
class="pi pi-exclamation-triangle text-4xl text-orange-500 mb-3"
|
|
3645
|
+
></i>
|
|
3646
|
+
<p class="text-lg font-medium text-color mb-2">
|
|
3647
|
+
{{ 'shared.file.selector.provider.not.configured' | translate }}
|
|
3648
|
+
</p>
|
|
3649
|
+
<p class="text-sm text-color-secondary mb-4">
|
|
3650
|
+
{{ 'shared.file.selector.add.provider' | translate }}
|
|
3651
|
+
<code class="inline-block bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 px-2 py-1 rounded font-mono text-xs">provideStorageProviders()</code>
|
|
3652
|
+
</p>
|
|
3653
|
+
<button
|
|
3654
|
+
pButton
|
|
3655
|
+
[label]="'shared.close' | translate"
|
|
3656
|
+
class="p-button-text"
|
|
3657
|
+
(click)="onCancel()"
|
|
3658
|
+
></button>
|
|
3659
|
+
</div>
|
|
3660
|
+
} @else {
|
|
3661
|
+
<!-- Upload Section (when withUploader is enabled) -->
|
|
3662
|
+
@if (withUploader()) {
|
|
3663
|
+
<div class="mb-4">
|
|
3664
|
+
<lib-file-uploader
|
|
3665
|
+
[uploadFile]="uploadFileFn"
|
|
3666
|
+
[uploadMultipleFiles]="uploadMultipleFilesFn"
|
|
3667
|
+
[uploadOptions]="{ storageConfigId: effectiveStorageConfigId() }"
|
|
3668
|
+
[acceptTypes]="acceptTypes()"
|
|
3669
|
+
[multiple]="multiple()"
|
|
3670
|
+
[maxFiles]="maxSelection()"
|
|
3671
|
+
[maxSizeMb]="10"
|
|
3672
|
+
[showPreview]="false"
|
|
3673
|
+
(fileUploaded)="onFileUploaded($event)"
|
|
3674
|
+
/>
|
|
3675
|
+
</div>
|
|
2776
3676
|
}
|
|
2777
|
-
</div>
|
|
2778
3677
|
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
[
|
|
2800
|
-
|
|
3678
|
+
<!-- Filters Row - All on same line on desktop -->
|
|
3679
|
+
<div
|
|
3680
|
+
class="flex flex-col sm:flex-row gap-2 mb-3 items-stretch sm:items-center"
|
|
3681
|
+
>
|
|
3682
|
+
<!-- Search -->
|
|
3683
|
+
<p-iconfield class="flex-1">
|
|
3684
|
+
<p-inputicon class="pi pi-search" />
|
|
3685
|
+
<input
|
|
3686
|
+
pInputText
|
|
3687
|
+
type="text"
|
|
3688
|
+
[ngModel]="searchTerm()"
|
|
3689
|
+
(ngModelChange)="onSearchChange($event)"
|
|
3690
|
+
[placeholder]="'shared.file.selector.search.placeholder' | translate"
|
|
3691
|
+
class="w-full"
|
|
3692
|
+
/>
|
|
3693
|
+
</p-iconfield>
|
|
3694
|
+
|
|
3695
|
+
<!-- Folder Selector -->
|
|
3696
|
+
@if (fileProvider.loadFolders) {
|
|
3697
|
+
<p-select
|
|
3698
|
+
[options]="folders()"
|
|
3699
|
+
[ngModel]="selectedFolderId()"
|
|
3700
|
+
(ngModelChange)="onFolderChange($event)"
|
|
3701
|
+
optionLabel="name"
|
|
3702
|
+
optionValue="id"
|
|
3703
|
+
[placeholder]="'shared.file.selector.all.folders' | translate"
|
|
3704
|
+
[showClear]="true"
|
|
3705
|
+
[filter]="true"
|
|
3706
|
+
filterBy="name"
|
|
3707
|
+
(onFilter)="onFolderSearch($event)"
|
|
3708
|
+
[loading]="foldersLoading()"
|
|
3709
|
+
class="w-full sm:w-40"
|
|
3710
|
+
(onShow)="loadFolders()"
|
|
2801
3711
|
>
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
<
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
<
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
3712
|
+
<ng-template pTemplate="selectedItem" let-folder>
|
|
3713
|
+
<div class="flex items-center gap-2">
|
|
3714
|
+
<i class="pi pi-folder text-color-secondary"></i>
|
|
3715
|
+
<span>{{ folder?.name }}</span>
|
|
3716
|
+
</div>
|
|
3717
|
+
</ng-template>
|
|
3718
|
+
<ng-template pTemplate="item" let-folder>
|
|
3719
|
+
<div class="flex items-center gap-2">
|
|
3720
|
+
<i class="pi pi-folder text-color-secondary"></i>
|
|
3721
|
+
<span>{{ folder.name }}</span>
|
|
3722
|
+
</div>
|
|
3723
|
+
</ng-template>
|
|
3724
|
+
</p-select>
|
|
3725
|
+
}
|
|
2815
3726
|
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
3727
|
+
<!-- Storage Config Selector -->
|
|
3728
|
+
@if (fileProvider.loadStorageConfigs) {
|
|
3729
|
+
<p-select
|
|
3730
|
+
[options]="storageConfigs()"
|
|
3731
|
+
[ngModel]="selectedStorageConfigId()"
|
|
3732
|
+
(ngModelChange)="onStorageConfigChange($event)"
|
|
3733
|
+
optionLabel="name"
|
|
3734
|
+
optionValue="id"
|
|
3735
|
+
[placeholder]="'shared.file.selector.all.storage' | translate"
|
|
3736
|
+
[showClear]="true"
|
|
3737
|
+
[filter]="true"
|
|
3738
|
+
filterBy="name"
|
|
3739
|
+
(onFilter)="onStorageConfigSearch($event)"
|
|
3740
|
+
[loading]="storageConfigsLoading()"
|
|
3741
|
+
class="w-full sm:w-40"
|
|
3742
|
+
(onShow)="loadStorageConfigs()"
|
|
3743
|
+
>
|
|
3744
|
+
<ng-template pTemplate="selectedItem" let-config>
|
|
3745
|
+
<div class="flex items-center gap-2">
|
|
3746
|
+
<i class="pi pi-database text-color-secondary"></i>
|
|
3747
|
+
<span>{{ config?.name }}</span>
|
|
3748
|
+
@if (config?.isDefault) {
|
|
3749
|
+
<span
|
|
3750
|
+
class="text-xs bg-primary text-primary-contrast px-1 rounded"
|
|
3751
|
+
>{{ 'shared.default' | translate }}</span
|
|
3752
|
+
>
|
|
3753
|
+
}
|
|
3754
|
+
</div>
|
|
3755
|
+
</ng-template>
|
|
3756
|
+
<ng-template pTemplate="item" let-config>
|
|
3757
|
+
<div class="flex items-center gap-2">
|
|
3758
|
+
<i class="pi pi-database text-color-secondary"></i>
|
|
3759
|
+
<span>{{ config.name }}</span>
|
|
3760
|
+
@if (config.isDefault) {
|
|
3761
|
+
<span
|
|
3762
|
+
class="text-xs bg-primary text-primary-contrast px-1 rounded"
|
|
3763
|
+
>{{ 'shared.default' | translate }}</span
|
|
3764
|
+
>
|
|
3765
|
+
}
|
|
3766
|
+
</div>
|
|
3767
|
+
</ng-template>
|
|
3768
|
+
</p-select>
|
|
2824
3769
|
}
|
|
2825
3770
|
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
3771
|
+
<!-- Selected count -->
|
|
3772
|
+
@if (multiple()) {
|
|
3773
|
+
<span
|
|
3774
|
+
class="text-sm text-color-secondary self-center whitespace-nowrap"
|
|
3775
|
+
>
|
|
3776
|
+
{{ 'shared.file.selector.selected' | translate: { count: selectedFiles().length } }}
|
|
3777
|
+
</span>
|
|
2830
3778
|
}
|
|
2831
|
-
|
|
2832
|
-
</div>
|
|
3779
|
+
</div>
|
|
2833
3780
|
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
(
|
|
2849
|
-
|
|
3781
|
+
<!-- File Grid - Responsive columns -->
|
|
3782
|
+
<div class="file-grid" #scrollContainer (scroll)="onScroll($event)">
|
|
3783
|
+
@if (isLoading() && files().length === 0) {
|
|
3784
|
+
<div class="col-span-full flex justify-center p-4">
|
|
3785
|
+
<i
|
|
3786
|
+
class="pi pi-spin pi-spinner text-4xl text-color-secondary"
|
|
3787
|
+
></i>
|
|
3788
|
+
</div>
|
|
3789
|
+
} @else if (files().length === 0) {
|
|
3790
|
+
<div class="col-span-full text-center p-4 text-color-secondary">
|
|
3791
|
+
<i class="pi pi-inbox text-4xl mb-2 block"></i>
|
|
3792
|
+
<p>{{ 'shared.file.selector.no.files' | translate }}</p>
|
|
3793
|
+
</div>
|
|
3794
|
+
} @else {
|
|
3795
|
+
@for (file of files(); track file.id) {
|
|
3796
|
+
<div
|
|
3797
|
+
class="file-card"
|
|
3798
|
+
[class.selected]="isSelected(file)"
|
|
3799
|
+
[class.disabled]="!isFileAllowed(file)"
|
|
3800
|
+
(click)="toggleSelection(file)"
|
|
3801
|
+
>
|
|
3802
|
+
<!-- File Preview -->
|
|
3803
|
+
<div class="file-preview">
|
|
3804
|
+
@if (isImage(file) && file.url) {
|
|
3805
|
+
<img
|
|
3806
|
+
[src]="file.url"
|
|
3807
|
+
[alt]="file.name"
|
|
3808
|
+
class="w-full h-full object-cover"
|
|
3809
|
+
/>
|
|
3810
|
+
} @else {
|
|
3811
|
+
<i
|
|
3812
|
+
[class]="getFileIcon(file)"
|
|
3813
|
+
class="text-4xl sm:text-5xl text-color-secondary"
|
|
3814
|
+
></i>
|
|
3815
|
+
}
|
|
3816
|
+
@if (isSelected(file)) {
|
|
3817
|
+
<div class="selected-overlay">
|
|
3818
|
+
<i class="pi pi-check text-xl sm:text-2xl"></i>
|
|
3819
|
+
</div>
|
|
3820
|
+
}
|
|
3821
|
+
</div>
|
|
3822
|
+
|
|
3823
|
+
<!-- File Info -->
|
|
3824
|
+
<div class="p-2 text-center bg-surface-0 dark:bg-surface-900">
|
|
3825
|
+
<span
|
|
3826
|
+
class="block text-xs sm:text-sm whitespace-nowrap overflow-hidden text-ellipsis"
|
|
3827
|
+
[title]="file.name"
|
|
3828
|
+
>
|
|
3829
|
+
{{ file.name }}
|
|
3830
|
+
</span>
|
|
3831
|
+
<span class="block text-xs text-color-secondary">{{
|
|
3832
|
+
formatSize(file.size)
|
|
3833
|
+
}}</span>
|
|
3834
|
+
</div>
|
|
3835
|
+
</div>
|
|
3836
|
+
}
|
|
3837
|
+
|
|
3838
|
+
@if (isLoading()) {
|
|
3839
|
+
<div class="col-span-full flex justify-center p-2">
|
|
3840
|
+
<i class="pi pi-spin pi-spinner text-color-secondary"></i>
|
|
3841
|
+
</div>
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
2850
3844
|
</div>
|
|
2851
|
-
|
|
3845
|
+
|
|
3846
|
+
<!-- Footer -->
|
|
3847
|
+
<ng-template pTemplate="footer">
|
|
3848
|
+
<div
|
|
3849
|
+
class="flex flex-col-reverse sm:flex-row gap-2 w-full sm:w-auto sm:justify-end"
|
|
3850
|
+
>
|
|
3851
|
+
<button
|
|
3852
|
+
pButton
|
|
3853
|
+
type="button"
|
|
3854
|
+
[label]="'shared.cancel' | translate"
|
|
3855
|
+
class="p-button-text w-full sm:w-auto"
|
|
3856
|
+
(click)="onCancel()"
|
|
3857
|
+
></button>
|
|
3858
|
+
<button
|
|
3859
|
+
pButton
|
|
3860
|
+
type="button"
|
|
3861
|
+
[label]="
|
|
3862
|
+
multiple()
|
|
3863
|
+
? ('shared.file.selector.select.multiple' | translate: { count: selectedFiles().length })
|
|
3864
|
+
: ('shared.file.selector.select' | translate)
|
|
3865
|
+
"
|
|
3866
|
+
[disabled]="selectedFiles().length === 0"
|
|
3867
|
+
class="w-full sm:w-auto"
|
|
3868
|
+
(click)="onConfirm()"
|
|
3869
|
+
></button>
|
|
3870
|
+
</div>
|
|
3871
|
+
</ng-template>
|
|
3872
|
+
}
|
|
2852
3873
|
</p-dialog>
|
|
2853
|
-
`,
|
|
2854
|
-
}], ctorParameters: () => [], propDecorators: {
|
|
3874
|
+
`, styles: [".file-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:.75rem;max-height:300px;overflow-y:auto;padding:.5rem}@media(min-width:480px){.file-grid{grid-template-columns:repeat(3,1fr);max-height:350px}}@media(min-width:640px){.file-grid{grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:1rem;max-height:400px}}.file-card{border:2px solid var(--p-surface-300);border-radius:var(--p-border-radius);cursor:pointer;transition:all .2s ease;overflow:hidden;background:var(--p-surface-0)}:host-context(.p-dark) .file-card,.dark .file-card{border-color:var(--p-surface-600);background:var(--p-surface-800)}.file-card:hover:not(.disabled){border-color:var(--p-primary-color);transform:translateY(-2px);box-shadow:var(--p-overlay-shadow)}.file-card.selected{border-color:var(--p-primary-color);background:var(--p-primary-50)}:host-context(.p-dark) .file-card.selected,.dark .file-card.selected{background:var(--p-primary-900)}.file-card.disabled{opacity:.5;cursor:not-allowed}.file-preview{position:relative;height:80px;display:flex;align-items:center;justify-content:center;background:var(--p-surface-100)}@media(min-width:640px){.file-preview{height:100px}}:host-context(.p-dark) .file-preview,.dark .file-preview{background:var(--p-surface-700)}.selected-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(var(--p-primary-500-rgb, 59, 130, 246),.3)}.selected-overlay i{color:var(--p-primary-color);background:var(--p-surface-0);border-radius:50%;padding:.5rem}:host-context(.p-dark) .selected-overlay i,.dark .selected-overlay i{background:var(--p-surface-900)}\n"] }]
|
|
3875
|
+
}], ctorParameters: () => [], propDecorators: { header: [{ type: i0.Input, args: [{ isSignal: true, alias: "header", required: false }] }], acceptTypes: [{ type: i0.Input, args: [{ isSignal: true, alias: "acceptTypes", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], maxSelection: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSelection", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], folderId: [{ type: i0.Input, args: [{ isSignal: true, alias: "folderId", required: false }] }], withUploader: [{ type: i0.Input, args: [{ isSignal: true, alias: "withUploader", required: false }] }], visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }, { type: i0.Output, args: ["visibleChange"] }], fileSelected: [{ type: i0.Output, args: ["fileSelected"] }], filesSelected: [{ type: i0.Output, args: ["filesSelected"] }], closed: [{ type: i0.Output, args: ["closed"] }], onError: [{ type: i0.Output, args: ["onError"] }] } });
|
|
2855
3876
|
|
|
2856
3877
|
function createGuard(guardName, redirectTo, evaluate, getDenialMessage) {
|
|
2857
3878
|
return () => {
|
|
@@ -2947,8 +3968,7 @@ function allPermissionsGuard(permissions, redirectTo = '/') {
|
|
|
2947
3968
|
* @Component({
|
|
2948
3969
|
* selector: 'app-product-form',
|
|
2949
3970
|
* standalone: true,
|
|
2950
|
-
*
|
|
2951
|
-
* template: `...`
|
|
3971
|
+
* * template: `...`
|
|
2952
3972
|
* })
|
|
2953
3973
|
* export class ProductFormComponent extends BaseFormPage<IProduct, IProductFormModel> {
|
|
2954
3974
|
* private readonly productService = inject(ProductApiService);
|
|
@@ -3015,6 +4035,7 @@ class BaseFormPage {
|
|
|
3015
4035
|
route = inject(ActivatedRoute);
|
|
3016
4036
|
messageService = inject(MessageService);
|
|
3017
4037
|
destroyRef = inject(DestroyRef);
|
|
4038
|
+
translateAdapter = inject(TRANSLATE_ADAPTER, { optional: true });
|
|
3018
4039
|
routeParams = toSignal(this.route.paramMap);
|
|
3019
4040
|
initialized = false;
|
|
3020
4041
|
/** Loading state for async operations */
|
|
@@ -3052,8 +4073,8 @@ class BaseFormPage {
|
|
|
3052
4073
|
: this.createItem(model);
|
|
3053
4074
|
operation$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
|
|
3054
4075
|
next: () => {
|
|
3055
|
-
const
|
|
3056
|
-
this.showSuccess(
|
|
4076
|
+
const messageKey = this.isEditMode() ? 'shared.update.success' : 'shared.create.success';
|
|
4077
|
+
this.showSuccess(this.t(messageKey));
|
|
3057
4078
|
this.router.navigate([this.getResourceRoute()]);
|
|
3058
4079
|
},
|
|
3059
4080
|
error: () => {
|
|
@@ -3078,8 +4099,8 @@ class BaseFormPage {
|
|
|
3078
4099
|
showValidationError() {
|
|
3079
4100
|
this.messageService.add({
|
|
3080
4101
|
severity: 'error',
|
|
3081
|
-
summary: '
|
|
3082
|
-
detail: '
|
|
4102
|
+
summary: this.t('shared.validation.error'),
|
|
4103
|
+
detail: this.t('shared.fill.all.fields'),
|
|
3083
4104
|
});
|
|
3084
4105
|
}
|
|
3085
4106
|
/**
|
|
@@ -3089,7 +4110,7 @@ class BaseFormPage {
|
|
|
3089
4110
|
showSuccess(detail) {
|
|
3090
4111
|
this.messageService.add({
|
|
3091
4112
|
severity: 'success',
|
|
3092
|
-
summary: '
|
|
4113
|
+
summary: this.t('shared.success'),
|
|
3093
4114
|
detail,
|
|
3094
4115
|
});
|
|
3095
4116
|
}
|
|
@@ -3100,10 +4121,16 @@ class BaseFormPage {
|
|
|
3100
4121
|
showError(detail) {
|
|
3101
4122
|
this.messageService.add({
|
|
3102
4123
|
severity: 'error',
|
|
3103
|
-
summary: '
|
|
4124
|
+
summary: this.t('shared.error'),
|
|
3104
4125
|
detail,
|
|
3105
4126
|
});
|
|
3106
4127
|
}
|
|
4128
|
+
/**
|
|
4129
|
+
* Translate a key using the adapter, fallback to key if not available.
|
|
4130
|
+
*/
|
|
4131
|
+
t(key, variables) {
|
|
4132
|
+
return this.translateAdapter?.translate(key, variables) ?? key;
|
|
4133
|
+
}
|
|
3107
4134
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: BaseFormPage, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
3108
4135
|
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.5", type: BaseFormPage, isStandalone: true, ngImport: i0 });
|
|
3109
4136
|
}
|
|
@@ -3140,6 +4167,7 @@ class BaseListPage {
|
|
|
3140
4167
|
appConfig = inject(APP_CONFIG);
|
|
3141
4168
|
confirmationService = inject(ConfirmationService);
|
|
3142
4169
|
destroyRef = inject(DestroyRef);
|
|
4170
|
+
translateAdapter = inject(TRANSLATE_ADAPTER, { optional: true });
|
|
3143
4171
|
/** Items list */
|
|
3144
4172
|
items = signal([], ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
3145
4173
|
/** Loading state */
|
|
@@ -3188,26 +4216,32 @@ class BaseListPage {
|
|
|
3188
4216
|
/**
|
|
3189
4217
|
* Show success toast message
|
|
3190
4218
|
*/
|
|
3191
|
-
showSuccess(detail, summary
|
|
3192
|
-
this.messageService.add({ severity: 'success', summary, detail });
|
|
4219
|
+
showSuccess(detail, summary) {
|
|
4220
|
+
this.messageService.add({ severity: 'success', summary: summary ?? this.t('shared.success'), detail });
|
|
3193
4221
|
}
|
|
3194
4222
|
/**
|
|
3195
4223
|
* Show error toast message
|
|
3196
4224
|
*/
|
|
3197
|
-
showError(detail, summary
|
|
3198
|
-
this.messageService.add({ severity: 'error', summary, detail });
|
|
4225
|
+
showError(detail, summary) {
|
|
4226
|
+
this.messageService.add({ severity: 'error', summary: summary ?? this.t('shared.error'), detail });
|
|
3199
4227
|
}
|
|
3200
4228
|
/**
|
|
3201
4229
|
* Show info toast message
|
|
3202
4230
|
*/
|
|
3203
|
-
showInfo(detail, summary
|
|
3204
|
-
this.messageService.add({ severity: 'info', summary, detail });
|
|
4231
|
+
showInfo(detail, summary) {
|
|
4232
|
+
this.messageService.add({ severity: 'info', summary: summary ?? this.t('shared.info'), detail });
|
|
3205
4233
|
}
|
|
3206
4234
|
/**
|
|
3207
4235
|
* Show warning toast message
|
|
3208
4236
|
*/
|
|
3209
|
-
showWarn(detail, summary
|
|
3210
|
-
this.messageService.add({ severity: 'warn', summary, detail });
|
|
4237
|
+
showWarn(detail, summary) {
|
|
4238
|
+
this.messageService.add({ severity: 'warn', summary: summary ?? this.t('shared.warning'), detail });
|
|
4239
|
+
}
|
|
4240
|
+
/**
|
|
4241
|
+
* Translate a key using the adapter, fallback to key if not available.
|
|
4242
|
+
*/
|
|
4243
|
+
t(key, variables) {
|
|
4244
|
+
return this.translateAdapter?.translate(key, variables) ?? key;
|
|
3211
4245
|
}
|
|
3212
4246
|
/**
|
|
3213
4247
|
* Delete an item with confirmation dialog
|
|
@@ -3219,7 +4253,7 @@ class BaseListPage {
|
|
|
3219
4253
|
onDelete(item, idGetter, deleteApiCall, options) {
|
|
3220
4254
|
this.confirmationService.confirm({
|
|
3221
4255
|
message: this.getDeleteConfirmMessage(item),
|
|
3222
|
-
header: options?.header ?? '
|
|
4256
|
+
header: options?.header ?? this.t('shared.confirm.delete.header'),
|
|
3223
4257
|
icon: 'pi pi-exclamation-triangle',
|
|
3224
4258
|
acceptButtonStyleClass: 'p-button-danger',
|
|
3225
4259
|
accept: () => {
|
|
@@ -3227,11 +4261,11 @@ class BaseListPage {
|
|
|
3227
4261
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
3228
4262
|
.subscribe({
|
|
3229
4263
|
next: () => {
|
|
3230
|
-
this.showSuccess(options?.successMessage ?? '
|
|
4264
|
+
this.showSuccess(options?.successMessage ?? this.t('shared.delete.success'));
|
|
3231
4265
|
this.loadData();
|
|
3232
4266
|
},
|
|
3233
4267
|
error: () => {
|
|
3234
|
-
this.showError(options?.errorMessage ?? '
|
|
4268
|
+
this.showError(options?.errorMessage ?? this.t('shared.delete.failed'));
|
|
3235
4269
|
},
|
|
3236
4270
|
});
|
|
3237
4271
|
},
|
|
@@ -3247,17 +4281,17 @@ class BaseListPage {
|
|
|
3247
4281
|
async onDeleteAsync(item, idGetter, deleteApiCall, options) {
|
|
3248
4282
|
this.confirmationService.confirm({
|
|
3249
4283
|
message: this.getDeleteConfirmMessage(item),
|
|
3250
|
-
header: options?.header ?? '
|
|
4284
|
+
header: options?.header ?? this.t('shared.confirm.delete.header'),
|
|
3251
4285
|
icon: 'pi pi-exclamation-triangle',
|
|
3252
4286
|
acceptButtonStyleClass: 'p-button-danger',
|
|
3253
4287
|
accept: async () => {
|
|
3254
4288
|
try {
|
|
3255
4289
|
await deleteApiCall(idGetter(item));
|
|
3256
|
-
this.showSuccess(options?.successMessage ?? '
|
|
4290
|
+
this.showSuccess(options?.successMessage ?? this.t('shared.delete.success'));
|
|
3257
4291
|
await this.loadData();
|
|
3258
4292
|
}
|
|
3259
4293
|
catch {
|
|
3260
|
-
this.showError(options?.errorMessage ?? '
|
|
4294
|
+
this.showError(options?.errorMessage ?? this.t('shared.delete.failed'));
|
|
3261
4295
|
}
|
|
3262
4296
|
},
|
|
3263
4297
|
});
|
|
@@ -3281,5 +4315,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
3281
4315
|
* Generated bundle index. Do not edit.
|
|
3282
4316
|
*/
|
|
3283
4317
|
|
|
3284
|
-
export { ACTION_PERMISSIONS, AUTH_STATE_PROVIDER, AngularModule, ApiResourceService, ApiResourceService as ApiService, BRANCH_PERMISSIONS, BaseFormControl, BaseFormPage, BaseListPage, BaseUserSelectComponent, COMPANY_ACTION_PERMISSIONS, COMPANY_API_PROVIDER, COMPANY_PERMISSIONS, ContactTypeEnum, CookieService, EMAIL_CONFIG_PERMISSIONS, EMAIL_TEMPLATE_PERMISSIONS, EditModeElementChangerDirective, FILE_PERMISSIONS, FILE_TYPE_FILTERS, FOLDER_PERMISSIONS, FORM_PERMISSIONS, FileSelectorDialogComponent, FileUploaderComponent, FileUrlService, HasPermissionDirective, IconComponent, IconTypeEnum, IsEmptyImageDirective, LazyMultiSelectComponent, LazySelectComponent, PERMISSIONS, PROFILE_PERMISSION_PROVIDER,
|
|
4318
|
+
export { ACTION_PERMISSIONS, AUTH_STATE_PROVIDER, AngularModule, ApiResourceService, ApiResourceService as ApiService, BRANCH_PERMISSIONS, BaseFormControl, BaseFormPage, BaseListPage, BaseUserSelectComponent, COMPANY_ACTION_PERMISSIONS, COMPANY_API_PROVIDER, COMPANY_PERMISSIONS, ContactTypeEnum, CookieService, EMAIL_CONFIG_PERMISSIONS, EMAIL_TEMPLATE_PERMISSIONS, EVENT_PARTICIPANT_PERMISSIONS, EVENT_PERMISSIONS, EditModeElementChangerDirective, FILE_PERMISSIONS, FILE_PROVIDER, FILE_TYPE_FILTERS, FOLDER_PERMISSIONS, FORM_PERMISSIONS, FileSelectorDialogComponent, FileUploaderComponent, FileUrlService, HasPermissionDirective, IconComponent, IconTypeEnum, IsEmptyImageDirective, LANGUAGE_PERMISSIONS, LazyMultiSelectComponent, LazySelectComponent, NOTIFICATION_PERMISSIONS, PERMISSIONS, PROFILE_PERMISSION_PROVIDER, PermissionValidatorService, PlatformService, PreventDefaultDirective, PrimeModule, ROLE_ACTION_PERMISSIONS, ROLE_PERMISSIONS, SHARED_MESSAGES, STORAGE_CONFIG_PERMISSIONS, TRANSLATION_KEY_PERMISSIONS, TRANSLATION_PERMISSIONS, TranslatePipe, USER_ACTION_PERMISSIONS, USER_LIST_PROVIDER, USER_PERMISSIONS, USER_PERMISSION_PROVIDER, USER_PROVIDER, USER_ROLE_PERMISSIONS, UserMultiSelectComponent, UserSelectComponent, allPermissionsGuard, anyPermissionGuard, checkScrollPagination, evaluateLogicNode, evaluatePermission, formatFileSize, getAcceptString, getFileIconClass, hasAllPermissions, hasAnyPermission, hasPermission, isFileTypeAllowed, permissionGuard, provideFallbackLocalization, provideValueAccessor, resolveTranslationModule };
|
|
3285
4319
|
//# sourceMappingURL=flusys-ng-shared.mjs.map
|