spiderly 19.7.1 → 19.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,6 +6,8 @@
6
6
 
7
7
  Spiderly Angular is a library designed to work with Spiderly, but it can also be used independently. It utilizes the PrimeNG UI library.
8
8
 
9
+ This npm package is the Angular library distribution. It is not the CLI package. The future npm CLI package is `spiderly-cli`, while the currently supported CLI remains the `Spiderly.CLI` NuGet tool.
10
+
9
11
  ## Sakai PrimeNG Theme
10
12
 
11
13
  Currently, Spiderly Angular uses and provides the layout of the free Sakai PrimeNG theme.
@@ -38,10 +38,11 @@ import * as i12 from 'primeng/button';
38
38
  import { ButtonModule } from 'primeng/button';
39
39
  import * as i1$4 from 'primeng/splitbutton';
40
40
  import { SplitButtonModule } from 'primeng/splitbutton';
41
- import { Subject, throttleTime, map, BehaviorSubject, of, delay as delay$1, withLatestFrom, combineLatest, filter as filter$1, firstValueFrom, finalize as finalize$1, tap as tap$1 } from 'rxjs';
41
+ import { Subject, throttleTime, map, BehaviorSubject, of, filter as filter$1, firstValueFrom, finalize as finalize$1, tap as tap$1 } from 'rxjs';
42
42
  import * as i3$2 from '@angular/router';
43
43
  import { NavigationEnd, RouterModule } from '@angular/router';
44
44
  import * as FileSaver from 'file-saver';
45
+ import mime from 'mime';
45
46
  import 'reflect-metadata';
46
47
  import * as i1$1 from 'primeng/api';
47
48
  import { ConfirmationService } from 'primeng/api';
@@ -59,7 +60,6 @@ import { SkeletonModule } from 'primeng/skeleton';
59
60
  import { trigger, state, transition, style, animate } from '@angular/animations';
60
61
  import * as i7 from 'primeng/avatar';
61
62
  import { AvatarModule } from 'primeng/avatar';
62
- import * as i8 from 'primeng/badge';
63
63
  import { BadgeModule } from 'primeng/badge';
64
64
  import * as i4$a from 'primeng/menubar';
65
65
  import { MenubarModule } from 'primeng/menubar';
@@ -70,6 +70,18 @@ import { ConfirmDialogModule } from 'primeng/confirmdialog';
70
70
  import * as i1$5 from 'primeng/dynamicdialog';
71
71
  import { NgxSpinnerService } from 'ngx-spinner';
72
72
 
73
+ /**
74
+ * Machine-readable error codes returned in `ApiErrorDTO.errorCode`.
75
+ * Mirror of Spiderly.Shared.DTO.ApiErrorCodes — keep in sync.
76
+ */
77
+ const ApiErrorCodes = {
78
+ InvalidToken: 'invalid_token',
79
+ ValidationFailed: 'validation_failed',
80
+ UniqueViolation: 'unique_violation',
81
+ ForeignKeyViolation: 'foreign_key_violation',
82
+ ConcurrencyConflict: 'concurrency_conflict',
83
+ };
84
+
73
85
  class BaseControl {
74
86
  constructor(translocoService) {
75
87
  this.translocoService = translocoService;
@@ -893,34 +905,7 @@ function validatePrecisionScale(value, precision, scale, ignoreTrailingZeros) {
893
905
  return true;
894
906
  }
895
907
  function getMimeTypeForFileName(fileName) {
896
- const mimeTypes = {
897
- '.jpg': 'image/jpeg',
898
- '.jpeg': 'image/jpeg',
899
- '.png': 'image/png',
900
- '.webp': 'image/webp',
901
- '.gif': 'image/gif',
902
- '.svg': 'image/svg+xml',
903
- '.pdf': 'application/pdf',
904
- '.txt': 'text/plain',
905
- '.html': 'text/html',
906
- '.css': 'text/css',
907
- '.js': 'application/javascript',
908
- '.json': 'application/json',
909
- '.csv': 'text/csv',
910
- '.xml': 'application/xml',
911
- '.zip': 'application/zip',
912
- '.mp4': 'video/mp4',
913
- '.mp3': 'audio/mpeg',
914
- '.wav': 'audio/wav',
915
- '.avi': 'video/x-msvideo',
916
- '.doc': 'application/msword',
917
- '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
918
- '.xls': 'application/vnd.ms-excel',
919
- '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
920
- // Add more as needed
921
- };
922
- const extension = fileName.substring(fileName.lastIndexOf('.')).toLowerCase();
923
- return mimeTypes[extension] || 'application/octet-stream'; // 'application/octet-stream' is a generic binary type
908
+ return mime.getType(fileName) ?? 'application/octet-stream';
924
909
  }
925
910
  function adjustColor(color, percent) {
926
911
  if (!/^#([0-9A-F]{3}){1,2}$/i.test(color)) {
@@ -1221,16 +1206,19 @@ class SpiderlyFileComponent extends BaseControl {
1221
1206
  this.onFileRemoved = new EventEmitter();
1222
1207
  this.acceptedFileTypes = ['image/*'];
1223
1208
  this.multiple = false;
1224
- this.isCloudinaryFileData = true;
1209
+ this.isUrlFileData = true;
1225
1210
  this.imageWidth = 0;
1226
1211
  this.imageHeight = 0;
1227
1212
  this.maxFileSize = 20_000_000;
1213
+ this.existingFileUrl = null;
1214
+ this.existingFileIsImage = false;
1215
+ this.existingFileName = '';
1228
1216
  this.files = [];
1229
1217
  }
1230
1218
  ngOnInit() {
1231
1219
  if (this.control?.value != null && this.fileData != null) {
1232
- if (this.isCloudinaryFileData) {
1233
- this.pushFileFromCloudinaryUrl(this.fileData);
1220
+ if (this.isUrlFileData) {
1221
+ this.setExistingFileUrl(this.fileData);
1234
1222
  }
1235
1223
  else {
1236
1224
  const file = this.getFileFromBase64(this.fileData);
@@ -1245,6 +1233,7 @@ class SpiderlyFileComponent extends BaseControl {
1245
1233
  }
1246
1234
  filesSelected(event) {
1247
1235
  const file = event.files[0];
1236
+ this.existingFileUrl = null;
1248
1237
  if (this.isFileImageType(file.type) &&
1249
1238
  this.hasImageDimensionConstraints()) {
1250
1239
  this.files = [];
@@ -1284,24 +1273,22 @@ class SpiderlyFileComponent extends BaseControl {
1284
1273
  }
1285
1274
  fileRemoved(removeFileCallback, index) {
1286
1275
  removeFileCallback(index);
1276
+ this.clearFile();
1277
+ }
1278
+ removeExistingFile() {
1279
+ this.existingFileUrl = null;
1280
+ this.clearFile();
1281
+ }
1282
+ clearFile() {
1287
1283
  this.control?.setValue(null);
1288
1284
  this.onFileRemoved.next(null);
1289
1285
  }
1290
- // Put inside global functions if you need it
1291
- async pushFileFromCloudinaryUrl(cloudinaryUrl) {
1292
- const response = await fetch(cloudinaryUrl);
1293
- if (!response.ok) {
1294
- throw new Error(`Failed to fetch file from Cloudinary: ${response.statusText}`);
1295
- }
1296
- const blob = await response.blob();
1297
- const urlParts = cloudinaryUrl.split('/');
1298
- const lastPart = urlParts[urlParts.length - 1];
1299
- const fileName = lastPart.split('?')[0];
1300
- const file = new File([blob], fileName, { type: blob.type });
1301
- this.files = [...this.files, file]; // this.files.push(file); doesn't work
1302
- return file;
1286
+ setExistingFileUrl(url) {
1287
+ this.existingFileUrl = url;
1288
+ this.existingFileName = url.split('/').pop()?.split('?')[0] ?? '';
1289
+ const mimeType = getMimeTypeForFileName(this.existingFileName);
1290
+ this.existingFileIsImage = isFileImageType(mimeType);
1303
1291
  }
1304
- // Put inside global functions if you need it
1305
1292
  getFileFromBase64(base64String) {
1306
1293
  const [header, base64Content] = base64String.split(';base64,');
1307
1294
  const fileName = header.split('=')[1];
@@ -1322,7 +1309,7 @@ class SpiderlyFileComponent extends BaseControl {
1322
1309
  return isExcelFileType(mimeType);
1323
1310
  }
1324
1311
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyFileComponent, deps: [{ token: i1.TranslocoService }, { token: SpiderlyMessageService }, { token: ValidatorAbstractService }], target: i0.ɵɵFactoryTarget.Component }); }
1325
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: SpiderlyFileComponent, isStandalone: true, selector: "spiderly-file", inputs: { objectId: "objectId", fileData: "fileData", acceptedFileTypes: "acceptedFileTypes", required: "required", multiple: "multiple", isCloudinaryFileData: "isCloudinaryFileData", imageWidth: "imageWidth", imageHeight: "imageHeight", maxFileSize: "maxFileSize", files: "files" }, outputs: { onFileSelected: "onFileSelected", onFileRemoved: "onFileRemoved" }, usesInheritance: true, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div style=\"display: flex; flex-direction: column; gap: 0.5rem; padding: 1px\">\n <div *ngIf=\"getTranslatedLabel() != '' && getTranslatedLabel() != null\">\n <label>{{ getTranslatedLabel() }}</label>\n <!-- It's okay for this control, because for the custom uploads where we are not initializing the control from the backend, there is no need for formControl. -->\n <required *ngIf=\"control?.required || required\"></required>\n </div>\n\n <p-fileUpload\n [files]=\"files\"\n [disabled]=\"disabled\"\n [name]=\"control?.label ?? label\"\n [multiple]=\"multiple\"\n [accept]=\"acceptedFileTypesCommaSeparated\"\n [maxFileSize]=\"maxFileSize\"\n (onSelect)=\"filesSelected($event)\"\n [class]=\"control?.invalid && control?.dirty ? 'control-error-border' : ''\"\n >\n <ng-template\n pTemplate=\"header\"\n let-files\n let-chooseCallback=\"chooseCallback\"\n let-clearCallback=\"clearCallback\"\n let-uploadCallback=\"uploadCallback\"\n >\n <div\n class=\"flex flex-wrap justify-between items-center flex-1 gap-2\"\n >\n <div class=\"flex gap-2\">\n <spiderly-button\n [disabled]=\"disabled\"\n (onClick)=\"choose($event, chooseCallback)\"\n icon=\"pi pi-upload\"\n [rounded]=\"true\"\n [outlined]=\"true\"\n />\n </div>\n </div>\n </ng-template>\n <ng-template\n pTemplate=\"content\"\n let-files\n let-removeFileCallback=\"removeFileCallback\"\n >\n <div *ngIf=\"files.length > 0\">\n <div class=\"flex justify-center p-0 gap-8\">\n <div\n *ngFor=\"let file of files; let index = index\"\n class=\"card m-0 px-4 py-4 flex flex-col items-center gap-4\"\n style=\"justify-content: center; overflow: hidden\"\n >\n <div *ngIf=\"isFileImageType(file.type)\" class=\"image-container\">\n <img role=\"presentation\" [src]=\"file.objectURL\" />\n </div>\n <div *ngIf=\"isExcelFileType(file.type)\" class=\"excel-container\">\n <div class=\"excel-details\">\n <i\n class=\"pi pi-file-excel\"\n style=\"color: green; margin-right: 4px\"\n ></i>\n <span class=\"file-name\">{{ file.name }}</span>\n </div>\n </div>\n <spiderly-button\n [disabled]=\"disabled\"\n icon=\"pi pi-times\"\n (onClick)=\"fileRemoved(removeFileCallback, index)\"\n [outlined]=\"true\"\n [rounded]=\"true\"\n severity=\"danger\"\n />\n </div>\n </div>\n </div>\n </ng-template>\n <ng-template pTemplate=\"file\"> </ng-template>\n <ng-template pTemplate=\"empty\">\n <div class=\"flex items-center justify-center flex-col\">\n <i\n class=\"pi pi-cloud-upload border-2 rounded-full p-8 text-8xl text-gray-400 border-gray-400 mt-4\"\n ></i>\n <p class=\"mt-6 mb-0\">{{ t(\"DragAndDropFilesHereToUpload\") }}</p>\n </div>\n </ng-template>\n </p-fileUpload>\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: FileUploadModule }, { kind: "component", type: i5$2.FileUpload, selector: "p-fileupload, p-fileUpload", inputs: ["name", "url", "method", "multiple", "accept", "disabled", "auto", "withCredentials", "maxFileSize", "invalidFileSizeMessageSummary", "invalidFileSizeMessageDetail", "invalidFileTypeMessageSummary", "invalidFileTypeMessageDetail", "invalidFileLimitMessageDetail", "invalidFileLimitMessageSummary", "style", "styleClass", "previewWidth", "chooseLabel", "uploadLabel", "cancelLabel", "chooseIcon", "uploadIcon", "cancelIcon", "showUploadButton", "showCancelButton", "mode", "headers", "customUpload", "fileLimit", "uploadStyleClass", "cancelStyleClass", "removeStyleClass", "chooseStyleClass", "chooseButtonProps", "uploadButtonProps", "cancelButtonProps", "files"], outputs: ["onBeforeUpload", "onSend", "onUpload", "onError", "onClear", "onRemove", "onSelect", "onProgress", "uploadHandler", "onImageError", "onRemoveUploadedFile"] }, { kind: "directive", type: i1$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: RequiredComponent, selector: "required" }, { kind: "component", type: SpiderlyButtonComponent, selector: "spiderly-button", inputs: ["type"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] }); }
1312
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: SpiderlyFileComponent, isStandalone: true, selector: "spiderly-file", inputs: { objectId: "objectId", fileData: "fileData", acceptedFileTypes: "acceptedFileTypes", required: "required", multiple: "multiple", isUrlFileData: "isUrlFileData", imageWidth: "imageWidth", imageHeight: "imageHeight", maxFileSize: "maxFileSize", files: "files" }, outputs: { onFileSelected: "onFileSelected", onFileRemoved: "onFileRemoved" }, usesInheritance: true, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div style=\"display: flex; flex-direction: column; gap: 0.5rem; padding: 1px\">\n <div *ngIf=\"getTranslatedLabel() != '' && getTranslatedLabel() != null\">\n <label>{{ getTranslatedLabel() }}</label>\n <!-- It's okay for this control, because for the custom uploads where we are not initializing the control from the backend, there is no need for formControl. -->\n <required *ngIf=\"control?.required || required\"></required>\n </div>\n\n <p-fileUpload\n [files]=\"files\"\n [disabled]=\"disabled\"\n [name]=\"control?.label ?? label\"\n [multiple]=\"multiple\"\n [accept]=\"acceptedFileTypesCommaSeparated\"\n [maxFileSize]=\"maxFileSize\"\n (onSelect)=\"filesSelected($event)\"\n [class]=\"control?.invalid && control?.dirty ? 'control-error-border' : ''\"\n >\n <ng-template\n pTemplate=\"header\"\n let-files\n let-chooseCallback=\"chooseCallback\"\n let-clearCallback=\"clearCallback\"\n let-uploadCallback=\"uploadCallback\"\n >\n <div\n class=\"flex flex-wrap justify-between items-center flex-1 gap-2\"\n >\n <div class=\"flex gap-2\">\n <spiderly-button\n [disabled]=\"disabled\"\n (onClick)=\"choose($event, chooseCallback)\"\n icon=\"pi pi-upload\"\n [rounded]=\"true\"\n [outlined]=\"true\"\n />\n </div>\n </div>\n </ng-template>\n <ng-template\n pTemplate=\"content\"\n let-files\n let-removeFileCallback=\"removeFileCallback\"\n >\n <div *ngIf=\"existingFileUrl\">\n <div class=\"flex justify-center p-0 gap-8\">\n <div\n class=\"card m-0 px-4 py-4 flex flex-col items-center gap-4\"\n style=\"justify-content: center; overflow: hidden\"\n >\n <div *ngIf=\"existingFileIsImage\" class=\"image-container\">\n <img [src]=\"existingFileUrl\" />\n </div>\n <div *ngIf=\"!existingFileIsImage\">\n <i class=\"pi pi-file\" style=\"margin-right: 4px\"></i>\n <span>{{ existingFileName }}</span>\n </div>\n <spiderly-button\n [disabled]=\"disabled\"\n icon=\"pi pi-times\"\n (onClick)=\"removeExistingFile()\"\n [outlined]=\"true\"\n [rounded]=\"true\"\n severity=\"danger\"\n />\n </div>\n </div>\n </div>\n <div *ngIf=\"!existingFileUrl && files.length > 0\">\n <div class=\"flex justify-center p-0 gap-8\">\n <div\n *ngFor=\"let file of files; let index = index\"\n class=\"card m-0 px-4 py-4 flex flex-col items-center gap-4\"\n style=\"justify-content: center; overflow: hidden\"\n >\n <div *ngIf=\"isFileImageType(file.type)\" class=\"image-container\">\n <img role=\"presentation\" [src]=\"file.objectURL\" />\n </div>\n <div *ngIf=\"isExcelFileType(file.type)\" class=\"excel-container\">\n <div class=\"excel-details\">\n <i\n class=\"pi pi-file-excel\"\n style=\"color: green; margin-right: 4px\"\n ></i>\n <span class=\"file-name\">{{ file.name }}</span>\n </div>\n </div>\n <spiderly-button\n [disabled]=\"disabled\"\n icon=\"pi pi-times\"\n (onClick)=\"fileRemoved(removeFileCallback, index)\"\n [outlined]=\"true\"\n [rounded]=\"true\"\n severity=\"danger\"\n />\n </div>\n </div>\n </div>\n </ng-template>\n <ng-template pTemplate=\"file\"> </ng-template>\n <ng-template pTemplate=\"empty\">\n <div *ngIf=\"!existingFileUrl\" class=\"flex items-center justify-center flex-col\">\n <i\n class=\"pi pi-cloud-upload border-2 rounded-full p-8 text-8xl text-gray-400 border-gray-400 mt-4\"\n ></i>\n <p class=\"mt-6 mb-0\">{{ t(\"DragAndDropFilesHereToUpload\") }}</p>\n </div>\n </ng-template>\n </p-fileUpload>\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: FileUploadModule }, { kind: "component", type: i5$2.FileUpload, selector: "p-fileupload, p-fileUpload", inputs: ["name", "url", "method", "multiple", "accept", "disabled", "auto", "withCredentials", "maxFileSize", "invalidFileSizeMessageSummary", "invalidFileSizeMessageDetail", "invalidFileTypeMessageSummary", "invalidFileTypeMessageDetail", "invalidFileLimitMessageDetail", "invalidFileLimitMessageSummary", "style", "styleClass", "previewWidth", "chooseLabel", "uploadLabel", "cancelLabel", "chooseIcon", "uploadIcon", "cancelIcon", "showUploadButton", "showCancelButton", "mode", "headers", "customUpload", "fileLimit", "uploadStyleClass", "cancelStyleClass", "removeStyleClass", "chooseStyleClass", "chooseButtonProps", "uploadButtonProps", "cancelButtonProps", "files"], outputs: ["onBeforeUpload", "onSend", "onUpload", "onError", "onClear", "onRemove", "onSelect", "onProgress", "uploadHandler", "onImageError", "onRemoveUploadedFile"] }, { kind: "directive", type: i1$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: RequiredComponent, selector: "required" }, { kind: "component", type: SpiderlyButtonComponent, selector: "spiderly-button", inputs: ["type"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] }); }
1326
1313
  }
1327
1314
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: SpiderlyFileComponent, decorators: [{
1328
1315
  type: Component,
@@ -1334,7 +1321,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
1334
1321
  RequiredComponent,
1335
1322
  SpiderlyButtonComponent,
1336
1323
  TranslocoDirective,
1337
- ], template: "<ng-container *transloco=\"let t\">\n <div style=\"display: flex; flex-direction: column; gap: 0.5rem; padding: 1px\">\n <div *ngIf=\"getTranslatedLabel() != '' && getTranslatedLabel() != null\">\n <label>{{ getTranslatedLabel() }}</label>\n <!-- It's okay for this control, because for the custom uploads where we are not initializing the control from the backend, there is no need for formControl. -->\n <required *ngIf=\"control?.required || required\"></required>\n </div>\n\n <p-fileUpload\n [files]=\"files\"\n [disabled]=\"disabled\"\n [name]=\"control?.label ?? label\"\n [multiple]=\"multiple\"\n [accept]=\"acceptedFileTypesCommaSeparated\"\n [maxFileSize]=\"maxFileSize\"\n (onSelect)=\"filesSelected($event)\"\n [class]=\"control?.invalid && control?.dirty ? 'control-error-border' : ''\"\n >\n <ng-template\n pTemplate=\"header\"\n let-files\n let-chooseCallback=\"chooseCallback\"\n let-clearCallback=\"clearCallback\"\n let-uploadCallback=\"uploadCallback\"\n >\n <div\n class=\"flex flex-wrap justify-between items-center flex-1 gap-2\"\n >\n <div class=\"flex gap-2\">\n <spiderly-button\n [disabled]=\"disabled\"\n (onClick)=\"choose($event, chooseCallback)\"\n icon=\"pi pi-upload\"\n [rounded]=\"true\"\n [outlined]=\"true\"\n />\n </div>\n </div>\n </ng-template>\n <ng-template\n pTemplate=\"content\"\n let-files\n let-removeFileCallback=\"removeFileCallback\"\n >\n <div *ngIf=\"files.length > 0\">\n <div class=\"flex justify-center p-0 gap-8\">\n <div\n *ngFor=\"let file of files; let index = index\"\n class=\"card m-0 px-4 py-4 flex flex-col items-center gap-4\"\n style=\"justify-content: center; overflow: hidden\"\n >\n <div *ngIf=\"isFileImageType(file.type)\" class=\"image-container\">\n <img role=\"presentation\" [src]=\"file.objectURL\" />\n </div>\n <div *ngIf=\"isExcelFileType(file.type)\" class=\"excel-container\">\n <div class=\"excel-details\">\n <i\n class=\"pi pi-file-excel\"\n style=\"color: green; margin-right: 4px\"\n ></i>\n <span class=\"file-name\">{{ file.name }}</span>\n </div>\n </div>\n <spiderly-button\n [disabled]=\"disabled\"\n icon=\"pi pi-times\"\n (onClick)=\"fileRemoved(removeFileCallback, index)\"\n [outlined]=\"true\"\n [rounded]=\"true\"\n severity=\"danger\"\n />\n </div>\n </div>\n </div>\n </ng-template>\n <ng-template pTemplate=\"file\"> </ng-template>\n <ng-template pTemplate=\"empty\">\n <div class=\"flex items-center justify-center flex-col\">\n <i\n class=\"pi pi-cloud-upload border-2 rounded-full p-8 text-8xl text-gray-400 border-gray-400 mt-4\"\n ></i>\n <p class=\"mt-6 mb-0\">{{ t(\"DragAndDropFilesHereToUpload\") }}</p>\n </div>\n </ng-template>\n </p-fileUpload>\n </div>\n</ng-container>\n" }]
1324
+ ], template: "<ng-container *transloco=\"let t\">\n <div style=\"display: flex; flex-direction: column; gap: 0.5rem; padding: 1px\">\n <div *ngIf=\"getTranslatedLabel() != '' && getTranslatedLabel() != null\">\n <label>{{ getTranslatedLabel() }}</label>\n <!-- It's okay for this control, because for the custom uploads where we are not initializing the control from the backend, there is no need for formControl. -->\n <required *ngIf=\"control?.required || required\"></required>\n </div>\n\n <p-fileUpload\n [files]=\"files\"\n [disabled]=\"disabled\"\n [name]=\"control?.label ?? label\"\n [multiple]=\"multiple\"\n [accept]=\"acceptedFileTypesCommaSeparated\"\n [maxFileSize]=\"maxFileSize\"\n (onSelect)=\"filesSelected($event)\"\n [class]=\"control?.invalid && control?.dirty ? 'control-error-border' : ''\"\n >\n <ng-template\n pTemplate=\"header\"\n let-files\n let-chooseCallback=\"chooseCallback\"\n let-clearCallback=\"clearCallback\"\n let-uploadCallback=\"uploadCallback\"\n >\n <div\n class=\"flex flex-wrap justify-between items-center flex-1 gap-2\"\n >\n <div class=\"flex gap-2\">\n <spiderly-button\n [disabled]=\"disabled\"\n (onClick)=\"choose($event, chooseCallback)\"\n icon=\"pi pi-upload\"\n [rounded]=\"true\"\n [outlined]=\"true\"\n />\n </div>\n </div>\n </ng-template>\n <ng-template\n pTemplate=\"content\"\n let-files\n let-removeFileCallback=\"removeFileCallback\"\n >\n <div *ngIf=\"existingFileUrl\">\n <div class=\"flex justify-center p-0 gap-8\">\n <div\n class=\"card m-0 px-4 py-4 flex flex-col items-center gap-4\"\n style=\"justify-content: center; overflow: hidden\"\n >\n <div *ngIf=\"existingFileIsImage\" class=\"image-container\">\n <img [src]=\"existingFileUrl\" />\n </div>\n <div *ngIf=\"!existingFileIsImage\">\n <i class=\"pi pi-file\" style=\"margin-right: 4px\"></i>\n <span>{{ existingFileName }}</span>\n </div>\n <spiderly-button\n [disabled]=\"disabled\"\n icon=\"pi pi-times\"\n (onClick)=\"removeExistingFile()\"\n [outlined]=\"true\"\n [rounded]=\"true\"\n severity=\"danger\"\n />\n </div>\n </div>\n </div>\n <div *ngIf=\"!existingFileUrl && files.length > 0\">\n <div class=\"flex justify-center p-0 gap-8\">\n <div\n *ngFor=\"let file of files; let index = index\"\n class=\"card m-0 px-4 py-4 flex flex-col items-center gap-4\"\n style=\"justify-content: center; overflow: hidden\"\n >\n <div *ngIf=\"isFileImageType(file.type)\" class=\"image-container\">\n <img role=\"presentation\" [src]=\"file.objectURL\" />\n </div>\n <div *ngIf=\"isExcelFileType(file.type)\" class=\"excel-container\">\n <div class=\"excel-details\">\n <i\n class=\"pi pi-file-excel\"\n style=\"color: green; margin-right: 4px\"\n ></i>\n <span class=\"file-name\">{{ file.name }}</span>\n </div>\n </div>\n <spiderly-button\n [disabled]=\"disabled\"\n icon=\"pi pi-times\"\n (onClick)=\"fileRemoved(removeFileCallback, index)\"\n [outlined]=\"true\"\n [rounded]=\"true\"\n severity=\"danger\"\n />\n </div>\n </div>\n </div>\n </ng-template>\n <ng-template pTemplate=\"file\"> </ng-template>\n <ng-template pTemplate=\"empty\">\n <div *ngIf=\"!existingFileUrl\" class=\"flex items-center justify-center flex-col\">\n <i\n class=\"pi pi-cloud-upload border-2 rounded-full p-8 text-8xl text-gray-400 border-gray-400 mt-4\"\n ></i>\n <p class=\"mt-6 mb-0\">{{ t(\"DragAndDropFilesHereToUpload\") }}</p>\n </div>\n </ng-template>\n </p-fileUpload>\n </div>\n</ng-container>\n" }]
1338
1325
  }], ctorParameters: () => [{ type: i1.TranslocoService }, { type: SpiderlyMessageService }, { type: ValidatorAbstractService }], propDecorators: { onFileSelected: [{
1339
1326
  type: Output
1340
1327
  }], onFileRemoved: [{
@@ -1349,7 +1336,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
1349
1336
  type: Input
1350
1337
  }], multiple: [{
1351
1338
  type: Input
1352
- }], isCloudinaryFileData: [{
1339
+ }], isUrlFileData: [{
1353
1340
  type: Input
1354
1341
  }], imageWidth: [{
1355
1342
  type: Input
@@ -1983,33 +1970,6 @@ class BaseFormComponent {
1983
1970
  this.baseFormService.showInvalidFieldsMessage();
1984
1971
  }
1985
1972
  };
1986
- /**
1987
- * Handles navigation after a successful save.
1988
- * Override this to customize the post-save navigation behavior.
1989
- * By default, navigates to the parent URL when `rerouteId` is not provided, or to the saved object's URL otherwise.
1990
- *
1991
- * @param rerouteId - The ID of the saved object, used to build the target URL. When not provided, navigates to the parent URL.
1992
- *
1993
- * @example
1994
- * ```ts
1995
- * // Override to navigate to a custom route after save
1996
- * rerouteToSavedObject = (rerouteId: number | string): void => {
1997
- * this.router.navigateByUrl(`/products/${rerouteId}/details`);
1998
- * };
1999
- * ```
2000
- */
2001
- this.rerouteToSavedObject = (rerouteId) => {
2002
- if (rerouteId == null) {
2003
- const currentUrl = this.router.url;
2004
- const parentUrl = getParentUrl(currentUrl);
2005
- this.router.navigateByUrl(parentUrl);
2006
- return;
2007
- }
2008
- const segments = this.router.url.split('/');
2009
- segments[segments.length - 1] = rerouteId.toString();
2010
- const newUrl = segments.join('/');
2011
- this.router.navigateByUrl(newUrl);
2012
- };
2013
1973
  /**
2014
1974
  * Hook that runs **before** form validation and the save request.
2015
1975
  * Use this to modify the save body or perform any pre-save logic (e.g., transforming data, setting computed fields).
@@ -2050,6 +2010,33 @@ class BaseFormComponent {
2050
2010
  this.onAfterSaveRequest = () => { };
2051
2011
  }
2052
2012
  ngOnInit() { }
2013
+ /**
2014
+ * Handles navigation after a successful save.
2015
+ * Override this to customize the post-save navigation behavior.
2016
+ * By default, navigates to the parent URL when `rerouteId` is not provided, or to the saved object's URL otherwise.
2017
+ *
2018
+ * @param rerouteId - The ID of the saved object, used to build the target URL. When not provided, navigates to the parent URL.
2019
+ *
2020
+ * @example
2021
+ * ```ts
2022
+ * // Override to navigate to a custom route after save
2023
+ * override rerouteToSavedObject(rerouteId: number | string): void {
2024
+ * this.router.navigateByUrl(`/products/${rerouteId}/details`);
2025
+ * }
2026
+ * ```
2027
+ */
2028
+ rerouteToSavedObject(rerouteId) {
2029
+ if (rerouteId == null) {
2030
+ const currentUrl = this.router.url;
2031
+ const parentUrl = getParentUrl(currentUrl);
2032
+ this.router.navigateByUrl(parentUrl);
2033
+ return;
2034
+ }
2035
+ const segments = this.router.url.split('/');
2036
+ segments[segments.length - 1] = rerouteId.toString();
2037
+ const newUrl = segments.join('/');
2038
+ this.router.navigateByUrl(newUrl);
2039
+ }
2053
2040
  //#endregion
2054
2041
  //#region Model List
2055
2042
  getFormArrayControlByIndex(formControlName, formArray, index, filter) {
@@ -2193,11 +2180,6 @@ class ApiSecurityService {
2193
2180
  this.getCurrentUserPermissionCodes = () => {
2194
2181
  return this.http.get(`${this.config.apiUrl}/Security/GetCurrentUserPermissionCodes`, this.config.httpSkipSpinnerOptions);
2195
2182
  };
2196
- //#endregion
2197
- //#region Notification
2198
- this.getUnreadNotificationsCountForCurrentUser = () => {
2199
- return this.http.get(`${this.config.apiUrl}/Notification/GetUnreadNotificationsCountForCurrentUser`, this.config.httpSkipSpinnerOptions);
2200
- };
2201
2183
  }
2202
2184
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ApiSecurityService, deps: [{ token: i1$2.HttpClient }, { token: ConfigServiceBase }], target: i0.ɵɵFactoryTarget.Injectable }); }
2203
2185
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ApiSecurityService, providedIn: 'root' }); }
@@ -2868,11 +2850,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
2868
2850
  }] } });
2869
2851
 
2870
2852
  class InitTopBarData extends BaseEntity {
2871
- constructor({ companyName, userProfilePath, unreadNotificationsCount, showProfileIcon, currentUser, } = {}) {
2853
+ constructor({ companyName, userProfilePath, showProfileIcon, currentUser, } = {}) {
2872
2854
  super();
2873
2855
  this.companyName = companyName;
2874
2856
  this.userProfilePath = userProfilePath;
2875
- this.unreadNotificationsCount = unreadNotificationsCount;
2876
2857
  this.showProfileIcon = showProfileIcon;
2877
2858
  this.currentUser = currentUser;
2878
2859
  }
@@ -2884,8 +2865,6 @@ class LayoutServiceBase {
2884
2865
  this.apiService = apiService;
2885
2866
  this.config = config;
2886
2867
  this.authService = authService;
2887
- this._unreadNotificationsNumber = new BehaviorSubject(null);
2888
- this.unreadNotificationsCount$ = this._unreadNotificationsNumber.asObservable();
2889
2868
  this.layoutConfig = {
2890
2869
  ripple: false,
2891
2870
  inputStyle: 'outlined',
@@ -2908,34 +2887,12 @@ class LayoutServiceBase {
2908
2887
  this.overlayOpen = new Subject();
2909
2888
  this.configUpdate$ = this.configUpdate.asObservable();
2910
2889
  this.overlayOpen$ = this.overlayOpen.asObservable();
2911
- this.initUnreadNotificationsCountForCurrentUser = () => {
2912
- this.initUnreadNotificationsCountForCurrentUserObservable()
2913
- .pipe(delay$1(1), // HACK: Adding delay so both additionalObservable and user emits single null value when logout
2914
- withLatestFrom(this.authService.user$))
2915
- .subscribe(([additionalObservable, user]) => {
2916
- if (user != null && additionalObservable !== undefined) {
2917
- this.setUnreadNotificationsCountForCurrentUser().subscribe();
2918
- }
2919
- });
2920
- };
2921
- this.initUnreadNotificationsCountForCurrentUserObservable = () => {
2922
- return this.authService.user$;
2923
- };
2924
- this.setUnreadNotificationsCountForCurrentUser = () => {
2925
- return this.apiService.getUnreadNotificationsCountForCurrentUser().pipe(map((unreadNotificationsCount) => {
2926
- this._unreadNotificationsNumber.next(unreadNotificationsCount);
2927
- }));
2928
- };
2929
2890
  //#region Top Bar
2930
2891
  this.initTopBarData = () => {
2931
- return combineLatest([
2932
- this.authService.user$,
2933
- this.unreadNotificationsCount$,
2934
- ]).pipe(map(([currentUser, unreadNotificationsCount]) => {
2892
+ return this.authService.user$.pipe(map((currentUser) => {
2935
2893
  return new InitTopBarData({
2936
2894
  companyName: this.config.companyName,
2937
2895
  userProfilePath: `/administration/users/${currentUser?.id}`,
2938
- unreadNotificationsCount: unreadNotificationsCount,
2939
2896
  showProfileIcon: true,
2940
2897
  currentUser: currentUser,
2941
2898
  });
@@ -3242,22 +3199,11 @@ class ProfileAvatarComponent {
3242
3199
  this.menuItems = [];
3243
3200
  this.initTopBarSubscription = null;
3244
3201
  this.showProfileIcon = false;
3245
- this.notificationMenuItem = {
3246
- label: this.translocoService.translate('Notifications'),
3247
- icon: 'pi-bell',
3248
- showNotificationBadge: true,
3249
- onClick: () => {
3250
- this.router.navigateByUrl(`/notifications`);
3251
- },
3252
- };
3253
3202
  this.onAfterNgOnInit = () => {
3254
3203
  this.initTopBarSubscription = this.layoutService
3255
3204
  .initTopBarData()
3256
3205
  .subscribe((initTopBarData) => {
3257
3206
  this.userProfilePath = initTopBarData.userProfilePath;
3258
- this.unreadNotificationsCount = initTopBarData.unreadNotificationsCount;
3259
- this.notificationMenuItem.showNotificationBadge =
3260
- initTopBarData.unreadNotificationsCount > 0;
3261
3207
  this.showProfileIcon = initTopBarData.showProfileIcon;
3262
3208
  this.currentUser = initTopBarData.currentUser;
3263
3209
  this.avatarLabel = initTopBarData.currentUser?.email
@@ -3277,7 +3223,6 @@ class ProfileAvatarComponent {
3277
3223
  this.routeToUserPage();
3278
3224
  },
3279
3225
  },
3280
- this.notificationMenuItem,
3281
3226
  {
3282
3227
  label: this.translocoService.translate('Logout'),
3283
3228
  icon: 'pi-sign-out',
@@ -3319,7 +3264,7 @@ class ProfileAvatarComponent {
3319
3264
  }
3320
3265
  }
3321
3266
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ProfileAvatarComponent, deps: [{ token: LayoutServiceBase }, { token: AuthServiceBase }, { token: i3$2.Router }, { token: i1.TranslocoService }, { token: ConfigServiceBase }], target: i0.ɵɵFactoryTarget.Component }); }
3322
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: ProfileAvatarComponent, isStandalone: true, selector: "spiderly-profile-avatar", inputs: { isSideMenuLayout: "isSideMenuLayout", routeOnLargeProfileAvatarClick: "routeOnLargeProfileAvatarClick", showLoginButton: "showLoginButton", routeToLoginPage: "routeToLoginPage", loginButtonOutlined: "loginButtonOutlined", loginButtonSeverity: "loginButtonSeverity", loginButtonSize: "loginButtonSize", menuItems: "menuItems" }, outputs: { onLoginButtonClick: "onLoginButtonClick" }, viewQueries: [{ propertyName: "menu", first: true, predicate: ["topbarmenu"], descendants: true }, { propertyName: "topbarProfileDropdownMenuButton", first: true, predicate: ["topbarprofiledropdownmenubutton"], descendants: true }], ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div *ngIf=\"this.currentUser != null\" #topbarmenu>\n <div\n #topbarprofiledropdownmenubutton\n (click)=\"layoutService.showProfileDropdownSidebar()\"\n >\n <p-avatar\n *ngIf=\"showProfileIcon\"\n [label]=\"avatarLabel\"\n [style]=\"{\n 'background-color': 'var(--p-primary-color)',\n color: '#fff',\n cursor: 'pointer',\n width: '34px',\n height: '34px',\n 'font-size': '21px',\n }\"\n pBadge\n [badgeStyleClass]=\"'p-badge-danger'\"\n [badgeDisabled]=\"\n unreadNotificationsCount == 0 || unreadNotificationsCount == null\n \"\n [value]=\"unreadNotificationsCount\"\n />\n </div>\n <div #topbarprofiledropdownmenu (document:click)=\"onDocumentClick($event)\">\n <div\n *ngIf=\"layoutService.state.profileDropdownSidebarVisible\"\n style=\"\n width: 280px;\n position: absolute;\n padding: 15px;\n background: var(--p-content-background);\n \"\n class=\"card\"\n [ngClass]=\"{\n 'side-menu-profile-dialog': isSideMenuLayout,\n 'top-menu-profile-dialog': !isSideMenuLayout,\n }\"\n >\n <div\n style=\"\n display: flex;\n flex-direction: column;\n justify-content: center;\n text-align: center;\n gap: 10px;\n \"\n >\n <p-avatar\n [label]=\"avatarLabel\"\n size=\"xlarge\"\n [style]=\"{\n 'background-color': 'var(--p-primary-color)',\n color: '#fff',\n margin: 'auto',\n cursor: routeOnLargeProfileAvatarClick ? 'pointer' : '',\n }\"\n (click)=\"routeToUserPage()\"\n />\n <div style=\"font-size: 15px\">{{ currentUser?.email }}</div>\n </div>\n <div style=\"margin-top: 15px\">\n <div\n *ngFor=\"let item of menuItems\"\n [style]=\"item.showSeparator ? 'margin-top: 5px;' : ''\"\n >\n <div *ngIf=\"item.showSeparator\" class=\"gray-separator\"></div>\n <div\n (click)=\"item.onClick()\"\n class=\"hover-card\"\n style=\"\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 5px;\n \"\n >\n <i\n class=\"pi pi-fw {{ item.icon }}\"\n style=\"font-size: 16px; position: relative\"\n >\n <span\n *ngIf=\"\n item.showNotificationBadge && unreadNotificationsCount != 0\n \"\n class=\"badge\"\n ></span>\n </i>\n <div style=\"font-size: 15px\">{{ item.label }}</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <div *ngIf=\"this.currentUser == null && this.showLoginButton\">\n <spiderly-button\n [label]=\"t('Login')\"\n (onClick)=\"loginButtonClick()\"\n icon=\"pi pi-sign-in\"\n [outlined]=\"loginButtonOutlined\"\n [severity]=\"loginButtonSeverity\"\n [size]=\"loginButtonSize\"\n ></spiderly-button>\n </div>\n</ng-container>\n", styles: [".side-menu-profile-dialog{right:26px;top:60px}.top-menu-profile-dialog{right:0;top:40px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: RouterModule }, { kind: "ngmodule", type: AvatarModule }, { kind: "component", type: i7.Avatar, selector: "p-avatar", inputs: ["label", "icon", "image", "size", "shape", "style", "styleClass", "ariaLabel", "ariaLabelledBy"], outputs: ["onImageError"] }, { kind: "ngmodule", type: BadgeModule }, { kind: "directive", type: i8.BadgeDirective, selector: "[pBadge]", inputs: ["badgeDisabled", "badgeSize", "size", "severity", "value", "badgeStyle", "badgeStyleClass"] }, { kind: "component", type: SpiderlyButtonComponent, selector: "spiderly-button", inputs: ["type"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] }); }
3267
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.13", type: ProfileAvatarComponent, isStandalone: true, selector: "spiderly-profile-avatar", inputs: { isSideMenuLayout: "isSideMenuLayout", routeOnLargeProfileAvatarClick: "routeOnLargeProfileAvatarClick", showLoginButton: "showLoginButton", routeToLoginPage: "routeToLoginPage", loginButtonOutlined: "loginButtonOutlined", loginButtonSeverity: "loginButtonSeverity", loginButtonSize: "loginButtonSize", menuItems: "menuItems" }, outputs: { onLoginButtonClick: "onLoginButtonClick" }, viewQueries: [{ propertyName: "menu", first: true, predicate: ["topbarmenu"], descendants: true }, { propertyName: "topbarProfileDropdownMenuButton", first: true, predicate: ["topbarprofiledropdownmenubutton"], descendants: true }], ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div *ngIf=\"this.currentUser != null\" #topbarmenu>\n <div\n #topbarprofiledropdownmenubutton\n (click)=\"layoutService.showProfileDropdownSidebar()\"\n >\n <p-avatar\n *ngIf=\"showProfileIcon\"\n [label]=\"avatarLabel\"\n [style]=\"{\n 'background-color': 'var(--p-primary-color)',\n color: '#fff',\n cursor: 'pointer',\n width: '34px',\n height: '34px',\n 'font-size': '21px',\n }\"\n />\n </div>\n <div #topbarprofiledropdownmenu (document:click)=\"onDocumentClick($event)\">\n <div\n *ngIf=\"layoutService.state.profileDropdownSidebarVisible\"\n style=\"\n width: 280px;\n position: absolute;\n padding: 15px;\n background: var(--p-content-background);\n \"\n class=\"card\"\n [ngClass]=\"{\n 'side-menu-profile-dialog': isSideMenuLayout,\n 'top-menu-profile-dialog': !isSideMenuLayout,\n }\"\n >\n <div\n style=\"\n display: flex;\n flex-direction: column;\n justify-content: center;\n text-align: center;\n gap: 10px;\n \"\n >\n <p-avatar\n [label]=\"avatarLabel\"\n size=\"xlarge\"\n [style]=\"{\n 'background-color': 'var(--p-primary-color)',\n color: '#fff',\n margin: 'auto',\n cursor: routeOnLargeProfileAvatarClick ? 'pointer' : '',\n }\"\n (click)=\"routeToUserPage()\"\n />\n <div style=\"font-size: 15px\">{{ currentUser?.email }}</div>\n </div>\n <div style=\"margin-top: 15px\">\n <div\n *ngFor=\"let item of menuItems\"\n [style]=\"item.showSeparator ? 'margin-top: 5px;' : ''\"\n >\n <div *ngIf=\"item.showSeparator\" class=\"gray-separator\"></div>\n <div\n (click)=\"item.onClick()\"\n class=\"hover-card\"\n style=\"\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 5px;\n \"\n >\n <i\n class=\"pi pi-fw {{ item.icon }}\"\n style=\"font-size: 16px; position: relative\"\n ></i>\n <div style=\"font-size: 15px\">{{ item.label }}</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <div *ngIf=\"this.currentUser == null && this.showLoginButton\">\n <spiderly-button\n [label]=\"t('Login')\"\n (onClick)=\"loginButtonClick()\"\n icon=\"pi pi-sign-in\"\n [outlined]=\"loginButtonOutlined\"\n [severity]=\"loginButtonSeverity\"\n [size]=\"loginButtonSize\"\n ></spiderly-button>\n </div>\n</ng-container>\n", styles: [".side-menu-profile-dialog{right:26px;top:60px}.top-menu-profile-dialog{right:0;top:40px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: RouterModule }, { kind: "ngmodule", type: AvatarModule }, { kind: "component", type: i7.Avatar, selector: "p-avatar", inputs: ["label", "icon", "image", "size", "shape", "style", "styleClass", "ariaLabel", "ariaLabelledBy"], outputs: ["onImageError"] }, { kind: "component", type: SpiderlyButtonComponent, selector: "spiderly-button", inputs: ["type"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] }); }
3323
3268
  }
3324
3269
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImport: i0, type: ProfileAvatarComponent, decorators: [{
3325
3270
  type: Component,
@@ -3327,10 +3272,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
3327
3272
  CommonModule,
3328
3273
  RouterModule,
3329
3274
  AvatarModule,
3330
- BadgeModule,
3331
3275
  SpiderlyButtonComponent,
3332
3276
  TranslocoDirective,
3333
- ], template: "<ng-container *transloco=\"let t\">\n <div *ngIf=\"this.currentUser != null\" #topbarmenu>\n <div\n #topbarprofiledropdownmenubutton\n (click)=\"layoutService.showProfileDropdownSidebar()\"\n >\n <p-avatar\n *ngIf=\"showProfileIcon\"\n [label]=\"avatarLabel\"\n [style]=\"{\n 'background-color': 'var(--p-primary-color)',\n color: '#fff',\n cursor: 'pointer',\n width: '34px',\n height: '34px',\n 'font-size': '21px',\n }\"\n pBadge\n [badgeStyleClass]=\"'p-badge-danger'\"\n [badgeDisabled]=\"\n unreadNotificationsCount == 0 || unreadNotificationsCount == null\n \"\n [value]=\"unreadNotificationsCount\"\n />\n </div>\n <div #topbarprofiledropdownmenu (document:click)=\"onDocumentClick($event)\">\n <div\n *ngIf=\"layoutService.state.profileDropdownSidebarVisible\"\n style=\"\n width: 280px;\n position: absolute;\n padding: 15px;\n background: var(--p-content-background);\n \"\n class=\"card\"\n [ngClass]=\"{\n 'side-menu-profile-dialog': isSideMenuLayout,\n 'top-menu-profile-dialog': !isSideMenuLayout,\n }\"\n >\n <div\n style=\"\n display: flex;\n flex-direction: column;\n justify-content: center;\n text-align: center;\n gap: 10px;\n \"\n >\n <p-avatar\n [label]=\"avatarLabel\"\n size=\"xlarge\"\n [style]=\"{\n 'background-color': 'var(--p-primary-color)',\n color: '#fff',\n margin: 'auto',\n cursor: routeOnLargeProfileAvatarClick ? 'pointer' : '',\n }\"\n (click)=\"routeToUserPage()\"\n />\n <div style=\"font-size: 15px\">{{ currentUser?.email }}</div>\n </div>\n <div style=\"margin-top: 15px\">\n <div\n *ngFor=\"let item of menuItems\"\n [style]=\"item.showSeparator ? 'margin-top: 5px;' : ''\"\n >\n <div *ngIf=\"item.showSeparator\" class=\"gray-separator\"></div>\n <div\n (click)=\"item.onClick()\"\n class=\"hover-card\"\n style=\"\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 5px;\n \"\n >\n <i\n class=\"pi pi-fw {{ item.icon }}\"\n style=\"font-size: 16px; position: relative\"\n >\n <span\n *ngIf=\"\n item.showNotificationBadge && unreadNotificationsCount != 0\n \"\n class=\"badge\"\n ></span>\n </i>\n <div style=\"font-size: 15px\">{{ item.label }}</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <div *ngIf=\"this.currentUser == null && this.showLoginButton\">\n <spiderly-button\n [label]=\"t('Login')\"\n (onClick)=\"loginButtonClick()\"\n icon=\"pi pi-sign-in\"\n [outlined]=\"loginButtonOutlined\"\n [severity]=\"loginButtonSeverity\"\n [size]=\"loginButtonSize\"\n ></spiderly-button>\n </div>\n</ng-container>\n", styles: [".side-menu-profile-dialog{right:26px;top:60px}.top-menu-profile-dialog{right:0;top:40px}\n"] }]
3277
+ ], template: "<ng-container *transloco=\"let t\">\n <div *ngIf=\"this.currentUser != null\" #topbarmenu>\n <div\n #topbarprofiledropdownmenubutton\n (click)=\"layoutService.showProfileDropdownSidebar()\"\n >\n <p-avatar\n *ngIf=\"showProfileIcon\"\n [label]=\"avatarLabel\"\n [style]=\"{\n 'background-color': 'var(--p-primary-color)',\n color: '#fff',\n cursor: 'pointer',\n width: '34px',\n height: '34px',\n 'font-size': '21px',\n }\"\n />\n </div>\n <div #topbarprofiledropdownmenu (document:click)=\"onDocumentClick($event)\">\n <div\n *ngIf=\"layoutService.state.profileDropdownSidebarVisible\"\n style=\"\n width: 280px;\n position: absolute;\n padding: 15px;\n background: var(--p-content-background);\n \"\n class=\"card\"\n [ngClass]=\"{\n 'side-menu-profile-dialog': isSideMenuLayout,\n 'top-menu-profile-dialog': !isSideMenuLayout,\n }\"\n >\n <div\n style=\"\n display: flex;\n flex-direction: column;\n justify-content: center;\n text-align: center;\n gap: 10px;\n \"\n >\n <p-avatar\n [label]=\"avatarLabel\"\n size=\"xlarge\"\n [style]=\"{\n 'background-color': 'var(--p-primary-color)',\n color: '#fff',\n margin: 'auto',\n cursor: routeOnLargeProfileAvatarClick ? 'pointer' : '',\n }\"\n (click)=\"routeToUserPage()\"\n />\n <div style=\"font-size: 15px\">{{ currentUser?.email }}</div>\n </div>\n <div style=\"margin-top: 15px\">\n <div\n *ngFor=\"let item of menuItems\"\n [style]=\"item.showSeparator ? 'margin-top: 5px;' : ''\"\n >\n <div *ngIf=\"item.showSeparator\" class=\"gray-separator\"></div>\n <div\n (click)=\"item.onClick()\"\n class=\"hover-card\"\n style=\"\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 5px;\n \"\n >\n <i\n class=\"pi pi-fw {{ item.icon }}\"\n style=\"font-size: 16px; position: relative\"\n ></i>\n <div style=\"font-size: 15px\">{{ item.label }}</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <div *ngIf=\"this.currentUser == null && this.showLoginButton\">\n <spiderly-button\n [label]=\"t('Login')\"\n (onClick)=\"loginButtonClick()\"\n icon=\"pi pi-sign-in\"\n [outlined]=\"loginButtonOutlined\"\n [severity]=\"loginButtonSeverity\"\n [size]=\"loginButtonSize\"\n ></spiderly-button>\n </div>\n</ng-container>\n", styles: [".side-menu-profile-dialog{right:26px;top:60px}.top-menu-profile-dialog{right:0;top:40px}\n"] }]
3334
3278
  }], ctorParameters: () => [{ type: LayoutServiceBase }, { type: AuthServiceBase }, { type: i3$2.Router }, { type: i1.TranslocoService }, { type: ConfigServiceBase }], propDecorators: { isSideMenuLayout: [{
3335
3279
  type: Input
3336
3280
  }], routeOnLargeProfileAvatarClick: [{
@@ -4872,7 +4816,11 @@ const unauthorizedInterceptor = (req, next) => {
4872
4816
  return of(err.message);
4873
4817
  }
4874
4818
  else if (err.status == 401) {
4875
- messageService.warningMessage(errorResponse.message ?? translocoService.translate('LoginRequired'), translocoService.translate('Warning'));
4819
+ if (errorResponse?.errorCode === ApiErrorCodes.InvalidToken) {
4820
+ authService.clearLocalStorage();
4821
+ return of(err.message);
4822
+ }
4823
+ messageService.warningMessage(errorResponse?.message ?? translocoService.translate('LoginRequired'), translocoService.translate('Warning'));
4876
4824
  return of(err.message);
4877
4825
  }
4878
4826
  else if (err.status == 403) {
@@ -4883,11 +4831,6 @@ const unauthorizedInterceptor = (req, next) => {
4883
4831
  messageService.warningMessage(translocoService.translate('NotFoundDetails'), translocoService.translate('NotFoundTitle'));
4884
4832
  return of(err.message);
4885
4833
  }
4886
- else if (err.status == 419) {
4887
- // Access token expired
4888
- authService.clearLocalStorage();
4889
- return of(err.message);
4890
- }
4891
4834
  else {
4892
4835
  messageService.errorMessage(translocoService.translate('UnexpectedErrorDetails'), translocoService.translate('UnexpectedErrorTitle'));
4893
4836
  return of(err.message);
@@ -4951,5 +4894,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.13", ngImpo
4951
4894
  * Generated bundle index. Do not edit.
4952
4895
  */
4953
4896
 
4954
- export { Action, AllClickEvent, ApiSecurityService, AppSidebarComponent, AuthGuard, AuthResult, AuthResultWithCookies, AuthServiceBase, BaseAutocompleteControl, BaseControl, BaseDropdownControl, BaseEntity, BaseFormComponent, BaseFormService, CardSkeletonComponent, Codebook, Column, ConfigServiceBase, ExternalProvider, Filter, FilterRule, FilterSortMeta, FooterComponent, GoogleButtonComponent, IndexCardComponent, InfoCardComponent, InitCompanyAuthDialogDetails, InitTopBarData, IsAuthorizedForSaveEvent, LastMenuIconIndexClicked, LayoutServiceBase, LazyLoadSelectedIdsResult, Login, LoginComponent, LoginVerificationComponent, LoginVerificationToken, MatchModeCodes, MenuChangeEvent, MenuitemComponent, Namebook, NotAuthGuard, NotFoundComponent, PROPS_KEY, PaginatedResult, PanelBodyComponent, PanelFooterComponent, PanelHeaderComponent, PrimengOption, ProfileAvatarComponent, ReflectProp, RefreshTokenRequest, RequiredComponent, RowClickEvent, SecurityPermissionCodes, SendLoginVerificationEmailResult, SideMenuTopBarComponent, SidebarMenuComponent, SimpleSaveResult, SpiderlyAutocompleteComponent, SpiderlyButtonBaseComponent, SpiderlyButtonComponent, SpiderlyCalendarComponent, SpiderlyCardComponent, SpiderlyCheckboxComponent, SpiderlyColorPickerComponent, SpiderlyControlsModule, SpiderlyDataTableComponent, SpiderlyDataViewComponent, SpiderlyDeleteConfirmationComponent, SpiderlyDropdownComponent, SpiderlyEditorComponent, SpiderlyErrorHandler, SpiderlyFileComponent, SpiderlyFileSelectEvent, SpiderlyFormArray, SpiderlyFormControl, SpiderlyFormGroup, SpiderlyLayoutComponent, SpiderlyMessageService, SpiderlyMultiAutocompleteComponent, SpiderlyMultiSelectComponent, SpiderlyNumberComponent, SpiderlyPanelComponent, SpiderlyPanelsModule, SpiderlyPasswordComponent, SpiderlyReturnButtonComponent, SpiderlySplitButtonComponent, SpiderlyTab, SpiderlyTemplateTypeDirective, SpiderlyTextareaComponent, SpiderlyTextboxComponent, SpiderlyTranslocoLoader, TopBarComponent, UserBase, UserRole, ValidatorAbstractService, VerificationTokenRequest, VerificationTypeCodes, VerificationWrapperComponent, adjustColor, authInitializer, capitalizeFirstChar, createFakeGoogleWrapper, deleteAction, exportListToExcel, firstCharToUpper, getFileNameFromContentDisposition, getHtmlImgDisplayString64, getImageDimensions, getMimeTypeForFileName, getMonth, getParentUrl, getPrimengAutocompleteCodebookOptions, getPrimengAutocompleteNamebookOptions, getPrimengDropdownCodebookOptions, getPrimengDropdownNamebookOptions, httpLoadingInterceptor, isExcelFileType, isFileImageType, isNullOrEmpty, jsonHttpInterceptor, jwtInterceptor, kebabToTitleCase, nameOf, nameof, primitiveArrayTypes, pushAction, selectedTab, singleOrDefault, splitPascalCase, toCommaSeparatedString, unauthorizedInterceptor, validatePrecisionScale };
4897
+ export { Action, AllClickEvent, ApiErrorCodes, ApiSecurityService, AppSidebarComponent, AuthGuard, AuthResult, AuthResultWithCookies, AuthServiceBase, BaseAutocompleteControl, BaseControl, BaseDropdownControl, BaseEntity, BaseFormComponent, BaseFormService, CardSkeletonComponent, Codebook, Column, ConfigServiceBase, ExternalProvider, Filter, FilterRule, FilterSortMeta, FooterComponent, GoogleButtonComponent, IndexCardComponent, InfoCardComponent, InitCompanyAuthDialogDetails, InitTopBarData, IsAuthorizedForSaveEvent, LastMenuIconIndexClicked, LayoutServiceBase, LazyLoadSelectedIdsResult, Login, LoginComponent, LoginVerificationComponent, LoginVerificationToken, MatchModeCodes, MenuChangeEvent, MenuitemComponent, Namebook, NotAuthGuard, NotFoundComponent, PROPS_KEY, PaginatedResult, PanelBodyComponent, PanelFooterComponent, PanelHeaderComponent, PrimengOption, ProfileAvatarComponent, ReflectProp, RefreshTokenRequest, RequiredComponent, RowClickEvent, SecurityPermissionCodes, SendLoginVerificationEmailResult, SideMenuTopBarComponent, SidebarMenuComponent, SimpleSaveResult, SpiderlyAutocompleteComponent, SpiderlyButtonBaseComponent, SpiderlyButtonComponent, SpiderlyCalendarComponent, SpiderlyCardComponent, SpiderlyCheckboxComponent, SpiderlyColorPickerComponent, SpiderlyControlsModule, SpiderlyDataTableComponent, SpiderlyDataViewComponent, SpiderlyDeleteConfirmationComponent, SpiderlyDropdownComponent, SpiderlyEditorComponent, SpiderlyErrorHandler, SpiderlyFileComponent, SpiderlyFileSelectEvent, SpiderlyFormArray, SpiderlyFormControl, SpiderlyFormGroup, SpiderlyLayoutComponent, SpiderlyMessageService, SpiderlyMultiAutocompleteComponent, SpiderlyMultiSelectComponent, SpiderlyNumberComponent, SpiderlyPanelComponent, SpiderlyPanelsModule, SpiderlyPasswordComponent, SpiderlyReturnButtonComponent, SpiderlySplitButtonComponent, SpiderlyTab, SpiderlyTemplateTypeDirective, SpiderlyTextareaComponent, SpiderlyTextboxComponent, SpiderlyTranslocoLoader, TopBarComponent, UserBase, UserRole, ValidatorAbstractService, VerificationTokenRequest, VerificationTypeCodes, VerificationWrapperComponent, adjustColor, authInitializer, capitalizeFirstChar, createFakeGoogleWrapper, deleteAction, exportListToExcel, firstCharToUpper, getFileNameFromContentDisposition, getHtmlImgDisplayString64, getImageDimensions, getMimeTypeForFileName, getMonth, getParentUrl, getPrimengAutocompleteCodebookOptions, getPrimengAutocompleteNamebookOptions, getPrimengDropdownCodebookOptions, getPrimengDropdownNamebookOptions, httpLoadingInterceptor, isExcelFileType, isFileImageType, isNullOrEmpty, jsonHttpInterceptor, jwtInterceptor, kebabToTitleCase, nameOf, nameof, primitiveArrayTypes, pushAction, selectedTab, singleOrDefault, splitPascalCase, toCommaSeparatedString, unauthorizedInterceptor, validatePrecisionScale };
4955
4898
  //# sourceMappingURL=spiderly.mjs.map