@flusys/ng-shared 0.1.0-beta.2 → 1.0.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 +1 -2
- package/fesm2022/flusys-ng-shared.mjs +1407 -117
- package/fesm2022/flusys-ng-shared.mjs.map +1 -1
- package/package.json +2 -2
- package/types/flusys-ng-shared.d.ts +441 -19
|
@@ -1,20 +1,22 @@
|
|
|
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';
|
|
7
|
+
import { of, firstValueFrom, skip, debounceTime, distinctUntilChanged, tap as tap$1, map as map$1 } from 'rxjs';
|
|
8
8
|
import { tap, catchError, map } from 'rxjs/operators';
|
|
9
|
-
import * as
|
|
10
|
-
import { NgControl,
|
|
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 * as i1$3 from 'primeng/button';
|
|
13
14
|
import { ButtonModule } from 'primeng/button';
|
|
14
15
|
import { CardModule } from 'primeng/card';
|
|
15
16
|
import * as i2 from 'primeng/checkbox';
|
|
16
17
|
import { CheckboxModule } from 'primeng/checkbox';
|
|
17
18
|
import { DatePickerModule } from 'primeng/datepicker';
|
|
19
|
+
import * as i6 from 'primeng/dialog';
|
|
18
20
|
import { DialogModule } from 'primeng/dialog';
|
|
19
21
|
import { DividerModule } from 'primeng/divider';
|
|
20
22
|
import { FileUploadModule } from 'primeng/fileupload';
|
|
@@ -30,6 +32,7 @@ import { PaginatorModule } from 'primeng/paginator';
|
|
|
30
32
|
import { PanelModule } from 'primeng/panel';
|
|
31
33
|
import { PasswordModule } from 'primeng/password';
|
|
32
34
|
import { PopoverModule } from 'primeng/popover';
|
|
35
|
+
import * as i2$1 from 'primeng/progressbar';
|
|
33
36
|
import { ProgressBarModule } from 'primeng/progressbar';
|
|
34
37
|
import { RadioButtonModule } from 'primeng/radiobutton';
|
|
35
38
|
import { RippleModule } from 'primeng/ripple';
|
|
@@ -46,10 +49,81 @@ import { ToggleSwitchModule } from 'primeng/toggleswitch';
|
|
|
46
49
|
import { TooltipModule } from 'primeng/tooltip';
|
|
47
50
|
import { TreeTableModule } from 'primeng/treetable';
|
|
48
51
|
import { toSignal, toObservable } from '@angular/core/rxjs-interop';
|
|
52
|
+
import * as i4 from 'primeng/api';
|
|
53
|
+
import { MessageService } from 'primeng/api';
|
|
49
54
|
|
|
50
55
|
;
|
|
51
56
|
;
|
|
52
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Common file type filters
|
|
60
|
+
*/
|
|
61
|
+
const FILE_TYPE_FILTERS = {
|
|
62
|
+
IMAGES: ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml'],
|
|
63
|
+
DOCUMENTS: [
|
|
64
|
+
'application/pdf',
|
|
65
|
+
'application/msword',
|
|
66
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
67
|
+
'application/vnd.ms-excel',
|
|
68
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
69
|
+
],
|
|
70
|
+
VIDEOS: ['video/mp4', 'video/webm', 'video/ogg', 'video/quicktime'],
|
|
71
|
+
AUDIO: ['audio/mpeg', 'audio/wav', 'audio/ogg', 'audio/webm'],
|
|
72
|
+
ALL: [],
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Get accept string for file input from content types
|
|
76
|
+
*/
|
|
77
|
+
function getAcceptString(contentTypes) {
|
|
78
|
+
if (!contentTypes.length)
|
|
79
|
+
return '*/*';
|
|
80
|
+
return contentTypes.join(',');
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check if file matches allowed content types
|
|
84
|
+
*/
|
|
85
|
+
function isFileTypeAllowed(file, allowedTypes) {
|
|
86
|
+
if (!allowedTypes.length)
|
|
87
|
+
return true;
|
|
88
|
+
return allowedTypes.some((type) => {
|
|
89
|
+
if (type.endsWith('/*')) {
|
|
90
|
+
return file.type.startsWith(type.replace('/*', '/'));
|
|
91
|
+
}
|
|
92
|
+
return file.type === type;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get file icon class based on content type
|
|
97
|
+
*/
|
|
98
|
+
function getFileIconClass(contentType) {
|
|
99
|
+
if (contentType.startsWith('image/'))
|
|
100
|
+
return 'pi pi-image';
|
|
101
|
+
if (contentType.startsWith('video/'))
|
|
102
|
+
return 'pi pi-video';
|
|
103
|
+
if (contentType.startsWith('audio/'))
|
|
104
|
+
return 'pi pi-volume-up';
|
|
105
|
+
if (contentType.includes('pdf'))
|
|
106
|
+
return 'pi pi-file-pdf';
|
|
107
|
+
if (contentType.includes('word') || contentType.includes('document'))
|
|
108
|
+
return 'pi pi-file-word';
|
|
109
|
+
if (contentType.includes('excel') || contentType.includes('spreadsheet'))
|
|
110
|
+
return 'pi pi-file-excel';
|
|
111
|
+
return 'pi pi-file';
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Format file size for display
|
|
115
|
+
*/
|
|
116
|
+
function formatFileSize(sizeInKb) {
|
|
117
|
+
const kb = typeof sizeInKb === 'string' ? parseFloat(sizeInKb) : sizeInKb;
|
|
118
|
+
if (kb < 1024)
|
|
119
|
+
return `${kb.toFixed(1)} KB`;
|
|
120
|
+
const mb = kb / 1024;
|
|
121
|
+
if (mb < 1024)
|
|
122
|
+
return `${mb.toFixed(1)} MB`;
|
|
123
|
+
const gb = mb / 1024;
|
|
124
|
+
return `${gb.toFixed(2)} GB`;
|
|
125
|
+
}
|
|
126
|
+
|
|
53
127
|
var ContactTypeEnum;
|
|
54
128
|
(function (ContactTypeEnum) {
|
|
55
129
|
ContactTypeEnum[ContactTypeEnum["PHONE"] = 1] = "PHONE";
|
|
@@ -68,10 +142,10 @@ class PlatformService {
|
|
|
68
142
|
get isServer() {
|
|
69
143
|
return isPlatformServer(this.platformId);
|
|
70
144
|
}
|
|
71
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
72
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.
|
|
145
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PlatformService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
146
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PlatformService, providedIn: 'root' });
|
|
73
147
|
}
|
|
74
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
148
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PlatformService, decorators: [{
|
|
75
149
|
type: Injectable,
|
|
76
150
|
args: [{
|
|
77
151
|
providedIn: 'root'
|
|
@@ -85,10 +159,10 @@ class CookieService {
|
|
|
85
159
|
get() {
|
|
86
160
|
return !this.platformService.isServer ? this.doc.cookie : this.request?.headers.get('cookie') ?? "";
|
|
87
161
|
}
|
|
88
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
89
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.
|
|
162
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CookieService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
163
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CookieService, providedIn: 'root' });
|
|
90
164
|
}
|
|
91
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
165
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CookieService, decorators: [{
|
|
92
166
|
type: Injectable,
|
|
93
167
|
args: [{
|
|
94
168
|
providedIn: 'root',
|
|
@@ -135,10 +209,7 @@ class FileUrlService {
|
|
|
135
209
|
const cache = new Map(this.urlCache());
|
|
136
210
|
files.forEach((file) => cache.set(file.id, file));
|
|
137
211
|
this.urlCache.set(cache);
|
|
138
|
-
}), catchError((
|
|
139
|
-
console.error('Failed to fetch file URLs:', error);
|
|
140
|
-
return of([]);
|
|
141
|
-
}));
|
|
212
|
+
}), catchError(() => of([])));
|
|
142
213
|
}
|
|
143
214
|
/**
|
|
144
215
|
* Fetch a single file URL.
|
|
@@ -162,10 +233,10 @@ class FileUrlService {
|
|
|
162
233
|
cache.delete(fileId);
|
|
163
234
|
this.urlCache.set(cache);
|
|
164
235
|
}
|
|
165
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
166
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.
|
|
236
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileUrlService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
237
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileUrlService, providedIn: 'root' });
|
|
167
238
|
}
|
|
168
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
239
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileUrlService, decorators: [{
|
|
169
240
|
type: Injectable,
|
|
170
241
|
args: [{ providedIn: 'root' }]
|
|
171
242
|
}] });
|
|
@@ -247,10 +318,10 @@ class PermissionValidatorService {
|
|
|
247
318
|
isPermissionsLoaded() {
|
|
248
319
|
return this._isLoaded();
|
|
249
320
|
}
|
|
250
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
251
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.
|
|
321
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionValidatorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
322
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionValidatorService, providedIn: 'root' });
|
|
252
323
|
}
|
|
253
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
324
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PermissionValidatorService, decorators: [{
|
|
254
325
|
type: Injectable,
|
|
255
326
|
args: [{
|
|
256
327
|
providedIn: 'root',
|
|
@@ -268,11 +339,6 @@ class EditModeElementChangerDirective {
|
|
|
268
339
|
this.updateControl(editMode);
|
|
269
340
|
});
|
|
270
341
|
}
|
|
271
|
-
ngAfterViewInit() {
|
|
272
|
-
const editMode = this.isEditMode();
|
|
273
|
-
this.updateElement(editMode);
|
|
274
|
-
this.updateControl(editMode);
|
|
275
|
-
}
|
|
276
342
|
updateControl(editMode) {
|
|
277
343
|
if (!editMode)
|
|
278
344
|
this.ngControl?.control?.disable();
|
|
@@ -284,44 +350,41 @@ class EditModeElementChangerDirective {
|
|
|
284
350
|
if (inputElement instanceof HTMLInputElement) {
|
|
285
351
|
if (!editMode) {
|
|
286
352
|
inputElement.setAttribute('readonly', 'true');
|
|
287
|
-
inputElement
|
|
353
|
+
inputElement.classList.add('edit-mode-element-css');
|
|
288
354
|
}
|
|
289
355
|
else {
|
|
290
356
|
inputElement.removeAttribute('readonly');
|
|
291
|
-
inputElement
|
|
357
|
+
inputElement.classList.remove('edit-mode-element-css');
|
|
292
358
|
}
|
|
293
359
|
}
|
|
294
|
-
if (inputElement.tagName
|
|
360
|
+
if (inputElement.tagName === 'P-SELECT') {
|
|
295
361
|
if (!editMode) {
|
|
296
|
-
inputElement.classList.add('edit-mode-element-css');
|
|
297
|
-
inputElement.classList.add('overflow-hidden');
|
|
362
|
+
inputElement.classList.add('edit-mode-element-css', 'overflow-hidden');
|
|
298
363
|
inputElement.querySelector('.p-select-dropdown').style.display = 'none';
|
|
299
364
|
inputElement.querySelector('.p-select-label').classList.add('edit-mode-element-css');
|
|
300
365
|
}
|
|
301
366
|
else {
|
|
302
|
-
inputElement.classList.remove(
|
|
367
|
+
inputElement.classList.remove('edit-mode-element-css', 'overflow-hidden');
|
|
303
368
|
inputElement.querySelector('.p-select-dropdown').style.display = 'flex';
|
|
304
|
-
inputElement.querySelector('.p-select-label').classList.remove(
|
|
305
|
-
inputElement.classList.remove('overflow-hidden');
|
|
369
|
+
inputElement.querySelector('.p-select-label').classList.remove('edit-mode-element-css');
|
|
306
370
|
}
|
|
307
371
|
}
|
|
308
|
-
if (inputElement.tagName
|
|
372
|
+
if (inputElement.tagName === 'P-CALENDAR') {
|
|
373
|
+
const firstChild = inputElement.firstElementChild;
|
|
309
374
|
if (!editMode) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
inputElement.firstElementChild.children[1]?.classList.add('hidden');
|
|
375
|
+
firstChild?.children[0]?.classList.add('edit-mode-element-css', 'cursor-auto');
|
|
376
|
+
firstChild?.children[1]?.classList.add('hidden');
|
|
313
377
|
}
|
|
314
378
|
else {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
inputElement.firstElementChild.children[1]?.classList.remove('hidden');
|
|
379
|
+
firstChild?.children[0]?.classList.remove('edit-mode-element-css', 'cursor-auto');
|
|
380
|
+
firstChild?.children[1]?.classList.remove('hidden');
|
|
318
381
|
}
|
|
319
382
|
}
|
|
320
383
|
}
|
|
321
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
322
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.
|
|
384
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: EditModeElementChangerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
385
|
+
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
386
|
}
|
|
324
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
387
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: EditModeElementChangerDirective, decorators: [{
|
|
325
388
|
type: Directive,
|
|
326
389
|
args: [{
|
|
327
390
|
selector: '[appEditModeElementChanger]',
|
|
@@ -478,10 +541,10 @@ class HasPermissionDirective {
|
|
|
478
541
|
this.viewCreated = false;
|
|
479
542
|
}
|
|
480
543
|
}
|
|
481
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
482
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.
|
|
544
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: HasPermissionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
545
|
+
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
546
|
}
|
|
484
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
547
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: HasPermissionDirective, decorators: [{
|
|
485
548
|
type: Directive,
|
|
486
549
|
args: [{
|
|
487
550
|
selector: '[hasPermission]',
|
|
@@ -503,10 +566,10 @@ class IsEmptyImageDirective {
|
|
|
503
566
|
onError() {
|
|
504
567
|
this.hasError.set(true);
|
|
505
568
|
}
|
|
506
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
507
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.
|
|
569
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: IsEmptyImageDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
570
|
+
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
571
|
}
|
|
509
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
572
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: IsEmptyImageDirective, decorators: [{
|
|
510
573
|
type: Directive,
|
|
511
574
|
args: [{
|
|
512
575
|
selector: 'img',
|
|
@@ -543,13 +606,12 @@ class PreventDefaultDirective {
|
|
|
543
606
|
}
|
|
544
607
|
processEvent(event) {
|
|
545
608
|
event.preventDefault();
|
|
546
|
-
|
|
547
|
-
this.action.emit(event);
|
|
609
|
+
this.action.emit(event);
|
|
548
610
|
}
|
|
549
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
550
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.
|
|
611
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PreventDefaultDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
612
|
+
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
613
|
}
|
|
552
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
614
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PreventDefaultDirective, decorators: [{
|
|
553
615
|
type: Directive,
|
|
554
616
|
args: [{
|
|
555
617
|
selector: '[appPreventDefault]',
|
|
@@ -567,60 +629,62 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
567
629
|
}] } });
|
|
568
630
|
|
|
569
631
|
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.
|
|
632
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AngularModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
633
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.1.3", ngImport: i0, type: AngularModule, imports: [CommonModule,
|
|
634
|
+
FormsModule,
|
|
635
|
+
ReactiveFormsModule,
|
|
572
636
|
RouterOutlet,
|
|
573
637
|
RouterLink,
|
|
574
638
|
IsEmptyImageDirective,
|
|
575
639
|
NgOptimizedImage,
|
|
576
640
|
NgComponentOutlet,
|
|
577
641
|
PreventDefaultDirective], exports: [CommonModule,
|
|
578
|
-
ReactiveFormsModule,
|
|
579
642
|
FormsModule,
|
|
643
|
+
ReactiveFormsModule,
|
|
580
644
|
RouterOutlet,
|
|
581
645
|
RouterLink,
|
|
582
646
|
IsEmptyImageDirective,
|
|
583
647
|
NgOptimizedImage,
|
|
584
648
|
NgComponentOutlet,
|
|
585
649
|
PreventDefaultDirective] });
|
|
586
|
-
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.1.
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
650
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AngularModule, providers: [DatePipe], imports: [CommonModule,
|
|
651
|
+
FormsModule,
|
|
652
|
+
ReactiveFormsModule, CommonModule,
|
|
653
|
+
FormsModule,
|
|
654
|
+
ReactiveFormsModule] });
|
|
591
655
|
}
|
|
592
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
656
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AngularModule, decorators: [{
|
|
593
657
|
type: NgModule,
|
|
594
658
|
args: [{
|
|
595
659
|
imports: [
|
|
596
660
|
CommonModule,
|
|
661
|
+
FormsModule,
|
|
662
|
+
ReactiveFormsModule,
|
|
597
663
|
RouterOutlet,
|
|
598
664
|
RouterLink,
|
|
599
665
|
IsEmptyImageDirective,
|
|
600
666
|
NgOptimizedImage,
|
|
601
667
|
NgComponentOutlet,
|
|
602
|
-
PreventDefaultDirective
|
|
603
|
-
],
|
|
604
|
-
providers: [
|
|
605
|
-
DatePipe,
|
|
668
|
+
PreventDefaultDirective,
|
|
606
669
|
],
|
|
670
|
+
providers: [DatePipe],
|
|
607
671
|
exports: [
|
|
608
672
|
CommonModule,
|
|
609
|
-
ReactiveFormsModule,
|
|
610
673
|
FormsModule,
|
|
674
|
+
ReactiveFormsModule,
|
|
611
675
|
RouterOutlet,
|
|
612
676
|
RouterLink,
|
|
613
677
|
IsEmptyImageDirective,
|
|
614
678
|
NgOptimizedImage,
|
|
615
679
|
NgComponentOutlet,
|
|
616
|
-
PreventDefaultDirective
|
|
680
|
+
PreventDefaultDirective,
|
|
617
681
|
],
|
|
618
682
|
}]
|
|
619
683
|
}] });
|
|
620
684
|
|
|
621
685
|
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.
|
|
686
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PrimeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
687
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.1.3", ngImport: i0, type: PrimeModule, exports: [InputTextModule,
|
|
624
688
|
TagModule,
|
|
625
689
|
SelectButtonModule,
|
|
626
690
|
PasswordModule,
|
|
@@ -653,7 +717,7 @@ class PrimeModule {
|
|
|
653
717
|
TabsModule,
|
|
654
718
|
DialogModule,
|
|
655
719
|
TreeTableModule] });
|
|
656
|
-
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.1.
|
|
720
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PrimeModule, imports: [InputTextModule,
|
|
657
721
|
TagModule,
|
|
658
722
|
SelectButtonModule,
|
|
659
723
|
PasswordModule,
|
|
@@ -687,7 +751,7 @@ class PrimeModule {
|
|
|
687
751
|
DialogModule,
|
|
688
752
|
TreeTableModule] });
|
|
689
753
|
}
|
|
690
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
754
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: PrimeModule, decorators: [{
|
|
691
755
|
type: NgModule,
|
|
692
756
|
args: [{
|
|
693
757
|
exports: [
|
|
@@ -775,6 +839,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
775
839
|
class ApiResourceService {
|
|
776
840
|
baseUrl;
|
|
777
841
|
loaderService = inject(ApiLoaderService);
|
|
842
|
+
injector = inject(Injector);
|
|
778
843
|
http;
|
|
779
844
|
moduleApiName;
|
|
780
845
|
// ==========================================================================
|
|
@@ -789,22 +854,54 @@ class ApiResourceService {
|
|
|
789
854
|
select: [],
|
|
790
855
|
sort: {},
|
|
791
856
|
}, ...(ngDevMode ? [{ debugName: "filterData" }] : []));
|
|
792
|
-
/**
|
|
793
|
-
|
|
857
|
+
/**
|
|
858
|
+
* Resource for list data - lazy initialized to prevent auto-fetch on service injection.
|
|
859
|
+
* Call initListResource() or any list method (fetchList, reload, etc.) to initialize.
|
|
860
|
+
*/
|
|
861
|
+
_listResource = null;
|
|
862
|
+
/** Whether the list resource has been initialized */
|
|
863
|
+
_resourceInitialized = false;
|
|
864
|
+
/** Get or create the list resource (lazy initialization) */
|
|
865
|
+
get listResource() {
|
|
866
|
+
if (!this._listResource) {
|
|
867
|
+
this.initListResource();
|
|
868
|
+
}
|
|
869
|
+
return this._listResource;
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Initialize the list resource. Called automatically when accessing listResource
|
|
873
|
+
* or any list-related computed signals/methods.
|
|
874
|
+
* Uses runInInjectionContext to support lazy initialization outside constructor.
|
|
875
|
+
*/
|
|
876
|
+
initListResource() {
|
|
877
|
+
if (this._resourceInitialized)
|
|
878
|
+
return;
|
|
879
|
+
this._resourceInitialized = true;
|
|
880
|
+
runInInjectionContext(this.injector, () => {
|
|
881
|
+
this._listResource = resource({ ...(ngDevMode ? { debugName: "_listResource" } : {}), params: () => ({
|
|
882
|
+
search: this.searchTerm(),
|
|
883
|
+
filter: this.filterData(),
|
|
884
|
+
}),
|
|
885
|
+
loader: async ({ params }) => {
|
|
886
|
+
const { search, filter } = params;
|
|
887
|
+
return this.fetchAllAsync(search, filter);
|
|
888
|
+
} });
|
|
889
|
+
});
|
|
890
|
+
}
|
|
794
891
|
// ==========================================================================
|
|
795
892
|
// Computed State Accessors
|
|
796
893
|
// ==========================================================================
|
|
797
894
|
/** Whether data is currently loading */
|
|
798
|
-
isLoading = computed(() => this.
|
|
895
|
+
isLoading = computed(() => this._listResource?.isLoading() ?? false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
799
896
|
/** List data array */
|
|
800
|
-
data = computed(() => this.
|
|
897
|
+
data = computed(() => this._listResource?.value()?.data ?? [], ...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
801
898
|
/** Total count of items */
|
|
802
|
-
total = computed(() => this.
|
|
899
|
+
total = computed(() => this._listResource?.value()?.meta?.total ?? 0, ...(ngDevMode ? [{ debugName: "total" }] : []));
|
|
803
900
|
/** Pagination metadata */
|
|
804
|
-
pageInfo = computed(() => this.
|
|
901
|
+
pageInfo = computed(() => this._listResource?.value()?.meta, ...(ngDevMode ? [{ debugName: "pageInfo" }] : []));
|
|
805
902
|
/** Whether there are more pages */
|
|
806
903
|
hasMore = computed(() => {
|
|
807
|
-
const meta = this.
|
|
904
|
+
const meta = this._listResource?.value()?.meta;
|
|
808
905
|
if (!meta)
|
|
809
906
|
return false;
|
|
810
907
|
return meta.hasMore ?? (meta.page + 1) * meta.pageSize < meta.total;
|
|
@@ -813,15 +910,7 @@ class ApiResourceService {
|
|
|
813
910
|
this.moduleApiName = moduleApiName;
|
|
814
911
|
this.baseUrl = inject(APP_CONFIG).apiBaseUrl + '/' + moduleApiName;
|
|
815
912
|
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
|
-
} });
|
|
913
|
+
// Resource is now lazy-initialized, not created in constructor
|
|
825
914
|
}
|
|
826
915
|
getHttpOptions(endpoint, params) {
|
|
827
916
|
return {
|
|
@@ -835,9 +924,10 @@ class ApiResourceService {
|
|
|
835
924
|
// List Management Methods
|
|
836
925
|
// ==========================================================================
|
|
837
926
|
/**
|
|
838
|
-
* Fetch list data (triggers resource reload)
|
|
927
|
+
* Fetch list data (triggers resource initialization and reload)
|
|
839
928
|
*/
|
|
840
929
|
fetchList(search = '', filter) {
|
|
930
|
+
this.initListResource();
|
|
841
931
|
this.searchTerm.set(search);
|
|
842
932
|
if (filter) {
|
|
843
933
|
this.filterData.update((prev) => ({ ...prev, ...filter }));
|
|
@@ -847,12 +937,14 @@ class ApiResourceService {
|
|
|
847
937
|
* Update pagination
|
|
848
938
|
*/
|
|
849
939
|
setPagination(pagination) {
|
|
940
|
+
this.initListResource();
|
|
850
941
|
this.filterData.update((prev) => ({ ...prev, pagination }));
|
|
851
942
|
}
|
|
852
943
|
/**
|
|
853
944
|
* Go to next page
|
|
854
945
|
*/
|
|
855
946
|
nextPage() {
|
|
947
|
+
this.initListResource();
|
|
856
948
|
this.filterData.update((prev) => ({
|
|
857
949
|
...prev,
|
|
858
950
|
pagination: {
|
|
@@ -865,6 +957,7 @@ class ApiResourceService {
|
|
|
865
957
|
* Reset to first page
|
|
866
958
|
*/
|
|
867
959
|
resetPagination() {
|
|
960
|
+
this.initListResource();
|
|
868
961
|
this.filterData.update((prev) => ({
|
|
869
962
|
...prev,
|
|
870
963
|
pagination: { currentPage: 0, pageSize: prev.pagination?.pageSize ?? 10 },
|
|
@@ -874,7 +967,9 @@ class ApiResourceService {
|
|
|
874
967
|
* Reload current data
|
|
875
968
|
*/
|
|
876
969
|
reload() {
|
|
877
|
-
this.
|
|
970
|
+
if (this._listResource) {
|
|
971
|
+
this._listResource.reload();
|
|
972
|
+
}
|
|
878
973
|
}
|
|
879
974
|
// ==========================================================================
|
|
880
975
|
// Observable-based API Methods (IApiService interface)
|
|
@@ -984,8 +1079,8 @@ class IconComponent {
|
|
|
984
1079
|
icon = input.required(...(ngDevMode ? [{ debugName: "icon" }] : []));
|
|
985
1080
|
iconType = input(IconTypeEnum.PRIMENG_ICON, ...(ngDevMode ? [{ debugName: "iconType" }] : []));
|
|
986
1081
|
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.
|
|
1082
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: IconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1083
|
+
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
1084
|
@if(icon()){ @if(iconType()==IconTypeEnum.PRIMENG_ICON){
|
|
990
1085
|
<i [ngClass]="icon()"></i>
|
|
991
1086
|
}@else if(iconType()==IconTypeEnum.IMAGE_FILE_LINK){
|
|
@@ -995,7 +1090,7 @@ class IconComponent {
|
|
|
995
1090
|
}@else{ I } }
|
|
996
1091
|
`, 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
1092
|
}
|
|
998
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
1093
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: IconComponent, decorators: [{
|
|
999
1094
|
type: Component,
|
|
1000
1095
|
args: [{
|
|
1001
1096
|
selector: 'lib-icon',
|
|
@@ -1109,10 +1204,10 @@ class BaseFormControl {
|
|
|
1109
1204
|
this.onTouched();
|
|
1110
1205
|
}
|
|
1111
1206
|
}
|
|
1112
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
1113
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.
|
|
1207
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: BaseFormControl, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1208
|
+
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
1209
|
}
|
|
1115
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
1210
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: BaseFormControl, decorators: [{
|
|
1116
1211
|
type: Directive
|
|
1117
1212
|
}], 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
1213
|
/**
|
|
@@ -1189,8 +1284,6 @@ class LazyMultiSelectComponent extends BaseFormControl {
|
|
|
1189
1284
|
})), { initialValue: this.searchTerm() });
|
|
1190
1285
|
});
|
|
1191
1286
|
}
|
|
1192
|
-
// Signal to toggle panel
|
|
1193
|
-
scrollTargetEl = null;
|
|
1194
1287
|
onScrollBound = this.onScroll.bind(this);
|
|
1195
1288
|
multiScrollContainer = viewChild.required('multiScrollContainer');
|
|
1196
1289
|
onScroll(event) {
|
|
@@ -1254,10 +1347,10 @@ class LazyMultiSelectComponent extends BaseFormControl {
|
|
|
1254
1347
|
event.stopPropagation();
|
|
1255
1348
|
this.value.set([]);
|
|
1256
1349
|
}
|
|
1257
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
1258
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.
|
|
1350
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LazyMultiSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1351
|
+
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: "directive", type: i1$1.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "component", type: i2.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: "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
1352
|
}
|
|
1260
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
1353
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LazyMultiSelectComponent, decorators: [{
|
|
1261
1354
|
type: Component,
|
|
1262
1355
|
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
1356
|
}], 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 +1424,6 @@ class LazySelectComponent extends BaseFormControl {
|
|
|
1331
1424
|
target.addEventListener('scroll', this.onScrollBound);
|
|
1332
1425
|
this.scrollTargetEl = target;
|
|
1333
1426
|
}
|
|
1334
|
-
else {
|
|
1335
|
-
console.warn('.p-select-list-container not found after panel show');
|
|
1336
|
-
}
|
|
1337
1427
|
}, 0);
|
|
1338
1428
|
}
|
|
1339
1429
|
else {
|
|
@@ -1346,10 +1436,10 @@ class LazySelectComponent extends BaseFormControl {
|
|
|
1346
1436
|
onBlur() {
|
|
1347
1437
|
this.markAsTouched();
|
|
1348
1438
|
}
|
|
1349
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
1350
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.1.
|
|
1439
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LazySelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1440
|
+
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: i1$1.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
1441
|
}
|
|
1352
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
1442
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LazySelectComponent, decorators: [{
|
|
1353
1443
|
type: Component,
|
|
1354
1444
|
args: [{ selector: 'lib-lazy-select', imports: [
|
|
1355
1445
|
AngularModule,
|
|
@@ -1412,7 +1502,1207 @@ const USER_PERMISSION_PROVIDER = new InjectionToken('USER_PERMISSION_PROVIDER',
|
|
|
1412
1502
|
throw new Error('USER_PERMISSION_PROVIDER not configured. Please provide an implementation in app.config.ts');
|
|
1413
1503
|
},
|
|
1414
1504
|
});
|
|
1505
|
+
/**
|
|
1506
|
+
* Auth State Provider Token
|
|
1507
|
+
*
|
|
1508
|
+
* Provides auth state access for feature packages (form-builder, etc.)
|
|
1509
|
+
*/
|
|
1510
|
+
const AUTH_STATE_PROVIDER = new InjectionToken('AUTH_STATE_PROVIDER', {
|
|
1511
|
+
providedIn: 'root',
|
|
1512
|
+
factory: () => {
|
|
1513
|
+
throw new Error('AUTH_STATE_PROVIDER not configured. Please provide an implementation in app.config.ts');
|
|
1514
|
+
},
|
|
1515
|
+
});
|
|
1516
|
+
|
|
1517
|
+
const DEFAULT_PAGE_SIZE$2 = 20;
|
|
1518
|
+
/**
|
|
1519
|
+
* User Select Component - Single user selection with lazy loading.
|
|
1520
|
+
*
|
|
1521
|
+
* Uses USER_PROVIDER internally by default, or accepts custom `loadUsers` function.
|
|
1522
|
+
*
|
|
1523
|
+
* Features:
|
|
1524
|
+
* - Search with debouncing (handled by lazy-select)
|
|
1525
|
+
* - Infinite scroll pagination
|
|
1526
|
+
* - Filter active users by default (configurable)
|
|
1527
|
+
* - Supports additional filters via `additionalFilters` input
|
|
1528
|
+
*
|
|
1529
|
+
* @example
|
|
1530
|
+
* ```html
|
|
1531
|
+
* <!-- Simple usage - uses USER_PROVIDER internally -->
|
|
1532
|
+
* <lib-user-select
|
|
1533
|
+
* [(value)]="selectedUserId"
|
|
1534
|
+
* [isEditMode]="true"
|
|
1535
|
+
* />
|
|
1536
|
+
*
|
|
1537
|
+
* <!-- With custom loadUsers function -->
|
|
1538
|
+
* <lib-user-select
|
|
1539
|
+
* [(value)]="selectedUserId"
|
|
1540
|
+
* [isEditMode]="true"
|
|
1541
|
+
* [loadUsers]="customLoadUsers"
|
|
1542
|
+
* />
|
|
1543
|
+
* ```
|
|
1544
|
+
*/
|
|
1545
|
+
class UserSelectComponent {
|
|
1546
|
+
destroyRef = inject(DestroyRef);
|
|
1547
|
+
userProvider = inject(USER_PROVIDER);
|
|
1548
|
+
abortController = null;
|
|
1549
|
+
// Optional: custom function to load users (uses USER_PROVIDER if not provided)
|
|
1550
|
+
loadUsers = input(...(ngDevMode ? [undefined, { debugName: "loadUsers" }] : []));
|
|
1551
|
+
// Inputs
|
|
1552
|
+
placeHolder = input('Select User', ...(ngDevMode ? [{ debugName: "placeHolder" }] : []));
|
|
1553
|
+
isEditMode = input.required(...(ngDevMode ? [{ debugName: "isEditMode" }] : []));
|
|
1554
|
+
filterActive = input(true, ...(ngDevMode ? [{ debugName: "filterActive" }] : []));
|
|
1555
|
+
additionalFilters = input({}, ...(ngDevMode ? [{ debugName: "additionalFilters" }] : []));
|
|
1556
|
+
pageSize = input(DEFAULT_PAGE_SIZE$2, ...(ngDevMode ? [{ debugName: "pageSize" }] : []));
|
|
1557
|
+
// Two-way bound value
|
|
1558
|
+
value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
|
|
1559
|
+
// Outputs
|
|
1560
|
+
userSelected = output();
|
|
1561
|
+
onError = output();
|
|
1562
|
+
// Internal state
|
|
1563
|
+
isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
1564
|
+
users = signal([], ...(ngDevMode ? [{ debugName: "users" }] : []));
|
|
1565
|
+
total = signal(undefined, ...(ngDevMode ? [{ debugName: "total" }] : []));
|
|
1566
|
+
pagination = signal({ pageSize: DEFAULT_PAGE_SIZE$2, currentPage: 0 }, ...(ngDevMode ? [{ debugName: "pagination" }] : []));
|
|
1567
|
+
searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
|
|
1568
|
+
// Computed dropdown data
|
|
1569
|
+
dropdownUsers = computed(() => this.users().map((user) => ({
|
|
1570
|
+
label: user.name || user.email,
|
|
1571
|
+
value: user.id,
|
|
1572
|
+
})), ...(ngDevMode ? [{ debugName: "dropdownUsers" }] : []));
|
|
1573
|
+
constructor() {
|
|
1574
|
+
// Cleanup on destroy
|
|
1575
|
+
this.destroyRef.onDestroy(() => {
|
|
1576
|
+
this.abortController?.abort();
|
|
1577
|
+
});
|
|
1578
|
+
// Update page size from input
|
|
1579
|
+
effect(() => {
|
|
1580
|
+
const size = this.pageSize();
|
|
1581
|
+
untracked(() => {
|
|
1582
|
+
this.pagination.update((p) => ({ ...p, pageSize: size }));
|
|
1583
|
+
});
|
|
1584
|
+
});
|
|
1585
|
+
// Load initial users after render
|
|
1586
|
+
afterNextRender(() => {
|
|
1587
|
+
this.fetchUsers();
|
|
1588
|
+
});
|
|
1589
|
+
// Emit selected user when value changes
|
|
1590
|
+
effect(() => {
|
|
1591
|
+
const selectedId = this.value();
|
|
1592
|
+
const users = this.users();
|
|
1593
|
+
untracked(() => {
|
|
1594
|
+
if (selectedId) {
|
|
1595
|
+
const user = users.find((u) => u.id === selectedId);
|
|
1596
|
+
this.userSelected.emit(user ?? null);
|
|
1597
|
+
}
|
|
1598
|
+
else {
|
|
1599
|
+
this.userSelected.emit(null);
|
|
1600
|
+
}
|
|
1601
|
+
});
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
handleSearch(search) {
|
|
1605
|
+
this.searchTerm.set(search);
|
|
1606
|
+
this.pagination.update((p) => ({ ...p, currentPage: 0 }));
|
|
1607
|
+
this.users.set([]);
|
|
1608
|
+
this.fetchUsers();
|
|
1609
|
+
}
|
|
1610
|
+
handlePagination(pagination) {
|
|
1611
|
+
this.pagination.set(pagination);
|
|
1612
|
+
this.fetchUsers(true);
|
|
1613
|
+
}
|
|
1614
|
+
async fetchUsers(append = false) {
|
|
1615
|
+
if (this.isLoading())
|
|
1616
|
+
return;
|
|
1617
|
+
// Cancel previous request
|
|
1618
|
+
this.abortController?.abort();
|
|
1619
|
+
this.abortController = new AbortController();
|
|
1620
|
+
this.isLoading.set(true);
|
|
1621
|
+
try {
|
|
1622
|
+
const pag = this.pagination();
|
|
1623
|
+
const filter = {
|
|
1624
|
+
page: pag.currentPage,
|
|
1625
|
+
pageSize: pag.pageSize,
|
|
1626
|
+
search: this.searchTerm(),
|
|
1627
|
+
...this.additionalFilters(),
|
|
1628
|
+
};
|
|
1629
|
+
// Use custom loadUsers if provided, otherwise use USER_PROVIDER
|
|
1630
|
+
const customLoadUsers = this.loadUsers();
|
|
1631
|
+
const response = await firstValueFrom(customLoadUsers
|
|
1632
|
+
? customLoadUsers(filter)
|
|
1633
|
+
: this.loadUsersFromProvider(filter));
|
|
1634
|
+
if (response.success && response.data) {
|
|
1635
|
+
if (append) {
|
|
1636
|
+
this.users.update((current) => [...current, ...response.data]);
|
|
1637
|
+
}
|
|
1638
|
+
else {
|
|
1639
|
+
this.users.set(response.data);
|
|
1640
|
+
}
|
|
1641
|
+
this.total.set(response.meta?.total);
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
catch (error) {
|
|
1645
|
+
if (error.name !== 'AbortError') {
|
|
1646
|
+
this.onError.emit(error);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
finally {
|
|
1650
|
+
this.isLoading.set(false);
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
/** Load users from USER_PROVIDER with active filter */
|
|
1654
|
+
loadUsersFromProvider(filter) {
|
|
1655
|
+
return this.userProvider
|
|
1656
|
+
.getUsers({
|
|
1657
|
+
page: filter.page,
|
|
1658
|
+
pageSize: filter.pageSize,
|
|
1659
|
+
search: filter.search,
|
|
1660
|
+
isActive: this.filterActive() ? true : undefined,
|
|
1661
|
+
})
|
|
1662
|
+
.pipe(map$1((res) => ({
|
|
1663
|
+
...res,
|
|
1664
|
+
data: res.data?.map((u) => ({
|
|
1665
|
+
id: u.id,
|
|
1666
|
+
name: u.name,
|
|
1667
|
+
email: u.email,
|
|
1668
|
+
})),
|
|
1669
|
+
})));
|
|
1670
|
+
}
|
|
1671
|
+
/** Reload users (useful when filters change externally) */
|
|
1672
|
+
reload() {
|
|
1673
|
+
this.pagination.update((p) => ({ ...p, currentPage: 0 }));
|
|
1674
|
+
this.users.set([]);
|
|
1675
|
+
this.fetchUsers();
|
|
1676
|
+
}
|
|
1677
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1678
|
+
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: `
|
|
1679
|
+
<lib-lazy-select
|
|
1680
|
+
[(value)]="value"
|
|
1681
|
+
[placeHolder]="placeHolder()"
|
|
1682
|
+
[optionLabel]="'label'"
|
|
1683
|
+
[optionValue]="'value'"
|
|
1684
|
+
[isEditMode]="isEditMode()"
|
|
1685
|
+
[isLoading]="isLoading()"
|
|
1686
|
+
[total]="total()"
|
|
1687
|
+
[pagination]="pagination()"
|
|
1688
|
+
[selectDataList]="dropdownUsers()"
|
|
1689
|
+
(onSearch)="handleSearch($event)"
|
|
1690
|
+
(onPagination)="handlePagination($event)"
|
|
1691
|
+
/>
|
|
1692
|
+
`, 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 });
|
|
1693
|
+
}
|
|
1694
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserSelectComponent, decorators: [{
|
|
1695
|
+
type: Component,
|
|
1696
|
+
args: [{
|
|
1697
|
+
selector: 'lib-user-select',
|
|
1698
|
+
standalone: true,
|
|
1699
|
+
imports: [AngularModule, PrimeModule, LazySelectComponent],
|
|
1700
|
+
template: `
|
|
1701
|
+
<lib-lazy-select
|
|
1702
|
+
[(value)]="value"
|
|
1703
|
+
[placeHolder]="placeHolder()"
|
|
1704
|
+
[optionLabel]="'label'"
|
|
1705
|
+
[optionValue]="'value'"
|
|
1706
|
+
[isEditMode]="isEditMode()"
|
|
1707
|
+
[isLoading]="isLoading()"
|
|
1708
|
+
[total]="total()"
|
|
1709
|
+
[pagination]="pagination()"
|
|
1710
|
+
[selectDataList]="dropdownUsers()"
|
|
1711
|
+
(onSearch)="handleSearch($event)"
|
|
1712
|
+
(onPagination)="handlePagination($event)"
|
|
1713
|
+
/>
|
|
1714
|
+
`,
|
|
1715
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1716
|
+
}]
|
|
1717
|
+
}], 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"] }] } });
|
|
1718
|
+
|
|
1719
|
+
const DEFAULT_PAGE_SIZE$1 = 20;
|
|
1720
|
+
/**
|
|
1721
|
+
* User Multi-Select Component - Multiple user selection with lazy loading.
|
|
1722
|
+
*
|
|
1723
|
+
* Uses USER_PROVIDER internally by default, or accepts custom `loadUsers` function.
|
|
1724
|
+
*
|
|
1725
|
+
* Features:
|
|
1726
|
+
* - Search with debouncing (handled by lazy-multi-select)
|
|
1727
|
+
* - Infinite scroll pagination
|
|
1728
|
+
* - Select all / deselect all
|
|
1729
|
+
* - Filter active users by default (configurable)
|
|
1730
|
+
* - Supports additional filters via `additionalFilters` input
|
|
1731
|
+
*
|
|
1732
|
+
* @example
|
|
1733
|
+
* ```html
|
|
1734
|
+
* <!-- Simple usage - uses USER_PROVIDER internally -->
|
|
1735
|
+
* <lib-user-multi-select
|
|
1736
|
+
* [(value)]="selectedUserIds"
|
|
1737
|
+
* [isEditMode]="true"
|
|
1738
|
+
* />
|
|
1739
|
+
*
|
|
1740
|
+
* <!-- With custom loadUsers function -->
|
|
1741
|
+
* <lib-user-multi-select
|
|
1742
|
+
* [(value)]="selectedUserIds"
|
|
1743
|
+
* [isEditMode]="true"
|
|
1744
|
+
* [loadUsers]="customLoadUsers"
|
|
1745
|
+
* />
|
|
1746
|
+
* ```
|
|
1747
|
+
*/
|
|
1748
|
+
class UserMultiSelectComponent {
|
|
1749
|
+
destroyRef = inject(DestroyRef);
|
|
1750
|
+
userProvider = inject(USER_PROVIDER);
|
|
1751
|
+
abortController = null;
|
|
1752
|
+
// Optional: custom function to load users (uses USER_PROVIDER if not provided)
|
|
1753
|
+
loadUsers = input(...(ngDevMode ? [undefined, { debugName: "loadUsers" }] : []));
|
|
1754
|
+
// Inputs
|
|
1755
|
+
placeHolder = input('Select Users', ...(ngDevMode ? [{ debugName: "placeHolder" }] : []));
|
|
1756
|
+
isEditMode = input.required(...(ngDevMode ? [{ debugName: "isEditMode" }] : []));
|
|
1757
|
+
filterActive = input(true, ...(ngDevMode ? [{ debugName: "filterActive" }] : []));
|
|
1758
|
+
additionalFilters = input({}, ...(ngDevMode ? [{ debugName: "additionalFilters" }] : []));
|
|
1759
|
+
pageSize = input(DEFAULT_PAGE_SIZE$1, ...(ngDevMode ? [{ debugName: "pageSize" }] : []));
|
|
1760
|
+
// Two-way bound value
|
|
1761
|
+
value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
|
|
1762
|
+
// Outputs
|
|
1763
|
+
usersSelected = output();
|
|
1764
|
+
onError = output();
|
|
1765
|
+
// Internal state
|
|
1766
|
+
isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
1767
|
+
users = signal([], ...(ngDevMode ? [{ debugName: "users" }] : []));
|
|
1768
|
+
total = signal(undefined, ...(ngDevMode ? [{ debugName: "total" }] : []));
|
|
1769
|
+
pagination = signal({ pageSize: DEFAULT_PAGE_SIZE$1, currentPage: 0 }, ...(ngDevMode ? [{ debugName: "pagination" }] : []));
|
|
1770
|
+
searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
|
|
1771
|
+
// Computed dropdown data
|
|
1772
|
+
dropdownUsers = computed(() => this.users().map((user) => ({
|
|
1773
|
+
label: user.name || user.email,
|
|
1774
|
+
value: user.id,
|
|
1775
|
+
})), ...(ngDevMode ? [{ debugName: "dropdownUsers" }] : []));
|
|
1776
|
+
constructor() {
|
|
1777
|
+
// Cleanup on destroy
|
|
1778
|
+
this.destroyRef.onDestroy(() => {
|
|
1779
|
+
this.abortController?.abort();
|
|
1780
|
+
});
|
|
1781
|
+
// Update page size from input
|
|
1782
|
+
effect(() => {
|
|
1783
|
+
const size = this.pageSize();
|
|
1784
|
+
untracked(() => {
|
|
1785
|
+
this.pagination.update((p) => ({ ...p, pageSize: size }));
|
|
1786
|
+
});
|
|
1787
|
+
});
|
|
1788
|
+
// Load initial users after render
|
|
1789
|
+
afterNextRender(() => {
|
|
1790
|
+
this.fetchUsers();
|
|
1791
|
+
});
|
|
1792
|
+
// Emit selected users when value changes
|
|
1793
|
+
effect(() => {
|
|
1794
|
+
const selectedIds = this.value() ?? [];
|
|
1795
|
+
const users = this.users();
|
|
1796
|
+
untracked(() => {
|
|
1797
|
+
const selectedUsers = users.filter((u) => selectedIds.includes(u.id));
|
|
1798
|
+
this.usersSelected.emit(selectedUsers);
|
|
1799
|
+
});
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
handleSearch(search) {
|
|
1803
|
+
this.searchTerm.set(search);
|
|
1804
|
+
this.pagination.update((p) => ({ ...p, currentPage: 0 }));
|
|
1805
|
+
this.users.set([]);
|
|
1806
|
+
this.fetchUsers();
|
|
1807
|
+
}
|
|
1808
|
+
handlePagination(pagination) {
|
|
1809
|
+
this.pagination.set(pagination);
|
|
1810
|
+
this.fetchUsers(true);
|
|
1811
|
+
}
|
|
1812
|
+
async fetchUsers(append = false) {
|
|
1813
|
+
if (this.isLoading())
|
|
1814
|
+
return;
|
|
1815
|
+
// Cancel previous request
|
|
1816
|
+
this.abortController?.abort();
|
|
1817
|
+
this.abortController = new AbortController();
|
|
1818
|
+
this.isLoading.set(true);
|
|
1819
|
+
try {
|
|
1820
|
+
const pag = this.pagination();
|
|
1821
|
+
const filter = {
|
|
1822
|
+
page: pag.currentPage,
|
|
1823
|
+
pageSize: pag.pageSize,
|
|
1824
|
+
search: this.searchTerm(),
|
|
1825
|
+
...this.additionalFilters(),
|
|
1826
|
+
};
|
|
1827
|
+
// Use custom loadUsers if provided, otherwise use USER_PROVIDER
|
|
1828
|
+
const customLoadUsers = this.loadUsers();
|
|
1829
|
+
const response = await firstValueFrom(customLoadUsers
|
|
1830
|
+
? customLoadUsers(filter)
|
|
1831
|
+
: this.loadUsersFromProvider(filter));
|
|
1832
|
+
if (response.success && response.data) {
|
|
1833
|
+
if (append) {
|
|
1834
|
+
this.users.update((current) => [...current, ...response.data]);
|
|
1835
|
+
}
|
|
1836
|
+
else {
|
|
1837
|
+
this.users.set(response.data);
|
|
1838
|
+
}
|
|
1839
|
+
this.total.set(response.meta?.total);
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
catch (error) {
|
|
1843
|
+
if (error.name !== 'AbortError') {
|
|
1844
|
+
this.onError.emit(error);
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
finally {
|
|
1848
|
+
this.isLoading.set(false);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
/** Load users from USER_PROVIDER with active filter */
|
|
1852
|
+
loadUsersFromProvider(filter) {
|
|
1853
|
+
return this.userProvider
|
|
1854
|
+
.getUsers({
|
|
1855
|
+
page: filter.page,
|
|
1856
|
+
pageSize: filter.pageSize,
|
|
1857
|
+
search: filter.search,
|
|
1858
|
+
isActive: this.filterActive() ? true : undefined,
|
|
1859
|
+
})
|
|
1860
|
+
.pipe(map$1((res) => ({
|
|
1861
|
+
...res,
|
|
1862
|
+
data: res.data?.map((u) => ({
|
|
1863
|
+
id: u.id,
|
|
1864
|
+
name: u.name,
|
|
1865
|
+
email: u.email,
|
|
1866
|
+
})),
|
|
1867
|
+
})));
|
|
1868
|
+
}
|
|
1869
|
+
/** Reload users (useful when filters change externally) */
|
|
1870
|
+
reload() {
|
|
1871
|
+
this.pagination.update((p) => ({ ...p, currentPage: 0 }));
|
|
1872
|
+
this.users.set([]);
|
|
1873
|
+
this.fetchUsers();
|
|
1874
|
+
}
|
|
1875
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserMultiSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1876
|
+
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: `
|
|
1877
|
+
<lib-lazy-multi-select
|
|
1878
|
+
[(value)]="value"
|
|
1879
|
+
[placeHolder]="placeHolder()"
|
|
1880
|
+
[isEditMode]="isEditMode()"
|
|
1881
|
+
[isLoading]="isLoading()"
|
|
1882
|
+
[total]="total()"
|
|
1883
|
+
[pagination]="pagination()"
|
|
1884
|
+
[selectDataList]="dropdownUsers()"
|
|
1885
|
+
(onSearch)="handleSearch($event)"
|
|
1886
|
+
(onPagination)="handlePagination($event)"
|
|
1887
|
+
/>
|
|
1888
|
+
`, 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 });
|
|
1889
|
+
}
|
|
1890
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserMultiSelectComponent, decorators: [{
|
|
1891
|
+
type: Component,
|
|
1892
|
+
args: [{
|
|
1893
|
+
selector: 'lib-user-multi-select',
|
|
1894
|
+
standalone: true,
|
|
1895
|
+
imports: [AngularModule, PrimeModule, LazyMultiSelectComponent],
|
|
1896
|
+
template: `
|
|
1897
|
+
<lib-lazy-multi-select
|
|
1898
|
+
[(value)]="value"
|
|
1899
|
+
[placeHolder]="placeHolder()"
|
|
1900
|
+
[isEditMode]="isEditMode()"
|
|
1901
|
+
[isLoading]="isLoading()"
|
|
1902
|
+
[total]="total()"
|
|
1903
|
+
[pagination]="pagination()"
|
|
1904
|
+
[selectDataList]="dropdownUsers()"
|
|
1905
|
+
(onSearch)="handleSearch($event)"
|
|
1906
|
+
(onPagination)="handlePagination($event)"
|
|
1907
|
+
/>
|
|
1908
|
+
`,
|
|
1909
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1910
|
+
}]
|
|
1911
|
+
}], 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"] }] } });
|
|
1912
|
+
|
|
1913
|
+
/**
|
|
1914
|
+
* File Uploader Component - Drag & drop file upload with type filtering.
|
|
1915
|
+
*
|
|
1916
|
+
* Pass your own `uploadFile` function - works with any storage API.
|
|
1917
|
+
*
|
|
1918
|
+
* Features:
|
|
1919
|
+
* - Drag & drop support
|
|
1920
|
+
* - File type filtering (images, documents, etc.)
|
|
1921
|
+
* - Upload progress indication
|
|
1922
|
+
* - Multiple file support (optional)
|
|
1923
|
+
* - Image compression options
|
|
1924
|
+
*
|
|
1925
|
+
* @example
|
|
1926
|
+
* ```typescript
|
|
1927
|
+
* // In component
|
|
1928
|
+
* readonly uploadService = inject(UploadService);
|
|
1929
|
+
*
|
|
1930
|
+
* readonly uploadFile: UploadFileFn = (file, options) =>
|
|
1931
|
+
* this.uploadService.uploadSingleFile(file, options);
|
|
1932
|
+
* ```
|
|
1933
|
+
*
|
|
1934
|
+
* ```html
|
|
1935
|
+
* <!-- Single image upload -->
|
|
1936
|
+
* <lib-file-uploader
|
|
1937
|
+
* [uploadFile]="uploadFile"
|
|
1938
|
+
* [acceptTypes]="['image/*']"
|
|
1939
|
+
* [multiple]="false"
|
|
1940
|
+
* (fileUploaded)="onFileUploaded($event)"
|
|
1941
|
+
* />
|
|
1942
|
+
*
|
|
1943
|
+
* <!-- Multiple document upload -->
|
|
1944
|
+
* <lib-file-uploader
|
|
1945
|
+
* [uploadFile]="uploadFile"
|
|
1946
|
+
* [acceptTypes]="FILE_TYPE_FILTERS.DOCUMENTS"
|
|
1947
|
+
* [multiple]="true"
|
|
1948
|
+
* [maxFiles]="5"
|
|
1949
|
+
* (filesUploaded)="onFilesUploaded($event)"
|
|
1950
|
+
* />
|
|
1951
|
+
* ```
|
|
1952
|
+
*/
|
|
1953
|
+
class FileUploaderComponent {
|
|
1954
|
+
messageService = inject(MessageService);
|
|
1955
|
+
// Required: function to upload file
|
|
1956
|
+
uploadFile = input.required(...(ngDevMode ? [{ debugName: "uploadFile" }] : []));
|
|
1957
|
+
// Inputs
|
|
1958
|
+
acceptTypes = input([], ...(ngDevMode ? [{ debugName: "acceptTypes" }] : []));
|
|
1959
|
+
multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : []));
|
|
1960
|
+
maxFiles = input(10, ...(ngDevMode ? [{ debugName: "maxFiles" }] : []));
|
|
1961
|
+
maxSizeMb = input(10, ...(ngDevMode ? [{ debugName: "maxSizeMb" }] : []));
|
|
1962
|
+
uploadOptions = input({}, ...(ngDevMode ? [{ debugName: "uploadOptions" }] : []));
|
|
1963
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
1964
|
+
showPreview = input(true, ...(ngDevMode ? [{ debugName: "showPreview" }] : []));
|
|
1965
|
+
autoUpload = input(true, ...(ngDevMode ? [{ debugName: "autoUpload" }] : []));
|
|
1966
|
+
// Outputs
|
|
1967
|
+
fileUploaded = output();
|
|
1968
|
+
filesUploaded = output();
|
|
1969
|
+
onError = output();
|
|
1970
|
+
fileSelected = output();
|
|
1971
|
+
// Internal state
|
|
1972
|
+
isUploading = signal(false, ...(ngDevMode ? [{ debugName: "isUploading" }] : []));
|
|
1973
|
+
isDragOver = signal(false, ...(ngDevMode ? [{ debugName: "isDragOver" }] : []));
|
|
1974
|
+
uploadProgress = signal(0, ...(ngDevMode ? [{ debugName: "uploadProgress" }] : []));
|
|
1975
|
+
uploadingFileName = signal('', ...(ngDevMode ? [{ debugName: "uploadingFileName" }] : []));
|
|
1976
|
+
selectedFiles = signal([], ...(ngDevMode ? [{ debugName: "selectedFiles" }] : []));
|
|
1977
|
+
// Computed
|
|
1978
|
+
acceptString = computed(() => getAcceptString(this.acceptTypes()), ...(ngDevMode ? [{ debugName: "acceptString" }] : []));
|
|
1979
|
+
acceptTypesDisplay = computed(() => {
|
|
1980
|
+
const types = this.acceptTypes();
|
|
1981
|
+
if (!types.length)
|
|
1982
|
+
return '';
|
|
1983
|
+
// Check if types match predefined filters
|
|
1984
|
+
const typesStr = JSON.stringify(types);
|
|
1985
|
+
if (typesStr === JSON.stringify(FILE_TYPE_FILTERS.IMAGES))
|
|
1986
|
+
return 'Images';
|
|
1987
|
+
if (typesStr === JSON.stringify(FILE_TYPE_FILTERS.DOCUMENTS))
|
|
1988
|
+
return 'Documents';
|
|
1989
|
+
if (typesStr === JSON.stringify(FILE_TYPE_FILTERS.VIDEOS))
|
|
1990
|
+
return 'Videos';
|
|
1991
|
+
if (typesStr === JSON.stringify(FILE_TYPE_FILTERS.AUDIO))
|
|
1992
|
+
return 'Audio';
|
|
1993
|
+
return types.map(t => t.split('/')[1] || t).join(', ');
|
|
1994
|
+
}, ...(ngDevMode ? [{ debugName: "acceptTypesDisplay" }] : []));
|
|
1995
|
+
onDragOver(event) {
|
|
1996
|
+
event.preventDefault();
|
|
1997
|
+
event.stopPropagation();
|
|
1998
|
+
if (!this.disabled() && !this.isUploading()) {
|
|
1999
|
+
this.isDragOver.set(true);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
onDragLeave(event) {
|
|
2003
|
+
event.preventDefault();
|
|
2004
|
+
event.stopPropagation();
|
|
2005
|
+
this.isDragOver.set(false);
|
|
2006
|
+
}
|
|
2007
|
+
onDrop(event) {
|
|
2008
|
+
event.preventDefault();
|
|
2009
|
+
event.stopPropagation();
|
|
2010
|
+
this.isDragOver.set(false);
|
|
2011
|
+
if (this.disabled() || this.isUploading())
|
|
2012
|
+
return;
|
|
2013
|
+
const files = event.dataTransfer?.files;
|
|
2014
|
+
if (files?.length) {
|
|
2015
|
+
this.handleFiles(Array.from(files));
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
onFileSelected(event) {
|
|
2019
|
+
const input = event.target;
|
|
2020
|
+
const files = input.files;
|
|
2021
|
+
if (files?.length) {
|
|
2022
|
+
this.handleFiles(Array.from(files));
|
|
2023
|
+
}
|
|
2024
|
+
// Reset input to allow selecting same file again
|
|
2025
|
+
input.value = '';
|
|
2026
|
+
}
|
|
2027
|
+
handleFiles(files) {
|
|
2028
|
+
// Filter by type
|
|
2029
|
+
const allowedTypes = this.acceptTypes();
|
|
2030
|
+
const validFiles = files.filter(file => {
|
|
2031
|
+
if (!isFileTypeAllowed(file, allowedTypes)) {
|
|
2032
|
+
this.messageService.add({
|
|
2033
|
+
severity: 'warn',
|
|
2034
|
+
summary: 'Invalid File Type',
|
|
2035
|
+
detail: `File type not allowed: ${file.name}`,
|
|
2036
|
+
});
|
|
2037
|
+
return false;
|
|
2038
|
+
}
|
|
2039
|
+
return true;
|
|
2040
|
+
});
|
|
2041
|
+
// Filter by size
|
|
2042
|
+
const maxSize = this.maxSizeMb() * 1024 * 1024;
|
|
2043
|
+
const sizeValidFiles = validFiles.filter(file => {
|
|
2044
|
+
if (file.size > maxSize) {
|
|
2045
|
+
this.messageService.add({
|
|
2046
|
+
severity: 'warn',
|
|
2047
|
+
summary: 'File Too Large',
|
|
2048
|
+
detail: `${file.name} exceeds ${this.maxSizeMb()}MB limit`,
|
|
2049
|
+
});
|
|
2050
|
+
return false;
|
|
2051
|
+
}
|
|
2052
|
+
return true;
|
|
2053
|
+
});
|
|
2054
|
+
// Limit number of files
|
|
2055
|
+
const limitedFiles = this.multiple()
|
|
2056
|
+
? sizeValidFiles.slice(0, this.maxFiles())
|
|
2057
|
+
: sizeValidFiles.slice(0, 1);
|
|
2058
|
+
if (!limitedFiles.length)
|
|
2059
|
+
return;
|
|
2060
|
+
this.selectedFiles.set(limitedFiles);
|
|
2061
|
+
this.fileSelected.emit(limitedFiles);
|
|
2062
|
+
if (this.autoUpload()) {
|
|
2063
|
+
this.uploadFiles(limitedFiles);
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
removeFile(file) {
|
|
2067
|
+
this.selectedFiles.update(files => files.filter(f => f !== file));
|
|
2068
|
+
}
|
|
2069
|
+
async uploadFiles(files) {
|
|
2070
|
+
const filesToUpload = files ?? this.selectedFiles();
|
|
2071
|
+
if (!filesToUpload.length || this.isUploading())
|
|
2072
|
+
return;
|
|
2073
|
+
this.isUploading.set(true);
|
|
2074
|
+
const uploadedFiles = [];
|
|
2075
|
+
try {
|
|
2076
|
+
for (const file of filesToUpload) {
|
|
2077
|
+
this.uploadingFileName.set(file.name);
|
|
2078
|
+
this.uploadProgress.set(0);
|
|
2079
|
+
const response = await firstValueFrom(this.uploadFile()(file, this.uploadOptions()));
|
|
2080
|
+
if (response.success && response.data) {
|
|
2081
|
+
uploadedFiles.push(response.data);
|
|
2082
|
+
this.fileUploaded.emit(response.data);
|
|
2083
|
+
}
|
|
2084
|
+
else {
|
|
2085
|
+
throw new Error(response.message || 'Upload failed');
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
this.filesUploaded.emit(uploadedFiles);
|
|
2089
|
+
this.selectedFiles.set([]);
|
|
2090
|
+
this.messageService.add({
|
|
2091
|
+
severity: 'success',
|
|
2092
|
+
summary: 'Upload Complete',
|
|
2093
|
+
detail: `${uploadedFiles.length} file(s) uploaded successfully`,
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
catch (error) {
|
|
2097
|
+
this.messageService.add({
|
|
2098
|
+
severity: 'error',
|
|
2099
|
+
summary: 'Upload Failed',
|
|
2100
|
+
detail: error.message || 'Failed to upload file',
|
|
2101
|
+
});
|
|
2102
|
+
this.onError.emit(error);
|
|
2103
|
+
}
|
|
2104
|
+
finally {
|
|
2105
|
+
this.isUploading.set(false);
|
|
2106
|
+
this.uploadingFileName.set('');
|
|
2107
|
+
this.uploadProgress.set(0);
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
getFileIcon(file) {
|
|
2111
|
+
return getFileIconClass(file.type);
|
|
2112
|
+
}
|
|
2113
|
+
formatSize(bytes) {
|
|
2114
|
+
const kb = bytes / 1024;
|
|
2115
|
+
if (kb < 1024)
|
|
2116
|
+
return `${kb.toFixed(1)} KB`;
|
|
2117
|
+
const mb = kb / 1024;
|
|
2118
|
+
return `${mb.toFixed(1)} MB`;
|
|
2119
|
+
}
|
|
2120
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileUploaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2121
|
+
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: `
|
|
2122
|
+
<div
|
|
2123
|
+
class="file-uploader"
|
|
2124
|
+
[class.drag-over]="isDragOver()"
|
|
2125
|
+
[class.disabled]="disabled()"
|
|
2126
|
+
(dragover)="onDragOver($event)"
|
|
2127
|
+
(dragleave)="onDragLeave($event)"
|
|
2128
|
+
(drop)="onDrop($event)"
|
|
2129
|
+
>
|
|
2130
|
+
<!-- Upload Area -->
|
|
2131
|
+
<div class="upload-area" (click)="fileInput.click()">
|
|
2132
|
+
@if (isUploading()) {
|
|
2133
|
+
<div class="uploading-state">
|
|
2134
|
+
<i class="pi pi-spin pi-spinner text-4xl text-primary"></i>
|
|
2135
|
+
<p class="mt-2">Uploading {{ uploadingFileName() }}...</p>
|
|
2136
|
+
@if (uploadProgress() > 0) {
|
|
2137
|
+
<p-progressBar [value]="uploadProgress()" [showValue]="true" />
|
|
2138
|
+
}
|
|
2139
|
+
</div>
|
|
2140
|
+
} @else {
|
|
2141
|
+
<div class="idle-state text-center">
|
|
2142
|
+
<i class="pi pi-cloud-upload text-4xl text-primary"></i>
|
|
2143
|
+
<p class="mt-2 mb-1 font-semibold">
|
|
2144
|
+
{{ multiple() ? 'Drop files here or click to upload' : 'Drop file here or click to upload' }}
|
|
2145
|
+
</p>
|
|
2146
|
+
<p class="text-sm text-color-secondary">
|
|
2147
|
+
@if (acceptTypesDisplay()) {
|
|
2148
|
+
Allowed: {{ acceptTypesDisplay() }}
|
|
2149
|
+
} @else {
|
|
2150
|
+
All file types allowed
|
|
2151
|
+
}
|
|
2152
|
+
@if (maxSizeMb()) {
|
|
2153
|
+
(Max {{ maxSizeMb() }}MB)
|
|
2154
|
+
}
|
|
2155
|
+
</p>
|
|
2156
|
+
</div>
|
|
2157
|
+
}
|
|
2158
|
+
</div>
|
|
2159
|
+
|
|
2160
|
+
<!-- Hidden File Input -->
|
|
2161
|
+
<input
|
|
2162
|
+
#fileInput
|
|
2163
|
+
type="file"
|
|
2164
|
+
[accept]="acceptString()"
|
|
2165
|
+
[multiple]="multiple()"
|
|
2166
|
+
[disabled]="disabled() || isUploading()"
|
|
2167
|
+
(change)="onFileSelected($event)"
|
|
2168
|
+
class="hidden"
|
|
2169
|
+
/>
|
|
2170
|
+
|
|
2171
|
+
<!-- Selected Files Preview -->
|
|
2172
|
+
@if (selectedFiles().length > 0 && showPreview()) {
|
|
2173
|
+
<div class="selected-files mt-3">
|
|
2174
|
+
@for (file of selectedFiles(); track file.name) {
|
|
2175
|
+
<div class="file-item flex align-items-center gap-2 p-2 border-round surface-border border-1 mb-2">
|
|
2176
|
+
<i [class]="getFileIcon(file)"></i>
|
|
2177
|
+
<span class="flex-1 text-overflow-ellipsis overflow-hidden">{{ file.name }}</span>
|
|
2178
|
+
<span class="text-sm text-color-secondary">{{ formatSize(file.size) }}</span>
|
|
2179
|
+
<button
|
|
2180
|
+
pButton
|
|
2181
|
+
type="button"
|
|
2182
|
+
icon="pi pi-times"
|
|
2183
|
+
class="p-button-text p-button-rounded p-button-sm"
|
|
2184
|
+
(click)="removeFile(file)"
|
|
2185
|
+
[disabled]="isUploading()"
|
|
2186
|
+
></button>
|
|
2187
|
+
</div>
|
|
2188
|
+
}
|
|
2189
|
+
</div>
|
|
2190
|
+
}
|
|
2191
|
+
</div>
|
|
2192
|
+
`, 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 });
|
|
2193
|
+
}
|
|
2194
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileUploaderComponent, decorators: [{
|
|
2195
|
+
type: Component,
|
|
2196
|
+
args: [{ selector: 'lib-file-uploader', standalone: true, imports: [AngularModule, PrimeModule], 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
|
+
`, 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"] }]
|
|
2268
|
+
}], 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"] }] } });
|
|
2269
|
+
|
|
2270
|
+
const DEFAULT_PAGE_SIZE = 20;
|
|
2271
|
+
/**
|
|
2272
|
+
* File Selector Dialog - Browse and select existing files with filtering.
|
|
2273
|
+
*
|
|
2274
|
+
* Pass your own `loadFiles` function - works with any storage API.
|
|
2275
|
+
*
|
|
2276
|
+
* Features:
|
|
2277
|
+
* - Search with debouncing
|
|
2278
|
+
* - File type filtering
|
|
2279
|
+
* - Infinite scroll pagination
|
|
2280
|
+
* - Single or multiple selection
|
|
2281
|
+
* - File preview with icons
|
|
2282
|
+
*
|
|
2283
|
+
* @example
|
|
2284
|
+
* ```typescript
|
|
2285
|
+
* // In component
|
|
2286
|
+
* readonly fileService = inject(FileManagerApiService);
|
|
2287
|
+
*
|
|
2288
|
+
* readonly loadFiles: LoadFilesFn = (filter) =>
|
|
2289
|
+
* this.fileService.getAll(filter.search, {
|
|
2290
|
+
* pagination: { currentPage: filter.page, pageSize: filter.pageSize },
|
|
2291
|
+
* filter: { contentTypes: filter.contentTypes },
|
|
2292
|
+
* }).pipe(
|
|
2293
|
+
* map(res => ({
|
|
2294
|
+
* ...res,
|
|
2295
|
+
* data: res.data?.map(f => ({
|
|
2296
|
+
* id: f.id,
|
|
2297
|
+
* name: f.name,
|
|
2298
|
+
* contentType: f.contentType,
|
|
2299
|
+
* size: f.size,
|
|
2300
|
+
* url: f.url
|
|
2301
|
+
* }))
|
|
2302
|
+
* }))
|
|
2303
|
+
* );
|
|
2304
|
+
* ```
|
|
2305
|
+
*
|
|
2306
|
+
* ```html
|
|
2307
|
+
* <lib-file-selector-dialog
|
|
2308
|
+
* [(visible)]="showFileSelector"
|
|
2309
|
+
* [loadFiles]="loadFiles"
|
|
2310
|
+
* [acceptTypes]="['image/*']"
|
|
2311
|
+
* [multiple]="false"
|
|
2312
|
+
* (fileSelected)="onFileSelected($event)"
|
|
2313
|
+
* />
|
|
2314
|
+
* ```
|
|
2315
|
+
*/
|
|
2316
|
+
class FileSelectorDialogComponent {
|
|
2317
|
+
destroyRef = inject(DestroyRef);
|
|
2318
|
+
abortController = null;
|
|
2319
|
+
searchDebounceTimer = null;
|
|
2320
|
+
// Required: function to load files
|
|
2321
|
+
loadFiles = input.required(...(ngDevMode ? [{ debugName: "loadFiles" }] : []));
|
|
2322
|
+
// Inputs
|
|
2323
|
+
header = input('Select File', ...(ngDevMode ? [{ debugName: "header" }] : []));
|
|
2324
|
+
acceptTypes = input([], ...(ngDevMode ? [{ debugName: "acceptTypes" }] : []));
|
|
2325
|
+
multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : []));
|
|
2326
|
+
maxSelection = input(10, ...(ngDevMode ? [{ debugName: "maxSelection" }] : []));
|
|
2327
|
+
pageSize = input(DEFAULT_PAGE_SIZE, ...(ngDevMode ? [{ debugName: "pageSize" }] : []));
|
|
2328
|
+
// Two-way visibility binding
|
|
2329
|
+
visible = model(false, ...(ngDevMode ? [{ debugName: "visible" }] : []));
|
|
2330
|
+
// Outputs
|
|
2331
|
+
fileSelected = output();
|
|
2332
|
+
filesSelected = output();
|
|
2333
|
+
closed = output();
|
|
2334
|
+
onError = output();
|
|
2335
|
+
// Internal state
|
|
2336
|
+
isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
2337
|
+
files = signal([], ...(ngDevMode ? [{ debugName: "files" }] : []));
|
|
2338
|
+
selectedFiles = signal([], ...(ngDevMode ? [{ debugName: "selectedFiles" }] : []));
|
|
2339
|
+
total = signal(undefined, ...(ngDevMode ? [{ debugName: "total" }] : []));
|
|
2340
|
+
pagination = signal({ pageSize: DEFAULT_PAGE_SIZE, currentPage: 0 }, ...(ngDevMode ? [{ debugName: "pagination" }] : []));
|
|
2341
|
+
searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : []));
|
|
2342
|
+
// Computed
|
|
2343
|
+
acceptString = computed(() => getAcceptString(this.acceptTypes()), ...(ngDevMode ? [{ debugName: "acceptString" }] : []));
|
|
2344
|
+
constructor() {
|
|
2345
|
+
this.destroyRef.onDestroy(() => {
|
|
2346
|
+
this.abortController?.abort();
|
|
2347
|
+
if (this.searchDebounceTimer) {
|
|
2348
|
+
clearTimeout(this.searchDebounceTimer);
|
|
2349
|
+
}
|
|
2350
|
+
});
|
|
2351
|
+
// Load files when dialog becomes visible
|
|
2352
|
+
effect(() => {
|
|
2353
|
+
const isVisible = this.visible();
|
|
2354
|
+
if (isVisible) {
|
|
2355
|
+
untracked(() => {
|
|
2356
|
+
this.resetState();
|
|
2357
|
+
this.fetchFiles();
|
|
2358
|
+
});
|
|
2359
|
+
}
|
|
2360
|
+
});
|
|
2361
|
+
// Update page size from input
|
|
2362
|
+
effect(() => {
|
|
2363
|
+
const size = this.pageSize();
|
|
2364
|
+
untracked(() => {
|
|
2365
|
+
this.pagination.update((p) => ({ ...p, pageSize: size }));
|
|
2366
|
+
});
|
|
2367
|
+
});
|
|
2368
|
+
}
|
|
2369
|
+
onSearchChange(value) {
|
|
2370
|
+
// Debounce search
|
|
2371
|
+
if (this.searchDebounceTimer) {
|
|
2372
|
+
clearTimeout(this.searchDebounceTimer);
|
|
2373
|
+
}
|
|
2374
|
+
this.searchDebounceTimer = setTimeout(() => {
|
|
2375
|
+
this.searchTerm.set(value);
|
|
2376
|
+
this.pagination.update((p) => ({ ...p, currentPage: 0 }));
|
|
2377
|
+
this.files.set([]);
|
|
2378
|
+
this.fetchFiles();
|
|
2379
|
+
}, 500);
|
|
2380
|
+
}
|
|
2381
|
+
onScroll(event) {
|
|
2382
|
+
const el = event.target;
|
|
2383
|
+
const nearBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 50;
|
|
2384
|
+
if (nearBottom && !this.isLoading()) {
|
|
2385
|
+
const pag = this.pagination();
|
|
2386
|
+
const nextPage = pag.currentPage + 1;
|
|
2387
|
+
const hasMore = nextPage * pag.pageSize < (this.total() ?? 0);
|
|
2388
|
+
if (hasMore) {
|
|
2389
|
+
this.pagination.update((p) => ({ ...p, currentPage: nextPage }));
|
|
2390
|
+
this.fetchFiles(true);
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
toggleSelection(file) {
|
|
2395
|
+
if (!this.isFileAllowed(file))
|
|
2396
|
+
return;
|
|
2397
|
+
if (this.multiple()) {
|
|
2398
|
+
const selected = this.selectedFiles();
|
|
2399
|
+
const isSelected = selected.some((f) => f.id === file.id);
|
|
2400
|
+
if (isSelected) {
|
|
2401
|
+
this.selectedFiles.update((files) => files.filter((f) => f.id !== file.id));
|
|
2402
|
+
}
|
|
2403
|
+
else if (selected.length < this.maxSelection()) {
|
|
2404
|
+
this.selectedFiles.update((files) => [...files, file]);
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
else {
|
|
2408
|
+
this.selectedFiles.set([file]);
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
isSelected(file) {
|
|
2412
|
+
return this.selectedFiles().some((f) => f.id === file.id);
|
|
2413
|
+
}
|
|
2414
|
+
isFileAllowed(file) {
|
|
2415
|
+
const allowedTypes = this.acceptTypes();
|
|
2416
|
+
if (!allowedTypes.length)
|
|
2417
|
+
return true;
|
|
2418
|
+
return allowedTypes.some((type) => {
|
|
2419
|
+
if (type.endsWith('/*')) {
|
|
2420
|
+
return file.contentType.startsWith(type.replace('/*', '/'));
|
|
2421
|
+
}
|
|
2422
|
+
return file.contentType === type;
|
|
2423
|
+
});
|
|
2424
|
+
}
|
|
2425
|
+
isImage(file) {
|
|
2426
|
+
return file.contentType.startsWith('image/') && !!file.url;
|
|
2427
|
+
}
|
|
2428
|
+
getFileIcon(file) {
|
|
2429
|
+
return getFileIconClass(file.contentType);
|
|
2430
|
+
}
|
|
2431
|
+
formatSize(size) {
|
|
2432
|
+
return formatFileSize(size);
|
|
2433
|
+
}
|
|
2434
|
+
onCancel() {
|
|
2435
|
+
this.visible.set(false);
|
|
2436
|
+
}
|
|
2437
|
+
onConfirm() {
|
|
2438
|
+
const selected = this.selectedFiles();
|
|
2439
|
+
if (selected.length === 0)
|
|
2440
|
+
return;
|
|
2441
|
+
if (this.multiple()) {
|
|
2442
|
+
this.filesSelected.emit(selected);
|
|
2443
|
+
}
|
|
2444
|
+
else {
|
|
2445
|
+
this.fileSelected.emit(selected[0]);
|
|
2446
|
+
}
|
|
2447
|
+
this.visible.set(false);
|
|
2448
|
+
}
|
|
2449
|
+
onDialogHide() {
|
|
2450
|
+
this.closed.emit();
|
|
2451
|
+
}
|
|
2452
|
+
resetState() {
|
|
2453
|
+
this.files.set([]);
|
|
2454
|
+
this.selectedFiles.set([]);
|
|
2455
|
+
this.searchTerm.set('');
|
|
2456
|
+
this.pagination.set({ pageSize: this.pageSize(), currentPage: 0 });
|
|
2457
|
+
this.total.set(undefined);
|
|
2458
|
+
}
|
|
2459
|
+
async fetchFiles(append = false) {
|
|
2460
|
+
if (this.isLoading())
|
|
2461
|
+
return;
|
|
2462
|
+
this.abortController?.abort();
|
|
2463
|
+
this.abortController = new AbortController();
|
|
2464
|
+
this.isLoading.set(true);
|
|
2465
|
+
try {
|
|
2466
|
+
const pag = this.pagination();
|
|
2467
|
+
const filter = {
|
|
2468
|
+
page: pag.currentPage,
|
|
2469
|
+
pageSize: pag.pageSize,
|
|
2470
|
+
search: this.searchTerm(),
|
|
2471
|
+
contentTypes: this.acceptTypes().length ? this.acceptTypes() : undefined,
|
|
2472
|
+
};
|
|
2473
|
+
const response = await firstValueFrom(this.loadFiles()(filter));
|
|
2474
|
+
if (response.success && response.data) {
|
|
2475
|
+
if (append) {
|
|
2476
|
+
this.files.update((current) => [...current, ...response.data]);
|
|
2477
|
+
}
|
|
2478
|
+
else {
|
|
2479
|
+
this.files.set(response.data);
|
|
2480
|
+
}
|
|
2481
|
+
this.total.set(response.meta?.total);
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
catch (error) {
|
|
2485
|
+
if (error.name !== 'AbortError') {
|
|
2486
|
+
this.onError.emit(error);
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
finally {
|
|
2490
|
+
this.isLoading.set(false);
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileSelectorDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2494
|
+
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: `
|
|
2495
|
+
<p-dialog
|
|
2496
|
+
[header]="header()"
|
|
2497
|
+
[(visible)]="visible"
|
|
2498
|
+
[modal]="true"
|
|
2499
|
+
[closable]="true"
|
|
2500
|
+
[draggable]="false"
|
|
2501
|
+
[resizable]="false"
|
|
2502
|
+
[style]="{ width: '800px', maxHeight: '90vh' }"
|
|
2503
|
+
(onHide)="onDialogHide()"
|
|
2504
|
+
>
|
|
2505
|
+
<!-- Search Bar -->
|
|
2506
|
+
<div class="flex gap-2 mb-3">
|
|
2507
|
+
<span class="p-input-icon-left flex-1">
|
|
2508
|
+
<i class="pi pi-search"></i>
|
|
2509
|
+
<input
|
|
2510
|
+
pInputText
|
|
2511
|
+
type="text"
|
|
2512
|
+
[ngModel]="searchTerm()"
|
|
2513
|
+
(ngModelChange)="onSearchChange($event)"
|
|
2514
|
+
placeholder="Search files..."
|
|
2515
|
+
class="w-full"
|
|
2516
|
+
/>
|
|
2517
|
+
</span>
|
|
2518
|
+
@if (multiple()) {
|
|
2519
|
+
<span class="text-sm text-color-secondary align-self-center">
|
|
2520
|
+
{{ selectedFiles().length }} selected
|
|
2521
|
+
</span>
|
|
2522
|
+
}
|
|
2523
|
+
</div>
|
|
2524
|
+
|
|
2525
|
+
<!-- File Grid -->
|
|
2526
|
+
<div
|
|
2527
|
+
class="file-grid"
|
|
2528
|
+
#scrollContainer
|
|
2529
|
+
(scroll)="onScroll($event)"
|
|
2530
|
+
>
|
|
2531
|
+
@if (isLoading() && files().length === 0) {
|
|
2532
|
+
<div class="flex justify-content-center p-4">
|
|
2533
|
+
<i class="pi pi-spin pi-spinner text-4xl"></i>
|
|
2534
|
+
</div>
|
|
2535
|
+
} @else if (files().length === 0) {
|
|
2536
|
+
<div class="text-center p-4 text-color-secondary">
|
|
2537
|
+
<i class="pi pi-inbox text-4xl mb-2"></i>
|
|
2538
|
+
<p>No files found</p>
|
|
2539
|
+
</div>
|
|
2540
|
+
} @else {
|
|
2541
|
+
@for (file of files(); track file.id) {
|
|
2542
|
+
<div
|
|
2543
|
+
class="file-card"
|
|
2544
|
+
[class.selected]="isSelected(file)"
|
|
2545
|
+
[class.disabled]="!isFileAllowed(file)"
|
|
2546
|
+
(click)="toggleSelection(file)"
|
|
2547
|
+
>
|
|
2548
|
+
<!-- File Preview -->
|
|
2549
|
+
<div class="file-preview">
|
|
2550
|
+
@if (isImage(file) && file.url) {
|
|
2551
|
+
<img [src]="file.url" [alt]="file.name" class="preview-image" />
|
|
2552
|
+
} @else {
|
|
2553
|
+
<i [class]="getFileIcon(file)" class="preview-icon"></i>
|
|
2554
|
+
}
|
|
2555
|
+
@if (isSelected(file)) {
|
|
2556
|
+
<div class="selected-overlay">
|
|
2557
|
+
<i class="pi pi-check"></i>
|
|
2558
|
+
</div>
|
|
2559
|
+
}
|
|
2560
|
+
</div>
|
|
2561
|
+
|
|
2562
|
+
<!-- File Info -->
|
|
2563
|
+
<div class="file-info">
|
|
2564
|
+
<span class="file-name" [title]="file.name">{{ file.name }}</span>
|
|
2565
|
+
<span class="file-size">{{ formatSize(file.size) }}</span>
|
|
2566
|
+
</div>
|
|
2567
|
+
</div>
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
@if (isLoading()) {
|
|
2571
|
+
<div class="flex justify-content-center p-2 w-full">
|
|
2572
|
+
<i class="pi pi-spin pi-spinner"></i>
|
|
2573
|
+
</div>
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
</div>
|
|
2577
|
+
|
|
2578
|
+
<!-- Footer -->
|
|
2579
|
+
<ng-template pTemplate="footer">
|
|
2580
|
+
<button
|
|
2581
|
+
pButton
|
|
2582
|
+
label="Cancel"
|
|
2583
|
+
class="p-button-text"
|
|
2584
|
+
(click)="onCancel()"
|
|
2585
|
+
></button>
|
|
2586
|
+
<button
|
|
2587
|
+
pButton
|
|
2588
|
+
[label]="multiple() ? 'Select (' + selectedFiles().length + ')' : 'Select'"
|
|
2589
|
+
[disabled]="selectedFiles().length === 0"
|
|
2590
|
+
(click)="onConfirm()"
|
|
2591
|
+
></button>
|
|
2592
|
+
</ng-template>
|
|
2593
|
+
</p-dialog>
|
|
2594
|
+
`, 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: i1$1.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "directive", type: i4.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: i6.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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2595
|
+
}
|
|
2596
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: FileSelectorDialogComponent, decorators: [{
|
|
2597
|
+
type: Component,
|
|
2598
|
+
args: [{ selector: 'lib-file-selector-dialog', standalone: true, imports: [AngularModule, PrimeModule], template: `
|
|
2599
|
+
<p-dialog
|
|
2600
|
+
[header]="header()"
|
|
2601
|
+
[(visible)]="visible"
|
|
2602
|
+
[modal]="true"
|
|
2603
|
+
[closable]="true"
|
|
2604
|
+
[draggable]="false"
|
|
2605
|
+
[resizable]="false"
|
|
2606
|
+
[style]="{ width: '800px', maxHeight: '90vh' }"
|
|
2607
|
+
(onHide)="onDialogHide()"
|
|
2608
|
+
>
|
|
2609
|
+
<!-- Search Bar -->
|
|
2610
|
+
<div class="flex gap-2 mb-3">
|
|
2611
|
+
<span class="p-input-icon-left flex-1">
|
|
2612
|
+
<i class="pi pi-search"></i>
|
|
2613
|
+
<input
|
|
2614
|
+
pInputText
|
|
2615
|
+
type="text"
|
|
2616
|
+
[ngModel]="searchTerm()"
|
|
2617
|
+
(ngModelChange)="onSearchChange($event)"
|
|
2618
|
+
placeholder="Search files..."
|
|
2619
|
+
class="w-full"
|
|
2620
|
+
/>
|
|
2621
|
+
</span>
|
|
2622
|
+
@if (multiple()) {
|
|
2623
|
+
<span class="text-sm text-color-secondary align-self-center">
|
|
2624
|
+
{{ selectedFiles().length }} selected
|
|
2625
|
+
</span>
|
|
2626
|
+
}
|
|
2627
|
+
</div>
|
|
2628
|
+
|
|
2629
|
+
<!-- File Grid -->
|
|
2630
|
+
<div
|
|
2631
|
+
class="file-grid"
|
|
2632
|
+
#scrollContainer
|
|
2633
|
+
(scroll)="onScroll($event)"
|
|
2634
|
+
>
|
|
2635
|
+
@if (isLoading() && files().length === 0) {
|
|
2636
|
+
<div class="flex justify-content-center p-4">
|
|
2637
|
+
<i class="pi pi-spin pi-spinner text-4xl"></i>
|
|
2638
|
+
</div>
|
|
2639
|
+
} @else if (files().length === 0) {
|
|
2640
|
+
<div class="text-center p-4 text-color-secondary">
|
|
2641
|
+
<i class="pi pi-inbox text-4xl mb-2"></i>
|
|
2642
|
+
<p>No files found</p>
|
|
2643
|
+
</div>
|
|
2644
|
+
} @else {
|
|
2645
|
+
@for (file of files(); track file.id) {
|
|
2646
|
+
<div
|
|
2647
|
+
class="file-card"
|
|
2648
|
+
[class.selected]="isSelected(file)"
|
|
2649
|
+
[class.disabled]="!isFileAllowed(file)"
|
|
2650
|
+
(click)="toggleSelection(file)"
|
|
2651
|
+
>
|
|
2652
|
+
<!-- File Preview -->
|
|
2653
|
+
<div class="file-preview">
|
|
2654
|
+
@if (isImage(file) && file.url) {
|
|
2655
|
+
<img [src]="file.url" [alt]="file.name" class="preview-image" />
|
|
2656
|
+
} @else {
|
|
2657
|
+
<i [class]="getFileIcon(file)" class="preview-icon"></i>
|
|
2658
|
+
}
|
|
2659
|
+
@if (isSelected(file)) {
|
|
2660
|
+
<div class="selected-overlay">
|
|
2661
|
+
<i class="pi pi-check"></i>
|
|
2662
|
+
</div>
|
|
2663
|
+
}
|
|
2664
|
+
</div>
|
|
2665
|
+
|
|
2666
|
+
<!-- File Info -->
|
|
2667
|
+
<div class="file-info">
|
|
2668
|
+
<span class="file-name" [title]="file.name">{{ file.name }}</span>
|
|
2669
|
+
<span class="file-size">{{ formatSize(file.size) }}</span>
|
|
2670
|
+
</div>
|
|
2671
|
+
</div>
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
@if (isLoading()) {
|
|
2675
|
+
<div class="flex justify-content-center p-2 w-full">
|
|
2676
|
+
<i class="pi pi-spin pi-spinner"></i>
|
|
2677
|
+
</div>
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
</div>
|
|
2681
|
+
|
|
2682
|
+
<!-- Footer -->
|
|
2683
|
+
<ng-template pTemplate="footer">
|
|
2684
|
+
<button
|
|
2685
|
+
pButton
|
|
2686
|
+
label="Cancel"
|
|
2687
|
+
class="p-button-text"
|
|
2688
|
+
(click)="onCancel()"
|
|
2689
|
+
></button>
|
|
2690
|
+
<button
|
|
2691
|
+
pButton
|
|
2692
|
+
[label]="multiple() ? 'Select (' + selectedFiles().length + ')' : 'Select'"
|
|
2693
|
+
[disabled]="selectedFiles().length === 0"
|
|
2694
|
+
(click)="onConfirm()"
|
|
2695
|
+
></button>
|
|
2696
|
+
</ng-template>
|
|
2697
|
+
</p-dialog>
|
|
2698
|
+
`, 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"] }]
|
|
2699
|
+
}], 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
2700
|
|
|
2701
|
+
/** Log only in dev mode */
|
|
2702
|
+
const devLog = (message) => {
|
|
2703
|
+
if (isDevMode())
|
|
2704
|
+
console.log(message);
|
|
2705
|
+
};
|
|
1416
2706
|
/**
|
|
1417
2707
|
* Permission Guard
|
|
1418
2708
|
*
|
|
@@ -1451,14 +2741,14 @@ function permissionGuard(permission, redirectTo = '/') {
|
|
|
1451
2741
|
const router = inject(Router);
|
|
1452
2742
|
// Check if permissions are loaded
|
|
1453
2743
|
if (!permissionValidator.isPermissionsLoaded()) {
|
|
1454
|
-
|
|
2744
|
+
devLog('[permissionGuard] Permissions not loaded, denying access to route');
|
|
1455
2745
|
return router.createUrlTree([redirectTo]);
|
|
1456
2746
|
}
|
|
1457
2747
|
const userPermissions = permissionValidator.permissions();
|
|
1458
2748
|
const hasPermission = evaluatePermission(permission, userPermissions);
|
|
1459
2749
|
if (!hasPermission) {
|
|
1460
2750
|
const permissionCode = typeof permission === 'string' ? permission : 'complex-logic';
|
|
1461
|
-
|
|
2751
|
+
devLog(`[permissionGuard] Access denied - missing permission: ${permissionCode}`);
|
|
1462
2752
|
return router.createUrlTree([redirectTo]);
|
|
1463
2753
|
}
|
|
1464
2754
|
return true;
|
|
@@ -1481,18 +2771,18 @@ function anyPermissionGuard(permissions, redirectTo = '/') {
|
|
|
1481
2771
|
const router = inject(Router);
|
|
1482
2772
|
// Validate permissions array
|
|
1483
2773
|
if (!permissions || permissions.length === 0) {
|
|
1484
|
-
|
|
2774
|
+
devLog('[anyPermissionGuard] Empty permissions array provided, denying access');
|
|
1485
2775
|
return router.createUrlTree([redirectTo]);
|
|
1486
2776
|
}
|
|
1487
2777
|
// Check if permissions are loaded
|
|
1488
2778
|
if (!permissionValidator.isPermissionsLoaded()) {
|
|
1489
|
-
|
|
2779
|
+
devLog('[anyPermissionGuard] Permissions not loaded, denying access to route');
|
|
1490
2780
|
return router.createUrlTree([redirectTo]);
|
|
1491
2781
|
}
|
|
1492
2782
|
const userPermissions = permissionValidator.permissions();
|
|
1493
2783
|
const hasPermission = hasAnyPermission(permissions, userPermissions);
|
|
1494
2784
|
if (!hasPermission) {
|
|
1495
|
-
|
|
2785
|
+
devLog(`[anyPermissionGuard] Access denied - missing any of: ${permissions.join(', ')}`);
|
|
1496
2786
|
return router.createUrlTree([redirectTo]);
|
|
1497
2787
|
}
|
|
1498
2788
|
return true;
|
|
@@ -1515,18 +2805,18 @@ function allPermissionsGuard(permissions, redirectTo = '/') {
|
|
|
1515
2805
|
const router = inject(Router);
|
|
1516
2806
|
// Validate permissions array
|
|
1517
2807
|
if (!permissions || permissions.length === 0) {
|
|
1518
|
-
|
|
2808
|
+
devLog('[allPermissionsGuard] Empty permissions array provided, denying access');
|
|
1519
2809
|
return router.createUrlTree([redirectTo]);
|
|
1520
2810
|
}
|
|
1521
2811
|
// Check if permissions are loaded
|
|
1522
2812
|
if (!permissionValidator.isPermissionsLoaded()) {
|
|
1523
|
-
|
|
2813
|
+
devLog('[allPermissionsGuard] Permissions not loaded, denying access to route');
|
|
1524
2814
|
return router.createUrlTree([redirectTo]);
|
|
1525
2815
|
}
|
|
1526
2816
|
const userPermissions = permissionValidator.permissions();
|
|
1527
2817
|
const hasPermission = hasAllPermissions(permissions, userPermissions);
|
|
1528
2818
|
if (!hasPermission) {
|
|
1529
|
-
|
|
2819
|
+
devLog(`[allPermissionsGuard] Access denied - missing all of: ${permissions.join(', ')}`);
|
|
1530
2820
|
return router.createUrlTree([redirectTo]);
|
|
1531
2821
|
}
|
|
1532
2822
|
return true;
|
|
@@ -1539,5 +2829,5 @@ function allPermissionsGuard(permissions, redirectTo = '/') {
|
|
|
1539
2829
|
* Generated bundle index. Do not edit.
|
|
1540
2830
|
*/
|
|
1541
2831
|
|
|
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 };
|
|
2832
|
+
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, PermissionValidatorService, PlatformService, PreventDefaultDirective, PrimeModule, USER_PERMISSION_PROVIDER, USER_PROVIDER, UserMultiSelectComponent, UserSelectComponent, allPermissionsGuard, anyPermissionGuard, evaluateLogicNode, evaluatePermission, formatFileSize, getAcceptString, getFileIconClass, hasAllPermissions, hasAnyPermission, isFileTypeAllowed, permissionGuard };
|
|
1543
2833
|
//# sourceMappingURL=flusys-ng-shared.mjs.map
|