@flusys/ng-shared 0.1.0-beta.3 → 1.1.0-beta
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 +308 -2
- package/fesm2022/flusys-ng-shared.mjs +1565 -200
- package/fesm2022/flusys-ng-shared.mjs.map +1 -1
- package/package.json +2 -2
- package/types/flusys-ng-shared.d.ts +752 -50
|
@@ -1,20 +1,24 @@
|
|
|
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, HostListener, NgModule,
|
|
2
|
+
import { inject, PLATFORM_ID, Injectable, DOCUMENT, REQUEST, signal, computed, ElementRef, input, effect, Directive, TemplateRef, ViewContainerRef, output, HostListener, NgModule, Injector, runInInjectionContext, resource, Component, model, untracked, forwardRef, viewChild, ChangeDetectionStrategy, InjectionToken, DestroyRef, afterNextRender, isDevMode } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common';
|
|
4
4
|
import { isPlatformServer, CommonModule, NgOptimizedImage, NgComponentOutlet, DatePipe } from '@angular/common';
|
|
5
5
|
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
|
|
6
6
|
import { APP_CONFIG, getServiceUrl, ApiLoaderService } from '@flusys/ng-core';
|
|
7
|
-
import { of, firstValueFrom, skip, debounceTime, distinctUntilChanged, tap as tap$1 } from 'rxjs';
|
|
8
|
-
import { tap, catchError
|
|
9
|
-
import * as
|
|
10
|
-
import { NgControl,
|
|
7
|
+
import { of, firstValueFrom, skip, debounceTime, distinctUntilChanged, tap as tap$1, map as map$1 } from 'rxjs';
|
|
8
|
+
import { map, tap, catchError } from 'rxjs/operators';
|
|
9
|
+
import * as i1$2 from '@angular/forms';
|
|
10
|
+
import { NgControl, FormsModule, ReactiveFormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
11
11
|
import { RouterOutlet, RouterLink, Router } from '@angular/router';
|
|
12
12
|
import { AutoCompleteModule } from 'primeng/autocomplete';
|
|
13
|
+
import { AvatarModule } from 'primeng/avatar';
|
|
14
|
+
import * as i1$3 from 'primeng/button';
|
|
13
15
|
import { ButtonModule } from 'primeng/button';
|
|
14
16
|
import { CardModule } from 'primeng/card';
|
|
15
|
-
import * as
|
|
17
|
+
import * as i1$1 from 'primeng/checkbox';
|
|
16
18
|
import { CheckboxModule } from 'primeng/checkbox';
|
|
19
|
+
import { ConfirmDialogModule } from 'primeng/confirmdialog';
|
|
17
20
|
import { DatePickerModule } from 'primeng/datepicker';
|
|
21
|
+
import * as i5 from 'primeng/dialog';
|
|
18
22
|
import { DialogModule } from 'primeng/dialog';
|
|
19
23
|
import { DividerModule } from 'primeng/divider';
|
|
20
24
|
import { FileUploadModule } from 'primeng/fileupload';
|
|
@@ -22,7 +26,7 @@ import { IconFieldModule } from 'primeng/iconfield';
|
|
|
22
26
|
import { ImageModule } from 'primeng/image';
|
|
23
27
|
import { InputIconModule } from 'primeng/inputicon';
|
|
24
28
|
import { InputNumberModule } from 'primeng/inputnumber';
|
|
25
|
-
import * as
|
|
29
|
+
import * as i2 from 'primeng/inputtext';
|
|
26
30
|
import { InputTextModule } from 'primeng/inputtext';
|
|
27
31
|
import { ListboxModule } from 'primeng/listbox';
|
|
28
32
|
import { MultiSelectModule } from 'primeng/multiselect';
|
|
@@ -30,26 +34,100 @@ import { PaginatorModule } from 'primeng/paginator';
|
|
|
30
34
|
import { PanelModule } from 'primeng/panel';
|
|
31
35
|
import { PasswordModule } from 'primeng/password';
|
|
32
36
|
import { PopoverModule } from 'primeng/popover';
|
|
37
|
+
import * as i2$1 from 'primeng/progressbar';
|
|
33
38
|
import { ProgressBarModule } from 'primeng/progressbar';
|
|
34
39
|
import { RadioButtonModule } from 'primeng/radiobutton';
|
|
35
40
|
import { RippleModule } from 'primeng/ripple';
|
|
36
41
|
import * as i3 from 'primeng/select';
|
|
37
42
|
import { SelectModule } from 'primeng/select';
|
|
38
43
|
import { SelectButtonModule } from 'primeng/selectbutton';
|
|
44
|
+
import { SkeletonModule } from 'primeng/skeleton';
|
|
39
45
|
import { SplitButtonModule } from 'primeng/splitbutton';
|
|
40
46
|
import { StepsModule } from 'primeng/steps';
|
|
41
47
|
import { TableModule } from 'primeng/table';
|
|
42
48
|
import { TabsModule } from 'primeng/tabs';
|
|
43
49
|
import { TagModule } from 'primeng/tag';
|
|
44
50
|
import { TextareaModule } from 'primeng/textarea';
|
|
51
|
+
import { ToastModule } from 'primeng/toast';
|
|
45
52
|
import { ToggleSwitchModule } from 'primeng/toggleswitch';
|
|
46
53
|
import { TooltipModule } from 'primeng/tooltip';
|
|
47
54
|
import { TreeTableModule } from 'primeng/treetable';
|
|
48
55
|
import { toSignal, toObservable } from '@angular/core/rxjs-interop';
|
|
56
|
+
import * as i3$1 from 'primeng/api';
|
|
57
|
+
import { MessageService } from 'primeng/api';
|
|
49
58
|
|
|
50
59
|
;
|
|
51
60
|
;
|
|
52
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Common file type filters
|
|
64
|
+
*/
|
|
65
|
+
const FILE_TYPE_FILTERS = {
|
|
66
|
+
IMAGES: ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml'],
|
|
67
|
+
DOCUMENTS: [
|
|
68
|
+
'application/pdf',
|
|
69
|
+
'application/msword',
|
|
70
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
71
|
+
'application/vnd.ms-excel',
|
|
72
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
73
|
+
],
|
|
74
|
+
VIDEOS: ['video/mp4', 'video/webm', 'video/ogg', 'video/quicktime'],
|
|
75
|
+
AUDIO: ['audio/mpeg', 'audio/wav', 'audio/ogg', 'audio/webm'],
|
|
76
|
+
ALL: [],
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Get accept string for file input from content types
|
|
80
|
+
*/
|
|
81
|
+
function getAcceptString(contentTypes) {
|
|
82
|
+
if (!contentTypes.length)
|
|
83
|
+
return '*/*';
|
|
84
|
+
return contentTypes.join(',');
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Check if file matches allowed content types
|
|
88
|
+
*/
|
|
89
|
+
function isFileTypeAllowed(file, allowedTypes) {
|
|
90
|
+
if (!allowedTypes.length)
|
|
91
|
+
return true;
|
|
92
|
+
return allowedTypes.some((type) => {
|
|
93
|
+
if (type.endsWith('/*')) {
|
|
94
|
+
return file.type.startsWith(type.replace('/*', '/'));
|
|
95
|
+
}
|
|
96
|
+
return file.type === type;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Get file icon class based on content type
|
|
101
|
+
*/
|
|
102
|
+
function getFileIconClass(contentType) {
|
|
103
|
+
if (contentType.startsWith('image/'))
|
|
104
|
+
return 'pi pi-image';
|
|
105
|
+
if (contentType.startsWith('video/'))
|
|
106
|
+
return 'pi pi-video';
|
|
107
|
+
if (contentType.startsWith('audio/'))
|
|
108
|
+
return 'pi pi-volume-up';
|
|
109
|
+
if (contentType.includes('pdf'))
|
|
110
|
+
return 'pi pi-file-pdf';
|
|
111
|
+
if (contentType.includes('word') || contentType.includes('document'))
|
|
112
|
+
return 'pi pi-file-word';
|
|
113
|
+
if (contentType.includes('excel') || contentType.includes('spreadsheet'))
|
|
114
|
+
return 'pi pi-file-excel';
|
|
115
|
+
return 'pi pi-file';
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Format file size for display
|
|
119
|
+
*/
|
|
120
|
+
function formatFileSize(sizeInKb) {
|
|
121
|
+
const kb = typeof sizeInKb === 'string' ? parseFloat(sizeInKb) : sizeInKb;
|
|
122
|
+
if (kb < 1024)
|
|
123
|
+
return `${kb.toFixed(1)} KB`;
|
|
124
|
+
const mb = kb / 1024;
|
|
125
|
+
if (mb < 1024)
|
|
126
|
+
return `${mb.toFixed(1)} MB`;
|
|
127
|
+
const gb = mb / 1024;
|
|
128
|
+
return `${gb.toFixed(2)} GB`;
|
|
129
|
+
}
|
|
130
|
+
|
|
53
131
|
var ContactTypeEnum;
|
|
54
132
|
(function (ContactTypeEnum) {
|
|
55
133
|
ContactTypeEnum[ContactTypeEnum["PHONE"] = 1] = "PHONE";
|
|
@@ -68,10 +146,10 @@ class PlatformService {
|
|
|
68
146
|
get isServer() {
|
|
69
147
|
return isPlatformServer(this.platformId);
|
|
70
148
|
}
|
|
71
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
72
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.
|
|
149
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PlatformService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
150
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PlatformService, providedIn: 'root' });
|
|
73
151
|
}
|
|
74
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
152
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PlatformService, decorators: [{
|
|
75
153
|
type: Injectable,
|
|
76
154
|
args: [{
|
|
77
155
|
providedIn: 'root'
|
|
@@ -85,10 +163,10 @@ class CookieService {
|
|
|
85
163
|
get() {
|
|
86
164
|
return !this.platformService.isServer ? this.doc.cookie : this.request?.headers.get('cookie') ?? "";
|
|
87
165
|
}
|
|
88
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
89
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.
|
|
166
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CookieService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
167
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CookieService, providedIn: 'root' });
|
|
90
168
|
}
|
|
91
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
169
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CookieService, decorators: [{
|
|
92
170
|
type: Injectable,
|
|
93
171
|
args: [{
|
|
94
172
|
providedIn: 'root',
|
|
@@ -124,28 +202,54 @@ class FileUrlService {
|
|
|
124
202
|
}
|
|
125
203
|
/**
|
|
126
204
|
* Fetch file URLs from backend and update cache.
|
|
127
|
-
*
|
|
205
|
+
* Skips IDs already in cache to prevent duplicate calls.
|
|
206
|
+
* Returns Observable of fetched files (including cached ones).
|
|
128
207
|
*/
|
|
129
|
-
fetchFileUrls(fileIds) {
|
|
208
|
+
fetchFileUrls(fileIds, forceRefresh = false) {
|
|
130
209
|
if (!fileIds.length)
|
|
131
210
|
return of([]);
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
211
|
+
const cache = this.urlCache();
|
|
212
|
+
// Filter out IDs already in cache (unless force refresh)
|
|
213
|
+
const missingIds = forceRefresh
|
|
214
|
+
? fileIds
|
|
215
|
+
: fileIds.filter((id) => !cache.has(id));
|
|
216
|
+
// If all files are cached, return from cache
|
|
217
|
+
if (missingIds.length === 0) {
|
|
218
|
+
const cachedFiles = fileIds
|
|
219
|
+
.map((id) => cache.get(id))
|
|
220
|
+
.filter((f) => !!f);
|
|
221
|
+
return of(cachedFiles);
|
|
222
|
+
}
|
|
223
|
+
const requestDto = missingIds.map((id) => ({ id }));
|
|
224
|
+
return this.http
|
|
225
|
+
.post(`${getServiceUrl(this.appConfig, 'storage')}/file-manager/get-files`, requestDto)
|
|
226
|
+
.pipe(map((response) => response.data ?? []), tap((files) => {
|
|
227
|
+
// Update cache with new files
|
|
228
|
+
const newCache = new Map(this.urlCache());
|
|
229
|
+
files.forEach((file) => newCache.set(file.id, file));
|
|
230
|
+
this.urlCache.set(newCache);
|
|
231
|
+
}), map(() => {
|
|
232
|
+
// Return all requested files (cached + newly fetched)
|
|
233
|
+
const allCache = this.urlCache();
|
|
234
|
+
return fileIds
|
|
235
|
+
.map((id) => allCache.get(id))
|
|
236
|
+
.filter((f) => !!f);
|
|
237
|
+
}), catchError(() => of([])));
|
|
142
238
|
}
|
|
143
239
|
/**
|
|
144
240
|
* Fetch a single file URL.
|
|
241
|
+
* Uses cache if available to prevent duplicate calls.
|
|
145
242
|
* Returns Observable of file info or null if not found.
|
|
146
243
|
*/
|
|
147
|
-
fetchSingleFileUrl(fileId) {
|
|
148
|
-
|
|
244
|
+
fetchSingleFileUrl(fileId, forceRefresh = false) {
|
|
245
|
+
// Return from cache immediately if available
|
|
246
|
+
if (!forceRefresh) {
|
|
247
|
+
const cached = this.urlCache().get(fileId);
|
|
248
|
+
if (cached) {
|
|
249
|
+
return of(cached);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return this.fetchFileUrls([fileId], forceRefresh).pipe(map((files) => files[0] ?? null));
|
|
149
253
|
}
|
|
150
254
|
/**
|
|
151
255
|
* Clear the URL cache.
|
|
@@ -162,10 +266,10 @@ class FileUrlService {
|
|
|
162
266
|
cache.delete(fileId);
|
|
163
267
|
this.urlCache.set(cache);
|
|
164
268
|
}
|
|
165
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
166
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.
|
|
269
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileUrlService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
270
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileUrlService, providedIn: 'root' });
|
|
167
271
|
}
|
|
168
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
272
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileUrlService, decorators: [{
|
|
169
273
|
type: Injectable,
|
|
170
274
|
args: [{ providedIn: 'root' }]
|
|
171
275
|
}] });
|
|
@@ -247,10 +351,10 @@ class PermissionValidatorService {
|
|
|
247
351
|
isPermissionsLoaded() {
|
|
248
352
|
return this._isLoaded();
|
|
249
353
|
}
|
|
250
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
251
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.
|
|
354
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionValidatorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
355
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionValidatorService, providedIn: 'root' });
|
|
252
356
|
}
|
|
253
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
357
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionValidatorService, decorators: [{
|
|
254
358
|
type: Injectable,
|
|
255
359
|
args: [{
|
|
256
360
|
providedIn: 'root',
|
|
@@ -268,11 +372,6 @@ class EditModeElementChangerDirective {
|
|
|
268
372
|
this.updateControl(editMode);
|
|
269
373
|
});
|
|
270
374
|
}
|
|
271
|
-
ngAfterViewInit() {
|
|
272
|
-
const editMode = this.isEditMode();
|
|
273
|
-
this.updateElement(editMode);
|
|
274
|
-
this.updateControl(editMode);
|
|
275
|
-
}
|
|
276
375
|
updateControl(editMode) {
|
|
277
376
|
if (!editMode)
|
|
278
377
|
this.ngControl?.control?.disable();
|
|
@@ -284,44 +383,41 @@ class EditModeElementChangerDirective {
|
|
|
284
383
|
if (inputElement instanceof HTMLInputElement) {
|
|
285
384
|
if (!editMode) {
|
|
286
385
|
inputElement.setAttribute('readonly', 'true');
|
|
287
|
-
inputElement
|
|
386
|
+
inputElement.classList.add('edit-mode-element-css');
|
|
288
387
|
}
|
|
289
388
|
else {
|
|
290
389
|
inputElement.removeAttribute('readonly');
|
|
291
|
-
inputElement
|
|
390
|
+
inputElement.classList.remove('edit-mode-element-css');
|
|
292
391
|
}
|
|
293
392
|
}
|
|
294
|
-
if (inputElement.tagName
|
|
393
|
+
if (inputElement.tagName === 'P-SELECT') {
|
|
295
394
|
if (!editMode) {
|
|
296
|
-
inputElement.classList.add('edit-mode-element-css');
|
|
297
|
-
inputElement.classList.add('overflow-hidden');
|
|
395
|
+
inputElement.classList.add('edit-mode-element-css', 'overflow-hidden');
|
|
298
396
|
inputElement.querySelector('.p-select-dropdown').style.display = 'none';
|
|
299
397
|
inputElement.querySelector('.p-select-label').classList.add('edit-mode-element-css');
|
|
300
398
|
}
|
|
301
399
|
else {
|
|
302
|
-
inputElement.classList.remove(
|
|
400
|
+
inputElement.classList.remove('edit-mode-element-css', 'overflow-hidden');
|
|
303
401
|
inputElement.querySelector('.p-select-dropdown').style.display = 'flex';
|
|
304
|
-
inputElement.querySelector('.p-select-label').classList.remove(
|
|
305
|
-
inputElement.classList.remove('overflow-hidden');
|
|
402
|
+
inputElement.querySelector('.p-select-label').classList.remove('edit-mode-element-css');
|
|
306
403
|
}
|
|
307
404
|
}
|
|
308
|
-
if (inputElement.tagName
|
|
405
|
+
if (inputElement.tagName === 'P-CALENDAR') {
|
|
406
|
+
const firstChild = inputElement.firstElementChild;
|
|
309
407
|
if (!editMode) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
inputElement.firstElementChild.children[1]?.classList.add('hidden');
|
|
408
|
+
firstChild?.children[0]?.classList.add('edit-mode-element-css', 'cursor-auto');
|
|
409
|
+
firstChild?.children[1]?.classList.add('hidden');
|
|
313
410
|
}
|
|
314
411
|
else {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
inputElement.firstElementChild.children[1]?.classList.remove('hidden');
|
|
412
|
+
firstChild?.children[0]?.classList.remove('edit-mode-element-css', 'cursor-auto');
|
|
413
|
+
firstChild?.children[1]?.classList.remove('hidden');
|
|
318
414
|
}
|
|
319
415
|
}
|
|
320
416
|
}
|
|
321
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
322
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.
|
|
417
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: EditModeElementChangerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
418
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.3", type: EditModeElementChangerDirective, isStandalone: true, selector: "[appEditModeElementChanger]", inputs: { isEditMode: { classPropertyName: "isEditMode", publicName: "isEditMode", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
|
|
323
419
|
}
|
|
324
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
420
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: EditModeElementChangerDirective, decorators: [{
|
|
325
421
|
type: Directive,
|
|
326
422
|
args: [{
|
|
327
423
|
selector: '[appEditModeElementChanger]',
|
|
@@ -478,10 +574,10 @@ class HasPermissionDirective {
|
|
|
478
574
|
this.viewCreated = false;
|
|
479
575
|
}
|
|
480
576
|
}
|
|
481
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
482
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.
|
|
577
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: HasPermissionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
578
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.3", type: HasPermissionDirective, isStandalone: true, selector: "[hasPermission]", inputs: { hasPermission: { classPropertyName: "hasPermission", publicName: "hasPermission", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
|
|
483
579
|
}
|
|
484
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
580
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: HasPermissionDirective, decorators: [{
|
|
485
581
|
type: Directive,
|
|
486
582
|
args: [{
|
|
487
583
|
selector: '[hasPermission]',
|
|
@@ -503,10 +599,10 @@ class IsEmptyImageDirective {
|
|
|
503
599
|
onError() {
|
|
504
600
|
this.hasError.set(true);
|
|
505
601
|
}
|
|
506
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
507
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.
|
|
602
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: IsEmptyImageDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
603
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.3", type: IsEmptyImageDirective, isStandalone: true, selector: "img", inputs: { src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "error": "onError()" }, properties: { "src": "imageSrc()" } }, ngImport: i0 });
|
|
508
604
|
}
|
|
509
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
605
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: IsEmptyImageDirective, decorators: [{
|
|
510
606
|
type: Directive,
|
|
511
607
|
args: [{
|
|
512
608
|
selector: 'img',
|
|
@@ -543,13 +639,12 @@ class PreventDefaultDirective {
|
|
|
543
639
|
}
|
|
544
640
|
processEvent(event) {
|
|
545
641
|
event.preventDefault();
|
|
546
|
-
|
|
547
|
-
this.action.emit(event);
|
|
642
|
+
this.action.emit(event);
|
|
548
643
|
}
|
|
549
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
550
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.
|
|
644
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PreventDefaultDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
645
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.3", type: PreventDefaultDirective, isStandalone: true, selector: "[appPreventDefault]", inputs: { eventType: { classPropertyName: "eventType", publicName: "eventType", isSignal: true, isRequired: false, transformFunction: null }, preventKey: { classPropertyName: "preventKey", publicName: "preventKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { action: "action" }, host: { listeners: { "click": "onClick($event)", "keydown": "onKeydown($event)", "keyup": "onKeyup($event)" } }, ngImport: i0 });
|
|
551
646
|
}
|
|
552
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
647
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PreventDefaultDirective, decorators: [{
|
|
553
648
|
type: Directive,
|
|
554
649
|
args: [{
|
|
555
650
|
selector: '[appPreventDefault]',
|
|
@@ -567,162 +662,176 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
567
662
|
}] } });
|
|
568
663
|
|
|
569
664
|
class AngularModule {
|
|
570
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
571
|
-
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.1.
|
|
665
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AngularModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
666
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.1.3", ngImport: i0, type: AngularModule, imports: [CommonModule,
|
|
667
|
+
FormsModule,
|
|
668
|
+
ReactiveFormsModule,
|
|
572
669
|
RouterOutlet,
|
|
573
670
|
RouterLink,
|
|
574
671
|
IsEmptyImageDirective,
|
|
575
672
|
NgOptimizedImage,
|
|
576
673
|
NgComponentOutlet,
|
|
577
674
|
PreventDefaultDirective], exports: [CommonModule,
|
|
578
|
-
ReactiveFormsModule,
|
|
579
675
|
FormsModule,
|
|
676
|
+
ReactiveFormsModule,
|
|
580
677
|
RouterOutlet,
|
|
581
678
|
RouterLink,
|
|
582
679
|
IsEmptyImageDirective,
|
|
583
680
|
NgOptimizedImage,
|
|
584
681
|
NgComponentOutlet,
|
|
585
682
|
PreventDefaultDirective] });
|
|
586
|
-
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.1.
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
683
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AngularModule, providers: [DatePipe], imports: [CommonModule,
|
|
684
|
+
FormsModule,
|
|
685
|
+
ReactiveFormsModule, CommonModule,
|
|
686
|
+
FormsModule,
|
|
687
|
+
ReactiveFormsModule] });
|
|
591
688
|
}
|
|
592
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
689
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AngularModule, decorators: [{
|
|
593
690
|
type: NgModule,
|
|
594
691
|
args: [{
|
|
595
692
|
imports: [
|
|
596
693
|
CommonModule,
|
|
694
|
+
FormsModule,
|
|
695
|
+
ReactiveFormsModule,
|
|
597
696
|
RouterOutlet,
|
|
598
697
|
RouterLink,
|
|
599
698
|
IsEmptyImageDirective,
|
|
600
699
|
NgOptimizedImage,
|
|
601
700
|
NgComponentOutlet,
|
|
602
|
-
PreventDefaultDirective
|
|
603
|
-
],
|
|
604
|
-
providers: [
|
|
605
|
-
DatePipe,
|
|
701
|
+
PreventDefaultDirective,
|
|
606
702
|
],
|
|
703
|
+
providers: [DatePipe],
|
|
607
704
|
exports: [
|
|
608
705
|
CommonModule,
|
|
609
|
-
ReactiveFormsModule,
|
|
610
706
|
FormsModule,
|
|
707
|
+
ReactiveFormsModule,
|
|
611
708
|
RouterOutlet,
|
|
612
709
|
RouterLink,
|
|
613
710
|
IsEmptyImageDirective,
|
|
614
711
|
NgOptimizedImage,
|
|
615
712
|
NgComponentOutlet,
|
|
616
|
-
PreventDefaultDirective
|
|
713
|
+
PreventDefaultDirective,
|
|
617
714
|
],
|
|
618
715
|
}]
|
|
619
716
|
}] });
|
|
620
717
|
|
|
621
718
|
class PrimeModule {
|
|
622
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
623
|
-
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.1.
|
|
624
|
-
|
|
625
|
-
SelectButtonModule,
|
|
626
|
-
PasswordModule,
|
|
719
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PrimeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
720
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.1.3", ngImport: i0, type: PrimeModule, exports: [AutoCompleteModule,
|
|
721
|
+
AvatarModule,
|
|
627
722
|
ButtonModule,
|
|
628
|
-
|
|
723
|
+
CardModule,
|
|
629
724
|
CheckboxModule,
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
TableModule,
|
|
635
|
-
InputNumberModule,
|
|
636
|
-
TextareaModule,
|
|
637
|
-
ProgressBarModule,
|
|
725
|
+
ConfirmDialogModule,
|
|
726
|
+
DatePickerModule,
|
|
727
|
+
DialogModule,
|
|
728
|
+
DividerModule,
|
|
638
729
|
FileUploadModule,
|
|
639
|
-
CardModule,
|
|
640
|
-
SelectModule,
|
|
641
|
-
InputIconModule,
|
|
642
730
|
IconFieldModule,
|
|
643
|
-
|
|
731
|
+
ImageModule,
|
|
732
|
+
InputIconModule,
|
|
733
|
+
InputNumberModule,
|
|
734
|
+
InputTextModule,
|
|
644
735
|
ListboxModule,
|
|
736
|
+
MultiSelectModule,
|
|
737
|
+
PaginatorModule,
|
|
738
|
+
PanelModule,
|
|
739
|
+
PasswordModule,
|
|
740
|
+
PopoverModule,
|
|
741
|
+
ProgressBarModule,
|
|
645
742
|
RadioButtonModule,
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
743
|
+
RippleModule,
|
|
744
|
+
SelectButtonModule,
|
|
745
|
+
SelectModule,
|
|
746
|
+
SkeletonModule,
|
|
649
747
|
SplitButtonModule,
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
AutoCompleteModule,
|
|
748
|
+
StepsModule,
|
|
749
|
+
TableModule,
|
|
653
750
|
TabsModule,
|
|
654
|
-
DialogModule,
|
|
655
|
-
TreeTableModule] });
|
|
656
|
-
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: PrimeModule, imports: [InputTextModule,
|
|
657
751
|
TagModule,
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
752
|
+
TextareaModule,
|
|
753
|
+
ToastModule,
|
|
754
|
+
ToggleSwitchModule,
|
|
661
755
|
TooltipModule,
|
|
756
|
+
TreeTableModule] });
|
|
757
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PrimeModule, imports: [AutoCompleteModule,
|
|
758
|
+
AvatarModule,
|
|
759
|
+
ButtonModule,
|
|
760
|
+
CardModule,
|
|
662
761
|
CheckboxModule,
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
TableModule,
|
|
668
|
-
InputNumberModule,
|
|
669
|
-
TextareaModule,
|
|
670
|
-
ProgressBarModule,
|
|
762
|
+
ConfirmDialogModule,
|
|
763
|
+
DatePickerModule,
|
|
764
|
+
DialogModule,
|
|
765
|
+
DividerModule,
|
|
671
766
|
FileUploadModule,
|
|
672
|
-
CardModule,
|
|
673
|
-
SelectModule,
|
|
674
|
-
InputIconModule,
|
|
675
767
|
IconFieldModule,
|
|
676
|
-
|
|
768
|
+
ImageModule,
|
|
769
|
+
InputIconModule,
|
|
770
|
+
InputNumberModule,
|
|
771
|
+
InputTextModule,
|
|
677
772
|
ListboxModule,
|
|
773
|
+
MultiSelectModule,
|
|
774
|
+
PaginatorModule,
|
|
775
|
+
PanelModule,
|
|
776
|
+
PasswordModule,
|
|
777
|
+
PopoverModule,
|
|
778
|
+
ProgressBarModule,
|
|
678
779
|
RadioButtonModule,
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
780
|
+
RippleModule,
|
|
781
|
+
SelectButtonModule,
|
|
782
|
+
SelectModule,
|
|
783
|
+
SkeletonModule,
|
|
682
784
|
SplitButtonModule,
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
AutoCompleteModule,
|
|
785
|
+
StepsModule,
|
|
786
|
+
TableModule,
|
|
686
787
|
TabsModule,
|
|
687
|
-
|
|
788
|
+
TagModule,
|
|
789
|
+
TextareaModule,
|
|
790
|
+
ToastModule,
|
|
791
|
+
ToggleSwitchModule,
|
|
792
|
+
TooltipModule,
|
|
688
793
|
TreeTableModule] });
|
|
689
794
|
}
|
|
690
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
795
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PrimeModule, decorators: [{
|
|
691
796
|
type: NgModule,
|
|
692
797
|
args: [{
|
|
693
798
|
exports: [
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
SelectButtonModule,
|
|
697
|
-
PasswordModule,
|
|
799
|
+
AutoCompleteModule,
|
|
800
|
+
AvatarModule,
|
|
698
801
|
ButtonModule,
|
|
699
|
-
|
|
802
|
+
CardModule,
|
|
700
803
|
CheckboxModule,
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
TableModule,
|
|
706
|
-
InputNumberModule,
|
|
707
|
-
TextareaModule,
|
|
708
|
-
ProgressBarModule,
|
|
804
|
+
ConfirmDialogModule,
|
|
805
|
+
DatePickerModule,
|
|
806
|
+
DialogModule,
|
|
807
|
+
DividerModule,
|
|
709
808
|
FileUploadModule,
|
|
710
|
-
CardModule,
|
|
711
|
-
SelectModule,
|
|
712
|
-
InputIconModule,
|
|
713
809
|
IconFieldModule,
|
|
714
|
-
|
|
810
|
+
ImageModule,
|
|
811
|
+
InputIconModule,
|
|
812
|
+
InputNumberModule,
|
|
813
|
+
InputTextModule,
|
|
715
814
|
ListboxModule,
|
|
815
|
+
MultiSelectModule,
|
|
816
|
+
PaginatorModule,
|
|
817
|
+
PanelModule,
|
|
818
|
+
PasswordModule,
|
|
819
|
+
PopoverModule,
|
|
820
|
+
ProgressBarModule,
|
|
716
821
|
RadioButtonModule,
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
822
|
+
RippleModule,
|
|
823
|
+
SelectButtonModule,
|
|
824
|
+
SelectModule,
|
|
825
|
+
SkeletonModule,
|
|
720
826
|
SplitButtonModule,
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
AutoCompleteModule,
|
|
827
|
+
StepsModule,
|
|
828
|
+
TableModule,
|
|
724
829
|
TabsModule,
|
|
725
|
-
|
|
830
|
+
TagModule,
|
|
831
|
+
TextareaModule,
|
|
832
|
+
ToastModule,
|
|
833
|
+
ToggleSwitchModule,
|
|
834
|
+
TooltipModule,
|
|
726
835
|
TreeTableModule,
|
|
727
836
|
],
|
|
728
837
|
}]
|
|
@@ -775,6 +884,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
775
884
|
class ApiResourceService {
|
|
776
885
|
baseUrl;
|
|
777
886
|
loaderService = inject(ApiLoaderService);
|
|
887
|
+
injector = inject(Injector);
|
|
778
888
|
http;
|
|
779
889
|
moduleApiName;
|
|
780
890
|
// ==========================================================================
|
|
@@ -789,22 +899,54 @@ class ApiResourceService {
|
|
|
789
899
|
select: [],
|
|
790
900
|
sort: {},
|
|
791
901
|
}, ...(ngDevMode ? [{ debugName: "filterData" }] : []));
|
|
792
|
-
/**
|
|
793
|
-
|
|
902
|
+
/**
|
|
903
|
+
* Resource for list data - lazy initialized to prevent auto-fetch on service injection.
|
|
904
|
+
* Call initListResource() or any list method (fetchList, reload, etc.) to initialize.
|
|
905
|
+
*/
|
|
906
|
+
_listResource = null;
|
|
907
|
+
/** Whether the list resource has been initialized */
|
|
908
|
+
_resourceInitialized = false;
|
|
909
|
+
/** Get or create the list resource (lazy initialization) */
|
|
910
|
+
get listResource() {
|
|
911
|
+
if (!this._listResource) {
|
|
912
|
+
this.initListResource();
|
|
913
|
+
}
|
|
914
|
+
return this._listResource;
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Initialize the list resource. Called automatically when accessing listResource
|
|
918
|
+
* or any list-related computed signals/methods.
|
|
919
|
+
* Uses runInInjectionContext to support lazy initialization outside constructor.
|
|
920
|
+
*/
|
|
921
|
+
initListResource() {
|
|
922
|
+
if (this._resourceInitialized)
|
|
923
|
+
return;
|
|
924
|
+
this._resourceInitialized = true;
|
|
925
|
+
runInInjectionContext(this.injector, () => {
|
|
926
|
+
this._listResource = resource({ ...(ngDevMode ? { debugName: "_listResource" } : {}), params: () => ({
|
|
927
|
+
search: this.searchTerm(),
|
|
928
|
+
filter: this.filterData(),
|
|
929
|
+
}),
|
|
930
|
+
loader: async ({ params }) => {
|
|
931
|
+
const { search, filter } = params;
|
|
932
|
+
return this.fetchAllAsync(search, filter);
|
|
933
|
+
} });
|
|
934
|
+
});
|
|
935
|
+
}
|
|
794
936
|
// ==========================================================================
|
|
795
937
|
// Computed State Accessors
|
|
796
938
|
// ==========================================================================
|
|
797
939
|
/** Whether data is currently loading */
|
|
798
|
-
isLoading = computed(() => this.
|
|
940
|
+
isLoading = computed(() => this._listResource?.isLoading() ?? false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
799
941
|
/** List data array */
|
|
800
|
-
data = computed(() => this.
|
|
942
|
+
data = computed(() => this._listResource?.value()?.data ?? [], ...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
801
943
|
/** Total count of items */
|
|
802
|
-
total = computed(() => this.
|
|
944
|
+
total = computed(() => this._listResource?.value()?.meta?.total ?? 0, ...(ngDevMode ? [{ debugName: "total" }] : []));
|
|
803
945
|
/** Pagination metadata */
|
|
804
|
-
pageInfo = computed(() => this.
|
|
946
|
+
pageInfo = computed(() => this._listResource?.value()?.meta, ...(ngDevMode ? [{ debugName: "pageInfo" }] : []));
|
|
805
947
|
/** Whether there are more pages */
|
|
806
948
|
hasMore = computed(() => {
|
|
807
|
-
const meta = this.
|
|
949
|
+
const meta = this._listResource?.value()?.meta;
|
|
808
950
|
if (!meta)
|
|
809
951
|
return false;
|
|
810
952
|
return meta.hasMore ?? (meta.page + 1) * meta.pageSize < meta.total;
|
|
@@ -813,15 +955,7 @@ class ApiResourceService {
|
|
|
813
955
|
this.moduleApiName = moduleApiName;
|
|
814
956
|
this.baseUrl = inject(APP_CONFIG).apiBaseUrl + '/' + moduleApiName;
|
|
815
957
|
this.http = http;
|
|
816
|
-
//
|
|
817
|
-
this.listResource = resource({ ...(ngDevMode ? { debugName: "listResource" } : {}), params: () => ({
|
|
818
|
-
search: this.searchTerm(),
|
|
819
|
-
filter: this.filterData(),
|
|
820
|
-
}),
|
|
821
|
-
loader: async ({ params }) => {
|
|
822
|
-
const { search, filter } = params;
|
|
823
|
-
return this.fetchAllAsync(search, filter);
|
|
824
|
-
} });
|
|
958
|
+
// Resource is now lazy-initialized, not created in constructor
|
|
825
959
|
}
|
|
826
960
|
getHttpOptions(endpoint, params) {
|
|
827
961
|
return {
|
|
@@ -835,9 +969,10 @@ class ApiResourceService {
|
|
|
835
969
|
// List Management Methods
|
|
836
970
|
// ==========================================================================
|
|
837
971
|
/**
|
|
838
|
-
* Fetch list data (triggers resource reload)
|
|
972
|
+
* Fetch list data (triggers resource initialization and reload)
|
|
839
973
|
*/
|
|
840
974
|
fetchList(search = '', filter) {
|
|
975
|
+
this.initListResource();
|
|
841
976
|
this.searchTerm.set(search);
|
|
842
977
|
if (filter) {
|
|
843
978
|
this.filterData.update((prev) => ({ ...prev, ...filter }));
|
|
@@ -847,12 +982,14 @@ class ApiResourceService {
|
|
|
847
982
|
* Update pagination
|
|
848
983
|
*/
|
|
849
984
|
setPagination(pagination) {
|
|
985
|
+
this.initListResource();
|
|
850
986
|
this.filterData.update((prev) => ({ ...prev, pagination }));
|
|
851
987
|
}
|
|
852
988
|
/**
|
|
853
989
|
* Go to next page
|
|
854
990
|
*/
|
|
855
991
|
nextPage() {
|
|
992
|
+
this.initListResource();
|
|
856
993
|
this.filterData.update((prev) => ({
|
|
857
994
|
...prev,
|
|
858
995
|
pagination: {
|
|
@@ -865,6 +1002,7 @@ class ApiResourceService {
|
|
|
865
1002
|
* Reset to first page
|
|
866
1003
|
*/
|
|
867
1004
|
resetPagination() {
|
|
1005
|
+
this.initListResource();
|
|
868
1006
|
this.filterData.update((prev) => ({
|
|
869
1007
|
...prev,
|
|
870
1008
|
pagination: { currentPage: 0, pageSize: prev.pagination?.pageSize ?? 10 },
|
|
@@ -874,7 +1012,9 @@ class ApiResourceService {
|
|
|
874
1012
|
* Reload current data
|
|
875
1013
|
*/
|
|
876
1014
|
reload() {
|
|
877
|
-
this.
|
|
1015
|
+
if (this._listResource) {
|
|
1016
|
+
this._listResource.reload();
|
|
1017
|
+
}
|
|
878
1018
|
}
|
|
879
1019
|
// ==========================================================================
|
|
880
1020
|
// Observable-based API Methods (IApiService interface)
|
|
@@ -984,8 +1124,8 @@ class IconComponent {
|
|
|
984
1124
|
icon = input.required(...(ngDevMode ? [{ debugName: "icon" }] : []));
|
|
985
1125
|
iconType = input(IconTypeEnum.PRIMENG_ICON, ...(ngDevMode ? [{ debugName: "iconType" }] : []));
|
|
986
1126
|
IconTypeEnum = IconTypeEnum; // Needed for template reference
|
|
987
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
988
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.
|
|
1127
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: IconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1128
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: IconComponent, isStandalone: true, selector: "lib-icon", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: true, transformFunction: null }, iconType: { classPropertyName: "iconType", publicName: "iconType", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
989
1129
|
@if(icon()){ @if(iconType()==IconTypeEnum.PRIMENG_ICON){
|
|
990
1130
|
<i [ngClass]="icon()"></i>
|
|
991
1131
|
}@else if(iconType()==IconTypeEnum.IMAGE_FILE_LINK){
|
|
@@ -995,7 +1135,7 @@ class IconComponent {
|
|
|
995
1135
|
}@else{ I } }
|
|
996
1136
|
`, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: IsEmptyImageDirective, selector: "img", inputs: ["src"] }] });
|
|
997
1137
|
}
|
|
998
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
1138
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: IconComponent, decorators: [{
|
|
999
1139
|
type: Component,
|
|
1000
1140
|
args: [{
|
|
1001
1141
|
selector: 'lib-icon',
|
|
@@ -1109,10 +1249,10 @@ class BaseFormControl {
|
|
|
1109
1249
|
this.onTouched();
|
|
1110
1250
|
}
|
|
1111
1251
|
}
|
|
1112
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
1113
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.
|
|
1252
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: BaseFormControl, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1253
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.3", type: BaseFormControl, isStandalone: true, inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", touched: "touchedChange" }, ngImport: i0 });
|
|
1114
1254
|
}
|
|
1115
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
1255
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: BaseFormControl, decorators: [{
|
|
1116
1256
|
type: Directive
|
|
1117
1257
|
}], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }] } });
|
|
1118
1258
|
/**
|
|
@@ -1189,8 +1329,6 @@ class LazyMultiSelectComponent extends BaseFormControl {
|
|
|
1189
1329
|
})), { initialValue: this.searchTerm() });
|
|
1190
1330
|
});
|
|
1191
1331
|
}
|
|
1192
|
-
// Signal to toggle panel
|
|
1193
|
-
scrollTargetEl = null;
|
|
1194
1332
|
onScrollBound = this.onScroll.bind(this);
|
|
1195
1333
|
multiScrollContainer = viewChild.required('multiScrollContainer');
|
|
1196
1334
|
onScroll(event) {
|
|
@@ -1254,10 +1392,10 @@ class LazyMultiSelectComponent extends BaseFormControl {
|
|
|
1254
1392
|
event.stopPropagation();
|
|
1255
1393
|
this.value.set([]);
|
|
1256
1394
|
}
|
|
1257
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
1258
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.
|
|
1395
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LazyMultiSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1396
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", 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" }, host: { listeners: { "document:click": "handleDocumentClick($event)" } }, providers: [provideValueAccessor(LazyMultiSelectComponent)], viewQueries: [{ propertyName: "multiScrollContainer", first: true, predicate: ["multiScrollContainer"], descendants: true, isSignal: true }, { propertyName: "pSelectRef", first: true, predicate: ["pSelect"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"p-select w-full\" #pSelect (click)=\"onSelectClick($event)\"\n [class.p-disabled]=\"disabled()\">\n @if(selectedValueDisplay()){\n <span class=\"p-select-label\">{{selectedValueDisplay()}}</span>\n }@else {\n <span class=\"p-select-label p-placeholder\">{{placeHolder()}}</span>\n }\n <span class=\"p-select-clear-icon\" (click)=\"clear($event)\"><i class=\"pi pi-times\"></i></span>\n <div class=\"p-select-dropdown\">\n <span class=\"p-select-dropdown-icon flex items-center\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"\n data-p-icon=\"chevron-down\" class=\"p-multiselect-dropdown-icon p-icon ng-star-inserted\"\n data-pc-section=\"triggericon\" aria-hidden=\"true\" pc75=\"\">\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\"></path>\n </svg>\n </span>\n </div>\n @if(openOptions()){\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 binary=\"true\" (onChange)=\"changeSelectAll($event)\" [ngModel]=\"isSelectAll()\" [disabled]=\"disabled()\"/>\n <input type=\"text\" pInputText class=\"w-full\" [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"searchTerm.set($event)\" [ngModelOptions]=\"{ standalone: true }\"\n placeholder=\"Search...\" />\n </div>\n <div class=\"p-select-list-container\" (scroll)=\"onScroll($event)\">\n <ul class=\"p-select-list\">\n @for (data of selectDataList(); track key(data); let i = $index) {\n <li class=\"p-select-option flex flex-row gap-2 items-center\"\n [ngClass]=\"{ 'p-select-option-selected': isSelected(data) }\">\n <p-checkbox binary=\"true\" (onChange)=\"selectValue($event,data)\" [ngModel]=\"isSelected(data)\" [disabled]=\"disabled()\" />\n <span>{{data.label}}</span>\n </li>\n }\n </ul>\n </div>\n </div>\n }\n</div>", styles: [".p-select-overlay{top:33px;z-index:1004;transform-origin:center top;margin-top:2px}.p-select-option:hover{background:var(--p-select-option-focus-background);color:var(--p-select-option-focus-color)}.p-select-list-container{max-height:200px}.p-select-option.p-select-option-selected{background:var(--p-select-option-selected-background);color:var(--p-select-option-selected-color)}.p-select-option.p-select-option-selected:hover{background:var(--p-select-option-selected-focus-background);color:var(--p-select-option-selected-focus-color)}\n"], dependencies: [{ kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i1$1.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.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$2.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$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1259
1397
|
}
|
|
1260
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
1398
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LazyMultiSelectComponent, decorators: [{
|
|
1261
1399
|
type: Component,
|
|
1262
1400
|
args: [{ selector: 'lib-lazy-multi-select', imports: [PrimeModule, AngularModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [provideValueAccessor(LazyMultiSelectComponent)], template: "<div class=\"p-select w-full\" #pSelect (click)=\"onSelectClick($event)\"\n [class.p-disabled]=\"disabled()\">\n @if(selectedValueDisplay()){\n <span class=\"p-select-label\">{{selectedValueDisplay()}}</span>\n }@else {\n <span class=\"p-select-label p-placeholder\">{{placeHolder()}}</span>\n }\n <span class=\"p-select-clear-icon\" (click)=\"clear($event)\"><i class=\"pi pi-times\"></i></span>\n <div class=\"p-select-dropdown\">\n <span class=\"p-select-dropdown-icon flex items-center\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"\n data-p-icon=\"chevron-down\" class=\"p-multiselect-dropdown-icon p-icon ng-star-inserted\"\n data-pc-section=\"triggericon\" aria-hidden=\"true\" pc75=\"\">\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\"></path>\n </svg>\n </span>\n </div>\n @if(openOptions()){\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 binary=\"true\" (onChange)=\"changeSelectAll($event)\" [ngModel]=\"isSelectAll()\" [disabled]=\"disabled()\"/>\n <input type=\"text\" pInputText class=\"w-full\" [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"searchTerm.set($event)\" [ngModelOptions]=\"{ standalone: true }\"\n placeholder=\"Search...\" />\n </div>\n <div class=\"p-select-list-container\" (scroll)=\"onScroll($event)\">\n <ul class=\"p-select-list\">\n @for (data of selectDataList(); track key(data); let i = $index) {\n <li class=\"p-select-option flex flex-row gap-2 items-center\"\n [ngClass]=\"{ 'p-select-option-selected': isSelected(data) }\">\n <p-checkbox binary=\"true\" (onChange)=\"selectValue($event,data)\" [ngModel]=\"isSelected(data)\" [disabled]=\"disabled()\" />\n <span>{{data.label}}</span>\n </li>\n }\n </ul>\n </div>\n </div>\n }\n</div>", styles: [".p-select-overlay{top:33px;z-index:1004;transform-origin:center top;margin-top:2px}.p-select-option:hover{background:var(--p-select-option-focus-background);color:var(--p-select-option-focus-color)}.p-select-list-container{max-height:200px}.p-select-option.p-select-option-selected{background:var(--p-select-option-selected-background);color:var(--p-select-option-selected-color)}.p-select-option.p-select-option-selected:hover{background:var(--p-select-option-selected-focus-background);color:var(--p-select-option-selected-focus-color)}\n"] }]
|
|
1263
1401
|
}], ctorParameters: () => [], propDecorators: { 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"] }], multiScrollContainer: [{ type: i0.ViewChild, args: ['multiScrollContainer', { isSignal: true }] }], pSelectRef: [{ type: i0.ViewChild, args: ['pSelect', { isSignal: true }] }], handleDocumentClick: [{
|
|
@@ -1331,9 +1469,6 @@ class LazySelectComponent extends BaseFormControl {
|
|
|
1331
1469
|
target.addEventListener('scroll', this.onScrollBound);
|
|
1332
1470
|
this.scrollTargetEl = target;
|
|
1333
1471
|
}
|
|
1334
|
-
else {
|
|
1335
|
-
console.warn('.p-select-list-container not found after panel show');
|
|
1336
|
-
}
|
|
1337
1472
|
}, 0);
|
|
1338
1473
|
}
|
|
1339
1474
|
else {
|
|
@@ -1346,10 +1481,10 @@ class LazySelectComponent extends BaseFormControl {
|
|
|
1346
1481
|
onBlur() {
|
|
1347
1482
|
this.markAsTouched();
|
|
1348
1483
|
}
|
|
1349
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
1350
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.1.
|
|
1484
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LazySelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1485
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.1.3", 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 [options]=\"selectDataList()\"\n [(ngModel)]=\"value\"\n [optionLabel]=\"optionLabel()\"\n [optionValue]=\"optionValue()\"\n [filter]=\"true\"\n [showClear]=\"true\"\n [placeholder]=\"placeHolder()\"\n [disabled]=\"disabled()\"\n class=\"w-full\"\n appEditModeElementChanger\n [isEditMode]=\"isEditMode()\"\n (click)=\"showPanel()\"\n (onBlur)=\"onBlur()\">\n <ng-template let-filter #filter>\n <input\n pInputText\n [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"searchTerm.set($event)\"\n [ngModelOptions]=\"{standalone:true}\"\n class=\"w-full\" />\n </ng-template>\n <ng-template #selectedItem let-selectedOption>\n {{ selectedOption[optionLabel()] }}\n </ng-template>\n <ng-template let-item #item>\n {{ item[optionLabel()] }}\n </ng-template>\n </p-select>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$2.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$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1351
1486
|
}
|
|
1352
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
1487
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LazySelectComponent, decorators: [{
|
|
1353
1488
|
type: Component,
|
|
1354
1489
|
args: [{ selector: 'lib-lazy-select', imports: [
|
|
1355
1490
|
AngularModule,
|
|
@@ -1412,7 +1547,1237 @@ const USER_PERMISSION_PROVIDER = new InjectionToken('USER_PERMISSION_PROVIDER',
|
|
|
1412
1547
|
throw new Error('USER_PERMISSION_PROVIDER not configured. Please provide an implementation in app.config.ts');
|
|
1413
1548
|
},
|
|
1414
1549
|
});
|
|
1550
|
+
/**
|
|
1551
|
+
* Auth State Provider Token
|
|
1552
|
+
*
|
|
1553
|
+
* Provides auth state access for feature packages (form-builder, etc.)
|
|
1554
|
+
*/
|
|
1555
|
+
const AUTH_STATE_PROVIDER = new InjectionToken('AUTH_STATE_PROVIDER', {
|
|
1556
|
+
providedIn: 'root',
|
|
1557
|
+
factory: () => {
|
|
1558
|
+
throw new Error('AUTH_STATE_PROVIDER not configured. Please provide an implementation in app.config.ts');
|
|
1559
|
+
},
|
|
1560
|
+
});
|
|
1561
|
+
/**
|
|
1562
|
+
* Profile Permission Provider Token
|
|
1563
|
+
*
|
|
1564
|
+
* Provides user permission data for profile display.
|
|
1565
|
+
* Optional - if not configured, profile permissions section is hidden.
|
|
1566
|
+
* Use with `inject(PROFILE_PERMISSION_PROVIDER, { optional: true })`.
|
|
1567
|
+
*/
|
|
1568
|
+
const PROFILE_PERMISSION_PROVIDER = new InjectionToken('PROFILE_PERMISSION_PROVIDER');
|
|
1569
|
+
/**
|
|
1570
|
+
* Profile Upload Provider Token
|
|
1571
|
+
*
|
|
1572
|
+
* Provides file upload functionality for profile pictures.
|
|
1573
|
+
* Optional - if not configured or storage not enabled, upload section is hidden.
|
|
1574
|
+
* Use with `inject(PROFILE_UPLOAD_PROVIDER, { optional: true })`.
|
|
1575
|
+
*/
|
|
1576
|
+
const PROFILE_UPLOAD_PROVIDER = new InjectionToken('PROFILE_UPLOAD_PROVIDER');
|
|
1577
|
+
/**
|
|
1578
|
+
* User List Provider Token
|
|
1579
|
+
*
|
|
1580
|
+
* Provides extra actions, columns, and data enrichment for user list.
|
|
1581
|
+
* Optional - if not configured, default user list behavior is used.
|
|
1582
|
+
* Use with `inject(USER_LIST_PROVIDER, { optional: true })`.
|
|
1583
|
+
*
|
|
1584
|
+
* @example
|
|
1585
|
+
* // In app.config.ts
|
|
1586
|
+
* providers: [
|
|
1587
|
+
* { provide: USER_LIST_PROVIDER, useClass: MyUserListProvider },
|
|
1588
|
+
* ]
|
|
1589
|
+
*/
|
|
1590
|
+
const USER_LIST_PROVIDER = new InjectionToken('USER_LIST_PROVIDER');
|
|
1591
|
+
|
|
1592
|
+
const DEFAULT_PAGE_SIZE$2 = 20;
|
|
1593
|
+
/**
|
|
1594
|
+
* User Select Component - Single user selection with lazy loading.
|
|
1595
|
+
*
|
|
1596
|
+
* Uses USER_PROVIDER internally by default, or accepts custom `loadUsers` function.
|
|
1597
|
+
*
|
|
1598
|
+
* Features:
|
|
1599
|
+
* - Search with debouncing (handled by lazy-select)
|
|
1600
|
+
* - Infinite scroll pagination
|
|
1601
|
+
* - Filter active users by default (configurable)
|
|
1602
|
+
* - Supports additional filters via `additionalFilters` input
|
|
1603
|
+
*
|
|
1604
|
+
* @example
|
|
1605
|
+
* ```html
|
|
1606
|
+
* <!-- Simple usage - uses USER_PROVIDER internally -->
|
|
1607
|
+
* <lib-user-select
|
|
1608
|
+
* [(value)]="selectedUserId"
|
|
1609
|
+
* [isEditMode]="true"
|
|
1610
|
+
* />
|
|
1611
|
+
*
|
|
1612
|
+
* <!-- With custom loadUsers function -->
|
|
1613
|
+
* <lib-user-select
|
|
1614
|
+
* [(value)]="selectedUserId"
|
|
1615
|
+
* [isEditMode]="true"
|
|
1616
|
+
* [loadUsers]="customLoadUsers"
|
|
1617
|
+
* />
|
|
1618
|
+
* ```
|
|
1619
|
+
*/
|
|
1620
|
+
class UserSelectComponent {
|
|
1621
|
+
destroyRef = inject(DestroyRef);
|
|
1622
|
+
userProvider = inject(USER_PROVIDER);
|
|
1623
|
+
abortController = null;
|
|
1624
|
+
// Optional: custom function to load users (uses USER_PROVIDER if not provided)
|
|
1625
|
+
loadUsers = input(...(ngDevMode ? [undefined, { debugName: "loadUsers" }] : []));
|
|
1626
|
+
// Inputs
|
|
1627
|
+
placeHolder = input('Select User', ...(ngDevMode ? [{ debugName: "placeHolder" }] : []));
|
|
1628
|
+
isEditMode = input.required(...(ngDevMode ? [{ debugName: "isEditMode" }] : []));
|
|
1629
|
+
filterActive = input(true, ...(ngDevMode ? [{ debugName: "filterActive" }] : []));
|
|
1630
|
+
additionalFilters = input({}, ...(ngDevMode ? [{ debugName: "additionalFilters" }] : []));
|
|
1631
|
+
pageSize = input(DEFAULT_PAGE_SIZE$2, ...(ngDevMode ? [{ debugName: "pageSize" }] : []));
|
|
1632
|
+
// Two-way bound value
|
|
1633
|
+
value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
|
|
1634
|
+
// Outputs
|
|
1635
|
+
userSelected = output();
|
|
1636
|
+
onError = output();
|
|
1637
|
+
// Internal state
|
|
1638
|
+
isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
1639
|
+
users = signal([], ...(ngDevMode ? [{ debugName: "users" }] : []));
|
|
1640
|
+
total = signal(undefined, ...(ngDevMode ? [{ debugName: "total" }] : []));
|
|
1641
|
+
pagination = signal({ pageSize: DEFAULT_PAGE_SIZE$2, currentPage: 0 }, ...(ngDevMode ? [{ debugName: "pagination" }] : []));
|
|
1642
|
+
searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
|
|
1643
|
+
// Computed dropdown data
|
|
1644
|
+
dropdownUsers = computed(() => this.users().map((user) => ({
|
|
1645
|
+
label: user.name || user.email,
|
|
1646
|
+
value: user.id,
|
|
1647
|
+
})), ...(ngDevMode ? [{ debugName: "dropdownUsers" }] : []));
|
|
1648
|
+
constructor() {
|
|
1649
|
+
// Cleanup on destroy
|
|
1650
|
+
this.destroyRef.onDestroy(() => {
|
|
1651
|
+
this.abortController?.abort();
|
|
1652
|
+
});
|
|
1653
|
+
// Update page size from input
|
|
1654
|
+
effect(() => {
|
|
1655
|
+
const size = this.pageSize();
|
|
1656
|
+
untracked(() => {
|
|
1657
|
+
this.pagination.update((p) => ({ ...p, pageSize: size }));
|
|
1658
|
+
});
|
|
1659
|
+
});
|
|
1660
|
+
// Load initial users after render
|
|
1661
|
+
afterNextRender(() => {
|
|
1662
|
+
this.fetchUsers();
|
|
1663
|
+
});
|
|
1664
|
+
// Emit selected user when value changes
|
|
1665
|
+
effect(() => {
|
|
1666
|
+
const selectedId = this.value();
|
|
1667
|
+
const users = this.users();
|
|
1668
|
+
untracked(() => {
|
|
1669
|
+
if (selectedId) {
|
|
1670
|
+
const user = users.find((u) => u.id === selectedId);
|
|
1671
|
+
this.userSelected.emit(user ?? null);
|
|
1672
|
+
}
|
|
1673
|
+
else {
|
|
1674
|
+
this.userSelected.emit(null);
|
|
1675
|
+
}
|
|
1676
|
+
});
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
handleSearch(search) {
|
|
1680
|
+
this.searchTerm.set(search);
|
|
1681
|
+
this.pagination.update((p) => ({ ...p, currentPage: 0 }));
|
|
1682
|
+
this.users.set([]);
|
|
1683
|
+
this.fetchUsers();
|
|
1684
|
+
}
|
|
1685
|
+
handlePagination(pagination) {
|
|
1686
|
+
this.pagination.set(pagination);
|
|
1687
|
+
this.fetchUsers(true);
|
|
1688
|
+
}
|
|
1689
|
+
async fetchUsers(append = false) {
|
|
1690
|
+
if (this.isLoading())
|
|
1691
|
+
return;
|
|
1692
|
+
// Cancel previous request
|
|
1693
|
+
this.abortController?.abort();
|
|
1694
|
+
this.abortController = new AbortController();
|
|
1695
|
+
this.isLoading.set(true);
|
|
1696
|
+
try {
|
|
1697
|
+
const pag = this.pagination();
|
|
1698
|
+
const filter = {
|
|
1699
|
+
page: pag.currentPage,
|
|
1700
|
+
pageSize: pag.pageSize,
|
|
1701
|
+
search: this.searchTerm(),
|
|
1702
|
+
...this.additionalFilters(),
|
|
1703
|
+
};
|
|
1704
|
+
// Use custom loadUsers if provided, otherwise use USER_PROVIDER
|
|
1705
|
+
const customLoadUsers = this.loadUsers();
|
|
1706
|
+
const response = await firstValueFrom(customLoadUsers
|
|
1707
|
+
? customLoadUsers(filter)
|
|
1708
|
+
: this.loadUsersFromProvider(filter));
|
|
1709
|
+
if (response.success && response.data) {
|
|
1710
|
+
if (append) {
|
|
1711
|
+
this.users.update((current) => [...current, ...response.data]);
|
|
1712
|
+
}
|
|
1713
|
+
else {
|
|
1714
|
+
this.users.set(response.data);
|
|
1715
|
+
}
|
|
1716
|
+
this.total.set(response.meta?.total);
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
catch (error) {
|
|
1720
|
+
if (error.name !== 'AbortError') {
|
|
1721
|
+
this.onError.emit(error);
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
finally {
|
|
1725
|
+
this.isLoading.set(false);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
/** Load users from USER_PROVIDER with active filter */
|
|
1729
|
+
loadUsersFromProvider(filter) {
|
|
1730
|
+
return this.userProvider
|
|
1731
|
+
.getUsers({
|
|
1732
|
+
page: filter.page,
|
|
1733
|
+
pageSize: filter.pageSize,
|
|
1734
|
+
search: filter.search,
|
|
1735
|
+
isActive: this.filterActive() ? true : undefined,
|
|
1736
|
+
})
|
|
1737
|
+
.pipe(map$1((res) => ({
|
|
1738
|
+
...res,
|
|
1739
|
+
data: res.data?.map((u) => ({
|
|
1740
|
+
id: u.id,
|
|
1741
|
+
name: u.name,
|
|
1742
|
+
email: u.email,
|
|
1743
|
+
})),
|
|
1744
|
+
})));
|
|
1745
|
+
}
|
|
1746
|
+
/** Reload users (useful when filters change externally) */
|
|
1747
|
+
reload() {
|
|
1748
|
+
this.pagination.update((p) => ({ ...p, currentPage: 0 }));
|
|
1749
|
+
this.users.set([]);
|
|
1750
|
+
this.fetchUsers();
|
|
1751
|
+
}
|
|
1752
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1753
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.3", type: UserSelectComponent, isStandalone: true, selector: "lib-user-select", 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 }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", userSelected: "userSelected", onError: "onError" }, ngImport: i0, template: `
|
|
1754
|
+
<lib-lazy-select
|
|
1755
|
+
[(value)]="value"
|
|
1756
|
+
[placeHolder]="placeHolder()"
|
|
1757
|
+
[optionLabel]="'label'"
|
|
1758
|
+
[optionValue]="'value'"
|
|
1759
|
+
[isEditMode]="isEditMode()"
|
|
1760
|
+
[isLoading]="isLoading()"
|
|
1761
|
+
[total]="total()"
|
|
1762
|
+
[pagination]="pagination()"
|
|
1763
|
+
[selectDataList]="dropdownUsers()"
|
|
1764
|
+
(onSearch)="handleSearch($event)"
|
|
1765
|
+
(onPagination)="handlePagination($event)"
|
|
1766
|
+
/>
|
|
1767
|
+
`, 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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1768
|
+
}
|
|
1769
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserSelectComponent, decorators: [{
|
|
1770
|
+
type: Component,
|
|
1771
|
+
args: [{
|
|
1772
|
+
selector: 'lib-user-select',
|
|
1773
|
+
standalone: true,
|
|
1774
|
+
imports: [AngularModule, PrimeModule, LazySelectComponent],
|
|
1775
|
+
template: `
|
|
1776
|
+
<lib-lazy-select
|
|
1777
|
+
[(value)]="value"
|
|
1778
|
+
[placeHolder]="placeHolder()"
|
|
1779
|
+
[optionLabel]="'label'"
|
|
1780
|
+
[optionValue]="'value'"
|
|
1781
|
+
[isEditMode]="isEditMode()"
|
|
1782
|
+
[isLoading]="isLoading()"
|
|
1783
|
+
[total]="total()"
|
|
1784
|
+
[pagination]="pagination()"
|
|
1785
|
+
[selectDataList]="dropdownUsers()"
|
|
1786
|
+
(onSearch)="handleSearch($event)"
|
|
1787
|
+
(onPagination)="handlePagination($event)"
|
|
1788
|
+
/>
|
|
1789
|
+
`,
|
|
1790
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1791
|
+
}]
|
|
1792
|
+
}], ctorParameters: () => [], propDecorators: { loadUsers: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadUsers", required: false }] }], placeHolder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeHolder", required: false }] }], isEditMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditMode", required: true }] }], filterActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterActive", required: false }] }], additionalFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "additionalFilters", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], userSelected: [{ type: i0.Output, args: ["userSelected"] }], onError: [{ type: i0.Output, args: ["onError"] }] } });
|
|
1793
|
+
|
|
1794
|
+
const DEFAULT_PAGE_SIZE$1 = 20;
|
|
1795
|
+
/**
|
|
1796
|
+
* User Multi-Select Component - Multiple user selection with lazy loading.
|
|
1797
|
+
*
|
|
1798
|
+
* Uses USER_PROVIDER internally by default, or accepts custom `loadUsers` function.
|
|
1799
|
+
*
|
|
1800
|
+
* Features:
|
|
1801
|
+
* - Search with debouncing (handled by lazy-multi-select)
|
|
1802
|
+
* - Infinite scroll pagination
|
|
1803
|
+
* - Select all / deselect all
|
|
1804
|
+
* - Filter active users by default (configurable)
|
|
1805
|
+
* - Supports additional filters via `additionalFilters` input
|
|
1806
|
+
*
|
|
1807
|
+
* @example
|
|
1808
|
+
* ```html
|
|
1809
|
+
* <!-- Simple usage - uses USER_PROVIDER internally -->
|
|
1810
|
+
* <lib-user-multi-select
|
|
1811
|
+
* [(value)]="selectedUserIds"
|
|
1812
|
+
* [isEditMode]="true"
|
|
1813
|
+
* />
|
|
1814
|
+
*
|
|
1815
|
+
* <!-- With custom loadUsers function -->
|
|
1816
|
+
* <lib-user-multi-select
|
|
1817
|
+
* [(value)]="selectedUserIds"
|
|
1818
|
+
* [isEditMode]="true"
|
|
1819
|
+
* [loadUsers]="customLoadUsers"
|
|
1820
|
+
* />
|
|
1821
|
+
* ```
|
|
1822
|
+
*/
|
|
1823
|
+
class UserMultiSelectComponent {
|
|
1824
|
+
destroyRef = inject(DestroyRef);
|
|
1825
|
+
userProvider = inject(USER_PROVIDER);
|
|
1826
|
+
abortController = null;
|
|
1827
|
+
// Optional: custom function to load users (uses USER_PROVIDER if not provided)
|
|
1828
|
+
loadUsers = input(...(ngDevMode ? [undefined, { debugName: "loadUsers" }] : []));
|
|
1829
|
+
// Inputs
|
|
1830
|
+
placeHolder = input('Select Users', ...(ngDevMode ? [{ debugName: "placeHolder" }] : []));
|
|
1831
|
+
isEditMode = input.required(...(ngDevMode ? [{ debugName: "isEditMode" }] : []));
|
|
1832
|
+
filterActive = input(true, ...(ngDevMode ? [{ debugName: "filterActive" }] : []));
|
|
1833
|
+
additionalFilters = input({}, ...(ngDevMode ? [{ debugName: "additionalFilters" }] : []));
|
|
1834
|
+
pageSize = input(DEFAULT_PAGE_SIZE$1, ...(ngDevMode ? [{ debugName: "pageSize" }] : []));
|
|
1835
|
+
// Two-way bound value
|
|
1836
|
+
value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
|
|
1837
|
+
// Outputs
|
|
1838
|
+
usersSelected = output();
|
|
1839
|
+
onError = output();
|
|
1840
|
+
// Internal state
|
|
1841
|
+
isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
1842
|
+
users = signal([], ...(ngDevMode ? [{ debugName: "users" }] : []));
|
|
1843
|
+
total = signal(undefined, ...(ngDevMode ? [{ debugName: "total" }] : []));
|
|
1844
|
+
pagination = signal({ pageSize: DEFAULT_PAGE_SIZE$1, currentPage: 0 }, ...(ngDevMode ? [{ debugName: "pagination" }] : []));
|
|
1845
|
+
searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
|
|
1846
|
+
// Computed dropdown data
|
|
1847
|
+
dropdownUsers = computed(() => this.users().map((user) => ({
|
|
1848
|
+
label: user.name || user.email,
|
|
1849
|
+
value: user.id,
|
|
1850
|
+
})), ...(ngDevMode ? [{ debugName: "dropdownUsers" }] : []));
|
|
1851
|
+
constructor() {
|
|
1852
|
+
// Cleanup on destroy
|
|
1853
|
+
this.destroyRef.onDestroy(() => {
|
|
1854
|
+
this.abortController?.abort();
|
|
1855
|
+
});
|
|
1856
|
+
// Update page size from input
|
|
1857
|
+
effect(() => {
|
|
1858
|
+
const size = this.pageSize();
|
|
1859
|
+
untracked(() => {
|
|
1860
|
+
this.pagination.update((p) => ({ ...p, pageSize: size }));
|
|
1861
|
+
});
|
|
1862
|
+
});
|
|
1863
|
+
// Load initial users after render
|
|
1864
|
+
afterNextRender(() => {
|
|
1865
|
+
this.fetchUsers();
|
|
1866
|
+
});
|
|
1867
|
+
// Emit selected users when value changes
|
|
1868
|
+
effect(() => {
|
|
1869
|
+
const selectedIds = this.value() ?? [];
|
|
1870
|
+
const users = this.users();
|
|
1871
|
+
untracked(() => {
|
|
1872
|
+
const selectedUsers = users.filter((u) => selectedIds.includes(u.id));
|
|
1873
|
+
this.usersSelected.emit(selectedUsers);
|
|
1874
|
+
});
|
|
1875
|
+
});
|
|
1876
|
+
}
|
|
1877
|
+
handleSearch(search) {
|
|
1878
|
+
this.searchTerm.set(search);
|
|
1879
|
+
this.pagination.update((p) => ({ ...p, currentPage: 0 }));
|
|
1880
|
+
this.users.set([]);
|
|
1881
|
+
this.fetchUsers();
|
|
1882
|
+
}
|
|
1883
|
+
handlePagination(pagination) {
|
|
1884
|
+
this.pagination.set(pagination);
|
|
1885
|
+
this.fetchUsers(true);
|
|
1886
|
+
}
|
|
1887
|
+
async fetchUsers(append = false) {
|
|
1888
|
+
if (this.isLoading())
|
|
1889
|
+
return;
|
|
1890
|
+
// Cancel previous request
|
|
1891
|
+
this.abortController?.abort();
|
|
1892
|
+
this.abortController = new AbortController();
|
|
1893
|
+
this.isLoading.set(true);
|
|
1894
|
+
try {
|
|
1895
|
+
const pag = this.pagination();
|
|
1896
|
+
const filter = {
|
|
1897
|
+
page: pag.currentPage,
|
|
1898
|
+
pageSize: pag.pageSize,
|
|
1899
|
+
search: this.searchTerm(),
|
|
1900
|
+
...this.additionalFilters(),
|
|
1901
|
+
};
|
|
1902
|
+
// Use custom loadUsers if provided, otherwise use USER_PROVIDER
|
|
1903
|
+
const customLoadUsers = this.loadUsers();
|
|
1904
|
+
const response = await firstValueFrom(customLoadUsers
|
|
1905
|
+
? customLoadUsers(filter)
|
|
1906
|
+
: this.loadUsersFromProvider(filter));
|
|
1907
|
+
if (response.success && response.data) {
|
|
1908
|
+
if (append) {
|
|
1909
|
+
this.users.update((current) => [...current, ...response.data]);
|
|
1910
|
+
}
|
|
1911
|
+
else {
|
|
1912
|
+
this.users.set(response.data);
|
|
1913
|
+
}
|
|
1914
|
+
this.total.set(response.meta?.total);
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
catch (error) {
|
|
1918
|
+
if (error.name !== 'AbortError') {
|
|
1919
|
+
this.onError.emit(error);
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
finally {
|
|
1923
|
+
this.isLoading.set(false);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
/** Load users from USER_PROVIDER with active filter */
|
|
1927
|
+
loadUsersFromProvider(filter) {
|
|
1928
|
+
return this.userProvider
|
|
1929
|
+
.getUsers({
|
|
1930
|
+
page: filter.page,
|
|
1931
|
+
pageSize: filter.pageSize,
|
|
1932
|
+
search: filter.search,
|
|
1933
|
+
isActive: this.filterActive() ? true : undefined,
|
|
1934
|
+
})
|
|
1935
|
+
.pipe(map$1((res) => ({
|
|
1936
|
+
...res,
|
|
1937
|
+
data: res.data?.map((u) => ({
|
|
1938
|
+
id: u.id,
|
|
1939
|
+
name: u.name,
|
|
1940
|
+
email: u.email,
|
|
1941
|
+
})),
|
|
1942
|
+
})));
|
|
1943
|
+
}
|
|
1944
|
+
/** Reload users (useful when filters change externally) */
|
|
1945
|
+
reload() {
|
|
1946
|
+
this.pagination.update((p) => ({ ...p, currentPage: 0 }));
|
|
1947
|
+
this.users.set([]);
|
|
1948
|
+
this.fetchUsers();
|
|
1949
|
+
}
|
|
1950
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserMultiSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1951
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.3", type: UserMultiSelectComponent, isStandalone: true, selector: "lib-user-multi-select", 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 }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", usersSelected: "usersSelected", onError: "onError" }, ngImport: i0, template: `
|
|
1952
|
+
<lib-lazy-multi-select
|
|
1953
|
+
[(value)]="value"
|
|
1954
|
+
[placeHolder]="placeHolder()"
|
|
1955
|
+
[isEditMode]="isEditMode()"
|
|
1956
|
+
[isLoading]="isLoading()"
|
|
1957
|
+
[total]="total()"
|
|
1958
|
+
[pagination]="pagination()"
|
|
1959
|
+
[selectDataList]="dropdownUsers()"
|
|
1960
|
+
(onSearch)="handleSearch($event)"
|
|
1961
|
+
(onPagination)="handlePagination($event)"
|
|
1962
|
+
/>
|
|
1963
|
+
`, 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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1964
|
+
}
|
|
1965
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserMultiSelectComponent, decorators: [{
|
|
1966
|
+
type: Component,
|
|
1967
|
+
args: [{
|
|
1968
|
+
selector: 'lib-user-multi-select',
|
|
1969
|
+
standalone: true,
|
|
1970
|
+
imports: [AngularModule, PrimeModule, LazyMultiSelectComponent],
|
|
1971
|
+
template: `
|
|
1972
|
+
<lib-lazy-multi-select
|
|
1973
|
+
[(value)]="value"
|
|
1974
|
+
[placeHolder]="placeHolder()"
|
|
1975
|
+
[isEditMode]="isEditMode()"
|
|
1976
|
+
[isLoading]="isLoading()"
|
|
1977
|
+
[total]="total()"
|
|
1978
|
+
[pagination]="pagination()"
|
|
1979
|
+
[selectDataList]="dropdownUsers()"
|
|
1980
|
+
(onSearch)="handleSearch($event)"
|
|
1981
|
+
(onPagination)="handlePagination($event)"
|
|
1982
|
+
/>
|
|
1983
|
+
`,
|
|
1984
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1985
|
+
}]
|
|
1986
|
+
}], ctorParameters: () => [], propDecorators: { loadUsers: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadUsers", required: false }] }], placeHolder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeHolder", required: false }] }], isEditMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditMode", required: true }] }], filterActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterActive", required: false }] }], additionalFilters: [{ type: i0.Input, args: [{ isSignal: true, alias: "additionalFilters", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], usersSelected: [{ type: i0.Output, args: ["usersSelected"] }], onError: [{ type: i0.Output, args: ["onError"] }] } });
|
|
1987
|
+
|
|
1988
|
+
/**
|
|
1989
|
+
* File Uploader Component - Drag & drop file upload with type filtering.
|
|
1990
|
+
*
|
|
1991
|
+
* Pass your own `uploadFile` function - works with any storage API.
|
|
1992
|
+
*
|
|
1993
|
+
* Features:
|
|
1994
|
+
* - Drag & drop support
|
|
1995
|
+
* - File type filtering (images, documents, etc.)
|
|
1996
|
+
* - Upload progress indication
|
|
1997
|
+
* - Multiple file support (optional)
|
|
1998
|
+
* - Image compression options
|
|
1999
|
+
*
|
|
2000
|
+
* @example
|
|
2001
|
+
* ```typescript
|
|
2002
|
+
* // In component
|
|
2003
|
+
* readonly uploadService = inject(UploadService);
|
|
2004
|
+
*
|
|
2005
|
+
* readonly uploadFile: UploadFileFn = (file, options) =>
|
|
2006
|
+
* this.uploadService.uploadSingleFile(file, options);
|
|
2007
|
+
* ```
|
|
2008
|
+
*
|
|
2009
|
+
* ```html
|
|
2010
|
+
* <!-- Single image upload -->
|
|
2011
|
+
* <lib-file-uploader
|
|
2012
|
+
* [uploadFile]="uploadFile"
|
|
2013
|
+
* [acceptTypes]="['image/*']"
|
|
2014
|
+
* [multiple]="false"
|
|
2015
|
+
* (fileUploaded)="onFileUploaded($event)"
|
|
2016
|
+
* />
|
|
2017
|
+
*
|
|
2018
|
+
* <!-- Multiple document upload -->
|
|
2019
|
+
* <lib-file-uploader
|
|
2020
|
+
* [uploadFile]="uploadFile"
|
|
2021
|
+
* [acceptTypes]="FILE_TYPE_FILTERS.DOCUMENTS"
|
|
2022
|
+
* [multiple]="true"
|
|
2023
|
+
* [maxFiles]="5"
|
|
2024
|
+
* (filesUploaded)="onFilesUploaded($event)"
|
|
2025
|
+
* />
|
|
2026
|
+
* ```
|
|
2027
|
+
*/
|
|
2028
|
+
class FileUploaderComponent {
|
|
2029
|
+
messageService = inject(MessageService);
|
|
2030
|
+
// Required: function to upload file
|
|
2031
|
+
uploadFile = input.required(...(ngDevMode ? [{ debugName: "uploadFile" }] : []));
|
|
2032
|
+
// Inputs
|
|
2033
|
+
acceptTypes = input([], ...(ngDevMode ? [{ debugName: "acceptTypes" }] : []));
|
|
2034
|
+
multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : []));
|
|
2035
|
+
maxFiles = input(10, ...(ngDevMode ? [{ debugName: "maxFiles" }] : []));
|
|
2036
|
+
maxSizeMb = input(10, ...(ngDevMode ? [{ debugName: "maxSizeMb" }] : []));
|
|
2037
|
+
uploadOptions = input({}, ...(ngDevMode ? [{ debugName: "uploadOptions" }] : []));
|
|
2038
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
2039
|
+
showPreview = input(true, ...(ngDevMode ? [{ debugName: "showPreview" }] : []));
|
|
2040
|
+
autoUpload = input(true, ...(ngDevMode ? [{ debugName: "autoUpload" }] : []));
|
|
2041
|
+
// Outputs
|
|
2042
|
+
fileUploaded = output();
|
|
2043
|
+
filesUploaded = output();
|
|
2044
|
+
onError = output();
|
|
2045
|
+
fileSelected = output();
|
|
2046
|
+
// Internal state
|
|
2047
|
+
isUploading = signal(false, ...(ngDevMode ? [{ debugName: "isUploading" }] : []));
|
|
2048
|
+
isDragOver = signal(false, ...(ngDevMode ? [{ debugName: "isDragOver" }] : []));
|
|
2049
|
+
uploadProgress = signal(0, ...(ngDevMode ? [{ debugName: "uploadProgress" }] : []));
|
|
2050
|
+
uploadingFileName = signal('', ...(ngDevMode ? [{ debugName: "uploadingFileName" }] : []));
|
|
2051
|
+
selectedFiles = signal([], ...(ngDevMode ? [{ debugName: "selectedFiles" }] : []));
|
|
2052
|
+
// Computed
|
|
2053
|
+
acceptString = computed(() => getAcceptString(this.acceptTypes()), ...(ngDevMode ? [{ debugName: "acceptString" }] : []));
|
|
2054
|
+
acceptTypesDisplay = computed(() => {
|
|
2055
|
+
const types = this.acceptTypes();
|
|
2056
|
+
if (!types.length)
|
|
2057
|
+
return '';
|
|
2058
|
+
// Check if types match predefined filters
|
|
2059
|
+
const typesStr = JSON.stringify(types);
|
|
2060
|
+
if (typesStr === JSON.stringify(FILE_TYPE_FILTERS.IMAGES))
|
|
2061
|
+
return 'Images';
|
|
2062
|
+
if (typesStr === JSON.stringify(FILE_TYPE_FILTERS.DOCUMENTS))
|
|
2063
|
+
return 'Documents';
|
|
2064
|
+
if (typesStr === JSON.stringify(FILE_TYPE_FILTERS.VIDEOS))
|
|
2065
|
+
return 'Videos';
|
|
2066
|
+
if (typesStr === JSON.stringify(FILE_TYPE_FILTERS.AUDIO))
|
|
2067
|
+
return 'Audio';
|
|
2068
|
+
return types.map(t => t.split('/')[1] || t).join(', ');
|
|
2069
|
+
}, ...(ngDevMode ? [{ debugName: "acceptTypesDisplay" }] : []));
|
|
2070
|
+
onDragOver(event) {
|
|
2071
|
+
event.preventDefault();
|
|
2072
|
+
event.stopPropagation();
|
|
2073
|
+
if (!this.disabled() && !this.isUploading()) {
|
|
2074
|
+
this.isDragOver.set(true);
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
onDragLeave(event) {
|
|
2078
|
+
event.preventDefault();
|
|
2079
|
+
event.stopPropagation();
|
|
2080
|
+
this.isDragOver.set(false);
|
|
2081
|
+
}
|
|
2082
|
+
onDrop(event) {
|
|
2083
|
+
event.preventDefault();
|
|
2084
|
+
event.stopPropagation();
|
|
2085
|
+
this.isDragOver.set(false);
|
|
2086
|
+
if (this.disabled() || this.isUploading())
|
|
2087
|
+
return;
|
|
2088
|
+
const files = event.dataTransfer?.files;
|
|
2089
|
+
if (files?.length) {
|
|
2090
|
+
this.handleFiles(Array.from(files));
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
onFileSelected(event) {
|
|
2094
|
+
const input = event.target;
|
|
2095
|
+
const files = input.files;
|
|
2096
|
+
if (files?.length) {
|
|
2097
|
+
this.handleFiles(Array.from(files));
|
|
2098
|
+
}
|
|
2099
|
+
// Reset input to allow selecting same file again
|
|
2100
|
+
input.value = '';
|
|
2101
|
+
}
|
|
2102
|
+
handleFiles(files) {
|
|
2103
|
+
// Filter by type
|
|
2104
|
+
const allowedTypes = this.acceptTypes();
|
|
2105
|
+
const validFiles = files.filter(file => {
|
|
2106
|
+
if (!isFileTypeAllowed(file, allowedTypes)) {
|
|
2107
|
+
this.messageService.add({
|
|
2108
|
+
severity: 'warn',
|
|
2109
|
+
summary: 'Invalid File Type',
|
|
2110
|
+
detail: `File type not allowed: ${file.name}`,
|
|
2111
|
+
});
|
|
2112
|
+
return false;
|
|
2113
|
+
}
|
|
2114
|
+
return true;
|
|
2115
|
+
});
|
|
2116
|
+
// Filter by size
|
|
2117
|
+
const maxSize = this.maxSizeMb() * 1024 * 1024;
|
|
2118
|
+
const sizeValidFiles = validFiles.filter(file => {
|
|
2119
|
+
if (file.size > maxSize) {
|
|
2120
|
+
this.messageService.add({
|
|
2121
|
+
severity: 'warn',
|
|
2122
|
+
summary: 'File Too Large',
|
|
2123
|
+
detail: `${file.name} exceeds ${this.maxSizeMb()}MB limit`,
|
|
2124
|
+
});
|
|
2125
|
+
return false;
|
|
2126
|
+
}
|
|
2127
|
+
return true;
|
|
2128
|
+
});
|
|
2129
|
+
// Limit number of files
|
|
2130
|
+
const limitedFiles = this.multiple()
|
|
2131
|
+
? sizeValidFiles.slice(0, this.maxFiles())
|
|
2132
|
+
: sizeValidFiles.slice(0, 1);
|
|
2133
|
+
if (!limitedFiles.length)
|
|
2134
|
+
return;
|
|
2135
|
+
this.selectedFiles.set(limitedFiles);
|
|
2136
|
+
this.fileSelected.emit(limitedFiles);
|
|
2137
|
+
if (this.autoUpload()) {
|
|
2138
|
+
this.uploadFiles(limitedFiles);
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
removeFile(file) {
|
|
2142
|
+
this.selectedFiles.update(files => files.filter(f => f !== file));
|
|
2143
|
+
}
|
|
2144
|
+
async uploadFiles(files) {
|
|
2145
|
+
const filesToUpload = files ?? this.selectedFiles();
|
|
2146
|
+
if (!filesToUpload.length || this.isUploading())
|
|
2147
|
+
return;
|
|
2148
|
+
this.isUploading.set(true);
|
|
2149
|
+
const uploadedFiles = [];
|
|
2150
|
+
try {
|
|
2151
|
+
for (const file of filesToUpload) {
|
|
2152
|
+
this.uploadingFileName.set(file.name);
|
|
2153
|
+
this.uploadProgress.set(0);
|
|
2154
|
+
const response = await firstValueFrom(this.uploadFile()(file, this.uploadOptions()));
|
|
2155
|
+
if (response.success && response.data) {
|
|
2156
|
+
uploadedFiles.push(response.data);
|
|
2157
|
+
this.fileUploaded.emit(response.data);
|
|
2158
|
+
}
|
|
2159
|
+
else {
|
|
2160
|
+
throw new Error(response.message || 'Upload failed');
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
this.filesUploaded.emit(uploadedFiles);
|
|
2164
|
+
this.selectedFiles.set([]);
|
|
2165
|
+
this.messageService.add({
|
|
2166
|
+
severity: 'success',
|
|
2167
|
+
summary: 'Upload Complete',
|
|
2168
|
+
detail: `${uploadedFiles.length} file(s) uploaded successfully`,
|
|
2169
|
+
});
|
|
2170
|
+
}
|
|
2171
|
+
catch (error) {
|
|
2172
|
+
this.messageService.add({
|
|
2173
|
+
severity: 'error',
|
|
2174
|
+
summary: 'Upload Failed',
|
|
2175
|
+
detail: error.message || 'Failed to upload file',
|
|
2176
|
+
});
|
|
2177
|
+
this.onError.emit(error);
|
|
2178
|
+
}
|
|
2179
|
+
finally {
|
|
2180
|
+
this.isUploading.set(false);
|
|
2181
|
+
this.uploadingFileName.set('');
|
|
2182
|
+
this.uploadProgress.set(0);
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
getFileIcon(file) {
|
|
2186
|
+
return getFileIconClass(file.type);
|
|
2187
|
+
}
|
|
2188
|
+
formatSize(bytes) {
|
|
2189
|
+
const kb = bytes / 1024;
|
|
2190
|
+
if (kb < 1024)
|
|
2191
|
+
return `${kb.toFixed(1)} KB`;
|
|
2192
|
+
const mb = kb / 1024;
|
|
2193
|
+
return `${mb.toFixed(1)} MB`;
|
|
2194
|
+
}
|
|
2195
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileUploaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2196
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", 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: `
|
|
2197
|
+
<div
|
|
2198
|
+
class="file-uploader"
|
|
2199
|
+
[class.drag-over]="isDragOver()"
|
|
2200
|
+
[class.disabled]="disabled()"
|
|
2201
|
+
(dragover)="onDragOver($event)"
|
|
2202
|
+
(dragleave)="onDragLeave($event)"
|
|
2203
|
+
(drop)="onDrop($event)"
|
|
2204
|
+
>
|
|
2205
|
+
<!-- Upload Area -->
|
|
2206
|
+
<div class="upload-area" (click)="fileInput.click()">
|
|
2207
|
+
@if (isUploading()) {
|
|
2208
|
+
<div class="uploading-state">
|
|
2209
|
+
<i class="pi pi-spin pi-spinner text-4xl text-primary"></i>
|
|
2210
|
+
<p class="mt-2">Uploading {{ uploadingFileName() }}...</p>
|
|
2211
|
+
@if (uploadProgress() > 0) {
|
|
2212
|
+
<p-progressBar [value]="uploadProgress()" [showValue]="true" />
|
|
2213
|
+
}
|
|
2214
|
+
</div>
|
|
2215
|
+
} @else {
|
|
2216
|
+
<div class="idle-state text-center">
|
|
2217
|
+
<i class="pi pi-cloud-upload text-4xl text-primary"></i>
|
|
2218
|
+
<p class="mt-2 mb-1 font-semibold">
|
|
2219
|
+
{{ multiple() ? 'Drop files here or click to upload' : 'Drop file here or click to upload' }}
|
|
2220
|
+
</p>
|
|
2221
|
+
<p class="text-sm text-color-secondary">
|
|
2222
|
+
@if (acceptTypesDisplay()) {
|
|
2223
|
+
Allowed: {{ acceptTypesDisplay() }}
|
|
2224
|
+
} @else {
|
|
2225
|
+
All file types allowed
|
|
2226
|
+
}
|
|
2227
|
+
@if (maxSizeMb()) {
|
|
2228
|
+
(Max {{ maxSizeMb() }}MB)
|
|
2229
|
+
}
|
|
2230
|
+
</p>
|
|
2231
|
+
</div>
|
|
2232
|
+
}
|
|
2233
|
+
</div>
|
|
2234
|
+
|
|
2235
|
+
<!-- Hidden File Input -->
|
|
2236
|
+
<input
|
|
2237
|
+
#fileInput
|
|
2238
|
+
type="file"
|
|
2239
|
+
[accept]="acceptString()"
|
|
2240
|
+
[multiple]="multiple()"
|
|
2241
|
+
[disabled]="disabled() || isUploading()"
|
|
2242
|
+
(change)="onFileSelected($event)"
|
|
2243
|
+
class="hidden"
|
|
2244
|
+
/>
|
|
2245
|
+
|
|
2246
|
+
<!-- Selected Files Preview -->
|
|
2247
|
+
@if (selectedFiles().length > 0 && showPreview()) {
|
|
2248
|
+
<div class="selected-files mt-3">
|
|
2249
|
+
@for (file of selectedFiles(); track file.name) {
|
|
2250
|
+
<div class="file-item flex align-items-center gap-2 p-2 border-round surface-border border-1 mb-2">
|
|
2251
|
+
<i [class]="getFileIcon(file)"></i>
|
|
2252
|
+
<span class="flex-1 text-overflow-ellipsis overflow-hidden">{{ file.name }}</span>
|
|
2253
|
+
<span class="text-sm text-color-secondary">{{ formatSize(file.size) }}</span>
|
|
2254
|
+
<button
|
|
2255
|
+
pButton
|
|
2256
|
+
type="button"
|
|
2257
|
+
icon="pi pi-times"
|
|
2258
|
+
class="p-button-text p-button-rounded p-button-sm"
|
|
2259
|
+
(click)="removeFile(file)"
|
|
2260
|
+
[disabled]="isUploading()"
|
|
2261
|
+
></button>
|
|
2262
|
+
</div>
|
|
2263
|
+
}
|
|
2264
|
+
</div>
|
|
2265
|
+
}
|
|
2266
|
+
</div>
|
|
2267
|
+
`, isInline: true, styles: [".file-uploader{width:100%}.upload-area{border:2px dashed var(--surface-border);border-radius:var(--border-radius);padding:2rem;cursor:pointer;transition:all .2s;background:var(--surface-ground)}.upload-area:hover{border-color:var(--primary-color);background:var(--surface-hover)}.drag-over .upload-area{border-color:var(--primary-color);background:var(--primary-100)}.disabled .upload-area{opacity:.6;cursor:not-allowed}.hidden{display:none}\n"], dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "ngmodule", type: PrimeModule }, { kind: "directive", type: i1$3.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: i2$1.ProgressBar, selector: "p-progressBar, p-progressbar, p-progress-bar", inputs: ["value", "showValue", "styleClass", "valueStyleClass", "unit", "mode", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2268
|
+
}
|
|
2269
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileUploaderComponent, decorators: [{
|
|
2270
|
+
type: Component,
|
|
2271
|
+
args: [{ selector: 'lib-file-uploader', standalone: true, imports: [AngularModule, PrimeModule], template: `
|
|
2272
|
+
<div
|
|
2273
|
+
class="file-uploader"
|
|
2274
|
+
[class.drag-over]="isDragOver()"
|
|
2275
|
+
[class.disabled]="disabled()"
|
|
2276
|
+
(dragover)="onDragOver($event)"
|
|
2277
|
+
(dragleave)="onDragLeave($event)"
|
|
2278
|
+
(drop)="onDrop($event)"
|
|
2279
|
+
>
|
|
2280
|
+
<!-- Upload Area -->
|
|
2281
|
+
<div class="upload-area" (click)="fileInput.click()">
|
|
2282
|
+
@if (isUploading()) {
|
|
2283
|
+
<div class="uploading-state">
|
|
2284
|
+
<i class="pi pi-spin pi-spinner text-4xl text-primary"></i>
|
|
2285
|
+
<p class="mt-2">Uploading {{ uploadingFileName() }}...</p>
|
|
2286
|
+
@if (uploadProgress() > 0) {
|
|
2287
|
+
<p-progressBar [value]="uploadProgress()" [showValue]="true" />
|
|
2288
|
+
}
|
|
2289
|
+
</div>
|
|
2290
|
+
} @else {
|
|
2291
|
+
<div class="idle-state text-center">
|
|
2292
|
+
<i class="pi pi-cloud-upload text-4xl text-primary"></i>
|
|
2293
|
+
<p class="mt-2 mb-1 font-semibold">
|
|
2294
|
+
{{ multiple() ? 'Drop files here or click to upload' : 'Drop file here or click to upload' }}
|
|
2295
|
+
</p>
|
|
2296
|
+
<p class="text-sm text-color-secondary">
|
|
2297
|
+
@if (acceptTypesDisplay()) {
|
|
2298
|
+
Allowed: {{ acceptTypesDisplay() }}
|
|
2299
|
+
} @else {
|
|
2300
|
+
All file types allowed
|
|
2301
|
+
}
|
|
2302
|
+
@if (maxSizeMb()) {
|
|
2303
|
+
(Max {{ maxSizeMb() }}MB)
|
|
2304
|
+
}
|
|
2305
|
+
</p>
|
|
2306
|
+
</div>
|
|
2307
|
+
}
|
|
2308
|
+
</div>
|
|
2309
|
+
|
|
2310
|
+
<!-- Hidden File Input -->
|
|
2311
|
+
<input
|
|
2312
|
+
#fileInput
|
|
2313
|
+
type="file"
|
|
2314
|
+
[accept]="acceptString()"
|
|
2315
|
+
[multiple]="multiple()"
|
|
2316
|
+
[disabled]="disabled() || isUploading()"
|
|
2317
|
+
(change)="onFileSelected($event)"
|
|
2318
|
+
class="hidden"
|
|
2319
|
+
/>
|
|
2320
|
+
|
|
2321
|
+
<!-- Selected Files Preview -->
|
|
2322
|
+
@if (selectedFiles().length > 0 && showPreview()) {
|
|
2323
|
+
<div class="selected-files mt-3">
|
|
2324
|
+
@for (file of selectedFiles(); track file.name) {
|
|
2325
|
+
<div class="file-item flex align-items-center gap-2 p-2 border-round surface-border border-1 mb-2">
|
|
2326
|
+
<i [class]="getFileIcon(file)"></i>
|
|
2327
|
+
<span class="flex-1 text-overflow-ellipsis overflow-hidden">{{ file.name }}</span>
|
|
2328
|
+
<span class="text-sm text-color-secondary">{{ formatSize(file.size) }}</span>
|
|
2329
|
+
<button
|
|
2330
|
+
pButton
|
|
2331
|
+
type="button"
|
|
2332
|
+
icon="pi pi-times"
|
|
2333
|
+
class="p-button-text p-button-rounded p-button-sm"
|
|
2334
|
+
(click)="removeFile(file)"
|
|
2335
|
+
[disabled]="isUploading()"
|
|
2336
|
+
></button>
|
|
2337
|
+
</div>
|
|
2338
|
+
}
|
|
2339
|
+
</div>
|
|
2340
|
+
}
|
|
2341
|
+
</div>
|
|
2342
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".file-uploader{width:100%}.upload-area{border:2px dashed var(--surface-border);border-radius:var(--border-radius);padding:2rem;cursor:pointer;transition:all .2s;background:var(--surface-ground)}.upload-area:hover{border-color:var(--primary-color);background:var(--surface-hover)}.drag-over .upload-area{border-color:var(--primary-color);background:var(--primary-100)}.disabled .upload-area{opacity:.6;cursor:not-allowed}.hidden{display:none}\n"] }]
|
|
2343
|
+
}], propDecorators: { uploadFile: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadFile", required: true }] }], 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"] }] } });
|
|
2344
|
+
|
|
2345
|
+
const DEFAULT_PAGE_SIZE = 20;
|
|
2346
|
+
/**
|
|
2347
|
+
* File Selector Dialog - Browse and select existing files with filtering.
|
|
2348
|
+
*
|
|
2349
|
+
* Pass your own `loadFiles` function - works with any storage API.
|
|
2350
|
+
*
|
|
2351
|
+
* Features:
|
|
2352
|
+
* - Search with debouncing
|
|
2353
|
+
* - File type filtering
|
|
2354
|
+
* - Infinite scroll pagination
|
|
2355
|
+
* - Single or multiple selection
|
|
2356
|
+
* - File preview with icons
|
|
2357
|
+
*
|
|
2358
|
+
* @example
|
|
2359
|
+
* ```typescript
|
|
2360
|
+
* // In component
|
|
2361
|
+
* readonly fileService = inject(FileManagerApiService);
|
|
2362
|
+
*
|
|
2363
|
+
* readonly loadFiles: LoadFilesFn = (filter) =>
|
|
2364
|
+
* this.fileService.getAll(filter.search, {
|
|
2365
|
+
* pagination: { currentPage: filter.page, pageSize: filter.pageSize },
|
|
2366
|
+
* filter: { contentTypes: filter.contentTypes },
|
|
2367
|
+
* }).pipe(
|
|
2368
|
+
* map(res => ({
|
|
2369
|
+
* ...res,
|
|
2370
|
+
* data: res.data?.map(f => ({
|
|
2371
|
+
* id: f.id,
|
|
2372
|
+
* name: f.name,
|
|
2373
|
+
* contentType: f.contentType,
|
|
2374
|
+
* size: f.size,
|
|
2375
|
+
* url: f.url
|
|
2376
|
+
* }))
|
|
2377
|
+
* }))
|
|
2378
|
+
* );
|
|
2379
|
+
* ```
|
|
2380
|
+
*
|
|
2381
|
+
* ```html
|
|
2382
|
+
* <lib-file-selector-dialog
|
|
2383
|
+
* [(visible)]="showFileSelector"
|
|
2384
|
+
* [loadFiles]="loadFiles"
|
|
2385
|
+
* [acceptTypes]="['image/*']"
|
|
2386
|
+
* [multiple]="false"
|
|
2387
|
+
* (fileSelected)="onFileSelected($event)"
|
|
2388
|
+
* />
|
|
2389
|
+
* ```
|
|
2390
|
+
*/
|
|
2391
|
+
class FileSelectorDialogComponent {
|
|
2392
|
+
destroyRef = inject(DestroyRef);
|
|
2393
|
+
abortController = null;
|
|
2394
|
+
searchDebounceTimer = null;
|
|
2395
|
+
// Required: function to load files
|
|
2396
|
+
loadFiles = input.required(...(ngDevMode ? [{ debugName: "loadFiles" }] : []));
|
|
2397
|
+
// Inputs
|
|
2398
|
+
header = input('Select File', ...(ngDevMode ? [{ debugName: "header" }] : []));
|
|
2399
|
+
acceptTypes = input([], ...(ngDevMode ? [{ debugName: "acceptTypes" }] : []));
|
|
2400
|
+
multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : []));
|
|
2401
|
+
maxSelection = input(10, ...(ngDevMode ? [{ debugName: "maxSelection" }] : []));
|
|
2402
|
+
pageSize = input(DEFAULT_PAGE_SIZE, ...(ngDevMode ? [{ debugName: "pageSize" }] : []));
|
|
2403
|
+
// Two-way visibility binding
|
|
2404
|
+
visible = model(false, ...(ngDevMode ? [{ debugName: "visible" }] : []));
|
|
2405
|
+
// Outputs
|
|
2406
|
+
fileSelected = output();
|
|
2407
|
+
filesSelected = output();
|
|
2408
|
+
closed = output();
|
|
2409
|
+
onError = output();
|
|
2410
|
+
// Internal state
|
|
2411
|
+
isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
2412
|
+
files = signal([], ...(ngDevMode ? [{ debugName: "files" }] : []));
|
|
2413
|
+
selectedFiles = signal([], ...(ngDevMode ? [{ debugName: "selectedFiles" }] : []));
|
|
2414
|
+
total = signal(undefined, ...(ngDevMode ? [{ debugName: "total" }] : []));
|
|
2415
|
+
pagination = signal({ pageSize: DEFAULT_PAGE_SIZE, currentPage: 0 }, ...(ngDevMode ? [{ debugName: "pagination" }] : []));
|
|
2416
|
+
searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
|
|
2417
|
+
// Computed
|
|
2418
|
+
acceptString = computed(() => getAcceptString(this.acceptTypes()), ...(ngDevMode ? [{ debugName: "acceptString" }] : []));
|
|
2419
|
+
constructor() {
|
|
2420
|
+
this.destroyRef.onDestroy(() => {
|
|
2421
|
+
this.abortController?.abort();
|
|
2422
|
+
if (this.searchDebounceTimer) {
|
|
2423
|
+
clearTimeout(this.searchDebounceTimer);
|
|
2424
|
+
}
|
|
2425
|
+
});
|
|
2426
|
+
// Load files when dialog becomes visible
|
|
2427
|
+
effect(() => {
|
|
2428
|
+
const isVisible = this.visible();
|
|
2429
|
+
if (isVisible) {
|
|
2430
|
+
untracked(() => {
|
|
2431
|
+
this.resetState();
|
|
2432
|
+
this.fetchFiles();
|
|
2433
|
+
});
|
|
2434
|
+
}
|
|
2435
|
+
});
|
|
2436
|
+
// Update page size from input
|
|
2437
|
+
effect(() => {
|
|
2438
|
+
const size = this.pageSize();
|
|
2439
|
+
untracked(() => {
|
|
2440
|
+
this.pagination.update((p) => ({ ...p, pageSize: size }));
|
|
2441
|
+
});
|
|
2442
|
+
});
|
|
2443
|
+
}
|
|
2444
|
+
onSearchChange(value) {
|
|
2445
|
+
// Debounce search
|
|
2446
|
+
if (this.searchDebounceTimer) {
|
|
2447
|
+
clearTimeout(this.searchDebounceTimer);
|
|
2448
|
+
}
|
|
2449
|
+
this.searchDebounceTimer = setTimeout(() => {
|
|
2450
|
+
this.searchTerm.set(value);
|
|
2451
|
+
this.pagination.update((p) => ({ ...p, currentPage: 0 }));
|
|
2452
|
+
this.files.set([]);
|
|
2453
|
+
this.fetchFiles();
|
|
2454
|
+
}, 500);
|
|
2455
|
+
}
|
|
2456
|
+
onScroll(event) {
|
|
2457
|
+
const el = event.target;
|
|
2458
|
+
const nearBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 50;
|
|
2459
|
+
if (nearBottom && !this.isLoading()) {
|
|
2460
|
+
const pag = this.pagination();
|
|
2461
|
+
const nextPage = pag.currentPage + 1;
|
|
2462
|
+
const hasMore = nextPage * pag.pageSize < (this.total() ?? 0);
|
|
2463
|
+
if (hasMore) {
|
|
2464
|
+
this.pagination.update((p) => ({ ...p, currentPage: nextPage }));
|
|
2465
|
+
this.fetchFiles(true);
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
toggleSelection(file) {
|
|
2470
|
+
if (!this.isFileAllowed(file))
|
|
2471
|
+
return;
|
|
2472
|
+
if (this.multiple()) {
|
|
2473
|
+
const selected = this.selectedFiles();
|
|
2474
|
+
const isSelected = selected.some((f) => f.id === file.id);
|
|
2475
|
+
if (isSelected) {
|
|
2476
|
+
this.selectedFiles.update((files) => files.filter((f) => f.id !== file.id));
|
|
2477
|
+
}
|
|
2478
|
+
else if (selected.length < this.maxSelection()) {
|
|
2479
|
+
this.selectedFiles.update((files) => [...files, file]);
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
else {
|
|
2483
|
+
this.selectedFiles.set([file]);
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
isSelected(file) {
|
|
2487
|
+
return this.selectedFiles().some((f) => f.id === file.id);
|
|
2488
|
+
}
|
|
2489
|
+
isFileAllowed(file) {
|
|
2490
|
+
const allowedTypes = this.acceptTypes();
|
|
2491
|
+
if (!allowedTypes.length)
|
|
2492
|
+
return true;
|
|
2493
|
+
return allowedTypes.some((type) => {
|
|
2494
|
+
if (type.endsWith('/*')) {
|
|
2495
|
+
return file.contentType.startsWith(type.replace('/*', '/'));
|
|
2496
|
+
}
|
|
2497
|
+
return file.contentType === type;
|
|
2498
|
+
});
|
|
2499
|
+
}
|
|
2500
|
+
isImage(file) {
|
|
2501
|
+
return file.contentType.startsWith('image/') && !!file.url;
|
|
2502
|
+
}
|
|
2503
|
+
getFileIcon(file) {
|
|
2504
|
+
return getFileIconClass(file.contentType);
|
|
2505
|
+
}
|
|
2506
|
+
formatSize(size) {
|
|
2507
|
+
return formatFileSize(size);
|
|
2508
|
+
}
|
|
2509
|
+
onCancel() {
|
|
2510
|
+
this.visible.set(false);
|
|
2511
|
+
}
|
|
2512
|
+
onConfirm() {
|
|
2513
|
+
const selected = this.selectedFiles();
|
|
2514
|
+
if (selected.length === 0)
|
|
2515
|
+
return;
|
|
2516
|
+
if (this.multiple()) {
|
|
2517
|
+
this.filesSelected.emit(selected);
|
|
2518
|
+
}
|
|
2519
|
+
else {
|
|
2520
|
+
this.fileSelected.emit(selected[0]);
|
|
2521
|
+
}
|
|
2522
|
+
this.visible.set(false);
|
|
2523
|
+
}
|
|
2524
|
+
onDialogHide() {
|
|
2525
|
+
this.closed.emit();
|
|
2526
|
+
}
|
|
2527
|
+
resetState() {
|
|
2528
|
+
this.files.set([]);
|
|
2529
|
+
this.selectedFiles.set([]);
|
|
2530
|
+
this.searchTerm.set('');
|
|
2531
|
+
this.pagination.set({ pageSize: this.pageSize(), currentPage: 0 });
|
|
2532
|
+
this.total.set(undefined);
|
|
2533
|
+
}
|
|
2534
|
+
async fetchFiles(append = false) {
|
|
2535
|
+
if (this.isLoading())
|
|
2536
|
+
return;
|
|
2537
|
+
this.abortController?.abort();
|
|
2538
|
+
this.abortController = new AbortController();
|
|
2539
|
+
this.isLoading.set(true);
|
|
2540
|
+
try {
|
|
2541
|
+
const pag = this.pagination();
|
|
2542
|
+
const filter = {
|
|
2543
|
+
page: pag.currentPage,
|
|
2544
|
+
pageSize: pag.pageSize,
|
|
2545
|
+
search: this.searchTerm(),
|
|
2546
|
+
contentTypes: this.acceptTypes().length ? this.acceptTypes() : undefined,
|
|
2547
|
+
};
|
|
2548
|
+
const response = await firstValueFrom(this.loadFiles()(filter));
|
|
2549
|
+
if (response.success && response.data) {
|
|
2550
|
+
if (append) {
|
|
2551
|
+
this.files.update((current) => [...current, ...response.data]);
|
|
2552
|
+
}
|
|
2553
|
+
else {
|
|
2554
|
+
this.files.set(response.data);
|
|
2555
|
+
}
|
|
2556
|
+
this.total.set(response.meta?.total);
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
catch (error) {
|
|
2560
|
+
if (error.name !== 'AbortError') {
|
|
2561
|
+
this.onError.emit(error);
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
finally {
|
|
2565
|
+
this.isLoading.set(false);
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileSelectorDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2569
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: FileSelectorDialogComponent, isStandalone: true, selector: "lib-file-selector-dialog", inputs: { loadFiles: { classPropertyName: "loadFiles", publicName: "loadFiles", isSignal: true, isRequired: true, transformFunction: null }, 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 }, 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: `
|
|
2570
|
+
<p-dialog
|
|
2571
|
+
[header]="header()"
|
|
2572
|
+
[(visible)]="visible"
|
|
2573
|
+
[modal]="true"
|
|
2574
|
+
[closable]="true"
|
|
2575
|
+
[draggable]="false"
|
|
2576
|
+
[resizable]="false"
|
|
2577
|
+
[style]="{ width: '800px', maxHeight: '90vh' }"
|
|
2578
|
+
(onHide)="onDialogHide()"
|
|
2579
|
+
>
|
|
2580
|
+
<!-- Search Bar -->
|
|
2581
|
+
<div class="flex gap-2 mb-3">
|
|
2582
|
+
<span class="p-input-icon-left flex-1">
|
|
2583
|
+
<i class="pi pi-search"></i>
|
|
2584
|
+
<input
|
|
2585
|
+
pInputText
|
|
2586
|
+
type="text"
|
|
2587
|
+
[ngModel]="searchTerm()"
|
|
2588
|
+
(ngModelChange)="onSearchChange($event)"
|
|
2589
|
+
placeholder="Search files..."
|
|
2590
|
+
class="w-full"
|
|
2591
|
+
/>
|
|
2592
|
+
</span>
|
|
2593
|
+
@if (multiple()) {
|
|
2594
|
+
<span class="text-sm text-color-secondary align-self-center">
|
|
2595
|
+
{{ selectedFiles().length }} selected
|
|
2596
|
+
</span>
|
|
2597
|
+
}
|
|
2598
|
+
</div>
|
|
2599
|
+
|
|
2600
|
+
<!-- File Grid -->
|
|
2601
|
+
<div
|
|
2602
|
+
class="file-grid"
|
|
2603
|
+
#scrollContainer
|
|
2604
|
+
(scroll)="onScroll($event)"
|
|
2605
|
+
>
|
|
2606
|
+
@if (isLoading() && files().length === 0) {
|
|
2607
|
+
<div class="flex justify-content-center p-4">
|
|
2608
|
+
<i class="pi pi-spin pi-spinner text-4xl"></i>
|
|
2609
|
+
</div>
|
|
2610
|
+
} @else if (files().length === 0) {
|
|
2611
|
+
<div class="text-center p-4 text-color-secondary">
|
|
2612
|
+
<i class="pi pi-inbox text-4xl mb-2"></i>
|
|
2613
|
+
<p>No files found</p>
|
|
2614
|
+
</div>
|
|
2615
|
+
} @else {
|
|
2616
|
+
@for (file of files(); track file.id) {
|
|
2617
|
+
<div
|
|
2618
|
+
class="file-card"
|
|
2619
|
+
[class.selected]="isSelected(file)"
|
|
2620
|
+
[class.disabled]="!isFileAllowed(file)"
|
|
2621
|
+
(click)="toggleSelection(file)"
|
|
2622
|
+
>
|
|
2623
|
+
<!-- File Preview -->
|
|
2624
|
+
<div class="file-preview">
|
|
2625
|
+
@if (isImage(file) && file.url) {
|
|
2626
|
+
<img [src]="file.url" [alt]="file.name" class="preview-image" />
|
|
2627
|
+
} @else {
|
|
2628
|
+
<i [class]="getFileIcon(file)" class="preview-icon"></i>
|
|
2629
|
+
}
|
|
2630
|
+
@if (isSelected(file)) {
|
|
2631
|
+
<div class="selected-overlay">
|
|
2632
|
+
<i class="pi pi-check"></i>
|
|
2633
|
+
</div>
|
|
2634
|
+
}
|
|
2635
|
+
</div>
|
|
2636
|
+
|
|
2637
|
+
<!-- File Info -->
|
|
2638
|
+
<div class="file-info">
|
|
2639
|
+
<span class="file-name" [title]="file.name">{{ file.name }}</span>
|
|
2640
|
+
<span class="file-size">{{ formatSize(file.size) }}</span>
|
|
2641
|
+
</div>
|
|
2642
|
+
</div>
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
@if (isLoading()) {
|
|
2646
|
+
<div class="flex justify-content-center p-2 w-full">
|
|
2647
|
+
<i class="pi pi-spin pi-spinner"></i>
|
|
2648
|
+
</div>
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
</div>
|
|
2652
|
+
|
|
2653
|
+
<!-- Footer -->
|
|
2654
|
+
<ng-template pTemplate="footer">
|
|
2655
|
+
<button
|
|
2656
|
+
pButton
|
|
2657
|
+
label="Cancel"
|
|
2658
|
+
class="p-button-text"
|
|
2659
|
+
(click)="onCancel()"
|
|
2660
|
+
></button>
|
|
2661
|
+
<button
|
|
2662
|
+
pButton
|
|
2663
|
+
[label]="multiple() ? 'Select (' + selectedFiles().length + ')' : 'Select'"
|
|
2664
|
+
[disabled]="selectedFiles().length === 0"
|
|
2665
|
+
(click)="onConfirm()"
|
|
2666
|
+
></button>
|
|
2667
|
+
</ng-template>
|
|
2668
|
+
</p-dialog>
|
|
2669
|
+
`, isInline: true, styles: [".file-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:1rem;max-height:400px;overflow-y:auto;padding:.5rem}.file-card{border:2px solid var(--surface-border);border-radius:var(--border-radius);cursor:pointer;transition:all .2s;overflow:hidden}.file-card:hover:not(.disabled){border-color:var(--primary-color)}.file-card.selected{border-color:var(--primary-color);background:var(--primary-50)}.file-card.disabled{opacity:.5;cursor:not-allowed}.file-preview{position:relative;height:100px;display:flex;align-items:center;justify-content:center;background:var(--surface-ground)}.preview-image{width:100%;height:100%;object-fit:cover}.preview-icon{font-size:3rem;color:var(--text-color-secondary)}.selected-overlay{position:absolute;inset:0;background:rgba(var(--primary-color-rgb),.3);display:flex;align-items:center;justify-content:center}.selected-overlay i{font-size:2rem;color:var(--primary-color);background:#fff;border-radius:50%;padding:.5rem}.file-info{padding:.5rem;text-align:center}.file-name{display:block;font-size:.875rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.file-size{display:block;font-size:.75rem;color:var(--text-color-secondary)}\n"], dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$2.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$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.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$3.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: "directive", type: i2.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2670
|
+
}
|
|
2671
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileSelectorDialogComponent, decorators: [{
|
|
2672
|
+
type: Component,
|
|
2673
|
+
args: [{ selector: 'lib-file-selector-dialog', standalone: true, imports: [AngularModule, PrimeModule], template: `
|
|
2674
|
+
<p-dialog
|
|
2675
|
+
[header]="header()"
|
|
2676
|
+
[(visible)]="visible"
|
|
2677
|
+
[modal]="true"
|
|
2678
|
+
[closable]="true"
|
|
2679
|
+
[draggable]="false"
|
|
2680
|
+
[resizable]="false"
|
|
2681
|
+
[style]="{ width: '800px', maxHeight: '90vh' }"
|
|
2682
|
+
(onHide)="onDialogHide()"
|
|
2683
|
+
>
|
|
2684
|
+
<!-- Search Bar -->
|
|
2685
|
+
<div class="flex gap-2 mb-3">
|
|
2686
|
+
<span class="p-input-icon-left flex-1">
|
|
2687
|
+
<i class="pi pi-search"></i>
|
|
2688
|
+
<input
|
|
2689
|
+
pInputText
|
|
2690
|
+
type="text"
|
|
2691
|
+
[ngModel]="searchTerm()"
|
|
2692
|
+
(ngModelChange)="onSearchChange($event)"
|
|
2693
|
+
placeholder="Search files..."
|
|
2694
|
+
class="w-full"
|
|
2695
|
+
/>
|
|
2696
|
+
</span>
|
|
2697
|
+
@if (multiple()) {
|
|
2698
|
+
<span class="text-sm text-color-secondary align-self-center">
|
|
2699
|
+
{{ selectedFiles().length }} selected
|
|
2700
|
+
</span>
|
|
2701
|
+
}
|
|
2702
|
+
</div>
|
|
2703
|
+
|
|
2704
|
+
<!-- File Grid -->
|
|
2705
|
+
<div
|
|
2706
|
+
class="file-grid"
|
|
2707
|
+
#scrollContainer
|
|
2708
|
+
(scroll)="onScroll($event)"
|
|
2709
|
+
>
|
|
2710
|
+
@if (isLoading() && files().length === 0) {
|
|
2711
|
+
<div class="flex justify-content-center p-4">
|
|
2712
|
+
<i class="pi pi-spin pi-spinner text-4xl"></i>
|
|
2713
|
+
</div>
|
|
2714
|
+
} @else if (files().length === 0) {
|
|
2715
|
+
<div class="text-center p-4 text-color-secondary">
|
|
2716
|
+
<i class="pi pi-inbox text-4xl mb-2"></i>
|
|
2717
|
+
<p>No files found</p>
|
|
2718
|
+
</div>
|
|
2719
|
+
} @else {
|
|
2720
|
+
@for (file of files(); track file.id) {
|
|
2721
|
+
<div
|
|
2722
|
+
class="file-card"
|
|
2723
|
+
[class.selected]="isSelected(file)"
|
|
2724
|
+
[class.disabled]="!isFileAllowed(file)"
|
|
2725
|
+
(click)="toggleSelection(file)"
|
|
2726
|
+
>
|
|
2727
|
+
<!-- File Preview -->
|
|
2728
|
+
<div class="file-preview">
|
|
2729
|
+
@if (isImage(file) && file.url) {
|
|
2730
|
+
<img [src]="file.url" [alt]="file.name" class="preview-image" />
|
|
2731
|
+
} @else {
|
|
2732
|
+
<i [class]="getFileIcon(file)" class="preview-icon"></i>
|
|
2733
|
+
}
|
|
2734
|
+
@if (isSelected(file)) {
|
|
2735
|
+
<div class="selected-overlay">
|
|
2736
|
+
<i class="pi pi-check"></i>
|
|
2737
|
+
</div>
|
|
2738
|
+
}
|
|
2739
|
+
</div>
|
|
2740
|
+
|
|
2741
|
+
<!-- File Info -->
|
|
2742
|
+
<div class="file-info">
|
|
2743
|
+
<span class="file-name" [title]="file.name">{{ file.name }}</span>
|
|
2744
|
+
<span class="file-size">{{ formatSize(file.size) }}</span>
|
|
2745
|
+
</div>
|
|
2746
|
+
</div>
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
@if (isLoading()) {
|
|
2750
|
+
<div class="flex justify-content-center p-2 w-full">
|
|
2751
|
+
<i class="pi pi-spin pi-spinner"></i>
|
|
2752
|
+
</div>
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
</div>
|
|
2756
|
+
|
|
2757
|
+
<!-- Footer -->
|
|
2758
|
+
<ng-template pTemplate="footer">
|
|
2759
|
+
<button
|
|
2760
|
+
pButton
|
|
2761
|
+
label="Cancel"
|
|
2762
|
+
class="p-button-text"
|
|
2763
|
+
(click)="onCancel()"
|
|
2764
|
+
></button>
|
|
2765
|
+
<button
|
|
2766
|
+
pButton
|
|
2767
|
+
[label]="multiple() ? 'Select (' + selectedFiles().length + ')' : 'Select'"
|
|
2768
|
+
[disabled]="selectedFiles().length === 0"
|
|
2769
|
+
(click)="onConfirm()"
|
|
2770
|
+
></button>
|
|
2771
|
+
</ng-template>
|
|
2772
|
+
</p-dialog>
|
|
2773
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".file-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:1rem;max-height:400px;overflow-y:auto;padding:.5rem}.file-card{border:2px solid var(--surface-border);border-radius:var(--border-radius);cursor:pointer;transition:all .2s;overflow:hidden}.file-card:hover:not(.disabled){border-color:var(--primary-color)}.file-card.selected{border-color:var(--primary-color);background:var(--primary-50)}.file-card.disabled{opacity:.5;cursor:not-allowed}.file-preview{position:relative;height:100px;display:flex;align-items:center;justify-content:center;background:var(--surface-ground)}.preview-image{width:100%;height:100%;object-fit:cover}.preview-icon{font-size:3rem;color:var(--text-color-secondary)}.selected-overlay{position:absolute;inset:0;background:rgba(var(--primary-color-rgb),.3);display:flex;align-items:center;justify-content:center}.selected-overlay i{font-size:2rem;color:var(--primary-color);background:#fff;border-radius:50%;padding:.5rem}.file-info{padding:.5rem;text-align:center}.file-name{display:block;font-size:.875rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.file-size{display:block;font-size:.75rem;color:var(--text-color-secondary)}\n"] }]
|
|
2774
|
+
}], ctorParameters: () => [], propDecorators: { loadFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadFiles", required: true }] }], 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 }] }], 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"] }] } });
|
|
1415
2775
|
|
|
2776
|
+
/** Log only in dev mode */
|
|
2777
|
+
const devLog = (message) => {
|
|
2778
|
+
if (isDevMode())
|
|
2779
|
+
console.log(message);
|
|
2780
|
+
};
|
|
1416
2781
|
/**
|
|
1417
2782
|
* Permission Guard
|
|
1418
2783
|
*
|
|
@@ -1451,14 +2816,14 @@ function permissionGuard(permission, redirectTo = '/') {
|
|
|
1451
2816
|
const router = inject(Router);
|
|
1452
2817
|
// Check if permissions are loaded
|
|
1453
2818
|
if (!permissionValidator.isPermissionsLoaded()) {
|
|
1454
|
-
|
|
2819
|
+
devLog('[permissionGuard] Permissions not loaded, denying access to route');
|
|
1455
2820
|
return router.createUrlTree([redirectTo]);
|
|
1456
2821
|
}
|
|
1457
2822
|
const userPermissions = permissionValidator.permissions();
|
|
1458
2823
|
const hasPermission = evaluatePermission(permission, userPermissions);
|
|
1459
2824
|
if (!hasPermission) {
|
|
1460
2825
|
const permissionCode = typeof permission === 'string' ? permission : 'complex-logic';
|
|
1461
|
-
|
|
2826
|
+
devLog(`[permissionGuard] Access denied - missing permission: ${permissionCode}`);
|
|
1462
2827
|
return router.createUrlTree([redirectTo]);
|
|
1463
2828
|
}
|
|
1464
2829
|
return true;
|
|
@@ -1481,18 +2846,18 @@ function anyPermissionGuard(permissions, redirectTo = '/') {
|
|
|
1481
2846
|
const router = inject(Router);
|
|
1482
2847
|
// Validate permissions array
|
|
1483
2848
|
if (!permissions || permissions.length === 0) {
|
|
1484
|
-
|
|
2849
|
+
devLog('[anyPermissionGuard] Empty permissions array provided, denying access');
|
|
1485
2850
|
return router.createUrlTree([redirectTo]);
|
|
1486
2851
|
}
|
|
1487
2852
|
// Check if permissions are loaded
|
|
1488
2853
|
if (!permissionValidator.isPermissionsLoaded()) {
|
|
1489
|
-
|
|
2854
|
+
devLog('[anyPermissionGuard] Permissions not loaded, denying access to route');
|
|
1490
2855
|
return router.createUrlTree([redirectTo]);
|
|
1491
2856
|
}
|
|
1492
2857
|
const userPermissions = permissionValidator.permissions();
|
|
1493
2858
|
const hasPermission = hasAnyPermission(permissions, userPermissions);
|
|
1494
2859
|
if (!hasPermission) {
|
|
1495
|
-
|
|
2860
|
+
devLog(`[anyPermissionGuard] Access denied - missing any of: ${permissions.join(', ')}`);
|
|
1496
2861
|
return router.createUrlTree([redirectTo]);
|
|
1497
2862
|
}
|
|
1498
2863
|
return true;
|
|
@@ -1515,18 +2880,18 @@ function allPermissionsGuard(permissions, redirectTo = '/') {
|
|
|
1515
2880
|
const router = inject(Router);
|
|
1516
2881
|
// Validate permissions array
|
|
1517
2882
|
if (!permissions || permissions.length === 0) {
|
|
1518
|
-
|
|
2883
|
+
devLog('[allPermissionsGuard] Empty permissions array provided, denying access');
|
|
1519
2884
|
return router.createUrlTree([redirectTo]);
|
|
1520
2885
|
}
|
|
1521
2886
|
// Check if permissions are loaded
|
|
1522
2887
|
if (!permissionValidator.isPermissionsLoaded()) {
|
|
1523
|
-
|
|
2888
|
+
devLog('[allPermissionsGuard] Permissions not loaded, denying access to route');
|
|
1524
2889
|
return router.createUrlTree([redirectTo]);
|
|
1525
2890
|
}
|
|
1526
2891
|
const userPermissions = permissionValidator.permissions();
|
|
1527
2892
|
const hasPermission = hasAllPermissions(permissions, userPermissions);
|
|
1528
2893
|
if (!hasPermission) {
|
|
1529
|
-
|
|
2894
|
+
devLog(`[allPermissionsGuard] Access denied - missing all of: ${permissions.join(', ')}`);
|
|
1530
2895
|
return router.createUrlTree([redirectTo]);
|
|
1531
2896
|
}
|
|
1532
2897
|
return true;
|
|
@@ -1539,5 +2904,5 @@ function allPermissionsGuard(permissions, redirectTo = '/') {
|
|
|
1539
2904
|
* Generated bundle index. Do not edit.
|
|
1540
2905
|
*/
|
|
1541
2906
|
|
|
1542
|
-
export { AngularModule, ApiResourceService, ApiResourceService as ApiService, COMPANY_API_PROVIDER, ContactTypeEnum, CookieService, EditModeElementChangerDirective, FileUrlService, HasPermissionDirective, IconComponent, IconTypeEnum, IsEmptyImageDirective, LazyMultiSelectComponent, LazySelectComponent, PermissionValidatorService, PlatformService, PreventDefaultDirective, PrimeModule, USER_PERMISSION_PROVIDER, USER_PROVIDER, allPermissionsGuard, anyPermissionGuard, evaluateLogicNode, evaluatePermission, hasAllPermissions, hasAnyPermission, permissionGuard };
|
|
2907
|
+
export { AUTH_STATE_PROVIDER, AngularModule, ApiResourceService, ApiResourceService as ApiService, COMPANY_API_PROVIDER, ContactTypeEnum, CookieService, EditModeElementChangerDirective, FILE_TYPE_FILTERS, FileSelectorDialogComponent, FileUploaderComponent, FileUrlService, HasPermissionDirective, IconComponent, IconTypeEnum, IsEmptyImageDirective, LazyMultiSelectComponent, LazySelectComponent, PROFILE_PERMISSION_PROVIDER, PROFILE_UPLOAD_PROVIDER, PermissionValidatorService, PlatformService, PreventDefaultDirective, PrimeModule, USER_LIST_PROVIDER, USER_PERMISSION_PROVIDER, USER_PROVIDER, UserMultiSelectComponent, UserSelectComponent, allPermissionsGuard, anyPermissionGuard, evaluateLogicNode, evaluatePermission, formatFileSize, getAcceptString, getFileIconClass, hasAllPermissions, hasAnyPermission, isFileTypeAllowed, permissionGuard };
|
|
1543
2908
|
//# sourceMappingURL=flusys-ng-shared.mjs.map
|