@eqproject/eqp-attachments 3.1.4 → 3.1.6
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/esm2022/lib/eqp-attachments.component.mjs +259 -80
- package/esm2022/lib/interfaces/IAttachment.mjs +1 -1
- package/fesm2022/eqproject-eqp-attachments.mjs +258 -79
- package/fesm2022/eqproject-eqp-attachments.mjs.map +1 -1
- package/lib/eqp-attachments.component.d.ts +15 -1
- package/lib/interfaces/IAttachment.d.ts +5 -0
- package/package.json +1 -1
|
@@ -387,7 +387,7 @@ class EqpAttachmentsComponent {
|
|
|
387
387
|
* Classe custom da assegnare al dialog del crop immagini
|
|
388
388
|
*/
|
|
389
389
|
cropDialogClass;
|
|
390
|
-
maxFileSizeMB =
|
|
390
|
+
maxFileSizeMB = 500; // Default max size of 100 MB
|
|
391
391
|
cardSize = 'small'; // Default
|
|
392
392
|
customCardWidthPx = 200; // Larghezza custom in px
|
|
393
393
|
customCardHeightPx = 180; // Altezza custom in px
|
|
@@ -418,6 +418,7 @@ class EqpAttachmentsComponent {
|
|
|
418
418
|
flipVerticalLabel = "Capovolgi verticalmente";
|
|
419
419
|
rotateRightLabel = "Ruota a destra";
|
|
420
420
|
rotateLeftLabel = "Ruota a sinistra";
|
|
421
|
+
base64LimitMB = 100;
|
|
421
422
|
uploadTitle = 'Upload file';
|
|
422
423
|
uploadSubtitle = 'Drag & drop files o click';
|
|
423
424
|
dropHereLabel = 'Rilascia i file qui';
|
|
@@ -434,6 +435,10 @@ class EqpAttachmentsComponent {
|
|
|
434
435
|
showSummary = false;
|
|
435
436
|
viewMode = 'table';
|
|
436
437
|
showUploadTitle = true;
|
|
438
|
+
showDropArea = true;
|
|
439
|
+
hiddenColumns = [];
|
|
440
|
+
hiddenActions = [];
|
|
441
|
+
videoCompression = { enabled: false, maxWidth: 1280, bitrate: 2500000 };
|
|
437
442
|
_customMenuActions = [];
|
|
438
443
|
_sortedMenuActions = [];
|
|
439
444
|
/**
|
|
@@ -670,6 +675,16 @@ class EqpAttachmentsComponent {
|
|
|
670
675
|
window.open(attachment.FilePath, "_blank");
|
|
671
676
|
return;
|
|
672
677
|
}
|
|
678
|
+
if (attachment.LargeFile) {
|
|
679
|
+
const file = attachment.LargeFile;
|
|
680
|
+
const url = URL.createObjectURL(file);
|
|
681
|
+
const link = document.createElement("a");
|
|
682
|
+
link.href = url;
|
|
683
|
+
link.download = attachment.FileName || file.name;
|
|
684
|
+
link.click();
|
|
685
|
+
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
673
688
|
if (attachment.FileDataBase64 && attachment.FileContentType && attachment.FileName) {
|
|
674
689
|
let source = `data:${attachment.FileContentType};base64,${attachment.FileDataBase64}`;
|
|
675
690
|
const link = document.createElement("a");
|
|
@@ -736,14 +751,14 @@ class EqpAttachmentsComponent {
|
|
|
736
751
|
disableSave() {
|
|
737
752
|
if (this.loadMultipleFiles != true) {
|
|
738
753
|
if (this.newAttachment.AttachmentType == AttachmentType.FILE) {
|
|
739
|
-
return !this.newAttachment.FileDataBase64;
|
|
754
|
+
return !this.newAttachment.FileDataBase64 && !this.newAttachment.LargeFile;
|
|
740
755
|
}
|
|
741
756
|
else {
|
|
742
757
|
return !this.newAttachment.FilePath;
|
|
743
758
|
}
|
|
744
759
|
}
|
|
745
760
|
else {
|
|
746
|
-
return (this.newMultipleAttachments.filter((p) => (p.AttachmentType == AttachmentType.FILE && !p.FileDataBase64) ||
|
|
761
|
+
return (this.newMultipleAttachments.filter((p) => (p.AttachmentType == AttachmentType.FILE && !p.FileDataBase64 && !p.LargeFile) ||
|
|
747
762
|
(p.AttachmentType == AttachmentType.LINK && !p.FilePath)).length > 0);
|
|
748
763
|
}
|
|
749
764
|
}
|
|
@@ -765,7 +780,6 @@ class EqpAttachmentsComponent {
|
|
|
765
780
|
this.attachmentsList = new Array();
|
|
766
781
|
this.attachmentsList = this.attachmentsList.concat(this.newMultipleAttachments);
|
|
767
782
|
}
|
|
768
|
-
// if (this.attachmentTable) this.attachmentTable.reloadDatatable();
|
|
769
783
|
this.localEditedAttachments.emit(this.attachmentsList);
|
|
770
784
|
if (this.newAttachment.IsImage) {
|
|
771
785
|
this.dialogRefCropImage.close();
|
|
@@ -776,6 +790,15 @@ class EqpAttachmentsComponent {
|
|
|
776
790
|
if (this.newAttachmentForm)
|
|
777
791
|
this.newAttachmentForm.reset();
|
|
778
792
|
}
|
|
793
|
+
this.resetSelectedFiles();
|
|
794
|
+
}
|
|
795
|
+
// 2. RESET TOTALE: Svuotiamo le proprietà di classe per evitare che
|
|
796
|
+
// residui di upload precedenti (singoli o multipli) vengano trascinati.
|
|
797
|
+
resetSelectedFiles() {
|
|
798
|
+
this.newAttachment = {};
|
|
799
|
+
this.newMultipleAttachments = [];
|
|
800
|
+
this.selectedFile = null;
|
|
801
|
+
this.selectedFiles = [];
|
|
779
802
|
}
|
|
780
803
|
/**
|
|
781
804
|
* Apre il dialog per l'anteprima dell'allegato selezionato.
|
|
@@ -783,17 +806,7 @@ class EqpAttachmentsComponent {
|
|
|
783
806
|
* @returns
|
|
784
807
|
*/
|
|
785
808
|
async openPreviewDialog(row) {
|
|
786
|
-
this.selectedAttachment =
|
|
787
|
-
if (this.selectedAttachment.AttachmentType == AttachmentType.FILE) {
|
|
788
|
-
if (this.selectedAttachment.FileContentType.startsWith("video")) {
|
|
789
|
-
EqpAttachmentDialogService.Warning(this.videoPreviewErrorMessage);
|
|
790
|
-
return;
|
|
791
|
-
}
|
|
792
|
-
else if (this.selectedAttachment.FileContentType.startsWith("audio")) {
|
|
793
|
-
EqpAttachmentDialogService.Warning(this.audioPreviewErrorMessage);
|
|
794
|
-
return;
|
|
795
|
-
}
|
|
796
|
-
}
|
|
809
|
+
this.selectedAttachment = { ...row };
|
|
797
810
|
if (this.getAttachmentEndpoint && this.selectedAttachment.IsImage && !this.selectedAttachment.FileDataBase64) {
|
|
798
811
|
await this.getAttachmentByID()
|
|
799
812
|
.then((res) => {
|
|
@@ -806,6 +819,11 @@ class EqpAttachmentsComponent {
|
|
|
806
819
|
if (this.selectedAttachment.AttachmentType == AttachmentType.LINK) {
|
|
807
820
|
this.selectedAttachment.TrustedUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.selectedAttachment.FilePath);
|
|
808
821
|
}
|
|
822
|
+
else if (this.selectedAttachment.FileContentType?.startsWith("video/") && this.selectedAttachment.LargeFile) {
|
|
823
|
+
const videoUrl = URL.createObjectURL(this.selectedAttachment.LargeFile);
|
|
824
|
+
// Usiamo bypassSecurityTrustUrl per la sorgente del video
|
|
825
|
+
this.selectedAttachment.TrustedUrl = this.sanitizer.bypassSecurityTrustUrl(videoUrl);
|
|
826
|
+
}
|
|
809
827
|
else if (this.selectedAttachment.IsImage &&
|
|
810
828
|
!this.selectedAttachment.FileDataBase64 &&
|
|
811
829
|
!this.selectedAttachment.FileThumbnailBase64) {
|
|
@@ -828,9 +846,16 @@ class EqpAttachmentsComponent {
|
|
|
828
846
|
return;
|
|
829
847
|
}
|
|
830
848
|
}
|
|
831
|
-
this.dialog.open(this.dialogPreview, {
|
|
832
|
-
|
|
833
|
-
|
|
849
|
+
const dialogRef = this.dialog.open(this.dialogPreview, {
|
|
850
|
+
panelClass: 'eqp-attachments-preview-dialog',
|
|
851
|
+
maxWidth: '95vw',
|
|
852
|
+
width: row.IsImage || row.FileContentType?.startsWith('video/') ? 'auto' : '1100px',
|
|
853
|
+
});
|
|
854
|
+
// Pulizia della memoria quando il dialog si chiude
|
|
855
|
+
dialogRef.afterClosed().subscribe(() => {
|
|
856
|
+
if (this.selectedAttachment?.FileContentType?.startsWith("video/")) {
|
|
857
|
+
URL.revokeObjectURL(this.selectedAttachment.TrustedUrl);
|
|
858
|
+
}
|
|
834
859
|
});
|
|
835
860
|
}
|
|
836
861
|
async getAttachmentByID() {
|
|
@@ -852,11 +877,17 @@ class EqpAttachmentsComponent {
|
|
|
852
877
|
return;
|
|
853
878
|
}
|
|
854
879
|
this.showCropImage = false;
|
|
855
|
-
|
|
880
|
+
this.resetSelectedFiles();
|
|
856
881
|
const filesOnInput = isFileDropped
|
|
857
882
|
? event
|
|
858
883
|
: Array.from(event.target?.files || []);
|
|
859
|
-
const { validFiles, oversizedFiles } = this.validationFile(filesOnInput
|
|
884
|
+
const { validFiles, oversizedFiles } = this.validationFile(filesOnInput);
|
|
885
|
+
if (!validFiles || validFiles.length === 0) {
|
|
886
|
+
if (!isFileDropped && event?.target instanceof HTMLInputElement) {
|
|
887
|
+
event.target.value = '';
|
|
888
|
+
}
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
860
891
|
//Se è stato richiesto il caricamento SINGOLO oppure se il caricamento è MULTIPLO ma è stato selezionato un solo file
|
|
861
892
|
//allora verifica se il file è un immagine (per mostrare il CROPPIE)
|
|
862
893
|
if ([...validFiles].length == 1 || this.loadMultipleFiles != true) {
|
|
@@ -881,15 +912,17 @@ class EqpAttachmentsComponent {
|
|
|
881
912
|
this.newAttachmentForm.disable();
|
|
882
913
|
this.newAttachmentForm.controls["customWidth"].enable();
|
|
883
914
|
this.newAttachmentForm.controls["customHeight"].enable();
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
915
|
+
setTimeout(() => {
|
|
916
|
+
this.showCropImage = true;
|
|
917
|
+
this.imageFile = event;
|
|
918
|
+
this.dialogRefCropImage = this.dialog.open(this.dialogCropImage, {
|
|
919
|
+
disableClose: true,
|
|
920
|
+
hasBackdrop: true,
|
|
921
|
+
width: "60%",
|
|
922
|
+
maxHeight: "80%",
|
|
923
|
+
maxWidth: "70vh",
|
|
924
|
+
panelClass: ['eqp-attachments-dialog', 'crop-dialog']
|
|
925
|
+
});
|
|
893
926
|
});
|
|
894
927
|
}
|
|
895
928
|
else {
|
|
@@ -934,7 +967,7 @@ class EqpAttachmentsComponent {
|
|
|
934
967
|
if (!isFileDropped)
|
|
935
968
|
event.target.value = "";
|
|
936
969
|
}
|
|
937
|
-
validationFile(filesOnInput
|
|
970
|
+
validationFile(filesOnInput) {
|
|
938
971
|
const validFiles = [];
|
|
939
972
|
const oversizedFiles = [];
|
|
940
973
|
for (const file of filesOnInput) {
|
|
@@ -946,18 +979,10 @@ class EqpAttachmentsComponent {
|
|
|
946
979
|
validFiles.push(file);
|
|
947
980
|
}
|
|
948
981
|
}
|
|
949
|
-
// Show a single error message for all oversized files
|
|
950
982
|
if (oversizedFiles.length > 0) {
|
|
951
983
|
const fileNames = oversizedFiles.join(', ');
|
|
952
984
|
this.showToast(`File(s) troppo grandi: ${fileNames}. Limite: ${this.maxFileSizeMB}MB`, 'error');
|
|
953
985
|
}
|
|
954
|
-
// Abort if no files are valid
|
|
955
|
-
if (validFiles.length === 0) {
|
|
956
|
-
if (!isFileDropped) {
|
|
957
|
-
event.target.value = '';
|
|
958
|
-
}
|
|
959
|
-
return;
|
|
960
|
-
}
|
|
961
986
|
return { validFiles, oversizedFiles };
|
|
962
987
|
}
|
|
963
988
|
/**
|
|
@@ -976,10 +1001,39 @@ class EqpAttachmentsComponent {
|
|
|
976
1001
|
newAttachment.FileName = currentFile.name;
|
|
977
1002
|
newAttachment.FileExtension = currentFile.name.substr(currentFile.name.lastIndexOf(".") + 1);
|
|
978
1003
|
newAttachment.IsImage = AttachmentHelperService.checkImageFromMimeType(currentFile.type);
|
|
1004
|
+
const fileSizeMB = currentFile.size / 1024 / 1024;
|
|
1005
|
+
const isLargeFile = fileSizeMB > this.base64LimitMB;
|
|
1006
|
+
const isVideo = currentFile.type.startsWith('video');
|
|
1007
|
+
if (isVideo) {
|
|
1008
|
+
let finalFile = currentFile;
|
|
1009
|
+
// Miniatura del video
|
|
1010
|
+
newAttachment.FileThumbnailBase64 = await this.generateVideoThumbnail(currentFile);
|
|
1011
|
+
// Se la compressione è abilitata, elaboriamo il video
|
|
1012
|
+
if (this.videoCompression.enabled) {
|
|
1013
|
+
// this.showToast("Compressione video in corso...", "info");
|
|
1014
|
+
finalFile = await this.compressVideoNative(currentFile, this.videoCompression);
|
|
1015
|
+
// Aggiorniamo i dati dell'allegato con il nuovo file compresso
|
|
1016
|
+
newAttachment.FileName = currentFile.name.replace(/\.[^/.]+$/, "") + ".webm";
|
|
1017
|
+
newAttachment.FileContentType = "video/webm";
|
|
1018
|
+
}
|
|
1019
|
+
newAttachment.LargeFile = finalFile;
|
|
1020
|
+
newAttachment.IsLargeFile = true;
|
|
1021
|
+
return newAttachment;
|
|
1022
|
+
}
|
|
1023
|
+
if (isLargeFile) {
|
|
1024
|
+
// CASO LARGE FILE:
|
|
1025
|
+
// Popoliamo OriginalFile e forziamo il Base64 a null.
|
|
1026
|
+
newAttachment.FileDataBase64 = null;
|
|
1027
|
+
newAttachment.LargeFile = currentFile;
|
|
1028
|
+
newAttachment.IsLargeFile = true;
|
|
1029
|
+
getBase64 = false;
|
|
1030
|
+
}
|
|
979
1031
|
if (getBase64 == true) {
|
|
980
1032
|
let base64Result = await this.getBase64FromFile(currentFile);
|
|
981
1033
|
newAttachment.FileDataBase64 = base64Result.Base64File;
|
|
982
|
-
newAttachment.FileContentType
|
|
1034
|
+
if (!newAttachment.FileContentType && base64Result.ContentType) {
|
|
1035
|
+
newAttachment.FileContentType = base64Result.ContentType;
|
|
1036
|
+
}
|
|
983
1037
|
if (newAttachment.IsImage && newAttachment.FileDataBase64 && cropFile) {
|
|
984
1038
|
this.getCroppedAndUpload(`data:${base64Result.ContentType};base64,${base64Result.Base64File}`, newAttachment);
|
|
985
1039
|
}
|
|
@@ -993,52 +1047,60 @@ class EqpAttachmentsComponent {
|
|
|
993
1047
|
* @returns Restituisce un oggetto avente le proprietà Base64File e ContentType
|
|
994
1048
|
*/
|
|
995
1049
|
async getBase64FromFile(currentFile) {
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1050
|
+
const fileSizeMB = currentFile.size / 1024 / 1024;
|
|
1051
|
+
if (fileSizeMB > this.base64LimitMB) {
|
|
1052
|
+
return {
|
|
1053
|
+
Base64File: null,
|
|
1054
|
+
ContentType: currentFile.type
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
// Procedura standard
|
|
1058
|
+
try {
|
|
1059
|
+
let base64File = await toBase64(currentFile);
|
|
1060
|
+
let contentType = null;
|
|
1061
|
+
if (base64File) {
|
|
1062
|
+
contentType = base64File.split(",")[0].split(":")[1].split(";")[0];
|
|
1063
|
+
base64File = base64File.split(",")[1];
|
|
1064
|
+
}
|
|
1065
|
+
return {
|
|
1066
|
+
Base64File: base64File,
|
|
1067
|
+
ContentType: contentType
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
catch (ex) {
|
|
1071
|
+
return { Base64File: null, ContentType: null };
|
|
1005
1072
|
}
|
|
1006
|
-
let result = {
|
|
1007
|
-
Base64File: base64File,
|
|
1008
|
-
ContentType: contentType
|
|
1009
|
-
};
|
|
1010
|
-
return result;
|
|
1011
1073
|
}
|
|
1012
1074
|
/**
|
|
1013
1075
|
* Controlla se il file che si sta caricando è supportato dal sistema.
|
|
1014
1076
|
* @returns
|
|
1015
1077
|
*/
|
|
1016
1078
|
checkAcceptedFiles() {
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
return false;
|
|
1020
|
-
if (this.acceptedFileTypes == "*")
|
|
1079
|
+
// Se accetto tutto, esco subito
|
|
1080
|
+
if (this.acceptedFileTypes === "*")
|
|
1021
1081
|
return true;
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
//Questo controllo permette di gestire le casistiche per cui vengono indicati come tipi validi, ad esempio, 'image/*'
|
|
1034
|
-
if (!accepted && this.loadMultipleFiles != true) {
|
|
1035
|
-
for (let t of this.acceptedFileTypes.split(",").filter((t) => t.includes("*"))) {
|
|
1036
|
-
accepted = this.selectedFile.type.startsWith(t.split("*")[0]);
|
|
1037
|
-
if (accepted)
|
|
1038
|
-
break;
|
|
1082
|
+
const isTypeValid = (mimeType) => {
|
|
1083
|
+
if (!mimeType)
|
|
1084
|
+
return false;
|
|
1085
|
+
// Controllo corrispondenza esatta (es. image/png)
|
|
1086
|
+
if (this.acceptedFileTypes.includes(mimeType))
|
|
1087
|
+
return true;
|
|
1088
|
+
const wildcards = this.acceptedFileTypes.split(",").filter(t => t.includes("*"));
|
|
1089
|
+
for (let t of wildcards) {
|
|
1090
|
+
const prefix = t.split("*")[0]; // Prende "image/" da "image/*"
|
|
1091
|
+
if (mimeType.startsWith(prefix))
|
|
1092
|
+
return true;
|
|
1039
1093
|
}
|
|
1094
|
+
return false;
|
|
1095
|
+
};
|
|
1096
|
+
if (this.loadMultipleFiles !== true) {
|
|
1097
|
+
// Caso Singolo
|
|
1098
|
+
return isTypeValid(this.selectedFile.type);
|
|
1099
|
+
}
|
|
1100
|
+
else {
|
|
1101
|
+
// Caso Multiplo: TUTTI i file selezionati devono essere validi
|
|
1102
|
+
return Array.from(this.selectedFiles).every(file => isTypeValid(file.type));
|
|
1040
1103
|
}
|
|
1041
|
-
return accepted;
|
|
1042
1104
|
}
|
|
1043
1105
|
/**
|
|
1044
1106
|
* Se eqp-attachments è stata configurata per il caricamento delle sole immagini allora verifica che il file passato in
|
|
@@ -1184,6 +1246,8 @@ class EqpAttachmentsComponent {
|
|
|
1184
1246
|
// Se il caricamento del file dropbox va a buon fine, la funzione di callback restituisce un array di oggetti.
|
|
1185
1247
|
// Viene poi fatta una XMLHttpRequest con responseType 'blob' per convertire il primo elemento della response in un Blob.
|
|
1186
1248
|
chooseDropboxFile() {
|
|
1249
|
+
if (this.isDisabled)
|
|
1250
|
+
return;
|
|
1187
1251
|
var options = {
|
|
1188
1252
|
success: (files) => {
|
|
1189
1253
|
const xhr = new XMLHttpRequest();
|
|
@@ -1217,6 +1281,8 @@ class EqpAttachmentsComponent {
|
|
|
1217
1281
|
* Apre il dialogo per l'inserimento del link.
|
|
1218
1282
|
*/
|
|
1219
1283
|
switchToAddingLinkMode() {
|
|
1284
|
+
if (this.isDisabled)
|
|
1285
|
+
return;
|
|
1220
1286
|
this.newAttachmentForm = this.formBuilder.group({
|
|
1221
1287
|
fileName: [''],
|
|
1222
1288
|
filePath: ['', [Validators.required, Validators.pattern('https?://.+')]]
|
|
@@ -1290,8 +1356,26 @@ class EqpAttachmentsComponent {
|
|
|
1290
1356
|
const isPreviewablePdf = att.FileContentType === 'application/pdf' && !!att.FileDataBase64;
|
|
1291
1357
|
// Case 3: It's a remote document with a URL (for Google Viewer).
|
|
1292
1358
|
const isRemoteDocument = !att.IsImage && !!att.FilePath && !!this.productionBaseUrl;
|
|
1359
|
+
//Case 4: Video - È un video e abbiamo il file binario salvato in LargeFile
|
|
1360
|
+
const isPreviewableVideo = att.FileContentType?.startsWith('video/') && !!att.LargeFile;
|
|
1293
1361
|
// A preview is possible if any of these conditions are true.
|
|
1294
|
-
return isPreviewableImage || isPreviewablePdf || isRemoteDocument;
|
|
1362
|
+
return isPreviewableImage || isPreviewablePdf || isRemoteDocument || isPreviewableVideo;
|
|
1363
|
+
}
|
|
1364
|
+
isColumnHidden(col) {
|
|
1365
|
+
// 1) hiddenColumns
|
|
1366
|
+
if (this.hiddenColumns?.includes(col.key))
|
|
1367
|
+
return true;
|
|
1368
|
+
// 2) hidden definito sulla colonna (boolean o funzione senza args)
|
|
1369
|
+
if (typeof col.hidden === 'function')
|
|
1370
|
+
return !!col.hidden();
|
|
1371
|
+
return !!col.hidden;
|
|
1372
|
+
}
|
|
1373
|
+
isActionHidden(action, att) {
|
|
1374
|
+
if (this.hiddenActions?.includes(action.key))
|
|
1375
|
+
return true;
|
|
1376
|
+
if (typeof action.hidden === 'function')
|
|
1377
|
+
return !!action.hidden(att);
|
|
1378
|
+
return !!action.hidden;
|
|
1295
1379
|
}
|
|
1296
1380
|
/**
|
|
1297
1381
|
* Unisce le colonne di default con quelle custom e le ordina.
|
|
@@ -1331,7 +1415,8 @@ class EqpAttachmentsComponent {
|
|
|
1331
1415
|
...processedCustomColumns
|
|
1332
1416
|
];
|
|
1333
1417
|
// Ordiniamo l'array finale in base alla proprietà 'position'
|
|
1334
|
-
this._tableColumns = allColumns.
|
|
1418
|
+
this._tableColumns = allColumns.filter(col => !this.isColumnHidden(col))
|
|
1419
|
+
.sort((a, b) => {
|
|
1335
1420
|
const posA = a.position || 99;
|
|
1336
1421
|
const posB = b.position || 99;
|
|
1337
1422
|
return posA - posB;
|
|
@@ -1339,6 +1424,7 @@ class EqpAttachmentsComponent {
|
|
|
1339
1424
|
}
|
|
1340
1425
|
setupMenuActions() {
|
|
1341
1426
|
const defaultPreviewAction = {
|
|
1427
|
+
key: 'preview',
|
|
1342
1428
|
icon: 'visibility',
|
|
1343
1429
|
name: 'Anteprima',
|
|
1344
1430
|
fn: (att) => this.openPreviewDialog(att),
|
|
@@ -1346,6 +1432,7 @@ class EqpAttachmentsComponent {
|
|
|
1346
1432
|
position: 10
|
|
1347
1433
|
};
|
|
1348
1434
|
const defaultDeleteAction = {
|
|
1435
|
+
key: 'delete',
|
|
1349
1436
|
icon: 'delete',
|
|
1350
1437
|
name: 'Elimina',
|
|
1351
1438
|
fn: (att) => this.deleteAttachment(att),
|
|
@@ -1364,12 +1451,92 @@ class EqpAttachmentsComponent {
|
|
|
1364
1451
|
return posA - posB;
|
|
1365
1452
|
});
|
|
1366
1453
|
}
|
|
1454
|
+
generateVideoThumbnail(file) {
|
|
1455
|
+
return new Promise((resolve, reject) => {
|
|
1456
|
+
const video = document.createElement('video');
|
|
1457
|
+
const canvas = document.createElement('canvas');
|
|
1458
|
+
const context = canvas.getContext('2d');
|
|
1459
|
+
// Crea un URL temporaneo per il file video
|
|
1460
|
+
const videoUrl = URL.createObjectURL(file);
|
|
1461
|
+
video.src = videoUrl;
|
|
1462
|
+
video.preload = 'metadata';
|
|
1463
|
+
video.muted = true;
|
|
1464
|
+
video.playsInline = true;
|
|
1465
|
+
video.onloadedmetadata = () => {
|
|
1466
|
+
// Vai al secondo 1 (per evitare il fotogramma nero iniziale)
|
|
1467
|
+
video.currentTime = 1;
|
|
1468
|
+
};
|
|
1469
|
+
video.onseeked = () => {
|
|
1470
|
+
// Imposta le dimensioni del canvas uguali al video
|
|
1471
|
+
canvas.width = video.videoWidth;
|
|
1472
|
+
canvas.height = video.videoHeight;
|
|
1473
|
+
// Disegna il fotogramma sul canvas
|
|
1474
|
+
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
1475
|
+
// Estrai il Base64 (qualità 0.7 per risparmiare spazio)
|
|
1476
|
+
const thumbnail = canvas.toDataURL('image/jpeg', 0.7);
|
|
1477
|
+
// Pulisci la memoria
|
|
1478
|
+
URL.revokeObjectURL(videoUrl);
|
|
1479
|
+
resolve(thumbnail.split(',')[1]); // Restituiamo solo la parte Base64
|
|
1480
|
+
};
|
|
1481
|
+
video.onerror = (err) => {
|
|
1482
|
+
URL.revokeObjectURL(videoUrl);
|
|
1483
|
+
reject(err);
|
|
1484
|
+
};
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
compressVideoNative(file, config) {
|
|
1488
|
+
return new Promise((resolve, reject) => {
|
|
1489
|
+
const video = document.createElement('video');
|
|
1490
|
+
const canvas = document.createElement('canvas');
|
|
1491
|
+
const ctx = canvas.getContext('2d');
|
|
1492
|
+
const videoUrl = URL.createObjectURL(file);
|
|
1493
|
+
video.src = videoUrl;
|
|
1494
|
+
video.muted = true;
|
|
1495
|
+
video.play();
|
|
1496
|
+
video.onloadeddata = () => {
|
|
1497
|
+
// Calcoliamo le nuove dimensioni mantenendo l'aspect ratio
|
|
1498
|
+
let width = video.videoWidth;
|
|
1499
|
+
let height = video.videoHeight;
|
|
1500
|
+
if (width > config.maxWidth) {
|
|
1501
|
+
height = Math.round((config.maxWidth * height) / width);
|
|
1502
|
+
width = config.maxWidth;
|
|
1503
|
+
}
|
|
1504
|
+
canvas.width = width;
|
|
1505
|
+
canvas.height = height;
|
|
1506
|
+
// Catturiamo lo stream dal canvas a 30fps
|
|
1507
|
+
const stream = canvas.captureStream(30);
|
|
1508
|
+
const recorder = new MediaRecorder(stream, {
|
|
1509
|
+
mimeType: 'video/webm;codecs=vp8',
|
|
1510
|
+
videoBitsPerSecond: config.bitrate
|
|
1511
|
+
});
|
|
1512
|
+
const chunks = [];
|
|
1513
|
+
recorder.ondataavailable = (e) => chunks.push(e.data);
|
|
1514
|
+
recorder.onstop = () => {
|
|
1515
|
+
const compressedBlob = new Blob(chunks, { type: 'video/webm' });
|
|
1516
|
+
URL.revokeObjectURL(videoUrl);
|
|
1517
|
+
resolve(compressedBlob);
|
|
1518
|
+
};
|
|
1519
|
+
recorder.start();
|
|
1520
|
+
// Funzione che disegna ogni frame sul canvas
|
|
1521
|
+
const drawFrame = () => {
|
|
1522
|
+
if (video.paused || video.ended) {
|
|
1523
|
+
recorder.stop();
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1526
|
+
ctx.drawImage(video, 0, 0, width, height);
|
|
1527
|
+
requestAnimationFrame(drawFrame);
|
|
1528
|
+
};
|
|
1529
|
+
drawFrame();
|
|
1530
|
+
};
|
|
1531
|
+
video.onerror = reject;
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1367
1534
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EqpAttachmentsComponent, deps: [{ token: i1.MatDialog }, { token: i2.FormBuilder }, { token: i3.DomSanitizer }, { token: i4.HttpClient }, { token: EqpAttachmentService }], target: i0.ɵɵFactoryTarget.Component });
|
|
1368
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: EqpAttachmentsComponent, selector: "eqp-attachments", inputs: { disableAction: "disableAction", showHeader: "showHeader", attachmentsList: "attachmentsList", singleAttachment: "singleAttachment", showMatCard: "showMatCard", multipleAttachment: "multipleAttachment", loadMultipleFiles: "loadMultipleFiles", emptyTableMessage: "emptyTableMessage", allowOnlyImages: "allowOnlyImages", acceptedFileTypes: "acceptedFileTypes", isDisabled: "isDisabled", showInlinePreview: "showInlinePreview", getAttachmentEndpoint: "getAttachmentEndpoint", productionBaseUrl: "productionBaseUrl", compressionOptions: "compressionOptions", allowedTypes: "allowedTypes", isEqpTableMultiLanguage: "isEqpTableMultiLanguage", tablePaginatorVisible: "tablePaginatorVisible", isTableSearcheable: "isTableSearcheable", tablePaginatorSize: "tablePaginatorSize", separatedUploadButtons: "separatedUploadButtons", showPreview: "showPreview", singleAttachmentDragAndDrop: "singleAttachmentDragAndDrop", cropOptions: "cropOptions", cropDialogClass: "cropDialogClass", maxFileSizeMB: "maxFileSizeMB", cardSize: "cardSize", customCardWidthPx: "customCardWidthPx", customCardHeightPx: "customCardHeightPx", layout: "layout", openLinkLabel: "openLinkLabel", addButtonLabel: "addButtonLabel", downloadLabel: "downloadLabel", deleteLabel: "deleteLabel", fileNameLabel: "fileNameLabel", previewLabel: "previewLabel", uploadFileLabel: "uploadFileLabel", confirmLabel: "confirmLabel", abortLabel: "abortLabel", saveLabel: "saveLabel", exitLabel: "exitLabel", uploadWithDropboxLabel: "uploadWithDropboxLabel", cropLabel: "cropLabel", deleteDialogTitle: "deleteDialogTitle", deleteDialogMessage: "deleteDialogMessage", noImageSelectedErrorMessage: "noImageSelectedErrorMessage", wrongTypeSelectedErrorMessage: "wrongTypeSelectedErrorMessage", videoPreviewErrorMessage: "videoPreviewErrorMessage", audioPreviewErrorMessage: ["videoPreviewErrorMessage", "audioPreviewErrorMessage"], flipHorinzontalLabel: "flipHorinzontalLabel", flipVerticalLabel: "flipVerticalLabel", rotateRightLabel: "rotateRightLabel", rotateLeftLabel: "rotateLeftLabel", uploadTitle: "uploadTitle", uploadSubtitle: "uploadSubtitle", dropHereLabel: "dropHereLabel", supportedFormatsLabel: "supportedFormatsLabel", browseFilesLabel: "browseFilesLabel", uploadSummaryLabel: "uploadSummaryLabel", filesLabel: "filesLabel", totalSizeLabel: "totalSizeLabel", emptyStateLabel: "emptyStateLabel", addedSuccessfullyLabel: "addedSuccessfullyLabel", removedLabel: "removedLabel", chooseView: "chooseView", showSummary: "showSummary", viewMode: "viewMode", showUploadTitle: "showUploadTitle", customMenuActions: "customMenuActions", customColumns: "customColumns" }, outputs: { localEditedAttachments: "localEditedAttachments", abortAddAttachment: "abortAddAttachment", downloadAttachment: "downloadAttachment", onDeleteAttachment: "onDeleteAttachment" }, viewQueries: [{ propertyName: "dialogAddAttachment", first: true, predicate: ["dialogAddAttachment"], descendants: true, static: true }, { propertyName: "dialogAddMultipleAttachment", first: true, predicate: ["dialogAddMultipleAttachment"], descendants: true, static: true }, { propertyName: "dialogCropImage", first: true, predicate: ["dialogCropImage"], descendants: true, static: true }, { propertyName: "addingLinkTemplate", first: true, predicate: ["addingLinkTemplate"], descendants: true }, { propertyName: "imageCropper", first: true, predicate: ImageCropperComponent, descendants: true }, { propertyName: "imageInput", first: true, predicate: ["imageInput"], descendants: true }, { propertyName: "inlinePreviewTemplate", first: true, predicate: ["inlinePreviewTemplate"], descendants: true, static: true }, { propertyName: "dialogPreview", first: true, predicate: ["dialogPreview"], descendants: true, static: true }, { propertyName: "defaultFileTemplate", first: true, predicate: ["defaultFileTemplate"], descendants: true, static: true }, { propertyName: "defaultActionsTemplate", first: true, predicate: ["defaultActionsTemplate"], descendants: true, static: true }], ngImport: i0, template: "<!-- Se richiesta la gestione singola mostra il pulsante di caricamento di un singolo file -->\r\n<div class=\"header\">\r\n @if(showUploadTitle == true){\r\n <h3>{{ uploadTitle }}</h3>\r\n }\r\n @if(chooseView == true){\r\n <mat-button-toggle-group [value]=\"viewMode\" (change)=\"setViewMode($event.value)\"\r\n aria-label=\"Modalit\u00E0 di visualizzazione\">\r\n <mat-button-toggle value=\"card\"><mat-icon>grid_view</mat-icon></mat-button-toggle>\r\n <mat-button-toggle value=\"table\"><mat-icon>view_list</mat-icon></mat-button-toggle>\r\n </mat-button-toggle-group>\r\n }\r\n</div>\r\n\r\n<!-- Gestione singolo -->\r\n@if (multipleAttachment != true) {\r\n@if (!singleAttachmentDragAndDrop) {\r\n@if (!addingLinkMode) {\r\n<div class=\"text-center\">\r\n <ng-container [ngTemplateOutlet]=\"addAttachmentButton\"></ng-container>\r\n</div>\r\n} @else {\r\n<div class=\"text-center\">\r\n <ng-container [ngTemplateOutlet]=\"addingLinkTemplate\"></ng-container>\r\n</div>\r\n}\r\n} @else {\r\n<input #fileInput id=\"file_attachment\" name=\"file_attachment\" type=\"file\" hidden (change)=\"onFileAdded($event)\"\r\n [accept]=\"acceptedFileTypes\" [multiple]=\"loadMultipleFiles\" />\r\n\r\n@if (allowedTypes && allowedTypes.includes(1) && (!attachmentsList || attachmentsList.length == 0 ||\r\n(attachmentsList.length > 0 && !attachmentsList[0]))) {\r\n\r\n<!-- FULL -->\r\n@if (layout === 'full') {\r\n\r\n<div class=\"dropbox\" [class.dragover]=\"dragOver\" (dragover)=\"dragOver = true; $event.preventDefault()\"\r\n (dragleave)=\"dragOver = false\" (drop)=\"dragOver = false; fileDropped($event)\"\r\n (click)=\"onSelectFile($event, fileInput)\" role=\"button\" tabindex=\"0\" (keydown.enter)=\"fileInput.click()\"\r\n (keydown.space)=\"fileInput.click()\">\r\n <div class=\"dropbox-icon\">\uD83D\uDCC1</div>\r\n <div class=\"dropbox-text\">{{ dropHereLabel }}</div>\r\n <div class=\"dropbox-subtext\">\r\n {{ supportedFormatsLabel }}\r\n </div>\r\n <button class=\"browse-btn\" type=\"button\" (click)=\"$event.stopPropagation(); fileInput.click()\">\r\n {{ browseFilesLabel }}\r\n </button>\r\n <div class=\"secondary-action\">\r\n @if (allowedTypes.includes(2) || allowedTypes.includes(3)) {\r\n @if (allowedTypes.includes(2) && allowedTypes.includes(3)) {\r\n <a class=\"secondary-action-link\" [matMenuTriggerFor]=\"isDisabled ? null : linkMenu\"\r\n [class.disabled-link]=\"isDisabled\" (click)=\"$event.stopPropagation()\">\r\n o aggiungi da web\r\n </a>\r\n <mat-menu #linkMenu=\"matMenu\">\r\n <button mat-menu-item (click)=\"switchToAddingLinkMode()\">\r\n <mat-icon>link</mat-icon>\r\n <span>Aggiungi da link</span>\r\n </button>\r\n <button mat-menu-item (click)=\"chooseDropboxFile()\">\r\n <mat-icon>cloud_queue</mat-icon>\r\n <span>Carica da Dropbox</span>\r\n </button>\r\n </mat-menu>\r\n }\r\n\r\n @else if (allowedTypes.includes(2)) {\r\n <a [class.disabled-link]=\"isDisabled\" class=\"secondary-action-link\"\r\n (click)=\"$event.stopPropagation(); switchToAddingLinkMode()\">\r\n aggiungi un link\r\n </a>\r\n }\r\n\r\n @else if (allowedTypes.includes(3)) {\r\n <a [class.disabled-link]=\"isDisabled\" class=\"secondary-action-link\"\r\n (click)=\"$event.stopPropagation(); chooseDropboxFile()\">\r\n carica da Dropbox\r\n </a>\r\n }\r\n }\r\n </div>\r\n</div>\r\n}@else {\r\n<div class=\"compact-uploader\" [class.dragover]=\"dragOver\" (dragover)=\"dragOver = true; $event.preventDefault()\"\r\n (dragleave)=\"dragOver = false\" (drop)=\"dragOver = false; fileDropped($event)\">\r\n <div class=\"compact-icon\"><mat-icon>folder_open</mat-icon></div>\r\n <div class=\"compact-text\" (click)=\"onSelectFile($event, fileInput)\">\r\n <div class=\"compact-title\">Trascina i file o seleziona dal computer</div>\r\n <div class=\"compact-subtitle\">{{ supportedFormatsLabel }}</div>\r\n </div>\r\n <div class=\"compact-actions\">\r\n <button mat-stroked-button color=\"primary\" (click)=\"$event.stopPropagation(); fileInput.click()\">Sfoglia</button>\r\n @if (allowedTypes.includes(2) || allowedTypes.includes(3)) {\r\n <button mat-stroked-button [matMenuTriggerFor]=\"compactLinkMenu\" (click)=\"$event.stopPropagation()\">Link</button>\r\n <mat-menu #compactLinkMenu=\"matMenu\">\r\n @if (allowedTypes.includes(2)) { <button mat-menu-item (click)=\"switchToAddingLinkMode()\">...</button> }\r\n @if (allowedTypes.includes(3)) { <button mat-menu-item (click)=\"chooseDropboxFile()\">...</button> }\r\n </mat-menu>\r\n }\r\n </div>\r\n</div>\r\n}\r\n\r\n}\r\n}\r\n\r\n<!-- Azioni singolo elemento (come prima) -->\r\n<div class=\"text-center\">\r\n @if (attachmentsList && attachmentsList.length > 0 && attachmentsList[0]) {\r\n <button class=\"mb-2 me-2 eqp-attachments-download-btn\" (click)=\"viewAttachment(attachmentsList[0])\" type=\"button\"\r\n mat-raised-button color=\"primary\">\r\n @if (attachmentsList[0].AttachmentType == AttachmentType.FILE) {\r\n <mat-icon>download</mat-icon>\r\n } @else {\r\n <mat-icon>open_in_new</mat-icon>\r\n }\r\n {{ attachmentsList[0].AttachmentType == AttachmentType.FILE ? downloadLabel : openLinkLabel }}\r\n </button>\r\n\r\n @if (showPreview && (!attachmentsList[0].FileContentType || (!attachmentsList[0].FileContentType.startsWith('video')\r\n && !attachmentsList[0].FileContentType.startsWith('audio'))) && attachmentsList[0].IsImage == true) {\r\n <button class=\"mb-2 me-2 eqp-attachments-preview-btn\" (click)=\"openPreviewDialog(attachmentsList[0])\" type=\"button\"\r\n mat-raised-button color=\"primary\">\r\n <mat-icon>visibility</mat-icon> {{ previewLabel }}\r\n </button>\r\n }\r\n\r\n <button [disabled]=\"disableAction\" class=\"mb-2 eqp-attachments-delete-btn\"\r\n (click)=\"deleteAttachment(attachmentsList[0])\" type=\"button\" mat-raised-button [disabled]=\"isDisabled\">\r\n <mat-icon>delete</mat-icon> {{ deleteLabel }}\r\n </button>\r\n }\r\n</div>\r\n}\r\n\r\n<!-- Gestione multipla -->\r\n@if (multipleAttachment == true) {\r\n<input #fileInput id=\"file_attachment_multi\" name=\"file_attachment_multi\" type=\"file\" hidden\r\n (change)=\"onFileAdded($event)\" [accept]=\"acceptedFileTypes\" [multiple]=\"loadMultipleFiles\" />\r\n\r\n@if (layout === 'full') {\r\n<div class=\"dropbox\" [class.dragover]=\"dragOver\" (dragover)=\"dragOver = true; $event.preventDefault()\"\r\n (dragleave)=\"dragOver = false\" (drop)=\"dragOver = false; fileDropped($event)\"\r\n (click)=\"onSelectFile($event, fileInput)\" role=\"button\" tabindex=\"0\" (keydown.enter)=\"fileInput.click()\"\r\n (keydown.space)=\"fileInput.click()\">\r\n <div class=\"dropbox-icon\">\uD83D\uDCC1</div>\r\n <div class=\"dropbox-text\">{{ dropHereLabel }}</div>\r\n <div class=\"dropbox-subtext\">\r\n {{ supportedFormatsLabel }}\r\n </div>\r\n <button class=\"browse-btn\" type=\"button\" (click)=\"$event.stopPropagation(); fileInput.click()\">\r\n {{ browseFilesLabel }}\r\n </button>\r\n @if (allowedTypes.includes(2)) {\r\n <div class=\"secondary-action-link\">\r\n o <a (click)=\"$event.stopPropagation(); switchToAddingLinkMode()\">aggiungi un link</a>\r\n </div>\r\n }\r\n</div>\r\n}@else {\r\n<div class=\"compact-uploader\" [class.dragover]=\"dragOver\" (dragover)=\"dragOver = true; $event.preventDefault()\"\r\n (dragleave)=\"dragOver = false\" (drop)=\"dragOver = false; fileDropped($event)\">\r\n <div class=\"compact-icon\"><mat-icon>folder_open</mat-icon></div>\r\n <div class=\"compact-text\" (click)=\"onSelectFile($event, fileInput)\">\r\n <div class=\"compact-title\">Trascina i file o seleziona dal computer</div>\r\n <div class=\"compact-subtitle\">{{ supportedFormatsLabel }}</div>\r\n </div>\r\n <div class=\"compact-actions\">\r\n <button mat-stroked-button color=\"primary\" (click)=\"$event.stopPropagation(); fileInput.click()\">Sfoglia</button>\r\n @if (allowedTypes.includes(2) || allowedTypes.includes(3)) {\r\n <button mat-stroked-button [matMenuTriggerFor]=\"compactLinkMenu\" (click)=\"$event.stopPropagation()\">Link</button>\r\n <mat-menu #compactLinkMenu=\"matMenu\">\r\n @if (allowedTypes.includes(2)) { <button mat-menu-item (click)=\"switchToAddingLinkMode()\">...</button> }\r\n @if (allowedTypes.includes(3)) { <button mat-menu-item (click)=\"chooseDropboxFile()\">...</button> }\r\n </mat-menu>\r\n }\r\n </div>\r\n</div>\r\n}\r\n}\r\n\r\n@if (attachmentsList?.length > 0 && showSummary) {\r\n<div class=\"upload-stats\">\r\n\r\n <div class=\"stats-info\">\r\n <div class=\"stat-item\">\r\n <div class=\"stat-value\">{{ attachmentsList?.length }}</div>\r\n <div class=\"stat-label\">{{ filesLabel }}</div>\r\n </div>\r\n <div class=\"stat-item\">\r\n <div class=\"stat-value\">{{ totalSizeFormatted }}</div>\r\n <div class=\"stat-label\">{{ totalSizeLabel }}</div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"progress-bar\">\r\n <div class=\"progress-fill\" [style.width.%]=\"progressPercent\"></div>\r\n </div>\r\n\r\n</div>\r\n} @else if(attachmentsList?.length == 0){\r\n<div class=\"empty-state\">{{ emptyStateLabel }}</div>\r\n}\r\n\r\n<!-- Griglia anteprime card (vale per singolo e multiplo) -->\r\n@if (viewMode === 'card') {\r\n\r\n<div class=\"file-previews\" [ngStyle]=\"getPreviewsContainerStyle()\">\r\n @for (att of attachmentsList; track att.ID) {\r\n <div [ngClass]=\"getCardClass(att)\">\r\n <div class=\"preview-img-container\" (click)=\"!att.isUploading && handlePrimaryAction(att)\">\r\n\r\n @if (att.IsImage) {\r\n <img class=\"preview-img\"\r\n [src]=\"'data:' + att.FileContentType + ';base64,' + (att.FileThumbnailBase64 || att.FileDataBase64)\"\r\n [alt]=\"att.FileName\" /> } @else {\r\n <div class=\"file-icon\"><i [ngClass]=\"getAttachmentIcon(att)\"></i></div>\r\n }\r\n\r\n <div class=\"preview-action-overlay\">\r\n @if (att.IsImage && canBePreviewed(att)) {\r\n } @else if (att.FileContentType === 'application/pdf' && canBePreviewed(att)) {\r\n <mat-icon>open_in_new</mat-icon>\r\n } @else if (att.AttachmentType === AttachmentType.FILE) {\r\n <mat-icon>download</mat-icon>\r\n } @else {\r\n <mat-icon>open_in_new</mat-icon>\r\n }\r\n </div>\r\n\r\n </div>\r\n\r\n <div class=\"file-info\">\r\n <div class=\"file-name\" [title]=\"att.FileName\">{{ att.FileName }}</div>\r\n </div>\r\n\r\n @if(!disableAction){\r\n <button mat-icon-button class=\"remove-btn\" type=\"button\" aria-label=\"Rimuovi allegato\"\r\n (click)=\"deleteAttachment(att)\" [disabled]=\"att.isUploading\">\r\n <mat-icon>delete</mat-icon>\r\n </button>\r\n }\r\n\r\n @if (att.isUploading) {\r\n <div class=\"upload-spinner\"></div>\r\n }\r\n </div>\r\n }\r\n</div>\r\n\r\n} @else if(viewMode === 'table' && attachmentsList?.length > 0) {\r\n\r\n\r\n<div class=\"table-view-container\">\r\n\r\n <div class=\"table-header\">\r\n @for (col of _tableColumns; track col.key) {\r\n <div class=\"table-cell\" [style.flex]=\"col.styles?.flex\" [ngClass]=\"col.class\">\r\n {{ col.display }}\r\n </div>\r\n }\r\n </div>\r\n\r\n @for (att of attachmentsList; track att.ID) {\r\n <div class=\"table-row\">\r\n\r\n @for (col of _tableColumns; track col.key) {\r\n <div class=\"table-cell\" [style.flex]=\"col.styles?.flex\" [ngClass]=\"col.class\">\r\n\r\n @switch (col.type) {\r\n\r\n @case ('template') {\r\n <div class=\"template-wrapper\">\r\n <ng-container [ngTemplateOutlet]=\"col.externalTemplate\"\r\n [ngTemplateOutletContext]=\"{ $implicit: att }\"></ng-container>\r\n </div>\r\n }\r\n @case ('date') {\r\n <span class=\"metadata-value\">{{ att[col.key] | date:'dd/MM/yyyy' }}</span>\r\n }\r\n @default {\r\n <span class=\"metadata-value\">{{ att[col.key] }}</span>\r\n }\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n</div>\r\n}\r\n\r\n<!-- Notifica toast -->\r\n<div class=\"upload-notification\" [class.show]=\"toast?.visible\" [class.success]=\"toast?.type === 'success'\"\r\n [class.error]=\"toast?.type === 'error'\">\r\n <span>{{ toast?.text }}</span>\r\n <div class=\"notification-progress\"></div>\r\n</div>\r\n\r\n<ng-template #defaultFileTemplate let-att>\r\n <i class=\"file-icon-small\" [ngClass]=\"getAttachmentIcon(att)\"></i>\r\n <div class=\"file-info-text\">\r\n <span class=\"file-name-table\">{{ att.FileName }}</span>\r\n <span class=\"file-type-table\">{{ att.FileExtension || 'Link' }}</span>\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #defaultActionsTemplate let-att>\r\n <button mat-icon-button (click)=\"viewAttachment(att)\" matTooltip=\"Visualizza/Scarica\">\r\n <mat-icon>{{ att.AttachmentType === attachmentType.FILE ? 'download' : 'open_in_new' }}</mat-icon>\r\n </button>\r\n\r\n <button mat-icon-button [matMenuTriggerFor]=\"actionsMenu\" matTooltip=\"Altre opzioni\">\r\n <mat-icon>more_vert</mat-icon>\r\n </button>\r\n\r\n <mat-menu #actionsMenu=\"matMenu\">\r\n\r\n @for (action of _sortedMenuActions; track action.name) {\r\n <button mat-menu-item (click)=\"action.fn(att)\" [disabled]=\"action.disabled ? action.disabled(att) : false\">\r\n\r\n <mat-icon [color]=\"action.icon === 'delete' ? 'warn' : undefined\">\r\n {{ action.icon }}\r\n </mat-icon>\r\n\r\n <span>{{ action.name }}</span>\r\n </button>\r\n }\r\n </mat-menu>\r\n</ng-template>\r\n\r\n\r\n<ng-template #dialogCropImage>\r\n <div style=\"overflow-x: hidden\" [ngClass]=\"cropDialogClass\">\r\n @if (showCropImage == true) {\r\n <ng-container [ngTemplateOutlet]=\"croppieTemplate\" [ngTemplateOutletContext]=\"{ form: newAttachmentForm }\">\r\n </ng-container>\r\n }\r\n </div>\r\n</ng-template>\r\n\r\n\r\n<ng-template #inlinePreviewTemplate let-row=\"row\">\r\n @if (row.AttachmentType != AttachmentType.LINK && row.IsImage) {\r\n <div class=\"inline-preview-container\" (click)=\"openPreviewDialog(row)\">\r\n <img [src]=\"'data:image/png;base64,' + (row.FileThumbnailBase64 ? row.FileThumbnailBase64 : row.FileDataBase64)\" />\r\n </div>\r\n } @else if (row.AttachmentType != AttachmentType.LINK && !row.IsImage) {\r\n <div class=\"inline-preview-container\" (click)=\"openPreviewDialog(row)\">\r\n <i [ngClass]=\"getAttachmentIcon(row)\"></i>\r\n </div>\r\n }\r\n</ng-template>\r\n\r\n\r\n<ng-template #dialogPreview>\r\n @if (selectedAttachment) {\r\n <h2 mat-dialog-title class=\"dialog-header\">\r\n <span>\r\n {{ previewLabel }} {{ selectedAttachment!.AttachmentType === attachmentType.FILE ? \"File\" : \"Link\" }}\r\n </span>\r\n <button mat-icon-button mat-dialog-close aria-label=\"Chiudi anteprima\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </h2>\r\n\r\n <mat-dialog-content class=\"dialog-content\">\r\n @if (selectedAttachment!.IsImage) {\r\n <img class=\"preview-image\"\r\n [src]=\"'data:image/png;base64,' + (selectedAttachment!.FileDataBase64 || selectedAttachment!.FileThumbnailBase64)\"\r\n alt=\"Anteprima allegato\" />\r\n } @else {\r\n <iframe class=\"preview-iframe\" [src]=\"selectedAttachment!.TrustedUrl\" [title]=\"selectedAttachment!.FileName\">\r\n </iframe>\r\n }\r\n </mat-dialog-content>\r\n\r\n <mat-dialog-actions [align]=\"'center'\">\r\n @if (selectedAttachment!.AttachmentType !== AttachmentType.LINK) {\r\n <button mat-raised-button color=\"primary\" (click)=\"viewAttachment(selectedAttachment!)\">\r\n <mat-icon>download</mat-icon>\r\n <span>{{ downloadLabel }}</span>\r\n </button>\r\n }\r\n </mat-dialog-actions>\r\n }\r\n</ng-template>\r\n\r\n\r\n<!-- TEMPLATE PER IL PULSANTE DI AGGIUNTA NUOVO ALLEGATO -->\r\n<ng-template #addAttachmentButton>\r\n <input #fileInput style=\"display: none\" id=\"file_attachment\" name=\"file_attachment\" type=\"file\"\r\n (change)=\"onFileAdded($event)\" [accept]=\"acceptedFileTypes\" [multiple]=\"loadMultipleFiles\" />\r\n\r\n @if (allowedTypes && allowedTypes.length == 1 && (multipleAttachment == true || !attachmentsList ||\r\n attachmentsList.length == 0 || (attachmentsList.length > 0 && !attachmentsList[0]))) {\r\n <button class=\"btn btn-primary mb-4 me-5 eqp-attachments-add-btn\" mat-raised-button color=\"primary\" type=\"button\"\r\n (click)=\"addFile(allowedTypes[0], fileInput)\" [disabled]=\"isDisabled\">\r\n @if (allowedTypes[0] == 1) { <mat-icon>cloud_upload</mat-icon> }\r\n @if (allowedTypes[0] == 2) { <i class=\"fas fa-link\"></i> }\r\n @if (allowedTypes[0] == 3) { <i class=\"fa-brands fa-dropbox\"></i> }\r\n <span style=\"margin-left: 10px\">\r\n {{ allowedTypes[0] == 1 ? (addButtonLabel + \" file\") : allowedTypes[0] == 2 ? (addButtonLabel + \" link\") :\r\n uploadWithDropboxLabel }}\r\n </span>\r\n </button>\r\n }\r\n\r\n @if (!separatedUploadButtons && allowedTypes && allowedTypes.length > 1 && (multipleAttachment == true ||\r\n !attachmentsList || attachmentsList.length == 0 || (attachmentsList.length > 0 && !attachmentsList[0]))) {\r\n <button class=\"btn btn-primary mb-4 me-5 eqp-attachments-add-btn\" mat-raised-button color=\"primary\" type=\"button\"\r\n [matMenuTriggerFor]=\"attachmentTypeMenu\" [disabled]=\"isDisabled\">\r\n @if (multipleAttachment != true) { <mat-icon>cloud_upload</mat-icon> } @else { <mat-icon>add</mat-icon> }\r\n <span style=\"margin-left: 0px\">{{ addButtonLabel }}</span>\r\n </button>\r\n\r\n <mat-menu #attachmentTypeMenu=\"matMenu\">\r\n <input #imageInput style=\"display: none\" id=\"file_attachment\" name=\"file_attachment\" type=\"file\"\r\n (change)=\"onFileAdded($event)\" [accept]=\"acceptedFileTypes\" [multiple]=\"loadMultipleFiles\" />\r\n @if (allowedTypes.includes(1)) {\r\n <button mat-menu-item (click)=\"imageInput.click()\" class=\"eqp-attachments-file-btn\">\r\n <i class=\"fas fa-file\"></i>\r\n <span style=\"margin-left: 10px\">File</span>\r\n </button>\r\n }\r\n @if (allowedTypes.includes(2)) {\r\n <button mat-menu-item (click)=\"switchToAddingLinkMode()\" class=\"eqp-attachments-link-btn\">\r\n <i class=\"fas fa-link\"></i>\r\n <span style=\"margin-left: 10px\">Link</span>\r\n </button>\r\n }\r\n @if (allowedTypes.includes(3)) {\r\n <button mat-menu-item (click)=\"chooseDropboxFile()\" class=\"eqp-attachments-link-btn\">\r\n <i class=\"fa-brands fa-dropbox\"></i>\r\n <span style=\"margin-left: 10px\">Dropbox</span>\r\n </button>\r\n }\r\n </mat-menu>\r\n }\r\n\r\n @if (separatedUploadButtons && allowedTypes && allowedTypes.length > 1 && (multipleAttachment == true ||\r\n !attachmentsList || attachmentsList.length == 0 || (attachmentsList.length > 0 && !attachmentsList[0]))) {\r\n <div class=\"btn-group\">\r\n @if (allowedTypes.includes(1)) {\r\n <button (click)=\"imageInput.click()\" class=\"btn btn-secondary eqp-attachments-add-btn\" mat-raised-button\r\n color=\"secondary\" type=\"button\">\r\n <i class=\"fa-solid fa-cloud-upload\"></i>\r\n <span style=\"margin-left: 10px\">File</span>\r\n </button>\r\n }\r\n <input #imageInput style=\"display: none\" id=\"file_attachment\" name=\"file_attachment\" type=\"file\"\r\n (change)=\"onFileAdded($event)\" [accept]=\"acceptedFileTypes\" [multiple]=\"loadMultipleFiles\" />\r\n @if (allowedTypes.includes(2)) {\r\n <button (click)=\"switchToAddingLinkMode()\" class=\"btn btn-secondary eqp-attachments-add-btn\" mat-raised-button\r\n color=\"secondary\" type=\"button\">\r\n <i class=\"fas fa-link\"></i>\r\n <span style=\"margin-left: 10px\">Link</span>\r\n </button>\r\n }\r\n @if (allowedTypes.includes(3)) {\r\n <button (click)=\"chooseDropboxFile()\" class=\"btn btn-secondary eqp-attachments-add-btn\" mat-raised-button\r\n color=\"secondary\" type=\"button\">\r\n <i class=\"fa-brands fa-dropbox\"></i>\r\n <span style=\"margin-left: 10px\">Dropbox</span>\r\n </button>\r\n }\r\n </div>\r\n }\r\n</ng-template>\r\n\r\n\r\n<ng-template #croppieTemplate>\r\n <!-- Header compatto -->\r\n <h2 class=\"dialog-header m-3\">\r\n <span>{{ cropLabel }}</span>\r\n <button mat-icon-button type=\"button\" aria-label=\"Chiudi\" (click)=\"abortFile()\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </h2>\r\n\r\n <!-- Contenuto scrollabile con griglia responsive -->\r\n <div class=\"dialog-content\">\r\n <!-- Toolbar controlli (large) -->\r\n <div class=\"controls-row crop-large\">\r\n @if (cropOptions.includes(1)) {\r\n <button [matTooltip]=\"rotateLeftLabel\" class=\"btn btn-primary mat-raised-button\" (click)=\"rotateLeft()\">\r\n <mat-icon>rotate_left</mat-icon>\r\n </button>\r\n <button [matTooltip]=\"rotateRightLabel\" class=\"btn btn-primary mat-raised-button\" (click)=\"rotateRight()\">\r\n <mat-icon>rotate_right</mat-icon>\r\n </button>\r\n }\r\n @if (cropOptions.includes(2)) {\r\n <button [matTooltip]=\"flipHorinzontalLabel\" class=\"btn btn-primary mat-raised-button\" (click)=\"flipHorizontal()\">\r\n <mat-icon>flip_horizontal</mat-icon>\r\n </button>\r\n <button [matTooltip]=\"flipVerticalLabel\" class=\"btn btn-primary mat-raised-button\" (click)=\"flipVertical()\">\r\n <mat-icon>flip_vertical</mat-icon>\r\n </button>\r\n }\r\n <button [matTooltip]=\"'Reset'\" class=\"btn btn-primary mat-raised-button\" (click)=\"restoreOriginalDimensions()\">\r\n <mat-icon>replay</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Toolbar controlli (small) -->\r\n <div class=\"controls-row crop-small\">\r\n @if (cropOptions.includes(1)) {\r\n <mat-icon class=\"control-icon\" (click)=\"rotateLeft()\">rotate_left</mat-icon>\r\n <mat-icon class=\"control-icon\" (click)=\"rotateRight()\">rotate_right</mat-icon>\r\n }\r\n @if (cropOptions.includes(2)) {\r\n <mat-icon class=\"control-icon\" (click)=\"flipHorizontal()\">flip_horizontal</mat-icon>\r\n <mat-icon class=\"control-icon\" (click)=\"flipVertical()\">flip_vertical</mat-icon>\r\n }\r\n <mat-icon class=\"control-icon\" (click)=\"restoreOriginalDimensions()\">replay</mat-icon>\r\n </div>\r\n\r\n <!-- Area crop con contenimento -->\r\n <div class=\"crop-area\">\r\n <div class=\"crop-container\">\r\n <image-cropper [imageFile]=\"selectedFile\" [maintainAspectRatio]=\"false\" [autoCrop]=\"false\"\r\n [containWithinAspectRatio]=\"false\" [aspectRatio]=\"4 / 3\" [cropperMinWidth]=\"128\" [onlyScaleDown]=\"true\"\r\n [roundCropper]=\"false\" [canvasRotation]=\"0\" [transform]=\"transform\" [alignImage]=\"'left'\" format=\"png\"\r\n (imageCropped)=\"imageCropped($event)\" [resizeToWidth]=\"customWidth\" [resizeToHeight]=\"customHeight\"\r\n [canvasRotation]=\"canvasRotation\" [output]=\"'base64'\">\r\n </image-cropper>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Azioni -->\r\n <div class=\"dialog-actions crop-large\">\r\n <button class=\"btn btn-primary mat-raised-button eqp-attachments-confirm-btn\" type=\"button\"\r\n (click)=\"confirmAddAttachment()\">\r\n {{ confirmLabel }}\r\n </button>\r\n <button class=\"btn mat-raised-button eqp-attachments-abort-btn\" type=\"button\" (click)=\"abortFile()\">\r\n {{ abortLabel }}\r\n </button>\r\n </div>\r\n\r\n <div class=\"dialog-actions crop-small\">\r\n <button class=\"icon-action\" type=\"button\" (click)=\"abortFile()\" aria-label=\"Abort\">\r\n <i class=\"fa fa-times\"></i>\r\n </button>\r\n <button class=\"icon-action\" type=\"button\" (click)=\"confirmAddAttachment()\" aria-label=\"Confirm\">\r\n <i class=\"fa fa-check\"></i>\r\n </button>\r\n </div>\r\n</ng-template>\r\n\r\n\r\n\r\n<!-- TEMPLATE PER FORM DI AGGIUNTA DI UN LINK -->\r\n<ng-template #addingLinkTemplate>\r\n <h2 mat-dialog-title>Aggiungi un link</h2>\r\n <mat-dialog-content class=\"add-link-dialog-content\">\r\n <form [formGroup]=\"newAttachmentForm\" class=\"add-link-form\">\r\n <mat-form-field appearance=\"outline\">\r\n <mat-label>URL *</mat-label>\r\n <input matInput formControlName=\"filePath\" placeholder=\"https://...\" required>\r\n @if (newAttachmentForm.get('filePath')?.hasError('pattern')) {\r\n <mat-error>L'URL non sembra valido.</mat-error>\r\n }\r\n </mat-form-field>\r\n <mat-form-field appearance=\"outline\">\r\n <mat-label>Nome (opzionale)</mat-label>\r\n <input matInput formControlName=\"fileName\" placeholder=\"Es. Documento Progetto\">\r\n </mat-form-field>\r\n </form>\r\n </mat-dialog-content>\r\n <mat-dialog-actions align=\"end\">\r\n <button mat-button mat-dialog-close>Annulla</button>\r\n <button mat-raised-button color=\"primary\" [mat-dialog-close]=\"newAttachmentForm.value\"\r\n [disabled]=\"newAttachmentForm.invalid\">\r\n Aggiungi\r\n </button>\r\n </mat-dialog-actions>\r\n</ng-template>", styles: ["@charset \"UTF-8\";:host{--primary-color: #6a5af9;--primary-color-dark: #5441f8;--success-color: #1ce593;--error-color: #ff5b5b;--background-light: #f7f9fc;--background-card: rgba(255, 255, 255, .7);--text-color: #1e293b;--text-color-light: #64748b;--border-color: rgba(203, 213, 225, .5);--shadow-color: rgba(99, 102, 241, .2);--border-radius: 16px;--transition-speed: .3s}@keyframes fadeIn{0%{opacity:0;transform:translateY(15px)}to{opacity:1;transform:translateY(0)}}@keyframes pulse{0%{box-shadow:0 0 0 0 var(--primary-color)}70%{box-shadow:0 0 0 10px #6a5af900}to{box-shadow:0 0 #6a5af900}}.container{width:100%;max-width:700px;margin:2rem auto;font-family:Inter,sans-serif}.header{display:flex;justify-content:space-between;align-items:center;margin-bottom:2rem;padding-bottom:1.5rem;border-bottom:1px solid var(--border-color)}.header h1{color:var(--text-color);font-size:1.8rem;font-weight:700;margin:0}.dropbox{width:100%;border:2px dashed var(--border-color);border-radius:var(--border-radius);padding:2.5rem;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;cursor:pointer;background-color:var(--background-card);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);transition:all var(--transition-speed) ease}.dropbox:hover{transform:translateY(-5px);box-shadow:0 10px 25px var(--shadow-color);border-color:var(--primary-color)}.dropbox.dragover{border-style:solid;border-color:var(--primary-color);transform:scale(1.02);box-shadow:0 0 25px var(--shadow-color);animation:pulse 1.5s infinite}.dropbox .dropbox-icon{font-size:3.5rem;color:var(--primary-color)}.dropbox .dropbox-text{font-size:1.1rem;font-weight:600;color:var(--text-color);margin-top:1rem}.dropbox .dropbox-subtext{color:var(--text-color-light);margin-top:.5rem}.dropbox .browse-btn{margin-top:1.5rem;padding:.75rem 1.5rem;background-color:var(--primary-color);color:#fff;border:none;border-radius:10px;font-weight:600;transition:all var(--transition-speed) ease}.dropbox .browse-btn:hover{background-color:var(--primary-color-dark);transform:translateY(-2px)}.file-previews{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:1.5rem;margin-top:2rem}.file-previews{display:grid;gap:16px;padding:16px;grid-template-columns:repeat(auto-fill,minmax(var(--card-min-width, 200px),1fr))}.file-preview{background-color:var(--background-card);border-radius:var(--border-radius);box-shadow:0 4px 15px #0000000d;border:1px solid var(--border-color);overflow:hidden;display:flex;flex-direction:column;position:relative;opacity:0;animation:fadeIn .5s ease-out forwards;transition:transform var(--transition-speed) ease,box-shadow var(--transition-speed) ease}.file-preview.uploading{cursor:wait}.file-preview.uploading .preview-img-container:after{content:\"\";position:absolute;inset:0;background:#ffffffb3;-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);pointer-events:none}.file-preview .preview-action-overlay{position:absolute;inset:0;display:flex;justify-content:center;align-items:center;background-color:#0006;color:#fff;opacity:1;transition:opacity .3s ease}.file-preview .preview-action-overlay .mat-icon{font-size:36px;width:36px;height:36px}.file-preview .remove-btn{position:absolute;top:8px;right:8px;z-index:2;background-color:#0000004d;color:#fff;width:32px;height:32px;display:flex;align-items:center;justify-content:center;padding:0;transition:all var(--transition-speed) ease}.file-preview .remove-btn:hover{background-color:#ff5b5b}.file-preview .remove-btn .mat-icon{font-size:18px}@media (hover: hover){.file-preview .remove-btn .file-preview .preview-action-overlay{opacity:0}.file-preview .remove-btn .file-preview:hover .preview-action-overlay,.file-preview .remove-btn .file-preview:hover .remove-btn{opacity:1}.file-preview .remove-btn .file-preview .remove-btn{opacity:0}}@media (hover: hover) and (min-width: 769px){.file-preview .remove-btn{opacity:0;transform:scale(.8)}.file-preview:hover{transform:translateY(-5px);box-shadow:0 10px 25px var(--shadow-color)}.file-preview:hover .remove-btn{opacity:1;transform:scale(1)}}.file-preview .preview-img-container{aspect-ratio:4/3;display:flex;align-items:center;justify-content:center;position:relative;cursor:pointer;background-color:#f0f2f5}.file-preview .preview-img{width:100%;height:100%;object-fit:cover}.file-preview .file-icon{font-size:4rem;color:var(--primary-color)}.file-preview .file-info{padding:12px;border-top:1px solid var(--border-color);flex-grow:1;display:flex;align-items:center}.file-preview .file-name{font-weight:600;font-size:.9rem;color:var(--text-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.file-preview .upload-spinner{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:40px;height:40px;border:4px solid rgba(0,0,0,.1);border-left-color:var(--primary-color);border-radius:50%;animation:spin 1s linear infinite;pointer-events:none}.file-preview.card-small{width:140px}.file-preview.card-small .preview-img-container{height:80px}.file-preview.card-small .file-info{padding:8px}.file-preview.card-small .file-name{font-size:.8rem}.file-preview.card-small .file-icon{font-size:3rem}.file-preview.card-small .remove-btn{width:28px;height:28px}.file-preview.card-small .remove-btn .mat-icon{font-size:16px}.file-preview.card-large{width:280px}.file-preview.card-large .preview-img-container{height:180px}.file-preview.card-large .file-info{padding:16px}.file-preview.card-large .file-name{font-size:1rem}.file-preview.card-large .file-icon{font-size:5rem}.file-preview.card-large .remove-btn{width:36px;height:36px}.file-preview.card-large .remove-btn .mat-icon{font-size:20px}.file-preview[style*=--eqp-card-width]{width:var(--eqp-card-width)}.file-preview[style*=--eqp-card-width] .preview-img-container{height:calc(var(--eqp-card-width) * .65)}@keyframes spin{to{transform:translate(-50%,-50%) rotate(360deg)}}.upload-stats{background-color:var(--background-card);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid var(--border-color);border-radius:var(--border-radius);padding:1.5rem;margin-top:2rem;animation:fadeIn .5s ease-out}.upload-stats .stats-info{display:flex;justify-content:space-around}.upload-stats .progress-bar{height:10px;background-color:var(--border-color);border-radius:5px;overflow:hidden;margin-top:1rem}.upload-stats .progress-fill{height:100%;background:linear-gradient(90deg,var(--primary-color),var(--success-color));transition:width var(--transition-speed) ease-out;border-radius:5px}.upload-notification{position:fixed;bottom:20px;left:50%;transform:translate(-50%,100px);padding:1rem 1.5rem;border-radius:var(--border-radius);color:#fff;font-weight:600;box-shadow:0 10px 30px #0003;transition:transform var(--transition-speed) cubic-bezier(.175,.885,.32,1.275)}.upload-notification.show{transform:translate(-50%)}.upload-notification.success{background-color:var(--success-color)}.upload-notification.error{background-color:var(--error-color)}.dialog-header{display:flex;justify-content:space-between;align-items:center;width:100%}.dialog-content{display:flex;justify-content:center;align-items:center;padding:16px 24px;overflow:auto}.preview-image{max-width:100%;max-height:70vh;height:auto;object-fit:contain}.preview-iframe{width:80vw;height:75vh;border:none;max-width:1200px}.dialog-header{display:flex;align-items:center;justify-content:space-between}.dialog-content{display:flex;flex-direction:column;gap:12px;max-height:60vh;overflow:auto;padding:0 16px 8px}.controls-row{display:flex;align-items:center;justify-content:center;gap:8px}.control-icon{font-size:28px;cursor:pointer}.crop-area{display:flex;justify-content:center}.crop-container{width:100%;max-width:720px;aspect-ratio:4/3;border-radius:8px;background:#f9f9f9;overflow:hidden}image-cropper,image-cropper canvas,image-cropper img{max-width:100%!important;max-height:100%!important}.dialog-actions{display:flex;justify-content:center;gap:12px;padding:12px 16px}.crop-large{display:flex}.crop-small{display:none}@media (max-width: 600px){.dialog-content{max-height:55vh}.crop-container{max-width:100%;aspect-ratio:auto}.crop-large{display:none}.crop-small{display:flex;justify-content:center;gap:14px}.control-icon{font-size:24px}}.stats-header{display:flex;justify-content:space-between;align-items:center;width:100%}.table-view-container{margin-top:1.5rem;background-color:var(--background-card);border-radius:var(--border-radius);border:1px solid var(--border-color);overflow:hidden;animation:fadeIn .5s ease-out}.table-header,.table-row{display:flex;align-items:center;padding:0 1rem;transition:background-color var(--transition-speed) ease}.table-header{background-color:#f8f9fa;font-weight:600;color:var(--text-color-light);font-size:.8rem;text-transform:uppercase;border-bottom:2px solid var(--border-color)}.table-row{border-bottom:1px solid var(--border-color)}.table-row:last-child{border-bottom:none}.table-row:hover{background-color:#00000005}.table-cell{padding:1rem .5rem;display:flex;align-items:center;gap:1rem}.name-col{flex:4}.size-col,.date-col{flex:2}.actions-col{flex:1;justify-content:flex-end;min-width:150px}.file-icon-small{font-size:1.5rem;color:var(--primary-color)}.file-info-text{display:flex;flex-direction:column}.file-name-table{font-weight:600;color:var(--text-color)}.file-type-table{font-size:.8rem;color:var(--text-color-light)}@media (max-width: 768px){.date-col{display:none}.name-col{flex:3}.size-col{flex:2}.actions-col{flex:2;min-width:120px}}.secondary-action-link{color:var(--primary-color);text-decoration:none;border-bottom:1px solid var(--primary-color);cursor:pointer;font-weight:500}.secondary-action-link:hover{color:var(--primary-color-dark);border-bottom-color:var(--primary-color-dark)}h2[mat-dialog-title]+mat-dialog-content.add-link-dialog-content{padding-top:20px}mat-dialog-content.add-link-dialog-content{padding-left:24px;padding-right:24px;padding-bottom:20px}.add-link-form{display:flex;flex-direction:column;gap:16px}:host ::ng-deep .eqp-attachments-dialog .mat-mdc-dialog-container{--mdc-dialog-subhead-size: 1.25rem;--mdc-dialog-subhead-line-height: 1.5;--mdc-dialog-subhead-weight: 600;--mdc-dialog-supporting-text-size: 1rem}.empty-state{display:flex;flex-direction:column;justify-content:center;align-items:center;padding:1.5rem 1rem;margin-top:1.5rem;border:2px dashed var(--border-color);border-radius:var(--border-radius);background-color:#f8f9fa;text-align:center;color:var(--text-color-light);animation:fadeIn .5s ease-out}.empty-state:before{content:\"\\1f4ed\";font-size:2.5rem;margin-bottom:1rem;opacity:.7}.secondary-action-link.disabled-link{color:#adb5bd;cursor:not-allowed;border-bottom-color:transparent}.secondary-action-link.disabled-link:hover{color:#adb5bd;border-bottom-color:transparent}.table-cell{padding:1rem .5rem;display:flex;align-items:center;gap:1rem;min-width:0;overflow:hidden}.table-cell.col-actions{flex:0 0 150px;justify-content:flex-end;overflow:visible}.compact-uploader{display:flex;align-items:center;gap:16px;padding:16px;border:2px dashed var(--border-color);border-radius:var(--border-radius);background-color:#fcfdff;transition:all .3s ease}.compact-uploader .compact-text{flex:1 1 auto;min-width:0;cursor:pointer}.compact-uploader .compact-text:hover .compact-title{color:var(--primary-color)}.compact-uploader .compact-title{font-weight:600;color:var(--text-color);transition:color .3s ease}.compact-uploader .compact-subtitle{font-size:.8rem;color:var(--text-color-light);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.compact-uploader .compact-icon mat-icon{font-size:36px;width:36px;height:36px;color:var(--primary-color)}.compact-uploader .compact-actions{display:flex;gap:8px;flex-shrink:0}.compact-uploader.dragover{background-color:#f4f8ff;border-color:var(--primary-color);box-shadow:0 0 10px var(--shadow-color)}\n"], dependencies: [{ kind: "component", type: i6.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i6.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "directive", type: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i8.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i8.MatLabel, selector: "mat-label" }, { kind: "directive", type: i8.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "component", type: i9.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i9.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i9.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "directive", type: i10.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i10.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "component", type: i11.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i1.MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: i12.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.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: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i13.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i13.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i13.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i14.ImageCropperComponent, selector: "image-cropper", inputs: ["imageChangedEvent", "imageURL", "imageBase64", "imageFile", "imageAltText", "options", "cropperFrameAriaLabel", "output", "format", "autoCrop", "cropper", "transform", "maintainAspectRatio", "aspectRatio", "resetCropOnAspectRatioChange", "resizeToWidth", "resizeToHeight", "cropperMinWidth", "cropperMinHeight", "cropperMaxHeight", "cropperMaxWidth", "cropperStaticWidth", "cropperStaticHeight", "canvasRotation", "initialStepSize", "roundCropper", "onlyScaleDown", "imageQuality", "backgroundColor", "containWithinAspectRatio", "hideResizeSquares", "allowMoveImage", "checkImageType", "alignImage", "disabled", "hidden"], outputs: ["imageCropped", "startCropImage", "imageLoaded", "cropperReady", "loadImageFailed", "transformChange", "cropperChange"] }, { kind: "pipe", type: i13.DatePipe, name: "date" }] });
|
|
1535
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: EqpAttachmentsComponent, selector: "eqp-attachments", inputs: { disableAction: "disableAction", showHeader: "showHeader", attachmentsList: "attachmentsList", singleAttachment: "singleAttachment", showMatCard: "showMatCard", multipleAttachment: "multipleAttachment", loadMultipleFiles: "loadMultipleFiles", emptyTableMessage: "emptyTableMessage", allowOnlyImages: "allowOnlyImages", acceptedFileTypes: "acceptedFileTypes", isDisabled: "isDisabled", showInlinePreview: "showInlinePreview", getAttachmentEndpoint: "getAttachmentEndpoint", productionBaseUrl: "productionBaseUrl", compressionOptions: "compressionOptions", allowedTypes: "allowedTypes", isEqpTableMultiLanguage: "isEqpTableMultiLanguage", tablePaginatorVisible: "tablePaginatorVisible", isTableSearcheable: "isTableSearcheable", tablePaginatorSize: "tablePaginatorSize", separatedUploadButtons: "separatedUploadButtons", showPreview: "showPreview", singleAttachmentDragAndDrop: "singleAttachmentDragAndDrop", cropOptions: "cropOptions", cropDialogClass: "cropDialogClass", maxFileSizeMB: "maxFileSizeMB", cardSize: "cardSize", customCardWidthPx: "customCardWidthPx", customCardHeightPx: "customCardHeightPx", layout: "layout", openLinkLabel: "openLinkLabel", addButtonLabel: "addButtonLabel", downloadLabel: "downloadLabel", deleteLabel: "deleteLabel", fileNameLabel: "fileNameLabel", previewLabel: "previewLabel", uploadFileLabel: "uploadFileLabel", confirmLabel: "confirmLabel", abortLabel: "abortLabel", saveLabel: "saveLabel", exitLabel: "exitLabel", uploadWithDropboxLabel: "uploadWithDropboxLabel", cropLabel: "cropLabel", deleteDialogTitle: "deleteDialogTitle", deleteDialogMessage: "deleteDialogMessage", noImageSelectedErrorMessage: "noImageSelectedErrorMessage", wrongTypeSelectedErrorMessage: "wrongTypeSelectedErrorMessage", videoPreviewErrorMessage: "videoPreviewErrorMessage", audioPreviewErrorMessage: ["videoPreviewErrorMessage", "audioPreviewErrorMessage"], flipHorinzontalLabel: "flipHorinzontalLabel", flipVerticalLabel: "flipVerticalLabel", rotateRightLabel: "rotateRightLabel", rotateLeftLabel: "rotateLeftLabel", base64LimitMB: "base64LimitMB", uploadTitle: "uploadTitle", uploadSubtitle: "uploadSubtitle", dropHereLabel: "dropHereLabel", supportedFormatsLabel: "supportedFormatsLabel", browseFilesLabel: "browseFilesLabel", uploadSummaryLabel: "uploadSummaryLabel", filesLabel: "filesLabel", totalSizeLabel: "totalSizeLabel", emptyStateLabel: "emptyStateLabel", addedSuccessfullyLabel: "addedSuccessfullyLabel", removedLabel: "removedLabel", chooseView: "chooseView", showSummary: "showSummary", viewMode: "viewMode", showUploadTitle: "showUploadTitle", showDropArea: "showDropArea", hiddenColumns: "hiddenColumns", hiddenActions: "hiddenActions", videoCompression: "videoCompression", customMenuActions: "customMenuActions", customColumns: "customColumns" }, outputs: { localEditedAttachments: "localEditedAttachments", abortAddAttachment: "abortAddAttachment", downloadAttachment: "downloadAttachment", onDeleteAttachment: "onDeleteAttachment" }, viewQueries: [{ propertyName: "dialogAddAttachment", first: true, predicate: ["dialogAddAttachment"], descendants: true, static: true }, { propertyName: "dialogAddMultipleAttachment", first: true, predicate: ["dialogAddMultipleAttachment"], descendants: true, static: true }, { propertyName: "dialogCropImage", first: true, predicate: ["dialogCropImage"], descendants: true, static: true }, { propertyName: "addingLinkTemplate", first: true, predicate: ["addingLinkTemplate"], descendants: true }, { propertyName: "imageCropper", first: true, predicate: ImageCropperComponent, descendants: true }, { propertyName: "imageInput", first: true, predicate: ["imageInput"], descendants: true }, { propertyName: "inlinePreviewTemplate", first: true, predicate: ["inlinePreviewTemplate"], descendants: true, static: true }, { propertyName: "dialogPreview", first: true, predicate: ["dialogPreview"], descendants: true, static: true }, { propertyName: "defaultFileTemplate", first: true, predicate: ["defaultFileTemplate"], descendants: true, static: true }, { propertyName: "defaultActionsTemplate", first: true, predicate: ["defaultActionsTemplate"], descendants: true, static: true }], ngImport: i0, template: "<!-- Se richiesta la gestione singola mostra il pulsante di caricamento di un singolo file -->\r\n<div class=\"header\">\r\n @if(showUploadTitle == true){\r\n <h4>{{ uploadTitle }}</h4>\r\n }\r\n @if(chooseView == true){\r\n <mat-button-toggle-group [value]=\"viewMode\" (change)=\"setViewMode($event.value)\"\r\n aria-label=\"Modalit\u00E0 di visualizzazione\">\r\n <mat-button-toggle value=\"card\"><mat-icon>grid_view</mat-icon></mat-button-toggle>\r\n <mat-button-toggle value=\"table\"><mat-icon>view_list</mat-icon></mat-button-toggle>\r\n </mat-button-toggle-group>\r\n }\r\n</div>\r\n\r\n<!-- Gestione singolo -->\r\n@if (multipleAttachment != true) {\r\n@if (!singleAttachmentDragAndDrop) {\r\n@if (!addingLinkMode) {\r\n<div class=\"text-center\">\r\n <ng-container [ngTemplateOutlet]=\"addAttachmentButton\"></ng-container>\r\n</div>\r\n} @else {\r\n<div class=\"text-center\">\r\n <ng-container [ngTemplateOutlet]=\"addingLinkTemplate\"></ng-container>\r\n</div>\r\n}\r\n} @else {\r\n<input #fileInput id=\"file_attachment\" name=\"file_attachment\" type=\"file\" hidden (change)=\"onFileAdded($event)\"\r\n [accept]=\"acceptedFileTypes\" [multiple]=\"loadMultipleFiles\" />\r\n\r\n@if (allowedTypes && allowedTypes.includes(1) && (!attachmentsList || attachmentsList.length == 0 ||\r\n(attachmentsList.length > 0 && !attachmentsList[0]))) {\r\n\r\n@if (showDropArea == true) {\r\n<!-- FULL -->\r\n@if (layout === 'full') {\r\n\r\n<div class=\"dropbox\" [class.dragover]=\"dragOver\" (dragover)=\"dragOver = true; $event.preventDefault()\"\r\n (dragleave)=\"dragOver = false\" (drop)=\"dragOver = false; fileDropped($event)\"\r\n (click)=\"onSelectFile($event, fileInput)\" role=\"button\" tabindex=\"0\" (keydown.enter)=\"fileInput.click()\"\r\n (keydown.space)=\"fileInput.click()\">\r\n <div class=\"dropbox-icon\">\uD83D\uDCC1</div>\r\n <div class=\"dropbox-text\">{{ dropHereLabel }}</div>\r\n <div class=\"dropbox-subtext\">\r\n {{ supportedFormatsLabel }}\r\n </div>\r\n <button class=\"browse-btn\" type=\"button\" (click)=\"$event.stopPropagation(); fileInput.click()\">\r\n {{ browseFilesLabel }}\r\n </button>\r\n <div class=\"secondary-action\">\r\n @if (allowedTypes.includes(2) || allowedTypes.includes(3)) {\r\n @if (allowedTypes.includes(2) && allowedTypes.includes(3)) {\r\n <a class=\"secondary-action-link\" [matMenuTriggerFor]=\"isDisabled ? null : linkMenu\"\r\n [class.disabled-link]=\"isDisabled\" (click)=\"$event.stopPropagation()\">\r\n o aggiungi da web\r\n </a>\r\n <mat-menu #linkMenu=\"matMenu\">\r\n <button mat-menu-item (click)=\"switchToAddingLinkMode()\">\r\n <mat-icon>link</mat-icon>\r\n <span>Aggiungi da link</span>\r\n </button>\r\n <button mat-menu-item (click)=\"chooseDropboxFile()\">\r\n <mat-icon>cloud_queue</mat-icon>\r\n <span>Carica da Dropbox</span>\r\n </button>\r\n </mat-menu>\r\n }\r\n\r\n @else if (allowedTypes.includes(2)) {\r\n <a [class.disabled-link]=\"isDisabled\" class=\"secondary-action-link\"\r\n (click)=\"$event.stopPropagation(); switchToAddingLinkMode()\">\r\n aggiungi un link\r\n </a>\r\n }\r\n\r\n @else if (allowedTypes.includes(3)) {\r\n <a [class.disabled-link]=\"isDisabled\" class=\"secondary-action-link\"\r\n (click)=\"$event.stopPropagation(); chooseDropboxFile()\">\r\n carica da Dropbox\r\n </a>\r\n }\r\n }\r\n </div>\r\n</div>\r\n}@else {\r\n<div class=\"compact-uploader\" [class.dragover]=\"dragOver\" (dragover)=\"dragOver = true; $event.preventDefault()\"\r\n (dragleave)=\"dragOver = false\" (drop)=\"dragOver = false; fileDropped($event)\">\r\n <div class=\"compact-icon\"><mat-icon>folder_open</mat-icon></div>\r\n <div class=\"compact-text\" (click)=\"onSelectFile($event, fileInput)\">\r\n <div class=\"compact-title\">Trascina i file o seleziona dal computer</div>\r\n <div class=\"compact-subtitle\">{{ supportedFormatsLabel }}</div>\r\n </div>\r\n <div class=\"compact-actions\">\r\n <button mat-stroked-button color=\"primary\" [disabled]=\"isDisabled\"\r\n (click)=\"$event.stopPropagation(); fileInput.click()\">Sfoglia</button>\r\n @if (allowedTypes.includes(2) || allowedTypes.includes(3)) {\r\n <button mat-stroked-button [disabled]=\"isDisabled\" [matMenuTriggerFor]=\"compactLinkMenu\"\r\n (click)=\"$event.stopPropagation()\">Link</button>\r\n <mat-menu #compactLinkMenu=\"matMenu\">\r\n @if (allowedTypes.includes(2)) { <button mat-menu-item [disabled]=\"isDisabled\"\r\n (click)=\"switchToAddingLinkMode()\">{{ openLinkLabel }}</button> }\r\n @if (allowedTypes.includes(3)) { <button mat-menu-item [disabled]=\"isDisabled\" (click)=\"chooseDropboxFile()\">{{\r\n uploadWithDropboxLabel }}</button> }\r\n </mat-menu>\r\n }\r\n </div>\r\n</div>\r\n}\r\n}\r\n}\r\n}\r\n\r\n<!-- Azioni singolo elemento (come prima) -->\r\n<div class=\"text-center\">\r\n @if (attachmentsList && attachmentsList.length > 0 && attachmentsList[0]) {\r\n <button class=\"mb-2 me-2 eqp-attachments-download-btn\" (click)=\"viewAttachment(attachmentsList[0])\" type=\"button\"\r\n mat-raised-button color=\"primary\">\r\n @if (attachmentsList[0].AttachmentType == AttachmentType.FILE) {\r\n <mat-icon>download</mat-icon>\r\n } @else {\r\n <mat-icon>open_in_new</mat-icon>\r\n }\r\n {{ attachmentsList[0].AttachmentType == AttachmentType.FILE ? downloadLabel : openLinkLabel }}\r\n </button>\r\n\r\n @if (showPreview && (!attachmentsList[0].FileContentType || (!attachmentsList[0].FileContentType.startsWith('video')\r\n && !attachmentsList[0].FileContentType.startsWith('audio'))) && attachmentsList[0].IsImage == true) {\r\n <button class=\"mb-2 me-2 eqp-attachments-preview-btn\" (click)=\"openPreviewDialog(attachmentsList[0])\" type=\"button\"\r\n mat-raised-button color=\"primary\">\r\n <mat-icon>visibility</mat-icon> {{ previewLabel }}\r\n </button>\r\n }\r\n\r\n <button [disabled]=\"disableAction\" class=\"mb-2 eqp-attachments-delete-btn\"\r\n (click)=\"deleteAttachment(attachmentsList[0])\" type=\"button\" mat-raised-button [disabled]=\"isDisabled\">\r\n <mat-icon>delete</mat-icon> {{ deleteLabel }}\r\n </button>\r\n }\r\n</div>\r\n}\r\n\r\n<!-- Gestione multipla -->\r\n@if (multipleAttachment == true && showDropArea == true) {\r\n<input #fileInput id=\"file_attachment_multi\" name=\"file_attachment_multi\" type=\"file\" hidden\r\n (change)=\"onFileAdded($event)\" [accept]=\"acceptedFileTypes\" [multiple]=\"loadMultipleFiles\" />\r\n\r\n@if (layout === 'full') {\r\n<div class=\"dropbox\" [class.dragover]=\"dragOver\" (dragover)=\"dragOver = true; $event.preventDefault()\"\r\n (dragleave)=\"dragOver = false\" (drop)=\"dragOver = false; fileDropped($event)\"\r\n (click)=\"onSelectFile($event, fileInput)\" role=\"button\" tabindex=\"0\" (keydown.enter)=\"fileInput.click()\"\r\n (keydown.space)=\"fileInput.click()\">\r\n <div class=\"dropbox-icon\">\uD83D\uDCC1</div>\r\n <div class=\"dropbox-text\">{{ dropHereLabel }}</div>\r\n <div class=\"dropbox-subtext\">\r\n {{ supportedFormatsLabel }}\r\n </div>\r\n <button class=\"browse-btn\" type=\"button\" (click)=\"$event.stopPropagation(); fileInput.click()\">\r\n {{ browseFilesLabel }}\r\n </button>\r\n @if (allowedTypes.includes(2)) {\r\n <div class=\"secondary-action-link\">\r\n o <a (click)=\"$event.stopPropagation(); switchToAddingLinkMode()\">aggiungi un link</a>\r\n </div>\r\n }\r\n</div>\r\n}@else{\r\n<div class=\"compact-uploader\" [class.dragover]=\"dragOver\" (dragover)=\"dragOver = true; $event.preventDefault()\"\r\n (dragleave)=\"dragOver = false\" (drop)=\"dragOver = false; fileDropped($event)\">\r\n <div class=\"compact-icon\"><mat-icon>folder_open</mat-icon></div>\r\n <div class=\"compact-text\" (click)=\"onSelectFile($event, fileInput)\">\r\n <div class=\"compact-title\">Trascina i file o seleziona dal computer</div>\r\n <div class=\"compact-subtitle\">{{ supportedFormatsLabel }}</div>\r\n </div>\r\n <div class=\"compact-actions\">\r\n <button mat-stroked-button [disabled]=\"isDisabled\" color=\"primary\"\r\n (click)=\"$event.stopPropagation(); fileInput.click()\">Sfoglia</button>\r\n @if (allowedTypes.includes(2) || allowedTypes.includes(3)) {\r\n <button mat-stroked-button [disabled]=\"isDisabled\" [matMenuTriggerFor]=\"compactLinkMenu\"\r\n (click)=\"$event.stopPropagation()\">Link</button>\r\n <mat-menu #compactLinkMenu=\"matMenu\">\r\n @if (allowedTypes.includes(2)) { <button mat-menu-item [disabled]=\"isDisabled\"\r\n (click)=\"switchToAddingLinkMode()\">{{ openLinkLabel }}</button> }\r\n @if (allowedTypes.includes(3)) { <button mat-menu-item [disabled]=\"isDisabled\" (click)=\"chooseDropboxFile()\">{{\r\n uploadWithDropboxLabel }}</button> }\r\n </mat-menu>\r\n }\r\n </div>\r\n</div>\r\n}\r\n}\r\n\r\n@if (attachmentsList?.length > 0 && showSummary) {\r\n<div class=\"upload-stats\">\r\n\r\n <div class=\"stats-info\">\r\n <div class=\"stat-item\">\r\n <div class=\"stat-value\">{{ attachmentsList?.length }}</div>\r\n <div class=\"stat-label\">{{ filesLabel }}</div>\r\n </div>\r\n <div class=\"stat-item\">\r\n <div class=\"stat-value\">{{ totalSizeFormatted }}</div>\r\n <div class=\"stat-label\">{{ totalSizeLabel }}</div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"progress-bar\">\r\n <div class=\"progress-fill\" [style.width.%]=\"progressPercent\"></div>\r\n </div>\r\n\r\n</div>\r\n} @else if(attachmentsList?.length == 0){\r\n<div class=\"empty-state\">{{ emptyStateLabel }}</div>\r\n}\r\n\r\n<!-- Griglia anteprime card (vale per singolo e multiplo) -->\r\n@if (viewMode === 'card') {\r\n\r\n<div class=\"file-previews\" [ngStyle]=\"getPreviewsContainerStyle()\">\r\n @for (att of attachmentsList; track att.ID) {\r\n <div [ngClass]=\"getCardClass(att)\">\r\n <div class=\"preview-img-container\" (click)=\"!att.isUploading && handlePrimaryAction(att)\">\r\n\r\n @if (att.IsImage || att.FileThumbnailBase64) {\r\n <img class=\"preview-img\" [src]=\"'data:image/jpeg;base64,' + (att.FileThumbnailBase64 || att.FileDataBase64)\"\r\n [alt]=\"att.FileName\" />\r\n } @else {\r\n <div class=\"file-icon\"><i [ngClass]=\"getAttachmentIcon(att)\"></i></div>\r\n }\r\n\r\n <div class=\"preview-action-overlay\">\r\n @if (att.FileContentType?.startsWith('video/')) {\r\n <mat-icon>play_arrow</mat-icon>\r\n }\r\n @else if (att.IsImage && canBePreviewed(att)) {\r\n <mat-icon>visibility</mat-icon>\r\n }\r\n @else if (att.FileContentType === 'application/pdf' && canBePreviewed(att)) {\r\n <mat-icon>open_in_new</mat-icon>\r\n }\r\n @else if (att.AttachmentType === AttachmentType.FILE) {\r\n <mat-icon>download</mat-icon>\r\n }\r\n @else {\r\n <mat-icon>open_in_new</mat-icon>\r\n }\r\n </div>\r\n\r\n </div>\r\n\r\n <div class=\"file-info\">\r\n <div class=\"file-name\" [title]=\"att.FileName\">{{ att.FileName }}</div>\r\n </div>\r\n\r\n @if(!disableAction){\r\n <button mat-icon-button class=\"remove-btn\" type=\"button\" aria-label=\"Rimuovi allegato\"\r\n (click)=\"deleteAttachment(att)\" [disabled]=\"att.isUploading\">\r\n <mat-icon>delete</mat-icon>\r\n </button>\r\n }\r\n\r\n @if (att.isUploading) {\r\n <div class=\"upload-spinner\"></div>\r\n }\r\n </div>\r\n }\r\n</div>\r\n\r\n} @else if(viewMode === 'table' && attachmentsList?.length > 0) {\r\n\r\n\r\n<div class=\"table-view-container\">\r\n\r\n <div class=\"table-header\">\r\n @for (col of _tableColumns; track col.key) {\r\n <div class=\"table-cell\" [style.flex]=\"col.styles?.flex\" [ngClass]=\"col.class\">\r\n {{ col.display }}\r\n </div>\r\n }\r\n </div>\r\n\r\n @for (att of attachmentsList; track att.ID) {\r\n <div class=\"table-row\">\r\n\r\n @for (col of _tableColumns; track col.key) {\r\n <div class=\"table-cell\" [style.flex]=\"col.styles?.flex\" [ngClass]=\"col.class\">\r\n\r\n @switch (col.type) {\r\n\r\n @case ('template') {\r\n <div class=\"template-wrapper\">\r\n <ng-container [ngTemplateOutlet]=\"col.externalTemplate\"\r\n [ngTemplateOutletContext]=\"{ $implicit: att }\"></ng-container>\r\n </div>\r\n }\r\n @case ('date') {\r\n <span class=\"metadata-value\">{{ att[col.key] | date:'dd/MM/yyyy' }}</span>\r\n }\r\n @default {\r\n <span class=\"metadata-value\">{{ att[col.key] }}</span>\r\n }\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n</div>\r\n}\r\n\r\n<!-- Notifica toast -->\r\n<div class=\"upload-notification\" [class.show]=\"toast?.visible\" [class.success]=\"toast?.type === 'success'\"\r\n [class.error]=\"toast?.type === 'error'\">\r\n <span>{{ toast?.text }}</span>\r\n <div class=\"notification-progress\"></div>\r\n</div>\r\n\r\n<ng-template #defaultFileTemplate let-att>\r\n <i class=\"file-icon-small\" [ngClass]=\"getAttachmentIcon(att)\"></i>\r\n <div class=\"file-info-text\">\r\n <span class=\"file-name-table\">{{ att.FileName }}</span>\r\n <span class=\"file-type-table\">{{ att.FileExtension || 'Link' }}</span>\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #defaultActionsTemplate let-att>\r\n <button mat-icon-button (click)=\"handlePrimaryAction(att)\"\r\n [matTooltip]=\"att.FileContentType?.startsWith('video/') ? 'Riproduci video' : 'Visualizza/Scarica'\">\r\n\r\n <mat-icon>\r\n @if (att.FileContentType?.startsWith('video/')) {\r\n play_arrow\r\n } @else {\r\n {{ att.AttachmentType === AttachmentType.FILE ? 'download' : 'open_in_new' }}\r\n }\r\n </mat-icon>\r\n </button>\r\n\r\n <button mat-icon-button [matMenuTriggerFor]=\"actionsMenu\" matTooltip=\"Altre opzioni\">\r\n <mat-icon>more_vert</mat-icon>\r\n </button>\r\n\r\n <mat-menu #actionsMenu=\"matMenu\">\r\n @for (action of _sortedMenuActions; track action.name) {\r\n @if (!isActionHidden(action, att)) {\r\n <button mat-menu-item (click)=\"action.fn(att)\" [disabled]=\"action.disabled ? action.disabled(att) : false\">\r\n <mat-icon [color]=\"action.icon === 'delete' ? 'warn' : undefined\">\r\n {{ action.icon }}\r\n </mat-icon>\r\n <span>{{ action.name }}</span>\r\n </button>\r\n }\r\n }\r\n </mat-menu>\r\n</ng-template>\r\n\r\n\r\n<ng-template #dialogCropImage>\r\n <div style=\"overflow-x: hidden\" [ngClass]=\"cropDialogClass\">\r\n <!-- @if (showCropImage == true) { -->\r\n <ng-container [ngTemplateOutlet]=\"croppieTemplate\" [ngTemplateOutletContext]=\"{ form: newAttachmentForm }\">\r\n </ng-container>\r\n <!-- } -->\r\n </div>\r\n</ng-template>\r\n\r\n\r\n<ng-template #inlinePreviewTemplate let-row=\"row\">\r\n @if (row.AttachmentType != AttachmentType.LINK && row.IsImage) {\r\n <div class=\"inline-preview-container\" (click)=\"openPreviewDialog(row)\">\r\n <img [src]=\"'data:image/png;base64,' + (row.FileThumbnailBase64 ? row.FileThumbnailBase64 : row.FileDataBase64)\" />\r\n </div>\r\n } @else if (row.AttachmentType != AttachmentType.LINK && !row.IsImage) {\r\n <div class=\"inline-preview-container\" (click)=\"openPreviewDialog(row)\">\r\n <i [ngClass]=\"getAttachmentIcon(row)\"></i>\r\n </div>\r\n }\r\n</ng-template>\r\n\r\n\r\n<ng-template #dialogPreview>\r\n <div class=\"modern-dialog-container\"> @if (selectedAttachment) {\r\n <div mat-dialog-title class=\"preview-header\">\r\n <div class=\"header-info\">\r\n <div class=\"title-group\">\r\n <span class=\"main-t\">{{ previewLabel }}</span>\r\n <span class=\"file-t\">{{ selectedAttachment.FileName }}</span>\r\n </div>\r\n </div>\r\n <button mat-icon-button mat-dialog-close class=\"close-btn\"><mat-icon>close</mat-icon></button>\r\n </div>\r\n\r\n <mat-dialog-content class=\"preview-content-area\">\r\n <div class=\"media-viewer-wrapper\">\r\n @if (selectedAttachment.IsImage) {\r\n <img class=\"main-preview-media\"\r\n [src]=\"'data:image/png;base64,' + (selectedAttachment.FileDataBase64 || selectedAttachment.FileThumbnailBase64)\"\r\n [alt]=\"selectedAttachment.FileName\" />\r\n }\r\n @else if (selectedAttachment.FileContentType?.startsWith('video/')) {\r\n <video controls autoplay playsinline class=\"main-preview-media video-player\">\r\n <source [src]=\"selectedAttachment.TrustedUrl\" [type]=\"selectedAttachment.FileContentType\">\r\n Il tuo browser non supporta il video.\r\n </video>\r\n }\r\n @else {\r\n <div class=\"iframe-container\">\r\n <iframe class=\"preview-iframe-modern\" [src]=\"selectedAttachment.TrustedUrl\"\r\n [title]=\"selectedAttachment.FileName\"></iframe>\r\n </div>\r\n }\r\n </div>\r\n </mat-dialog-content>\r\n\r\n <mat-dialog-actions align=\"end\" class=\"preview-actions\">\r\n <button mat-button mat-dialog-close class=\"btn-close\">Chiudi</button>\r\n @if (selectedAttachment.AttachmentType !== AttachmentType.LINK) {\r\n <button mat-raised-button color=\"primary\" (click)=\"viewAttachment(selectedAttachment)\" class=\"btn-download\">\r\n <mat-icon>download</mat-icon> <span>{{ downloadLabel }}</span>\r\n </button>\r\n }\r\n </mat-dialog-actions>\r\n }\r\n </div>\r\n</ng-template>\r\n\r\n\r\n<!-- TEMPLATE PER IL PULSANTE DI AGGIUNTA NUOVO ALLEGATO -->\r\n<ng-template #addAttachmentButton>\r\n <input #fileInput style=\"display: none\" id=\"file_attachment\" name=\"file_attachment\" type=\"file\"\r\n (change)=\"onFileAdded($event)\" [accept]=\"acceptedFileTypes\" [multiple]=\"loadMultipleFiles\" />\r\n\r\n @if (allowedTypes && allowedTypes.length == 1 && (multipleAttachment == true || !attachmentsList ||\r\n attachmentsList.length == 0 || (attachmentsList.length > 0 && !attachmentsList[0]))) {\r\n <button class=\"btn btn-primary mb-4 me-5 eqp-attachments-add-btn\" mat-raised-button color=\"primary\" type=\"button\"\r\n (click)=\"addFile(allowedTypes[0], fileInput)\" [disabled]=\"isDisabled\">\r\n @if (allowedTypes[0] == 1) { <mat-icon>cloud_upload</mat-icon> }\r\n @if (allowedTypes[0] == 2) { <i class=\"fas fa-link\"></i> }\r\n @if (allowedTypes[0] == 3) { <i class=\"fa-brands fa-dropbox\"></i> }\r\n <span style=\"margin-left: 10px\">\r\n {{ allowedTypes[0] == 1 ? (addButtonLabel + \" file\") : allowedTypes[0] == 2 ? (addButtonLabel + \" link\") :\r\n uploadWithDropboxLabel }}\r\n </span>\r\n </button>\r\n }\r\n\r\n @if (!separatedUploadButtons && allowedTypes && allowedTypes.length > 1 && (multipleAttachment == true ||\r\n !attachmentsList || attachmentsList.length == 0 || (attachmentsList.length > 0 && !attachmentsList[0]))) {\r\n <button class=\"btn btn-primary mb-4 me-5 eqp-attachments-add-btn\" mat-raised-button color=\"primary\" type=\"button\"\r\n [matMenuTriggerFor]=\"attachmentTypeMenu\" [disabled]=\"isDisabled\">\r\n @if (multipleAttachment != true) { <mat-icon>cloud_upload</mat-icon> } @else { <mat-icon>add</mat-icon> }\r\n <span style=\"margin-left: 0px\">{{ addButtonLabel }}</span>\r\n </button>\r\n\r\n <mat-menu #attachmentTypeMenu=\"matMenu\">\r\n <input #imageInput style=\"display: none\" id=\"file_attachment\" name=\"file_attachment\" type=\"file\"\r\n (change)=\"onFileAdded($event)\" [accept]=\"acceptedFileTypes\" [multiple]=\"loadMultipleFiles\" />\r\n @if (allowedTypes.includes(1)) {\r\n <button mat-menu-item (click)=\"imageInput.click()\" class=\"eqp-attachments-file-btn\">\r\n <i class=\"fas fa-file\"></i>\r\n <span style=\"margin-left: 10px\">File</span>\r\n </button>\r\n }\r\n @if (allowedTypes.includes(2)) {\r\n <button mat-menu-item [disabled]=\"isDisabled\" (click)=\"switchToAddingLinkMode()\" class=\"eqp-attachments-link-btn\">\r\n <i class=\"fas fa-link\"></i>\r\n <span style=\"margin-left: 10px\">Link</span>\r\n </button>\r\n }\r\n @if (allowedTypes.includes(3)) {\r\n <button mat-menu-item (click)=\"chooseDropboxFile()\" class=\"eqp-attachments-link-btn\">\r\n <i class=\"fa-brands fa-dropbox\"></i>\r\n <span style=\"margin-left: 10px\">Dropbox</span>\r\n </button>\r\n }\r\n </mat-menu>\r\n }\r\n\r\n @if (separatedUploadButtons && allowedTypes && allowedTypes.length > 1 && (multipleAttachment == true ||\r\n !attachmentsList || attachmentsList.length == 0 || (attachmentsList.length > 0 && !attachmentsList[0]))) {\r\n <div class=\"btn-group\">\r\n @if (allowedTypes.includes(1)) {\r\n <button (click)=\"imageInput.click()\" class=\"btn btn-secondary eqp-attachments-add-btn\" mat-raised-button\r\n color=\"secondary\" type=\"button\">\r\n <i class=\"fa-solid fa-cloud-upload\"></i>\r\n <span style=\"margin-left: 10px\">File</span>\r\n </button>\r\n }\r\n <input #imageInput style=\"display: none\" id=\"file_attachment\" name=\"file_attachment\" type=\"file\"\r\n (change)=\"onFileAdded($event)\" [accept]=\"acceptedFileTypes\" [multiple]=\"loadMultipleFiles\" />\r\n @if (allowedTypes.includes(2)) {\r\n <button (click)=\"switchToAddingLinkMode()\" class=\"btn btn-secondary eqp-attachments-add-btn\" mat-raised-button\r\n color=\"secondary\" type=\"button\">\r\n <i class=\"fas fa-link\"></i>\r\n <span style=\"margin-left: 10px\">Link</span>\r\n </button>\r\n }\r\n @if (allowedTypes.includes(3)) {\r\n <button (click)=\"chooseDropboxFile()\" class=\"btn btn-secondary eqp-attachments-add-btn\" mat-raised-button\r\n color=\"secondary\" type=\"button\">\r\n <i class=\"fa-brands fa-dropbox\"></i>\r\n <span style=\"margin-left: 10px\">Dropbox</span>\r\n </button>\r\n }\r\n </div>\r\n }\r\n</ng-template>\r\n\r\n\r\n<ng-template #croppieTemplate>\r\n <div class=\"modern-dialog-container\">\r\n <div mat-dialog-title class=\"preview-header\">\r\n <div class=\"header-info\">\r\n <div class=\"title-group\">\r\n <span class=\"main-t\">{{ cropLabel }}</span>\r\n <span class=\"file-t\">Regola le dimensioni e l'orientamento</span>\r\n </div>\r\n </div>\r\n <button mat-icon-button type=\"button\" (click)=\"abortFile()\" class=\"close-btn\"><mat-icon>close</mat-icon></button>\r\n </div>\r\n\r\n <mat-dialog-content class=\"preview-content-area\">\r\n <div class=\"crop-toolbar\">\r\n @if (cropOptions.includes(1)) {\r\n <button mat-icon-button [matTooltip]=\"rotateLeftLabel\"\r\n (click)=\"rotateLeft()\"><mat-icon>rotate_left</mat-icon></button>\r\n <button mat-icon-button [matTooltip]=\"rotateRightLabel\"\r\n (click)=\"rotateRight()\"><mat-icon>rotate_right</mat-icon></button>\r\n }\r\n @if (cropOptions.includes(2)) {\r\n <button mat-icon-button [matTooltip]=\"flipHorinzontalLabel\"\r\n (click)=\"flipHorizontal()\"><mat-icon>flip_horizontal</mat-icon></button>\r\n <button mat-icon-button [matTooltip]=\"flipVerticalLabel\"\r\n (click)=\"flipVertical()\"><mat-icon>flip_vertical</mat-icon></button>\r\n }\r\n <button mat-icon-button matTooltip=\"Reset\"\r\n (click)=\"restoreOriginalDimensions()\"><mat-icon>replay</mat-icon></button>\r\n </div>\r\n\r\n <div class=\"media-viewer-wrapper\">\r\n <div class=\"crop-container-modern\">\r\n <image-cropper [imageFile]=\"selectedFile\" [maintainAspectRatio]=\"false\" [canvasRotation]=\"canvasRotation\"\r\n [transform]=\"transform\" format=\"png\" (imageCropped)=\"imageCropped($event)\" [resizeToWidth]=\"customWidth\"\r\n [resizeToHeight]=\"customHeight\" [output]=\"'base64'\" [containWithinAspectRatio]=\"true\" [onlyScaleDown]=\"true\"\r\n [alignImage]=\"'center'\">\r\n </image-cropper>\r\n </div>\r\n </div>\r\n </mat-dialog-content>\r\n\r\n <mat-dialog-actions align=\"end\" class=\"preview-actions\">\r\n <button mat-button (click)=\"abortFile()\" class=\"btn-close\">{{ abortLabel }}</button>\r\n <button mat-raised-button color=\"primary\" (click)=\"confirmAddAttachment()\" class=\"btn-download\">\r\n <mat-icon>check</mat-icon> {{ confirmLabel }}\r\n </button>\r\n </mat-dialog-actions>\r\n </div>\r\n</ng-template>\r\n\r\n\r\n\r\n<!-- TEMPLATE PER FORM DI AGGIUNTA DI UN LINK -->\r\n<ng-template #addingLinkTemplate>\r\n <div class=\"modern-dialog-container\">\r\n <div mat-dialog-title class=\"preview-header\">\r\n <div class=\"header-info\">\r\n <div class=\"type-icon-wrapper\">\r\n <mat-icon>link</mat-icon>\r\n </div>\r\n <div class=\"title-group\">\r\n <span class=\"main-t\">Aggiungi un link</span>\r\n <span class=\"file-t\">Inserisci l'URL della risorsa web</span>\r\n </div>\r\n </div>\r\n <button mat-icon-button mat-dialog-close class=\"close-btn\"><mat-icon>close</mat-icon></button>\r\n </div>\r\n\r\n <mat-dialog-content class=\"add-link-modern-content\">\r\n <form [formGroup]=\"newAttachmentForm\" class=\"add-link-form\">\r\n <mat-form-field appearance=\"outline\" class=\"w-100 mt-3\">\r\n <mat-label>URL del collegamento</mat-label>\r\n <input matInput formControlName=\"filePath\" placeholder=\"https://...\" required>\r\n <mat-icon matSuffix>language</mat-icon>\r\n </mat-form-field>\r\n\r\n <mat-form-field appearance=\"outline\" class=\"w-100\">\r\n <mat-label>Titolo (opzionale)</mat-label>\r\n <input matInput formControlName=\"fileName\" placeholder=\"Es. Documento Progetto\">\r\n <mat-icon matSuffix>title</mat-icon>\r\n </mat-form-field>\r\n </form>\r\n </mat-dialog-content>\r\n\r\n <mat-dialog-actions align=\"end\" class=\"preview-actions\">\r\n <button mat-button mat-dialog-close class=\"btn-close\">Annulla</button>\r\n <button mat-raised-button color=\"primary\" [mat-dialog-close]=\"newAttachmentForm.value\"\r\n [disabled]=\"newAttachmentForm.invalid\" class=\"btn-download\">\r\n <mat-icon>add</mat-icon> Aggiungi\r\n </button>\r\n </mat-dialog-actions>\r\n </div>\r\n</ng-template>", styles: ["@charset \"UTF-8\";:host{--primary-color: #6a5af9;--primary-color-dark: #5441f8;--success-color: #1ce593;--error-color: #ff5b5b;--background-light: #f7f9fc;--background-card: rgba(255, 255, 255, .7);--text-color: #1e293b;--text-color-light: #64748b;--border-color: rgba(203, 213, 225, .5);--shadow-color: rgba(99, 102, 241, .2);--border-radius: 16px;--transition-speed: .3s}@keyframes fadeIn{0%{opacity:0;transform:translateY(15px)}to{opacity:1;transform:translateY(0)}}@keyframes pulse{0%{box-shadow:0 0 0 0 var(--primary-color)}70%{box-shadow:0 0 0 10px #6a5af900}to{box-shadow:0 0 #6a5af900}}.container{width:100%;max-width:700px;margin:2rem auto;font-family:Inter,sans-serif}.header{display:flex;justify-content:space-between;align-items:center;margin-bottom:2rem;padding-bottom:1.5rem;border-bottom:1px solid var(--border-color)}.header h1{color:var(--text-color);font-size:1.8rem;font-weight:700;margin:0}.dropbox{width:100%;border:2px dashed var(--border-color);border-radius:var(--border-radius);padding:2.5rem;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;cursor:pointer;background-color:var(--background-card);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);transition:all var(--transition-speed) ease}.dropbox:hover{transform:translateY(-5px);box-shadow:0 10px 25px var(--shadow-color);border-color:var(--primary-color)}.dropbox.dragover{border-style:solid;border-color:var(--primary-color);transform:scale(1.02);box-shadow:0 0 25px var(--shadow-color);animation:pulse 1.5s infinite}.dropbox .dropbox-icon{font-size:3.5rem;color:var(--primary-color)}.dropbox .dropbox-text{font-size:1.1rem;font-weight:600;color:var(--text-color);margin-top:1rem}.dropbox .dropbox-subtext{color:var(--text-color-light);margin-top:.5rem}.dropbox .browse-btn{margin-top:1.5rem;padding:.75rem 1.5rem;background-color:var(--primary-color);color:#fff;border:none;border-radius:10px;font-weight:600;transition:all var(--transition-speed) ease}.dropbox .browse-btn:hover{background-color:var(--primary-color-dark);transform:translateY(-2px)}.file-previews{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:1.5rem;margin-top:2rem}.file-previews{display:grid;gap:16px;padding:16px;grid-template-columns:repeat(auto-fill,minmax(var(--card-min-width, 200px),1fr))}.file-preview{background-color:var(--background-card);border-radius:var(--border-radius);box-shadow:0 4px 15px #0000000d;border:1px solid var(--border-color);overflow:hidden;display:flex;flex-direction:column;position:relative;opacity:0;animation:fadeIn .5s ease-out forwards;transition:transform var(--transition-speed) ease,box-shadow var(--transition-speed) ease}.file-preview.uploading{cursor:wait}.file-preview.uploading .preview-img-container:after{content:\"\";position:absolute;inset:0;background:#ffffffb3;-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);pointer-events:none}.file-preview .preview-action-overlay{position:absolute;inset:0;display:flex;justify-content:center;align-items:center;background-color:#0006;color:#fff;opacity:1;transition:opacity .3s ease}.file-preview .preview-action-overlay .mat-icon{font-size:36px;width:36px;height:36px}.file-preview .remove-btn{position:absolute;top:8px;right:8px;z-index:2;background-color:#0000004d;color:#fff;width:32px;height:32px;display:flex;align-items:center;justify-content:center;padding:0;transition:all var(--transition-speed) ease}.file-preview .remove-btn:hover{background-color:#ff5b5b}.file-preview .remove-btn .mat-icon{font-size:18px}@media (hover: hover){.file-preview .remove-btn .file-preview .preview-action-overlay{opacity:0}.file-preview .remove-btn .file-preview:hover .preview-action-overlay,.file-preview .remove-btn .file-preview:hover .remove-btn{opacity:1}.file-preview .remove-btn .file-preview .remove-btn{opacity:0}}@media (hover: hover) and (min-width: 769px){.file-preview .remove-btn{opacity:0;transform:scale(.8)}.file-preview:hover{transform:translateY(-5px);box-shadow:0 10px 25px var(--shadow-color)}.file-preview:hover .remove-btn{opacity:1;transform:scale(1)}}.file-preview .preview-img-container{aspect-ratio:4/3;display:flex;align-items:center;justify-content:center;position:relative;cursor:pointer;background-color:#f0f2f5}.file-preview .preview-img{width:100%;height:100%;object-fit:cover}.file-preview .file-icon{font-size:4rem;color:var(--primary-color)}.file-preview .file-info{padding:12px;border-top:1px solid var(--border-color);flex-grow:1;display:flex;align-items:center}.file-preview .file-name{font-weight:600;font-size:.9rem;color:var(--text-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.file-preview .upload-spinner{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:40px;height:40px;border:4px solid rgba(0,0,0,.1);border-left-color:var(--primary-color);border-radius:50%;animation:spin 1s linear infinite;pointer-events:none}.file-preview.card-small{width:140px}.file-preview.card-small .preview-img-container{height:80px}.file-preview.card-small .file-info{padding:8px}.file-preview.card-small .file-name{font-size:.8rem}.file-preview.card-small .file-icon{font-size:3rem}.file-preview.card-small .remove-btn{width:28px;height:28px}.file-preview.card-small .remove-btn .mat-icon{font-size:16px}.file-preview.card-large{width:280px}.file-preview.card-large .preview-img-container{height:180px}.file-preview.card-large .file-info{padding:16px}.file-preview.card-large .file-name{font-size:1rem}.file-preview.card-large .file-icon{font-size:5rem}.file-preview.card-large .remove-btn{width:36px;height:36px}.file-preview.card-large .remove-btn .mat-icon{font-size:20px}.file-preview[style*=--eqp-card-width]{width:var(--eqp-card-width)}.file-preview[style*=--eqp-card-width] .preview-img-container{height:calc(var(--eqp-card-width) * .65)}@keyframes spin{to{transform:translate(-50%,-50%) rotate(360deg)}}.upload-stats{background-color:var(--background-card);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid var(--border-color);border-radius:var(--border-radius);padding:1.5rem;margin-top:2rem;animation:fadeIn .5s ease-out}.upload-stats .stats-info{display:flex;justify-content:space-around}.upload-stats .progress-bar{height:10px;background-color:var(--border-color);border-radius:5px;overflow:hidden;margin-top:1rem}.upload-stats .progress-fill{height:100%;background:linear-gradient(90deg,var(--primary-color),var(--success-color));transition:width var(--transition-speed) ease-out;border-radius:5px}.upload-notification{position:fixed;bottom:20px;left:50%;transform:translate(-50%,100px);padding:1rem 1.5rem;border-radius:var(--border-radius);color:#fff;font-weight:600;box-shadow:0 10px 30px #0003;transition:transform var(--transition-speed) cubic-bezier(.175,.885,.32,1.275)}.upload-notification.show{transform:translate(-50%)}.upload-notification.success{background-color:var(--success-color)}.upload-notification.error{background-color:var(--error-color)}::ng-deep .cdk-overlay-pane.eqp-crop-dialog{transform:translateY(-4vh)}@media (max-width: 600px){::ng-deep .cdk-overlay-pane.crop-dialog{width:auto!important}}::ng-deep .eqp-attachments-dialog .mat-mdc-dialog-container .mdc-dialog__surface{border-radius:20px!important;padding:0!important;overflow:hidden!important;width:fit-content!important;min-width:350px!important;max-width:95vw!important;margin:0 auto;background-color:#fff!important}::ng-deep .eqp-attachments-dialog .mat-mdc-dialog-content{padding:0!important;margin:0!important;max-height:none!important;display:flex;flex-direction:column}.modern-dialog-container{display:flex;flex-direction:column;width:100%;max-width:100%;height:100%;max-height:90vh;background:#fff;--preview-header-h: 66px;--preview-footer-h: 66px;--preview-padding-y: 30px}.modern-dialog-container .main-preview-media{display:block;max-width:100%;max-height:var(--media-max-h);width:auto;height:auto;object-fit:contain;border-radius:12px;background:#fff;padding:4px;box-shadow:0 10px 30px #00000014}.modern-dialog-container video.main-preview-media{width:min(100%,1100px);background:#000}.modern-dialog-container .iframe-container{width:min(100%,1100px);height:var(--media-max-h);display:flex;width:100%;max-width:1100px}.modern-dialog-container .preview-iframe-modern{width:100%;height:100%;border:0;border-radius:12px;background:#f8fafc}.modern-dialog-container .preview-header{flex:0 0 auto;flex-shrink:0;padding:14px 20px;display:flex;align-items:center;justify-content:flex-start;border-bottom:1px solid var(--border-color);background:#f8fafc;z-index:10;width:100%}.modern-dialog-container .preview-header .header-info{display:flex;align-items:center;justify-content:flex-start;gap:12px;flex:1 1 auto;min-width:0;text-align:left}.modern-dialog-container .preview-header .header-info .type-icon-wrapper{background:var(--primary-color);color:#fff;width:38px;height:38px;border-radius:10px;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 10px var(--shadow-color)}.modern-dialog-container .preview-header .header-info .title-group{display:flex;flex-direction:column;align-items:flex-start;text-align:left}.modern-dialog-container .preview-header .header-info .title-group .main-t{font-weight:800;color:var(--text-color);font-size:.95rem;line-height:1.2;text-align:left}.modern-dialog-container .preview-header .header-info .title-group .file-t{font-size:.75rem;color:var(--text-color-light);max-width:250px;overflow:hidden;text-overflow:ellipsis;text-align:left}.modern-dialog-container .preview-content-area{flex:1;background:#fff!important;display:flex;flex-direction:column;padding:15px 15px 22px!important;overflow:hidden!important;min-height:0;width:auto;--media-max-h: calc(90vh - var(--preview-header-h) - var(--preview-footer-h) - var(--preview-padding-y));width:100%}.modern-dialog-container .preview-content-area .crop-toolbar{flex-shrink:0;z-index:20;background:#0f172a14;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);padding:6px 16px;border-radius:30px;margin:0 auto 15px;display:flex;gap:8px;box-shadow:0 4px 12px #0000000d}.modern-dialog-container .preview-content-area .crop-toolbar button{color:var(--text-color)!important}.modern-dialog-container .preview-content-area .media-viewer-wrapper{flex:1;width:100%;display:flex;justify-content:center;align-items:center;min-height:0;overflow:visible}.modern-dialog-container .preview-content-area .media-viewer-wrapper .crop-container-modern{width:100%;height:100%;display:flex;justify-content:center;align-items:center;background:transparent;border-radius:12px;padding:0}.modern-dialog-container .preview-content-area .media-viewer-wrapper .crop-container-modern image-cropper{max-width:100%;max-height:100%;display:block}.modern-dialog-container .add-link-modern-content{flex:1;background:#fff;padding:24px!important}.modern-dialog-container .add-link-modern-content .add-link-form{display:flex;flex-direction:column;gap:10px}.modern-dialog-container .preview-actions{flex-shrink:0;padding:12px 24px;background:#f8fafc;border-top:1px solid #e2e8f0;margin:0}.modern-dialog-container .preview-actions .btn-download{border-radius:12px;font-weight:600;box-shadow:0 4px 12px var(--shadow-color)}@media (max-width: 600px){.modern-dialog-container{max-height:95vh}.modern-dialog-container .preview-content-area{padding:10px!important}.modern-dialog-container .preview-content-area .media-viewer-wrapper video.main-preview-media{width:100%!important;max-height:55vh!important;border-radius:4px;box-shadow:none}.modern-dialog-container .preview-content-area .media-viewer-wrapper .iframe-container{height:50vh}.modern-dialog-container .preview-content-area .crop-toolbar{width:calc(100% - 20px);max-width:400px;margin-bottom:10px;padding:4px 8px;gap:4px;overflow-x:auto;justify-content:center;-webkit-overflow-scrolling:touch;scrollbar-width:none}.modern-dialog-container .preview-content-area .crop-toolbar::-webkit-scrollbar{display:none}.modern-dialog-container .preview-content-area .crop-toolbar button{flex-shrink:0;transform:scale(.85);width:40px;height:40px}.modern-dialog-container .preview-actions{padding:10px 15px;flex-direction:column-reverse;gap:8px}.modern-dialog-container .preview-actions button{width:100%;margin:0!important}}@media (max-width: 360px){.modern-dialog-container .preview-content-area .crop-toolbar{gap:2px}.modern-dialog-container .preview-content-area .crop-toolbar button{transform:scale(.75)}}.control-icon{font-size:28px;cursor:pointer}image-cropper,image-cropper canvas,image-cropper img{max-width:100%!important;max-height:100%!important}.crop-large{display:flex}.crop-small{display:none}@media (max-width: 600px){.dialog-content{max-height:55vh}.crop-container{max-width:100%;aspect-ratio:auto}.crop-large{display:none}.crop-small{display:flex;justify-content:center;gap:14px}.control-icon{font-size:24px}}.stats-header{display:flex;justify-content:space-between;align-items:center;width:100%}.table-view-container{margin-top:1.5rem;background-color:var(--background-card);border-radius:var(--border-radius);border:1px solid var(--border-color);overflow:hidden;animation:fadeIn .5s ease-out}.table-header,.table-row{display:flex;align-items:center;padding:0 1rem;transition:background-color var(--transition-speed) ease}.table-header{background-color:#f8f9fa;font-weight:600;color:var(--text-color-light);font-size:.8rem;text-transform:uppercase;border-bottom:2px solid var(--border-color)}.table-row{border-bottom:1px solid var(--border-color)}.table-row:last-child{border-bottom:none}.table-row:hover{background-color:#00000005}.table-cell{padding:1rem .5rem;display:flex;align-items:center;gap:1rem}.name-col{flex:4}.size-col,.date-col{flex:2}.actions-col{flex:1;justify-content:flex-end;min-width:150px}.file-icon-small{font-size:1.5rem;color:var(--primary-color)}.file-info-text{display:flex;flex-direction:column}.file-name-table{font-weight:600;color:var(--text-color)}.file-type-table{font-size:.8rem;color:var(--text-color-light)}@media (max-width: 768px){.date-col{display:none}.name-col{flex:3}.size-col{flex:2}.actions-col{flex:2;min-width:120px}}.secondary-action-link{color:var(--primary-color);text-decoration:none;border-bottom:1px solid var(--primary-color);cursor:pointer;font-weight:500}.secondary-action-link:hover{color:var(--primary-color-dark);border-bottom-color:var(--primary-color-dark)}h2[mat-dialog-title]+mat-dialog-content.add-link-dialog-content{padding-top:20px}mat-dialog-content.add-link-dialog-content{padding-left:24px;padding-right:24px;padding-bottom:20px}.add-link-form{display:flex;flex-direction:column;gap:16px}::ng-deep .eqp-attachments-preview-dialog{--mdc-dialog-subhead-line-height: normal}::ng-deep .eqp-attachments-preview-dialog .mat-mdc-dialog-content{padding:0!important;margin:0!important;max-height:none!important;background:#fff!important}:host ::ng-deep .eqp-attachments-dialog .mat-mdc-dialog-container{--mdc-dialog-subhead-size: 1.25rem;--mdc-dialog-subhead-line-height: 1.5;--mdc-dialog-subhead-weight: 600;--mdc-dialog-supporting-text-size: 1rem}.empty-state{display:flex;flex-direction:column;justify-content:center;align-items:center;padding:1.5rem 1rem;margin-top:1.5rem;border:2px dashed var(--border-color);border-radius:var(--border-radius);background-color:#f8f9fa;text-align:center;color:var(--text-color-light);animation:fadeIn .5s ease-out}.empty-state:before{content:\"\\1f4ed\";font-size:2.5rem;margin-bottom:1rem;opacity:.7}.secondary-action-link.disabled-link{color:#adb5bd;cursor:not-allowed;border-bottom-color:transparent}.secondary-action-link.disabled-link:hover{color:#adb5bd;border-bottom-color:transparent}.table-cell{padding:1rem .5rem;display:flex;align-items:center;gap:1rem;min-width:0;overflow:hidden}.table-cell.col-actions{flex:0 0 150px;justify-content:flex-end;overflow:visible}.compact-uploader{display:flex;align-items:center;gap:16px;padding:16px;border:2px dashed var(--border-color);border-radius:var(--border-radius);background-color:#fcfdff;transition:all .3s ease}.compact-uploader .compact-text{flex:1 1 auto;min-width:0;cursor:pointer}.compact-uploader .compact-text:hover .compact-title{color:var(--primary-color)}.compact-uploader .compact-title{font-weight:600;color:var(--text-color);transition:color .3s ease}.compact-uploader .compact-subtitle{font-size:.8rem;color:var(--text-color-light);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.compact-uploader .compact-icon mat-icon{font-size:36px;width:36px;height:36px;color:var(--primary-color)}.compact-uploader .compact-actions{display:flex;gap:8px;flex-shrink:0}.compact-uploader.dragover{background-color:#f4f8ff;border-color:var(--primary-color);box-shadow:0 0 10px var(--shadow-color)}\n"], dependencies: [{ kind: "component", type: i6.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i6.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "directive", type: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i8.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i8.MatLabel, selector: "mat-label" }, { kind: "directive", type: i8.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "component", type: i9.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i9.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i9.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "directive", type: i10.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i10.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "component", type: i11.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i1.MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["aria-label", "type", "mat-dialog-close", "matDialogClose"], exportAs: ["matDialogClose"] }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: i12.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.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: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i13.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i13.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i13.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i14.ImageCropperComponent, selector: "image-cropper", inputs: ["imageChangedEvent", "imageURL", "imageBase64", "imageFile", "imageAltText", "options", "cropperFrameAriaLabel", "output", "format", "autoCrop", "cropper", "transform", "maintainAspectRatio", "aspectRatio", "resetCropOnAspectRatioChange", "resizeToWidth", "resizeToHeight", "cropperMinWidth", "cropperMinHeight", "cropperMaxHeight", "cropperMaxWidth", "cropperStaticWidth", "cropperStaticHeight", "canvasRotation", "initialStepSize", "roundCropper", "onlyScaleDown", "imageQuality", "backgroundColor", "containWithinAspectRatio", "hideResizeSquares", "allowMoveImage", "checkImageType", "alignImage", "disabled", "hidden"], outputs: ["imageCropped", "startCropImage", "imageLoaded", "cropperReady", "loadImageFailed", "transformChange", "cropperChange"] }, { kind: "pipe", type: i13.DatePipe, name: "date" }] });
|
|
1369
1536
|
}
|
|
1370
1537
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EqpAttachmentsComponent, decorators: [{
|
|
1371
1538
|
type: Component,
|
|
1372
|
-
args: [{ selector: "eqp-attachments", template: "<!-- Se richiesta la gestione singola mostra il pulsante di caricamento di un singolo file -->\r\n<div class=\"header\">\r\n @if(showUploadTitle == true){\r\n <h3>{{ uploadTitle }}</h3>\r\n }\r\n @if(chooseView == true){\r\n <mat-button-toggle-group [value]=\"viewMode\" (change)=\"setViewMode($event.value)\"\r\n aria-label=\"Modalit\u00E0 di visualizzazione\">\r\n <mat-button-toggle value=\"card\"><mat-icon>grid_view</mat-icon></mat-button-toggle>\r\n <mat-button-toggle value=\"table\"><mat-icon>view_list</mat-icon></mat-button-toggle>\r\n </mat-button-toggle-group>\r\n }\r\n</div>\r\n\r\n<!-- Gestione singolo -->\r\n@if (multipleAttachment != true) {\r\n@if (!singleAttachmentDragAndDrop) {\r\n@if (!addingLinkMode) {\r\n<div class=\"text-center\">\r\n <ng-container [ngTemplateOutlet]=\"addAttachmentButton\"></ng-container>\r\n</div>\r\n} @else {\r\n<div class=\"text-center\">\r\n <ng-container [ngTemplateOutlet]=\"addingLinkTemplate\"></ng-container>\r\n</div>\r\n}\r\n} @else {\r\n<input #fileInput id=\"file_attachment\" name=\"file_attachment\" type=\"file\" hidden (change)=\"onFileAdded($event)\"\r\n [accept]=\"acceptedFileTypes\" [multiple]=\"loadMultipleFiles\" />\r\n\r\n@if (allowedTypes && allowedTypes.includes(1) && (!attachmentsList || attachmentsList.length == 0 ||\r\n(attachmentsList.length > 0 && !attachmentsList[0]))) {\r\n\r\n<!-- FULL -->\r\n@if (layout === 'full') {\r\n\r\n<div class=\"dropbox\" [class.dragover]=\"dragOver\" (dragover)=\"dragOver = true; $event.preventDefault()\"\r\n (dragleave)=\"dragOver = false\" (drop)=\"dragOver = false; fileDropped($event)\"\r\n (click)=\"onSelectFile($event, fileInput)\" role=\"button\" tabindex=\"0\" (keydown.enter)=\"fileInput.click()\"\r\n (keydown.space)=\"fileInput.click()\">\r\n <div class=\"dropbox-icon\">\uD83D\uDCC1</div>\r\n <div class=\"dropbox-text\">{{ dropHereLabel }}</div>\r\n <div class=\"dropbox-subtext\">\r\n {{ supportedFormatsLabel }}\r\n </div>\r\n <button class=\"browse-btn\" type=\"button\" (click)=\"$event.stopPropagation(); fileInput.click()\">\r\n {{ browseFilesLabel }}\r\n </button>\r\n <div class=\"secondary-action\">\r\n @if (allowedTypes.includes(2) || allowedTypes.includes(3)) {\r\n @if (allowedTypes.includes(2) && allowedTypes.includes(3)) {\r\n <a class=\"secondary-action-link\" [matMenuTriggerFor]=\"isDisabled ? null : linkMenu\"\r\n [class.disabled-link]=\"isDisabled\" (click)=\"$event.stopPropagation()\">\r\n o aggiungi da web\r\n </a>\r\n <mat-menu #linkMenu=\"matMenu\">\r\n <button mat-menu-item (click)=\"switchToAddingLinkMode()\">\r\n <mat-icon>link</mat-icon>\r\n <span>Aggiungi da link</span>\r\n </button>\r\n <button mat-menu-item (click)=\"chooseDropboxFile()\">\r\n <mat-icon>cloud_queue</mat-icon>\r\n <span>Carica da Dropbox</span>\r\n </button>\r\n </mat-menu>\r\n }\r\n\r\n @else if (allowedTypes.includes(2)) {\r\n <a [class.disabled-link]=\"isDisabled\" class=\"secondary-action-link\"\r\n (click)=\"$event.stopPropagation(); switchToAddingLinkMode()\">\r\n aggiungi un link\r\n </a>\r\n }\r\n\r\n @else if (allowedTypes.includes(3)) {\r\n <a [class.disabled-link]=\"isDisabled\" class=\"secondary-action-link\"\r\n (click)=\"$event.stopPropagation(); chooseDropboxFile()\">\r\n carica da Dropbox\r\n </a>\r\n }\r\n }\r\n </div>\r\n</div>\r\n}@else {\r\n<div class=\"compact-uploader\" [class.dragover]=\"dragOver\" (dragover)=\"dragOver = true; $event.preventDefault()\"\r\n (dragleave)=\"dragOver = false\" (drop)=\"dragOver = false; fileDropped($event)\">\r\n <div class=\"compact-icon\"><mat-icon>folder_open</mat-icon></div>\r\n <div class=\"compact-text\" (click)=\"onSelectFile($event, fileInput)\">\r\n <div class=\"compact-title\">Trascina i file o seleziona dal computer</div>\r\n <div class=\"compact-subtitle\">{{ supportedFormatsLabel }}</div>\r\n </div>\r\n <div class=\"compact-actions\">\r\n <button mat-stroked-button color=\"primary\" (click)=\"$event.stopPropagation(); fileInput.click()\">Sfoglia</button>\r\n @if (allowedTypes.includes(2) || allowedTypes.includes(3)) {\r\n <button mat-stroked-button [matMenuTriggerFor]=\"compactLinkMenu\" (click)=\"$event.stopPropagation()\">Link</button>\r\n <mat-menu #compactLinkMenu=\"matMenu\">\r\n @if (allowedTypes.includes(2)) { <button mat-menu-item (click)=\"switchToAddingLinkMode()\">...</button> }\r\n @if (allowedTypes.includes(3)) { <button mat-menu-item (click)=\"chooseDropboxFile()\">...</button> }\r\n </mat-menu>\r\n }\r\n </div>\r\n</div>\r\n}\r\n\r\n}\r\n}\r\n\r\n<!-- Azioni singolo elemento (come prima) -->\r\n<div class=\"text-center\">\r\n @if (attachmentsList && attachmentsList.length > 0 && attachmentsList[0]) {\r\n <button class=\"mb-2 me-2 eqp-attachments-download-btn\" (click)=\"viewAttachment(attachmentsList[0])\" type=\"button\"\r\n mat-raised-button color=\"primary\">\r\n @if (attachmentsList[0].AttachmentType == AttachmentType.FILE) {\r\n <mat-icon>download</mat-icon>\r\n } @else {\r\n <mat-icon>open_in_new</mat-icon>\r\n }\r\n {{ attachmentsList[0].AttachmentType == AttachmentType.FILE ? downloadLabel : openLinkLabel }}\r\n </button>\r\n\r\n @if (showPreview && (!attachmentsList[0].FileContentType || (!attachmentsList[0].FileContentType.startsWith('video')\r\n && !attachmentsList[0].FileContentType.startsWith('audio'))) && attachmentsList[0].IsImage == true) {\r\n <button class=\"mb-2 me-2 eqp-attachments-preview-btn\" (click)=\"openPreviewDialog(attachmentsList[0])\" type=\"button\"\r\n mat-raised-button color=\"primary\">\r\n <mat-icon>visibility</mat-icon> {{ previewLabel }}\r\n </button>\r\n }\r\n\r\n <button [disabled]=\"disableAction\" class=\"mb-2 eqp-attachments-delete-btn\"\r\n (click)=\"deleteAttachment(attachmentsList[0])\" type=\"button\" mat-raised-button [disabled]=\"isDisabled\">\r\n <mat-icon>delete</mat-icon> {{ deleteLabel }}\r\n </button>\r\n }\r\n</div>\r\n}\r\n\r\n<!-- Gestione multipla -->\r\n@if (multipleAttachment == true) {\r\n<input #fileInput id=\"file_attachment_multi\" name=\"file_attachment_multi\" type=\"file\" hidden\r\n (change)=\"onFileAdded($event)\" [accept]=\"acceptedFileTypes\" [multiple]=\"loadMultipleFiles\" />\r\n\r\n@if (layout === 'full') {\r\n<div class=\"dropbox\" [class.dragover]=\"dragOver\" (dragover)=\"dragOver = true; $event.preventDefault()\"\r\n (dragleave)=\"dragOver = false\" (drop)=\"dragOver = false; fileDropped($event)\"\r\n (click)=\"onSelectFile($event, fileInput)\" role=\"button\" tabindex=\"0\" (keydown.enter)=\"fileInput.click()\"\r\n (keydown.space)=\"fileInput.click()\">\r\n <div class=\"dropbox-icon\">\uD83D\uDCC1</div>\r\n <div class=\"dropbox-text\">{{ dropHereLabel }}</div>\r\n <div class=\"dropbox-subtext\">\r\n {{ supportedFormatsLabel }}\r\n </div>\r\n <button class=\"browse-btn\" type=\"button\" (click)=\"$event.stopPropagation(); fileInput.click()\">\r\n {{ browseFilesLabel }}\r\n </button>\r\n @if (allowedTypes.includes(2)) {\r\n <div class=\"secondary-action-link\">\r\n o <a (click)=\"$event.stopPropagation(); switchToAddingLinkMode()\">aggiungi un link</a>\r\n </div>\r\n }\r\n</div>\r\n}@else {\r\n<div class=\"compact-uploader\" [class.dragover]=\"dragOver\" (dragover)=\"dragOver = true; $event.preventDefault()\"\r\n (dragleave)=\"dragOver = false\" (drop)=\"dragOver = false; fileDropped($event)\">\r\n <div class=\"compact-icon\"><mat-icon>folder_open</mat-icon></div>\r\n <div class=\"compact-text\" (click)=\"onSelectFile($event, fileInput)\">\r\n <div class=\"compact-title\">Trascina i file o seleziona dal computer</div>\r\n <div class=\"compact-subtitle\">{{ supportedFormatsLabel }}</div>\r\n </div>\r\n <div class=\"compact-actions\">\r\n <button mat-stroked-button color=\"primary\" (click)=\"$event.stopPropagation(); fileInput.click()\">Sfoglia</button>\r\n @if (allowedTypes.includes(2) || allowedTypes.includes(3)) {\r\n <button mat-stroked-button [matMenuTriggerFor]=\"compactLinkMenu\" (click)=\"$event.stopPropagation()\">Link</button>\r\n <mat-menu #compactLinkMenu=\"matMenu\">\r\n @if (allowedTypes.includes(2)) { <button mat-menu-item (click)=\"switchToAddingLinkMode()\">...</button> }\r\n @if (allowedTypes.includes(3)) { <button mat-menu-item (click)=\"chooseDropboxFile()\">...</button> }\r\n </mat-menu>\r\n }\r\n </div>\r\n</div>\r\n}\r\n}\r\n\r\n@if (attachmentsList?.length > 0 && showSummary) {\r\n<div class=\"upload-stats\">\r\n\r\n <div class=\"stats-info\">\r\n <div class=\"stat-item\">\r\n <div class=\"stat-value\">{{ attachmentsList?.length }}</div>\r\n <div class=\"stat-label\">{{ filesLabel }}</div>\r\n </div>\r\n <div class=\"stat-item\">\r\n <div class=\"stat-value\">{{ totalSizeFormatted }}</div>\r\n <div class=\"stat-label\">{{ totalSizeLabel }}</div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"progress-bar\">\r\n <div class=\"progress-fill\" [style.width.%]=\"progressPercent\"></div>\r\n </div>\r\n\r\n</div>\r\n} @else if(attachmentsList?.length == 0){\r\n<div class=\"empty-state\">{{ emptyStateLabel }}</div>\r\n}\r\n\r\n<!-- Griglia anteprime card (vale per singolo e multiplo) -->\r\n@if (viewMode === 'card') {\r\n\r\n<div class=\"file-previews\" [ngStyle]=\"getPreviewsContainerStyle()\">\r\n @for (att of attachmentsList; track att.ID) {\r\n <div [ngClass]=\"getCardClass(att)\">\r\n <div class=\"preview-img-container\" (click)=\"!att.isUploading && handlePrimaryAction(att)\">\r\n\r\n @if (att.IsImage) {\r\n <img class=\"preview-img\"\r\n [src]=\"'data:' + att.FileContentType + ';base64,' + (att.FileThumbnailBase64 || att.FileDataBase64)\"\r\n [alt]=\"att.FileName\" /> } @else {\r\n <div class=\"file-icon\"><i [ngClass]=\"getAttachmentIcon(att)\"></i></div>\r\n }\r\n\r\n <div class=\"preview-action-overlay\">\r\n @if (att.IsImage && canBePreviewed(att)) {\r\n } @else if (att.FileContentType === 'application/pdf' && canBePreviewed(att)) {\r\n <mat-icon>open_in_new</mat-icon>\r\n } @else if (att.AttachmentType === AttachmentType.FILE) {\r\n <mat-icon>download</mat-icon>\r\n } @else {\r\n <mat-icon>open_in_new</mat-icon>\r\n }\r\n </div>\r\n\r\n </div>\r\n\r\n <div class=\"file-info\">\r\n <div class=\"file-name\" [title]=\"att.FileName\">{{ att.FileName }}</div>\r\n </div>\r\n\r\n @if(!disableAction){\r\n <button mat-icon-button class=\"remove-btn\" type=\"button\" aria-label=\"Rimuovi allegato\"\r\n (click)=\"deleteAttachment(att)\" [disabled]=\"att.isUploading\">\r\n <mat-icon>delete</mat-icon>\r\n </button>\r\n }\r\n\r\n @if (att.isUploading) {\r\n <div class=\"upload-spinner\"></div>\r\n }\r\n </div>\r\n }\r\n</div>\r\n\r\n} @else if(viewMode === 'table' && attachmentsList?.length > 0) {\r\n\r\n\r\n<div class=\"table-view-container\">\r\n\r\n <div class=\"table-header\">\r\n @for (col of _tableColumns; track col.key) {\r\n <div class=\"table-cell\" [style.flex]=\"col.styles?.flex\" [ngClass]=\"col.class\">\r\n {{ col.display }}\r\n </div>\r\n }\r\n </div>\r\n\r\n @for (att of attachmentsList; track att.ID) {\r\n <div class=\"table-row\">\r\n\r\n @for (col of _tableColumns; track col.key) {\r\n <div class=\"table-cell\" [style.flex]=\"col.styles?.flex\" [ngClass]=\"col.class\">\r\n\r\n @switch (col.type) {\r\n\r\n @case ('template') {\r\n <div class=\"template-wrapper\">\r\n <ng-container [ngTemplateOutlet]=\"col.externalTemplate\"\r\n [ngTemplateOutletContext]=\"{ $implicit: att }\"></ng-container>\r\n </div>\r\n }\r\n @case ('date') {\r\n <span class=\"metadata-value\">{{ att[col.key] | date:'dd/MM/yyyy' }}</span>\r\n }\r\n @default {\r\n <span class=\"metadata-value\">{{ att[col.key] }}</span>\r\n }\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n</div>\r\n}\r\n\r\n<!-- Notifica toast -->\r\n<div class=\"upload-notification\" [class.show]=\"toast?.visible\" [class.success]=\"toast?.type === 'success'\"\r\n [class.error]=\"toast?.type === 'error'\">\r\n <span>{{ toast?.text }}</span>\r\n <div class=\"notification-progress\"></div>\r\n</div>\r\n\r\n<ng-template #defaultFileTemplate let-att>\r\n <i class=\"file-icon-small\" [ngClass]=\"getAttachmentIcon(att)\"></i>\r\n <div class=\"file-info-text\">\r\n <span class=\"file-name-table\">{{ att.FileName }}</span>\r\n <span class=\"file-type-table\">{{ att.FileExtension || 'Link' }}</span>\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #defaultActionsTemplate let-att>\r\n <button mat-icon-button (click)=\"viewAttachment(att)\" matTooltip=\"Visualizza/Scarica\">\r\n <mat-icon>{{ att.AttachmentType === attachmentType.FILE ? 'download' : 'open_in_new' }}</mat-icon>\r\n </button>\r\n\r\n <button mat-icon-button [matMenuTriggerFor]=\"actionsMenu\" matTooltip=\"Altre opzioni\">\r\n <mat-icon>more_vert</mat-icon>\r\n </button>\r\n\r\n <mat-menu #actionsMenu=\"matMenu\">\r\n\r\n @for (action of _sortedMenuActions; track action.name) {\r\n <button mat-menu-item (click)=\"action.fn(att)\" [disabled]=\"action.disabled ? action.disabled(att) : false\">\r\n\r\n <mat-icon [color]=\"action.icon === 'delete' ? 'warn' : undefined\">\r\n {{ action.icon }}\r\n </mat-icon>\r\n\r\n <span>{{ action.name }}</span>\r\n </button>\r\n }\r\n </mat-menu>\r\n</ng-template>\r\n\r\n\r\n<ng-template #dialogCropImage>\r\n <div style=\"overflow-x: hidden\" [ngClass]=\"cropDialogClass\">\r\n @if (showCropImage == true) {\r\n <ng-container [ngTemplateOutlet]=\"croppieTemplate\" [ngTemplateOutletContext]=\"{ form: newAttachmentForm }\">\r\n </ng-container>\r\n }\r\n </div>\r\n</ng-template>\r\n\r\n\r\n<ng-template #inlinePreviewTemplate let-row=\"row\">\r\n @if (row.AttachmentType != AttachmentType.LINK && row.IsImage) {\r\n <div class=\"inline-preview-container\" (click)=\"openPreviewDialog(row)\">\r\n <img [src]=\"'data:image/png;base64,' + (row.FileThumbnailBase64 ? row.FileThumbnailBase64 : row.FileDataBase64)\" />\r\n </div>\r\n } @else if (row.AttachmentType != AttachmentType.LINK && !row.IsImage) {\r\n <div class=\"inline-preview-container\" (click)=\"openPreviewDialog(row)\">\r\n <i [ngClass]=\"getAttachmentIcon(row)\"></i>\r\n </div>\r\n }\r\n</ng-template>\r\n\r\n\r\n<ng-template #dialogPreview>\r\n @if (selectedAttachment) {\r\n <h2 mat-dialog-title class=\"dialog-header\">\r\n <span>\r\n {{ previewLabel }} {{ selectedAttachment!.AttachmentType === attachmentType.FILE ? \"File\" : \"Link\" }}\r\n </span>\r\n <button mat-icon-button mat-dialog-close aria-label=\"Chiudi anteprima\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </h2>\r\n\r\n <mat-dialog-content class=\"dialog-content\">\r\n @if (selectedAttachment!.IsImage) {\r\n <img class=\"preview-image\"\r\n [src]=\"'data:image/png;base64,' + (selectedAttachment!.FileDataBase64 || selectedAttachment!.FileThumbnailBase64)\"\r\n alt=\"Anteprima allegato\" />\r\n } @else {\r\n <iframe class=\"preview-iframe\" [src]=\"selectedAttachment!.TrustedUrl\" [title]=\"selectedAttachment!.FileName\">\r\n </iframe>\r\n }\r\n </mat-dialog-content>\r\n\r\n <mat-dialog-actions [align]=\"'center'\">\r\n @if (selectedAttachment!.AttachmentType !== AttachmentType.LINK) {\r\n <button mat-raised-button color=\"primary\" (click)=\"viewAttachment(selectedAttachment!)\">\r\n <mat-icon>download</mat-icon>\r\n <span>{{ downloadLabel }}</span>\r\n </button>\r\n }\r\n </mat-dialog-actions>\r\n }\r\n</ng-template>\r\n\r\n\r\n<!-- TEMPLATE PER IL PULSANTE DI AGGIUNTA NUOVO ALLEGATO -->\r\n<ng-template #addAttachmentButton>\r\n <input #fileInput style=\"display: none\" id=\"file_attachment\" name=\"file_attachment\" type=\"file\"\r\n (change)=\"onFileAdded($event)\" [accept]=\"acceptedFileTypes\" [multiple]=\"loadMultipleFiles\" />\r\n\r\n @if (allowedTypes && allowedTypes.length == 1 && (multipleAttachment == true || !attachmentsList ||\r\n attachmentsList.length == 0 || (attachmentsList.length > 0 && !attachmentsList[0]))) {\r\n <button class=\"btn btn-primary mb-4 me-5 eqp-attachments-add-btn\" mat-raised-button color=\"primary\" type=\"button\"\r\n (click)=\"addFile(allowedTypes[0], fileInput)\" [disabled]=\"isDisabled\">\r\n @if (allowedTypes[0] == 1) { <mat-icon>cloud_upload</mat-icon> }\r\n @if (allowedTypes[0] == 2) { <i class=\"fas fa-link\"></i> }\r\n @if (allowedTypes[0] == 3) { <i class=\"fa-brands fa-dropbox\"></i> }\r\n <span style=\"margin-left: 10px\">\r\n {{ allowedTypes[0] == 1 ? (addButtonLabel + \" file\") : allowedTypes[0] == 2 ? (addButtonLabel + \" link\") :\r\n uploadWithDropboxLabel }}\r\n </span>\r\n </button>\r\n }\r\n\r\n @if (!separatedUploadButtons && allowedTypes && allowedTypes.length > 1 && (multipleAttachment == true ||\r\n !attachmentsList || attachmentsList.length == 0 || (attachmentsList.length > 0 && !attachmentsList[0]))) {\r\n <button class=\"btn btn-primary mb-4 me-5 eqp-attachments-add-btn\" mat-raised-button color=\"primary\" type=\"button\"\r\n [matMenuTriggerFor]=\"attachmentTypeMenu\" [disabled]=\"isDisabled\">\r\n @if (multipleAttachment != true) { <mat-icon>cloud_upload</mat-icon> } @else { <mat-icon>add</mat-icon> }\r\n <span style=\"margin-left: 0px\">{{ addButtonLabel }}</span>\r\n </button>\r\n\r\n <mat-menu #attachmentTypeMenu=\"matMenu\">\r\n <input #imageInput style=\"display: none\" id=\"file_attachment\" name=\"file_attachment\" type=\"file\"\r\n (change)=\"onFileAdded($event)\" [accept]=\"acceptedFileTypes\" [multiple]=\"loadMultipleFiles\" />\r\n @if (allowedTypes.includes(1)) {\r\n <button mat-menu-item (click)=\"imageInput.click()\" class=\"eqp-attachments-file-btn\">\r\n <i class=\"fas fa-file\"></i>\r\n <span style=\"margin-left: 10px\">File</span>\r\n </button>\r\n }\r\n @if (allowedTypes.includes(2)) {\r\n <button mat-menu-item (click)=\"switchToAddingLinkMode()\" class=\"eqp-attachments-link-btn\">\r\n <i class=\"fas fa-link\"></i>\r\n <span style=\"margin-left: 10px\">Link</span>\r\n </button>\r\n }\r\n @if (allowedTypes.includes(3)) {\r\n <button mat-menu-item (click)=\"chooseDropboxFile()\" class=\"eqp-attachments-link-btn\">\r\n <i class=\"fa-brands fa-dropbox\"></i>\r\n <span style=\"margin-left: 10px\">Dropbox</span>\r\n </button>\r\n }\r\n </mat-menu>\r\n }\r\n\r\n @if (separatedUploadButtons && allowedTypes && allowedTypes.length > 1 && (multipleAttachment == true ||\r\n !attachmentsList || attachmentsList.length == 0 || (attachmentsList.length > 0 && !attachmentsList[0]))) {\r\n <div class=\"btn-group\">\r\n @if (allowedTypes.includes(1)) {\r\n <button (click)=\"imageInput.click()\" class=\"btn btn-secondary eqp-attachments-add-btn\" mat-raised-button\r\n color=\"secondary\" type=\"button\">\r\n <i class=\"fa-solid fa-cloud-upload\"></i>\r\n <span style=\"margin-left: 10px\">File</span>\r\n </button>\r\n }\r\n <input #imageInput style=\"display: none\" id=\"file_attachment\" name=\"file_attachment\" type=\"file\"\r\n (change)=\"onFileAdded($event)\" [accept]=\"acceptedFileTypes\" [multiple]=\"loadMultipleFiles\" />\r\n @if (allowedTypes.includes(2)) {\r\n <button (click)=\"switchToAddingLinkMode()\" class=\"btn btn-secondary eqp-attachments-add-btn\" mat-raised-button\r\n color=\"secondary\" type=\"button\">\r\n <i class=\"fas fa-link\"></i>\r\n <span style=\"margin-left: 10px\">Link</span>\r\n </button>\r\n }\r\n @if (allowedTypes.includes(3)) {\r\n <button (click)=\"chooseDropboxFile()\" class=\"btn btn-secondary eqp-attachments-add-btn\" mat-raised-button\r\n color=\"secondary\" type=\"button\">\r\n <i class=\"fa-brands fa-dropbox\"></i>\r\n <span style=\"margin-left: 10px\">Dropbox</span>\r\n </button>\r\n }\r\n </div>\r\n }\r\n</ng-template>\r\n\r\n\r\n<ng-template #croppieTemplate>\r\n <!-- Header compatto -->\r\n <h2 class=\"dialog-header m-3\">\r\n <span>{{ cropLabel }}</span>\r\n <button mat-icon-button type=\"button\" aria-label=\"Chiudi\" (click)=\"abortFile()\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </h2>\r\n\r\n <!-- Contenuto scrollabile con griglia responsive -->\r\n <div class=\"dialog-content\">\r\n <!-- Toolbar controlli (large) -->\r\n <div class=\"controls-row crop-large\">\r\n @if (cropOptions.includes(1)) {\r\n <button [matTooltip]=\"rotateLeftLabel\" class=\"btn btn-primary mat-raised-button\" (click)=\"rotateLeft()\">\r\n <mat-icon>rotate_left</mat-icon>\r\n </button>\r\n <button [matTooltip]=\"rotateRightLabel\" class=\"btn btn-primary mat-raised-button\" (click)=\"rotateRight()\">\r\n <mat-icon>rotate_right</mat-icon>\r\n </button>\r\n }\r\n @if (cropOptions.includes(2)) {\r\n <button [matTooltip]=\"flipHorinzontalLabel\" class=\"btn btn-primary mat-raised-button\" (click)=\"flipHorizontal()\">\r\n <mat-icon>flip_horizontal</mat-icon>\r\n </button>\r\n <button [matTooltip]=\"flipVerticalLabel\" class=\"btn btn-primary mat-raised-button\" (click)=\"flipVertical()\">\r\n <mat-icon>flip_vertical</mat-icon>\r\n </button>\r\n }\r\n <button [matTooltip]=\"'Reset'\" class=\"btn btn-primary mat-raised-button\" (click)=\"restoreOriginalDimensions()\">\r\n <mat-icon>replay</mat-icon>\r\n </button>\r\n </div>\r\n\r\n <!-- Toolbar controlli (small) -->\r\n <div class=\"controls-row crop-small\">\r\n @if (cropOptions.includes(1)) {\r\n <mat-icon class=\"control-icon\" (click)=\"rotateLeft()\">rotate_left</mat-icon>\r\n <mat-icon class=\"control-icon\" (click)=\"rotateRight()\">rotate_right</mat-icon>\r\n }\r\n @if (cropOptions.includes(2)) {\r\n <mat-icon class=\"control-icon\" (click)=\"flipHorizontal()\">flip_horizontal</mat-icon>\r\n <mat-icon class=\"control-icon\" (click)=\"flipVertical()\">flip_vertical</mat-icon>\r\n }\r\n <mat-icon class=\"control-icon\" (click)=\"restoreOriginalDimensions()\">replay</mat-icon>\r\n </div>\r\n\r\n <!-- Area crop con contenimento -->\r\n <div class=\"crop-area\">\r\n <div class=\"crop-container\">\r\n <image-cropper [imageFile]=\"selectedFile\" [maintainAspectRatio]=\"false\" [autoCrop]=\"false\"\r\n [containWithinAspectRatio]=\"false\" [aspectRatio]=\"4 / 3\" [cropperMinWidth]=\"128\" [onlyScaleDown]=\"true\"\r\n [roundCropper]=\"false\" [canvasRotation]=\"0\" [transform]=\"transform\" [alignImage]=\"'left'\" format=\"png\"\r\n (imageCropped)=\"imageCropped($event)\" [resizeToWidth]=\"customWidth\" [resizeToHeight]=\"customHeight\"\r\n [canvasRotation]=\"canvasRotation\" [output]=\"'base64'\">\r\n </image-cropper>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Azioni -->\r\n <div class=\"dialog-actions crop-large\">\r\n <button class=\"btn btn-primary mat-raised-button eqp-attachments-confirm-btn\" type=\"button\"\r\n (click)=\"confirmAddAttachment()\">\r\n {{ confirmLabel }}\r\n </button>\r\n <button class=\"btn mat-raised-button eqp-attachments-abort-btn\" type=\"button\" (click)=\"abortFile()\">\r\n {{ abortLabel }}\r\n </button>\r\n </div>\r\n\r\n <div class=\"dialog-actions crop-small\">\r\n <button class=\"icon-action\" type=\"button\" (click)=\"abortFile()\" aria-label=\"Abort\">\r\n <i class=\"fa fa-times\"></i>\r\n </button>\r\n <button class=\"icon-action\" type=\"button\" (click)=\"confirmAddAttachment()\" aria-label=\"Confirm\">\r\n <i class=\"fa fa-check\"></i>\r\n </button>\r\n </div>\r\n</ng-template>\r\n\r\n\r\n\r\n<!-- TEMPLATE PER FORM DI AGGIUNTA DI UN LINK -->\r\n<ng-template #addingLinkTemplate>\r\n <h2 mat-dialog-title>Aggiungi un link</h2>\r\n <mat-dialog-content class=\"add-link-dialog-content\">\r\n <form [formGroup]=\"newAttachmentForm\" class=\"add-link-form\">\r\n <mat-form-field appearance=\"outline\">\r\n <mat-label>URL *</mat-label>\r\n <input matInput formControlName=\"filePath\" placeholder=\"https://...\" required>\r\n @if (newAttachmentForm.get('filePath')?.hasError('pattern')) {\r\n <mat-error>L'URL non sembra valido.</mat-error>\r\n }\r\n </mat-form-field>\r\n <mat-form-field appearance=\"outline\">\r\n <mat-label>Nome (opzionale)</mat-label>\r\n <input matInput formControlName=\"fileName\" placeholder=\"Es. Documento Progetto\">\r\n </mat-form-field>\r\n </form>\r\n </mat-dialog-content>\r\n <mat-dialog-actions align=\"end\">\r\n <button mat-button mat-dialog-close>Annulla</button>\r\n <button mat-raised-button color=\"primary\" [mat-dialog-close]=\"newAttachmentForm.value\"\r\n [disabled]=\"newAttachmentForm.invalid\">\r\n Aggiungi\r\n </button>\r\n </mat-dialog-actions>\r\n</ng-template>", styles: ["@charset \"UTF-8\";:host{--primary-color: #6a5af9;--primary-color-dark: #5441f8;--success-color: #1ce593;--error-color: #ff5b5b;--background-light: #f7f9fc;--background-card: rgba(255, 255, 255, .7);--text-color: #1e293b;--text-color-light: #64748b;--border-color: rgba(203, 213, 225, .5);--shadow-color: rgba(99, 102, 241, .2);--border-radius: 16px;--transition-speed: .3s}@keyframes fadeIn{0%{opacity:0;transform:translateY(15px)}to{opacity:1;transform:translateY(0)}}@keyframes pulse{0%{box-shadow:0 0 0 0 var(--primary-color)}70%{box-shadow:0 0 0 10px #6a5af900}to{box-shadow:0 0 #6a5af900}}.container{width:100%;max-width:700px;margin:2rem auto;font-family:Inter,sans-serif}.header{display:flex;justify-content:space-between;align-items:center;margin-bottom:2rem;padding-bottom:1.5rem;border-bottom:1px solid var(--border-color)}.header h1{color:var(--text-color);font-size:1.8rem;font-weight:700;margin:0}.dropbox{width:100%;border:2px dashed var(--border-color);border-radius:var(--border-radius);padding:2.5rem;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;cursor:pointer;background-color:var(--background-card);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);transition:all var(--transition-speed) ease}.dropbox:hover{transform:translateY(-5px);box-shadow:0 10px 25px var(--shadow-color);border-color:var(--primary-color)}.dropbox.dragover{border-style:solid;border-color:var(--primary-color);transform:scale(1.02);box-shadow:0 0 25px var(--shadow-color);animation:pulse 1.5s infinite}.dropbox .dropbox-icon{font-size:3.5rem;color:var(--primary-color)}.dropbox .dropbox-text{font-size:1.1rem;font-weight:600;color:var(--text-color);margin-top:1rem}.dropbox .dropbox-subtext{color:var(--text-color-light);margin-top:.5rem}.dropbox .browse-btn{margin-top:1.5rem;padding:.75rem 1.5rem;background-color:var(--primary-color);color:#fff;border:none;border-radius:10px;font-weight:600;transition:all var(--transition-speed) ease}.dropbox .browse-btn:hover{background-color:var(--primary-color-dark);transform:translateY(-2px)}.file-previews{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:1.5rem;margin-top:2rem}.file-previews{display:grid;gap:16px;padding:16px;grid-template-columns:repeat(auto-fill,minmax(var(--card-min-width, 200px),1fr))}.file-preview{background-color:var(--background-card);border-radius:var(--border-radius);box-shadow:0 4px 15px #0000000d;border:1px solid var(--border-color);overflow:hidden;display:flex;flex-direction:column;position:relative;opacity:0;animation:fadeIn .5s ease-out forwards;transition:transform var(--transition-speed) ease,box-shadow var(--transition-speed) ease}.file-preview.uploading{cursor:wait}.file-preview.uploading .preview-img-container:after{content:\"\";position:absolute;inset:0;background:#ffffffb3;-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);pointer-events:none}.file-preview .preview-action-overlay{position:absolute;inset:0;display:flex;justify-content:center;align-items:center;background-color:#0006;color:#fff;opacity:1;transition:opacity .3s ease}.file-preview .preview-action-overlay .mat-icon{font-size:36px;width:36px;height:36px}.file-preview .remove-btn{position:absolute;top:8px;right:8px;z-index:2;background-color:#0000004d;color:#fff;width:32px;height:32px;display:flex;align-items:center;justify-content:center;padding:0;transition:all var(--transition-speed) ease}.file-preview .remove-btn:hover{background-color:#ff5b5b}.file-preview .remove-btn .mat-icon{font-size:18px}@media (hover: hover){.file-preview .remove-btn .file-preview .preview-action-overlay{opacity:0}.file-preview .remove-btn .file-preview:hover .preview-action-overlay,.file-preview .remove-btn .file-preview:hover .remove-btn{opacity:1}.file-preview .remove-btn .file-preview .remove-btn{opacity:0}}@media (hover: hover) and (min-width: 769px){.file-preview .remove-btn{opacity:0;transform:scale(.8)}.file-preview:hover{transform:translateY(-5px);box-shadow:0 10px 25px var(--shadow-color)}.file-preview:hover .remove-btn{opacity:1;transform:scale(1)}}.file-preview .preview-img-container{aspect-ratio:4/3;display:flex;align-items:center;justify-content:center;position:relative;cursor:pointer;background-color:#f0f2f5}.file-preview .preview-img{width:100%;height:100%;object-fit:cover}.file-preview .file-icon{font-size:4rem;color:var(--primary-color)}.file-preview .file-info{padding:12px;border-top:1px solid var(--border-color);flex-grow:1;display:flex;align-items:center}.file-preview .file-name{font-weight:600;font-size:.9rem;color:var(--text-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.file-preview .upload-spinner{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:40px;height:40px;border:4px solid rgba(0,0,0,.1);border-left-color:var(--primary-color);border-radius:50%;animation:spin 1s linear infinite;pointer-events:none}.file-preview.card-small{width:140px}.file-preview.card-small .preview-img-container{height:80px}.file-preview.card-small .file-info{padding:8px}.file-preview.card-small .file-name{font-size:.8rem}.file-preview.card-small .file-icon{font-size:3rem}.file-preview.card-small .remove-btn{width:28px;height:28px}.file-preview.card-small .remove-btn .mat-icon{font-size:16px}.file-preview.card-large{width:280px}.file-preview.card-large .preview-img-container{height:180px}.file-preview.card-large .file-info{padding:16px}.file-preview.card-large .file-name{font-size:1rem}.file-preview.card-large .file-icon{font-size:5rem}.file-preview.card-large .remove-btn{width:36px;height:36px}.file-preview.card-large .remove-btn .mat-icon{font-size:20px}.file-preview[style*=--eqp-card-width]{width:var(--eqp-card-width)}.file-preview[style*=--eqp-card-width] .preview-img-container{height:calc(var(--eqp-card-width) * .65)}@keyframes spin{to{transform:translate(-50%,-50%) rotate(360deg)}}.upload-stats{background-color:var(--background-card);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid var(--border-color);border-radius:var(--border-radius);padding:1.5rem;margin-top:2rem;animation:fadeIn .5s ease-out}.upload-stats .stats-info{display:flex;justify-content:space-around}.upload-stats .progress-bar{height:10px;background-color:var(--border-color);border-radius:5px;overflow:hidden;margin-top:1rem}.upload-stats .progress-fill{height:100%;background:linear-gradient(90deg,var(--primary-color),var(--success-color));transition:width var(--transition-speed) ease-out;border-radius:5px}.upload-notification{position:fixed;bottom:20px;left:50%;transform:translate(-50%,100px);padding:1rem 1.5rem;border-radius:var(--border-radius);color:#fff;font-weight:600;box-shadow:0 10px 30px #0003;transition:transform var(--transition-speed) cubic-bezier(.175,.885,.32,1.275)}.upload-notification.show{transform:translate(-50%)}.upload-notification.success{background-color:var(--success-color)}.upload-notification.error{background-color:var(--error-color)}.dialog-header{display:flex;justify-content:space-between;align-items:center;width:100%}.dialog-content{display:flex;justify-content:center;align-items:center;padding:16px 24px;overflow:auto}.preview-image{max-width:100%;max-height:70vh;height:auto;object-fit:contain}.preview-iframe{width:80vw;height:75vh;border:none;max-width:1200px}.dialog-header{display:flex;align-items:center;justify-content:space-between}.dialog-content{display:flex;flex-direction:column;gap:12px;max-height:60vh;overflow:auto;padding:0 16px 8px}.controls-row{display:flex;align-items:center;justify-content:center;gap:8px}.control-icon{font-size:28px;cursor:pointer}.crop-area{display:flex;justify-content:center}.crop-container{width:100%;max-width:720px;aspect-ratio:4/3;border-radius:8px;background:#f9f9f9;overflow:hidden}image-cropper,image-cropper canvas,image-cropper img{max-width:100%!important;max-height:100%!important}.dialog-actions{display:flex;justify-content:center;gap:12px;padding:12px 16px}.crop-large{display:flex}.crop-small{display:none}@media (max-width: 600px){.dialog-content{max-height:55vh}.crop-container{max-width:100%;aspect-ratio:auto}.crop-large{display:none}.crop-small{display:flex;justify-content:center;gap:14px}.control-icon{font-size:24px}}.stats-header{display:flex;justify-content:space-between;align-items:center;width:100%}.table-view-container{margin-top:1.5rem;background-color:var(--background-card);border-radius:var(--border-radius);border:1px solid var(--border-color);overflow:hidden;animation:fadeIn .5s ease-out}.table-header,.table-row{display:flex;align-items:center;padding:0 1rem;transition:background-color var(--transition-speed) ease}.table-header{background-color:#f8f9fa;font-weight:600;color:var(--text-color-light);font-size:.8rem;text-transform:uppercase;border-bottom:2px solid var(--border-color)}.table-row{border-bottom:1px solid var(--border-color)}.table-row:last-child{border-bottom:none}.table-row:hover{background-color:#00000005}.table-cell{padding:1rem .5rem;display:flex;align-items:center;gap:1rem}.name-col{flex:4}.size-col,.date-col{flex:2}.actions-col{flex:1;justify-content:flex-end;min-width:150px}.file-icon-small{font-size:1.5rem;color:var(--primary-color)}.file-info-text{display:flex;flex-direction:column}.file-name-table{font-weight:600;color:var(--text-color)}.file-type-table{font-size:.8rem;color:var(--text-color-light)}@media (max-width: 768px){.date-col{display:none}.name-col{flex:3}.size-col{flex:2}.actions-col{flex:2;min-width:120px}}.secondary-action-link{color:var(--primary-color);text-decoration:none;border-bottom:1px solid var(--primary-color);cursor:pointer;font-weight:500}.secondary-action-link:hover{color:var(--primary-color-dark);border-bottom-color:var(--primary-color-dark)}h2[mat-dialog-title]+mat-dialog-content.add-link-dialog-content{padding-top:20px}mat-dialog-content.add-link-dialog-content{padding-left:24px;padding-right:24px;padding-bottom:20px}.add-link-form{display:flex;flex-direction:column;gap:16px}:host ::ng-deep .eqp-attachments-dialog .mat-mdc-dialog-container{--mdc-dialog-subhead-size: 1.25rem;--mdc-dialog-subhead-line-height: 1.5;--mdc-dialog-subhead-weight: 600;--mdc-dialog-supporting-text-size: 1rem}.empty-state{display:flex;flex-direction:column;justify-content:center;align-items:center;padding:1.5rem 1rem;margin-top:1.5rem;border:2px dashed var(--border-color);border-radius:var(--border-radius);background-color:#f8f9fa;text-align:center;color:var(--text-color-light);animation:fadeIn .5s ease-out}.empty-state:before{content:\"\\1f4ed\";font-size:2.5rem;margin-bottom:1rem;opacity:.7}.secondary-action-link.disabled-link{color:#adb5bd;cursor:not-allowed;border-bottom-color:transparent}.secondary-action-link.disabled-link:hover{color:#adb5bd;border-bottom-color:transparent}.table-cell{padding:1rem .5rem;display:flex;align-items:center;gap:1rem;min-width:0;overflow:hidden}.table-cell.col-actions{flex:0 0 150px;justify-content:flex-end;overflow:visible}.compact-uploader{display:flex;align-items:center;gap:16px;padding:16px;border:2px dashed var(--border-color);border-radius:var(--border-radius);background-color:#fcfdff;transition:all .3s ease}.compact-uploader .compact-text{flex:1 1 auto;min-width:0;cursor:pointer}.compact-uploader .compact-text:hover .compact-title{color:var(--primary-color)}.compact-uploader .compact-title{font-weight:600;color:var(--text-color);transition:color .3s ease}.compact-uploader .compact-subtitle{font-size:.8rem;color:var(--text-color-light);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.compact-uploader .compact-icon mat-icon{font-size:36px;width:36px;height:36px;color:var(--primary-color)}.compact-uploader .compact-actions{display:flex;gap:8px;flex-shrink:0}.compact-uploader.dragover{background-color:#f4f8ff;border-color:var(--primary-color);box-shadow:0 0 10px var(--shadow-color)}\n"] }]
|
|
1539
|
+
args: [{ selector: "eqp-attachments", template: "<!-- Se richiesta la gestione singola mostra il pulsante di caricamento di un singolo file -->\r\n<div class=\"header\">\r\n @if(showUploadTitle == true){\r\n <h4>{{ uploadTitle }}</h4>\r\n }\r\n @if(chooseView == true){\r\n <mat-button-toggle-group [value]=\"viewMode\" (change)=\"setViewMode($event.value)\"\r\n aria-label=\"Modalit\u00E0 di visualizzazione\">\r\n <mat-button-toggle value=\"card\"><mat-icon>grid_view</mat-icon></mat-button-toggle>\r\n <mat-button-toggle value=\"table\"><mat-icon>view_list</mat-icon></mat-button-toggle>\r\n </mat-button-toggle-group>\r\n }\r\n</div>\r\n\r\n<!-- Gestione singolo -->\r\n@if (multipleAttachment != true) {\r\n@if (!singleAttachmentDragAndDrop) {\r\n@if (!addingLinkMode) {\r\n<div class=\"text-center\">\r\n <ng-container [ngTemplateOutlet]=\"addAttachmentButton\"></ng-container>\r\n</div>\r\n} @else {\r\n<div class=\"text-center\">\r\n <ng-container [ngTemplateOutlet]=\"addingLinkTemplate\"></ng-container>\r\n</div>\r\n}\r\n} @else {\r\n<input #fileInput id=\"file_attachment\" name=\"file_attachment\" type=\"file\" hidden (change)=\"onFileAdded($event)\"\r\n [accept]=\"acceptedFileTypes\" [multiple]=\"loadMultipleFiles\" />\r\n\r\n@if (allowedTypes && allowedTypes.includes(1) && (!attachmentsList || attachmentsList.length == 0 ||\r\n(attachmentsList.length > 0 && !attachmentsList[0]))) {\r\n\r\n@if (showDropArea == true) {\r\n<!-- FULL -->\r\n@if (layout === 'full') {\r\n\r\n<div class=\"dropbox\" [class.dragover]=\"dragOver\" (dragover)=\"dragOver = true; $event.preventDefault()\"\r\n (dragleave)=\"dragOver = false\" (drop)=\"dragOver = false; fileDropped($event)\"\r\n (click)=\"onSelectFile($event, fileInput)\" role=\"button\" tabindex=\"0\" (keydown.enter)=\"fileInput.click()\"\r\n (keydown.space)=\"fileInput.click()\">\r\n <div class=\"dropbox-icon\">\uD83D\uDCC1</div>\r\n <div class=\"dropbox-text\">{{ dropHereLabel }}</div>\r\n <div class=\"dropbox-subtext\">\r\n {{ supportedFormatsLabel }}\r\n </div>\r\n <button class=\"browse-btn\" type=\"button\" (click)=\"$event.stopPropagation(); fileInput.click()\">\r\n {{ browseFilesLabel }}\r\n </button>\r\n <div class=\"secondary-action\">\r\n @if (allowedTypes.includes(2) || allowedTypes.includes(3)) {\r\n @if (allowedTypes.includes(2) && allowedTypes.includes(3)) {\r\n <a class=\"secondary-action-link\" [matMenuTriggerFor]=\"isDisabled ? null : linkMenu\"\r\n [class.disabled-link]=\"isDisabled\" (click)=\"$event.stopPropagation()\">\r\n o aggiungi da web\r\n </a>\r\n <mat-menu #linkMenu=\"matMenu\">\r\n <button mat-menu-item (click)=\"switchToAddingLinkMode()\">\r\n <mat-icon>link</mat-icon>\r\n <span>Aggiungi da link</span>\r\n </button>\r\n <button mat-menu-item (click)=\"chooseDropboxFile()\">\r\n <mat-icon>cloud_queue</mat-icon>\r\n <span>Carica da Dropbox</span>\r\n </button>\r\n </mat-menu>\r\n }\r\n\r\n @else if (allowedTypes.includes(2)) {\r\n <a [class.disabled-link]=\"isDisabled\" class=\"secondary-action-link\"\r\n (click)=\"$event.stopPropagation(); switchToAddingLinkMode()\">\r\n aggiungi un link\r\n </a>\r\n }\r\n\r\n @else if (allowedTypes.includes(3)) {\r\n <a [class.disabled-link]=\"isDisabled\" class=\"secondary-action-link\"\r\n (click)=\"$event.stopPropagation(); chooseDropboxFile()\">\r\n carica da Dropbox\r\n </a>\r\n }\r\n }\r\n </div>\r\n</div>\r\n}@else {\r\n<div class=\"compact-uploader\" [class.dragover]=\"dragOver\" (dragover)=\"dragOver = true; $event.preventDefault()\"\r\n (dragleave)=\"dragOver = false\" (drop)=\"dragOver = false; fileDropped($event)\">\r\n <div class=\"compact-icon\"><mat-icon>folder_open</mat-icon></div>\r\n <div class=\"compact-text\" (click)=\"onSelectFile($event, fileInput)\">\r\n <div class=\"compact-title\">Trascina i file o seleziona dal computer</div>\r\n <div class=\"compact-subtitle\">{{ supportedFormatsLabel }}</div>\r\n </div>\r\n <div class=\"compact-actions\">\r\n <button mat-stroked-button color=\"primary\" [disabled]=\"isDisabled\"\r\n (click)=\"$event.stopPropagation(); fileInput.click()\">Sfoglia</button>\r\n @if (allowedTypes.includes(2) || allowedTypes.includes(3)) {\r\n <button mat-stroked-button [disabled]=\"isDisabled\" [matMenuTriggerFor]=\"compactLinkMenu\"\r\n (click)=\"$event.stopPropagation()\">Link</button>\r\n <mat-menu #compactLinkMenu=\"matMenu\">\r\n @if (allowedTypes.includes(2)) { <button mat-menu-item [disabled]=\"isDisabled\"\r\n (click)=\"switchToAddingLinkMode()\">{{ openLinkLabel }}</button> }\r\n @if (allowedTypes.includes(3)) { <button mat-menu-item [disabled]=\"isDisabled\" (click)=\"chooseDropboxFile()\">{{\r\n uploadWithDropboxLabel }}</button> }\r\n </mat-menu>\r\n }\r\n </div>\r\n</div>\r\n}\r\n}\r\n}\r\n}\r\n\r\n<!-- Azioni singolo elemento (come prima) -->\r\n<div class=\"text-center\">\r\n @if (attachmentsList && attachmentsList.length > 0 && attachmentsList[0]) {\r\n <button class=\"mb-2 me-2 eqp-attachments-download-btn\" (click)=\"viewAttachment(attachmentsList[0])\" type=\"button\"\r\n mat-raised-button color=\"primary\">\r\n @if (attachmentsList[0].AttachmentType == AttachmentType.FILE) {\r\n <mat-icon>download</mat-icon>\r\n } @else {\r\n <mat-icon>open_in_new</mat-icon>\r\n }\r\n {{ attachmentsList[0].AttachmentType == AttachmentType.FILE ? downloadLabel : openLinkLabel }}\r\n </button>\r\n\r\n @if (showPreview && (!attachmentsList[0].FileContentType || (!attachmentsList[0].FileContentType.startsWith('video')\r\n && !attachmentsList[0].FileContentType.startsWith('audio'))) && attachmentsList[0].IsImage == true) {\r\n <button class=\"mb-2 me-2 eqp-attachments-preview-btn\" (click)=\"openPreviewDialog(attachmentsList[0])\" type=\"button\"\r\n mat-raised-button color=\"primary\">\r\n <mat-icon>visibility</mat-icon> {{ previewLabel }}\r\n </button>\r\n }\r\n\r\n <button [disabled]=\"disableAction\" class=\"mb-2 eqp-attachments-delete-btn\"\r\n (click)=\"deleteAttachment(attachmentsList[0])\" type=\"button\" mat-raised-button [disabled]=\"isDisabled\">\r\n <mat-icon>delete</mat-icon> {{ deleteLabel }}\r\n </button>\r\n }\r\n</div>\r\n}\r\n\r\n<!-- Gestione multipla -->\r\n@if (multipleAttachment == true && showDropArea == true) {\r\n<input #fileInput id=\"file_attachment_multi\" name=\"file_attachment_multi\" type=\"file\" hidden\r\n (change)=\"onFileAdded($event)\" [accept]=\"acceptedFileTypes\" [multiple]=\"loadMultipleFiles\" />\r\n\r\n@if (layout === 'full') {\r\n<div class=\"dropbox\" [class.dragover]=\"dragOver\" (dragover)=\"dragOver = true; $event.preventDefault()\"\r\n (dragleave)=\"dragOver = false\" (drop)=\"dragOver = false; fileDropped($event)\"\r\n (click)=\"onSelectFile($event, fileInput)\" role=\"button\" tabindex=\"0\" (keydown.enter)=\"fileInput.click()\"\r\n (keydown.space)=\"fileInput.click()\">\r\n <div class=\"dropbox-icon\">\uD83D\uDCC1</div>\r\n <div class=\"dropbox-text\">{{ dropHereLabel }}</div>\r\n <div class=\"dropbox-subtext\">\r\n {{ supportedFormatsLabel }}\r\n </div>\r\n <button class=\"browse-btn\" type=\"button\" (click)=\"$event.stopPropagation(); fileInput.click()\">\r\n {{ browseFilesLabel }}\r\n </button>\r\n @if (allowedTypes.includes(2)) {\r\n <div class=\"secondary-action-link\">\r\n o <a (click)=\"$event.stopPropagation(); switchToAddingLinkMode()\">aggiungi un link</a>\r\n </div>\r\n }\r\n</div>\r\n}@else{\r\n<div class=\"compact-uploader\" [class.dragover]=\"dragOver\" (dragover)=\"dragOver = true; $event.preventDefault()\"\r\n (dragleave)=\"dragOver = false\" (drop)=\"dragOver = false; fileDropped($event)\">\r\n <div class=\"compact-icon\"><mat-icon>folder_open</mat-icon></div>\r\n <div class=\"compact-text\" (click)=\"onSelectFile($event, fileInput)\">\r\n <div class=\"compact-title\">Trascina i file o seleziona dal computer</div>\r\n <div class=\"compact-subtitle\">{{ supportedFormatsLabel }}</div>\r\n </div>\r\n <div class=\"compact-actions\">\r\n <button mat-stroked-button [disabled]=\"isDisabled\" color=\"primary\"\r\n (click)=\"$event.stopPropagation(); fileInput.click()\">Sfoglia</button>\r\n @if (allowedTypes.includes(2) || allowedTypes.includes(3)) {\r\n <button mat-stroked-button [disabled]=\"isDisabled\" [matMenuTriggerFor]=\"compactLinkMenu\"\r\n (click)=\"$event.stopPropagation()\">Link</button>\r\n <mat-menu #compactLinkMenu=\"matMenu\">\r\n @if (allowedTypes.includes(2)) { <button mat-menu-item [disabled]=\"isDisabled\"\r\n (click)=\"switchToAddingLinkMode()\">{{ openLinkLabel }}</button> }\r\n @if (allowedTypes.includes(3)) { <button mat-menu-item [disabled]=\"isDisabled\" (click)=\"chooseDropboxFile()\">{{\r\n uploadWithDropboxLabel }}</button> }\r\n </mat-menu>\r\n }\r\n </div>\r\n</div>\r\n}\r\n}\r\n\r\n@if (attachmentsList?.length > 0 && showSummary) {\r\n<div class=\"upload-stats\">\r\n\r\n <div class=\"stats-info\">\r\n <div class=\"stat-item\">\r\n <div class=\"stat-value\">{{ attachmentsList?.length }}</div>\r\n <div class=\"stat-label\">{{ filesLabel }}</div>\r\n </div>\r\n <div class=\"stat-item\">\r\n <div class=\"stat-value\">{{ totalSizeFormatted }}</div>\r\n <div class=\"stat-label\">{{ totalSizeLabel }}</div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"progress-bar\">\r\n <div class=\"progress-fill\" [style.width.%]=\"progressPercent\"></div>\r\n </div>\r\n\r\n</div>\r\n} @else if(attachmentsList?.length == 0){\r\n<div class=\"empty-state\">{{ emptyStateLabel }}</div>\r\n}\r\n\r\n<!-- Griglia anteprime card (vale per singolo e multiplo) -->\r\n@if (viewMode === 'card') {\r\n\r\n<div class=\"file-previews\" [ngStyle]=\"getPreviewsContainerStyle()\">\r\n @for (att of attachmentsList; track att.ID) {\r\n <div [ngClass]=\"getCardClass(att)\">\r\n <div class=\"preview-img-container\" (click)=\"!att.isUploading && handlePrimaryAction(att)\">\r\n\r\n @if (att.IsImage || att.FileThumbnailBase64) {\r\n <img class=\"preview-img\" [src]=\"'data:image/jpeg;base64,' + (att.FileThumbnailBase64 || att.FileDataBase64)\"\r\n [alt]=\"att.FileName\" />\r\n } @else {\r\n <div class=\"file-icon\"><i [ngClass]=\"getAttachmentIcon(att)\"></i></div>\r\n }\r\n\r\n <div class=\"preview-action-overlay\">\r\n @if (att.FileContentType?.startsWith('video/')) {\r\n <mat-icon>play_arrow</mat-icon>\r\n }\r\n @else if (att.IsImage && canBePreviewed(att)) {\r\n <mat-icon>visibility</mat-icon>\r\n }\r\n @else if (att.FileContentType === 'application/pdf' && canBePreviewed(att)) {\r\n <mat-icon>open_in_new</mat-icon>\r\n }\r\n @else if (att.AttachmentType === AttachmentType.FILE) {\r\n <mat-icon>download</mat-icon>\r\n }\r\n @else {\r\n <mat-icon>open_in_new</mat-icon>\r\n }\r\n </div>\r\n\r\n </div>\r\n\r\n <div class=\"file-info\">\r\n <div class=\"file-name\" [title]=\"att.FileName\">{{ att.FileName }}</div>\r\n </div>\r\n\r\n @if(!disableAction){\r\n <button mat-icon-button class=\"remove-btn\" type=\"button\" aria-label=\"Rimuovi allegato\"\r\n (click)=\"deleteAttachment(att)\" [disabled]=\"att.isUploading\">\r\n <mat-icon>delete</mat-icon>\r\n </button>\r\n }\r\n\r\n @if (att.isUploading) {\r\n <div class=\"upload-spinner\"></div>\r\n }\r\n </div>\r\n }\r\n</div>\r\n\r\n} @else if(viewMode === 'table' && attachmentsList?.length > 0) {\r\n\r\n\r\n<div class=\"table-view-container\">\r\n\r\n <div class=\"table-header\">\r\n @for (col of _tableColumns; track col.key) {\r\n <div class=\"table-cell\" [style.flex]=\"col.styles?.flex\" [ngClass]=\"col.class\">\r\n {{ col.display }}\r\n </div>\r\n }\r\n </div>\r\n\r\n @for (att of attachmentsList; track att.ID) {\r\n <div class=\"table-row\">\r\n\r\n @for (col of _tableColumns; track col.key) {\r\n <div class=\"table-cell\" [style.flex]=\"col.styles?.flex\" [ngClass]=\"col.class\">\r\n\r\n @switch (col.type) {\r\n\r\n @case ('template') {\r\n <div class=\"template-wrapper\">\r\n <ng-container [ngTemplateOutlet]=\"col.externalTemplate\"\r\n [ngTemplateOutletContext]=\"{ $implicit: att }\"></ng-container>\r\n </div>\r\n }\r\n @case ('date') {\r\n <span class=\"metadata-value\">{{ att[col.key] | date:'dd/MM/yyyy' }}</span>\r\n }\r\n @default {\r\n <span class=\"metadata-value\">{{ att[col.key] }}</span>\r\n }\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n</div>\r\n}\r\n\r\n<!-- Notifica toast -->\r\n<div class=\"upload-notification\" [class.show]=\"toast?.visible\" [class.success]=\"toast?.type === 'success'\"\r\n [class.error]=\"toast?.type === 'error'\">\r\n <span>{{ toast?.text }}</span>\r\n <div class=\"notification-progress\"></div>\r\n</div>\r\n\r\n<ng-template #defaultFileTemplate let-att>\r\n <i class=\"file-icon-small\" [ngClass]=\"getAttachmentIcon(att)\"></i>\r\n <div class=\"file-info-text\">\r\n <span class=\"file-name-table\">{{ att.FileName }}</span>\r\n <span class=\"file-type-table\">{{ att.FileExtension || 'Link' }}</span>\r\n </div>\r\n</ng-template>\r\n\r\n<ng-template #defaultActionsTemplate let-att>\r\n <button mat-icon-button (click)=\"handlePrimaryAction(att)\"\r\n [matTooltip]=\"att.FileContentType?.startsWith('video/') ? 'Riproduci video' : 'Visualizza/Scarica'\">\r\n\r\n <mat-icon>\r\n @if (att.FileContentType?.startsWith('video/')) {\r\n play_arrow\r\n } @else {\r\n {{ att.AttachmentType === AttachmentType.FILE ? 'download' : 'open_in_new' }}\r\n }\r\n </mat-icon>\r\n </button>\r\n\r\n <button mat-icon-button [matMenuTriggerFor]=\"actionsMenu\" matTooltip=\"Altre opzioni\">\r\n <mat-icon>more_vert</mat-icon>\r\n </button>\r\n\r\n <mat-menu #actionsMenu=\"matMenu\">\r\n @for (action of _sortedMenuActions; track action.name) {\r\n @if (!isActionHidden(action, att)) {\r\n <button mat-menu-item (click)=\"action.fn(att)\" [disabled]=\"action.disabled ? action.disabled(att) : false\">\r\n <mat-icon [color]=\"action.icon === 'delete' ? 'warn' : undefined\">\r\n {{ action.icon }}\r\n </mat-icon>\r\n <span>{{ action.name }}</span>\r\n </button>\r\n }\r\n }\r\n </mat-menu>\r\n</ng-template>\r\n\r\n\r\n<ng-template #dialogCropImage>\r\n <div style=\"overflow-x: hidden\" [ngClass]=\"cropDialogClass\">\r\n <!-- @if (showCropImage == true) { -->\r\n <ng-container [ngTemplateOutlet]=\"croppieTemplate\" [ngTemplateOutletContext]=\"{ form: newAttachmentForm }\">\r\n </ng-container>\r\n <!-- } -->\r\n </div>\r\n</ng-template>\r\n\r\n\r\n<ng-template #inlinePreviewTemplate let-row=\"row\">\r\n @if (row.AttachmentType != AttachmentType.LINK && row.IsImage) {\r\n <div class=\"inline-preview-container\" (click)=\"openPreviewDialog(row)\">\r\n <img [src]=\"'data:image/png;base64,' + (row.FileThumbnailBase64 ? row.FileThumbnailBase64 : row.FileDataBase64)\" />\r\n </div>\r\n } @else if (row.AttachmentType != AttachmentType.LINK && !row.IsImage) {\r\n <div class=\"inline-preview-container\" (click)=\"openPreviewDialog(row)\">\r\n <i [ngClass]=\"getAttachmentIcon(row)\"></i>\r\n </div>\r\n }\r\n</ng-template>\r\n\r\n\r\n<ng-template #dialogPreview>\r\n <div class=\"modern-dialog-container\"> @if (selectedAttachment) {\r\n <div mat-dialog-title class=\"preview-header\">\r\n <div class=\"header-info\">\r\n <div class=\"title-group\">\r\n <span class=\"main-t\">{{ previewLabel }}</span>\r\n <span class=\"file-t\">{{ selectedAttachment.FileName }}</span>\r\n </div>\r\n </div>\r\n <button mat-icon-button mat-dialog-close class=\"close-btn\"><mat-icon>close</mat-icon></button>\r\n </div>\r\n\r\n <mat-dialog-content class=\"preview-content-area\">\r\n <div class=\"media-viewer-wrapper\">\r\n @if (selectedAttachment.IsImage) {\r\n <img class=\"main-preview-media\"\r\n [src]=\"'data:image/png;base64,' + (selectedAttachment.FileDataBase64 || selectedAttachment.FileThumbnailBase64)\"\r\n [alt]=\"selectedAttachment.FileName\" />\r\n }\r\n @else if (selectedAttachment.FileContentType?.startsWith('video/')) {\r\n <video controls autoplay playsinline class=\"main-preview-media video-player\">\r\n <source [src]=\"selectedAttachment.TrustedUrl\" [type]=\"selectedAttachment.FileContentType\">\r\n Il tuo browser non supporta il video.\r\n </video>\r\n }\r\n @else {\r\n <div class=\"iframe-container\">\r\n <iframe class=\"preview-iframe-modern\" [src]=\"selectedAttachment.TrustedUrl\"\r\n [title]=\"selectedAttachment.FileName\"></iframe>\r\n </div>\r\n }\r\n </div>\r\n </mat-dialog-content>\r\n\r\n <mat-dialog-actions align=\"end\" class=\"preview-actions\">\r\n <button mat-button mat-dialog-close class=\"btn-close\">Chiudi</button>\r\n @if (selectedAttachment.AttachmentType !== AttachmentType.LINK) {\r\n <button mat-raised-button color=\"primary\" (click)=\"viewAttachment(selectedAttachment)\" class=\"btn-download\">\r\n <mat-icon>download</mat-icon> <span>{{ downloadLabel }}</span>\r\n </button>\r\n }\r\n </mat-dialog-actions>\r\n }\r\n </div>\r\n</ng-template>\r\n\r\n\r\n<!-- TEMPLATE PER IL PULSANTE DI AGGIUNTA NUOVO ALLEGATO -->\r\n<ng-template #addAttachmentButton>\r\n <input #fileInput style=\"display: none\" id=\"file_attachment\" name=\"file_attachment\" type=\"file\"\r\n (change)=\"onFileAdded($event)\" [accept]=\"acceptedFileTypes\" [multiple]=\"loadMultipleFiles\" />\r\n\r\n @if (allowedTypes && allowedTypes.length == 1 && (multipleAttachment == true || !attachmentsList ||\r\n attachmentsList.length == 0 || (attachmentsList.length > 0 && !attachmentsList[0]))) {\r\n <button class=\"btn btn-primary mb-4 me-5 eqp-attachments-add-btn\" mat-raised-button color=\"primary\" type=\"button\"\r\n (click)=\"addFile(allowedTypes[0], fileInput)\" [disabled]=\"isDisabled\">\r\n @if (allowedTypes[0] == 1) { <mat-icon>cloud_upload</mat-icon> }\r\n @if (allowedTypes[0] == 2) { <i class=\"fas fa-link\"></i> }\r\n @if (allowedTypes[0] == 3) { <i class=\"fa-brands fa-dropbox\"></i> }\r\n <span style=\"margin-left: 10px\">\r\n {{ allowedTypes[0] == 1 ? (addButtonLabel + \" file\") : allowedTypes[0] == 2 ? (addButtonLabel + \" link\") :\r\n uploadWithDropboxLabel }}\r\n </span>\r\n </button>\r\n }\r\n\r\n @if (!separatedUploadButtons && allowedTypes && allowedTypes.length > 1 && (multipleAttachment == true ||\r\n !attachmentsList || attachmentsList.length == 0 || (attachmentsList.length > 0 && !attachmentsList[0]))) {\r\n <button class=\"btn btn-primary mb-4 me-5 eqp-attachments-add-btn\" mat-raised-button color=\"primary\" type=\"button\"\r\n [matMenuTriggerFor]=\"attachmentTypeMenu\" [disabled]=\"isDisabled\">\r\n @if (multipleAttachment != true) { <mat-icon>cloud_upload</mat-icon> } @else { <mat-icon>add</mat-icon> }\r\n <span style=\"margin-left: 0px\">{{ addButtonLabel }}</span>\r\n </button>\r\n\r\n <mat-menu #attachmentTypeMenu=\"matMenu\">\r\n <input #imageInput style=\"display: none\" id=\"file_attachment\" name=\"file_attachment\" type=\"file\"\r\n (change)=\"onFileAdded($event)\" [accept]=\"acceptedFileTypes\" [multiple]=\"loadMultipleFiles\" />\r\n @if (allowedTypes.includes(1)) {\r\n <button mat-menu-item (click)=\"imageInput.click()\" class=\"eqp-attachments-file-btn\">\r\n <i class=\"fas fa-file\"></i>\r\n <span style=\"margin-left: 10px\">File</span>\r\n </button>\r\n }\r\n @if (allowedTypes.includes(2)) {\r\n <button mat-menu-item [disabled]=\"isDisabled\" (click)=\"switchToAddingLinkMode()\" class=\"eqp-attachments-link-btn\">\r\n <i class=\"fas fa-link\"></i>\r\n <span style=\"margin-left: 10px\">Link</span>\r\n </button>\r\n }\r\n @if (allowedTypes.includes(3)) {\r\n <button mat-menu-item (click)=\"chooseDropboxFile()\" class=\"eqp-attachments-link-btn\">\r\n <i class=\"fa-brands fa-dropbox\"></i>\r\n <span style=\"margin-left: 10px\">Dropbox</span>\r\n </button>\r\n }\r\n </mat-menu>\r\n }\r\n\r\n @if (separatedUploadButtons && allowedTypes && allowedTypes.length > 1 && (multipleAttachment == true ||\r\n !attachmentsList || attachmentsList.length == 0 || (attachmentsList.length > 0 && !attachmentsList[0]))) {\r\n <div class=\"btn-group\">\r\n @if (allowedTypes.includes(1)) {\r\n <button (click)=\"imageInput.click()\" class=\"btn btn-secondary eqp-attachments-add-btn\" mat-raised-button\r\n color=\"secondary\" type=\"button\">\r\n <i class=\"fa-solid fa-cloud-upload\"></i>\r\n <span style=\"margin-left: 10px\">File</span>\r\n </button>\r\n }\r\n <input #imageInput style=\"display: none\" id=\"file_attachment\" name=\"file_attachment\" type=\"file\"\r\n (change)=\"onFileAdded($event)\" [accept]=\"acceptedFileTypes\" [multiple]=\"loadMultipleFiles\" />\r\n @if (allowedTypes.includes(2)) {\r\n <button (click)=\"switchToAddingLinkMode()\" class=\"btn btn-secondary eqp-attachments-add-btn\" mat-raised-button\r\n color=\"secondary\" type=\"button\">\r\n <i class=\"fas fa-link\"></i>\r\n <span style=\"margin-left: 10px\">Link</span>\r\n </button>\r\n }\r\n @if (allowedTypes.includes(3)) {\r\n <button (click)=\"chooseDropboxFile()\" class=\"btn btn-secondary eqp-attachments-add-btn\" mat-raised-button\r\n color=\"secondary\" type=\"button\">\r\n <i class=\"fa-brands fa-dropbox\"></i>\r\n <span style=\"margin-left: 10px\">Dropbox</span>\r\n </button>\r\n }\r\n </div>\r\n }\r\n</ng-template>\r\n\r\n\r\n<ng-template #croppieTemplate>\r\n <div class=\"modern-dialog-container\">\r\n <div mat-dialog-title class=\"preview-header\">\r\n <div class=\"header-info\">\r\n <div class=\"title-group\">\r\n <span class=\"main-t\">{{ cropLabel }}</span>\r\n <span class=\"file-t\">Regola le dimensioni e l'orientamento</span>\r\n </div>\r\n </div>\r\n <button mat-icon-button type=\"button\" (click)=\"abortFile()\" class=\"close-btn\"><mat-icon>close</mat-icon></button>\r\n </div>\r\n\r\n <mat-dialog-content class=\"preview-content-area\">\r\n <div class=\"crop-toolbar\">\r\n @if (cropOptions.includes(1)) {\r\n <button mat-icon-button [matTooltip]=\"rotateLeftLabel\"\r\n (click)=\"rotateLeft()\"><mat-icon>rotate_left</mat-icon></button>\r\n <button mat-icon-button [matTooltip]=\"rotateRightLabel\"\r\n (click)=\"rotateRight()\"><mat-icon>rotate_right</mat-icon></button>\r\n }\r\n @if (cropOptions.includes(2)) {\r\n <button mat-icon-button [matTooltip]=\"flipHorinzontalLabel\"\r\n (click)=\"flipHorizontal()\"><mat-icon>flip_horizontal</mat-icon></button>\r\n <button mat-icon-button [matTooltip]=\"flipVerticalLabel\"\r\n (click)=\"flipVertical()\"><mat-icon>flip_vertical</mat-icon></button>\r\n }\r\n <button mat-icon-button matTooltip=\"Reset\"\r\n (click)=\"restoreOriginalDimensions()\"><mat-icon>replay</mat-icon></button>\r\n </div>\r\n\r\n <div class=\"media-viewer-wrapper\">\r\n <div class=\"crop-container-modern\">\r\n <image-cropper [imageFile]=\"selectedFile\" [maintainAspectRatio]=\"false\" [canvasRotation]=\"canvasRotation\"\r\n [transform]=\"transform\" format=\"png\" (imageCropped)=\"imageCropped($event)\" [resizeToWidth]=\"customWidth\"\r\n [resizeToHeight]=\"customHeight\" [output]=\"'base64'\" [containWithinAspectRatio]=\"true\" [onlyScaleDown]=\"true\"\r\n [alignImage]=\"'center'\">\r\n </image-cropper>\r\n </div>\r\n </div>\r\n </mat-dialog-content>\r\n\r\n <mat-dialog-actions align=\"end\" class=\"preview-actions\">\r\n <button mat-button (click)=\"abortFile()\" class=\"btn-close\">{{ abortLabel }}</button>\r\n <button mat-raised-button color=\"primary\" (click)=\"confirmAddAttachment()\" class=\"btn-download\">\r\n <mat-icon>check</mat-icon> {{ confirmLabel }}\r\n </button>\r\n </mat-dialog-actions>\r\n </div>\r\n</ng-template>\r\n\r\n\r\n\r\n<!-- TEMPLATE PER FORM DI AGGIUNTA DI UN LINK -->\r\n<ng-template #addingLinkTemplate>\r\n <div class=\"modern-dialog-container\">\r\n <div mat-dialog-title class=\"preview-header\">\r\n <div class=\"header-info\">\r\n <div class=\"type-icon-wrapper\">\r\n <mat-icon>link</mat-icon>\r\n </div>\r\n <div class=\"title-group\">\r\n <span class=\"main-t\">Aggiungi un link</span>\r\n <span class=\"file-t\">Inserisci l'URL della risorsa web</span>\r\n </div>\r\n </div>\r\n <button mat-icon-button mat-dialog-close class=\"close-btn\"><mat-icon>close</mat-icon></button>\r\n </div>\r\n\r\n <mat-dialog-content class=\"add-link-modern-content\">\r\n <form [formGroup]=\"newAttachmentForm\" class=\"add-link-form\">\r\n <mat-form-field appearance=\"outline\" class=\"w-100 mt-3\">\r\n <mat-label>URL del collegamento</mat-label>\r\n <input matInput formControlName=\"filePath\" placeholder=\"https://...\" required>\r\n <mat-icon matSuffix>language</mat-icon>\r\n </mat-form-field>\r\n\r\n <mat-form-field appearance=\"outline\" class=\"w-100\">\r\n <mat-label>Titolo (opzionale)</mat-label>\r\n <input matInput formControlName=\"fileName\" placeholder=\"Es. Documento Progetto\">\r\n <mat-icon matSuffix>title</mat-icon>\r\n </mat-form-field>\r\n </form>\r\n </mat-dialog-content>\r\n\r\n <mat-dialog-actions align=\"end\" class=\"preview-actions\">\r\n <button mat-button mat-dialog-close class=\"btn-close\">Annulla</button>\r\n <button mat-raised-button color=\"primary\" [mat-dialog-close]=\"newAttachmentForm.value\"\r\n [disabled]=\"newAttachmentForm.invalid\" class=\"btn-download\">\r\n <mat-icon>add</mat-icon> Aggiungi\r\n </button>\r\n </mat-dialog-actions>\r\n </div>\r\n</ng-template>", styles: ["@charset \"UTF-8\";:host{--primary-color: #6a5af9;--primary-color-dark: #5441f8;--success-color: #1ce593;--error-color: #ff5b5b;--background-light: #f7f9fc;--background-card: rgba(255, 255, 255, .7);--text-color: #1e293b;--text-color-light: #64748b;--border-color: rgba(203, 213, 225, .5);--shadow-color: rgba(99, 102, 241, .2);--border-radius: 16px;--transition-speed: .3s}@keyframes fadeIn{0%{opacity:0;transform:translateY(15px)}to{opacity:1;transform:translateY(0)}}@keyframes pulse{0%{box-shadow:0 0 0 0 var(--primary-color)}70%{box-shadow:0 0 0 10px #6a5af900}to{box-shadow:0 0 #6a5af900}}.container{width:100%;max-width:700px;margin:2rem auto;font-family:Inter,sans-serif}.header{display:flex;justify-content:space-between;align-items:center;margin-bottom:2rem;padding-bottom:1.5rem;border-bottom:1px solid var(--border-color)}.header h1{color:var(--text-color);font-size:1.8rem;font-weight:700;margin:0}.dropbox{width:100%;border:2px dashed var(--border-color);border-radius:var(--border-radius);padding:2.5rem;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;cursor:pointer;background-color:var(--background-card);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);transition:all var(--transition-speed) ease}.dropbox:hover{transform:translateY(-5px);box-shadow:0 10px 25px var(--shadow-color);border-color:var(--primary-color)}.dropbox.dragover{border-style:solid;border-color:var(--primary-color);transform:scale(1.02);box-shadow:0 0 25px var(--shadow-color);animation:pulse 1.5s infinite}.dropbox .dropbox-icon{font-size:3.5rem;color:var(--primary-color)}.dropbox .dropbox-text{font-size:1.1rem;font-weight:600;color:var(--text-color);margin-top:1rem}.dropbox .dropbox-subtext{color:var(--text-color-light);margin-top:.5rem}.dropbox .browse-btn{margin-top:1.5rem;padding:.75rem 1.5rem;background-color:var(--primary-color);color:#fff;border:none;border-radius:10px;font-weight:600;transition:all var(--transition-speed) ease}.dropbox .browse-btn:hover{background-color:var(--primary-color-dark);transform:translateY(-2px)}.file-previews{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:1.5rem;margin-top:2rem}.file-previews{display:grid;gap:16px;padding:16px;grid-template-columns:repeat(auto-fill,minmax(var(--card-min-width, 200px),1fr))}.file-preview{background-color:var(--background-card);border-radius:var(--border-radius);box-shadow:0 4px 15px #0000000d;border:1px solid var(--border-color);overflow:hidden;display:flex;flex-direction:column;position:relative;opacity:0;animation:fadeIn .5s ease-out forwards;transition:transform var(--transition-speed) ease,box-shadow var(--transition-speed) ease}.file-preview.uploading{cursor:wait}.file-preview.uploading .preview-img-container:after{content:\"\";position:absolute;inset:0;background:#ffffffb3;-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);pointer-events:none}.file-preview .preview-action-overlay{position:absolute;inset:0;display:flex;justify-content:center;align-items:center;background-color:#0006;color:#fff;opacity:1;transition:opacity .3s ease}.file-preview .preview-action-overlay .mat-icon{font-size:36px;width:36px;height:36px}.file-preview .remove-btn{position:absolute;top:8px;right:8px;z-index:2;background-color:#0000004d;color:#fff;width:32px;height:32px;display:flex;align-items:center;justify-content:center;padding:0;transition:all var(--transition-speed) ease}.file-preview .remove-btn:hover{background-color:#ff5b5b}.file-preview .remove-btn .mat-icon{font-size:18px}@media (hover: hover){.file-preview .remove-btn .file-preview .preview-action-overlay{opacity:0}.file-preview .remove-btn .file-preview:hover .preview-action-overlay,.file-preview .remove-btn .file-preview:hover .remove-btn{opacity:1}.file-preview .remove-btn .file-preview .remove-btn{opacity:0}}@media (hover: hover) and (min-width: 769px){.file-preview .remove-btn{opacity:0;transform:scale(.8)}.file-preview:hover{transform:translateY(-5px);box-shadow:0 10px 25px var(--shadow-color)}.file-preview:hover .remove-btn{opacity:1;transform:scale(1)}}.file-preview .preview-img-container{aspect-ratio:4/3;display:flex;align-items:center;justify-content:center;position:relative;cursor:pointer;background-color:#f0f2f5}.file-preview .preview-img{width:100%;height:100%;object-fit:cover}.file-preview .file-icon{font-size:4rem;color:var(--primary-color)}.file-preview .file-info{padding:12px;border-top:1px solid var(--border-color);flex-grow:1;display:flex;align-items:center}.file-preview .file-name{font-weight:600;font-size:.9rem;color:var(--text-color);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.file-preview .upload-spinner{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:40px;height:40px;border:4px solid rgba(0,0,0,.1);border-left-color:var(--primary-color);border-radius:50%;animation:spin 1s linear infinite;pointer-events:none}.file-preview.card-small{width:140px}.file-preview.card-small .preview-img-container{height:80px}.file-preview.card-small .file-info{padding:8px}.file-preview.card-small .file-name{font-size:.8rem}.file-preview.card-small .file-icon{font-size:3rem}.file-preview.card-small .remove-btn{width:28px;height:28px}.file-preview.card-small .remove-btn .mat-icon{font-size:16px}.file-preview.card-large{width:280px}.file-preview.card-large .preview-img-container{height:180px}.file-preview.card-large .file-info{padding:16px}.file-preview.card-large .file-name{font-size:1rem}.file-preview.card-large .file-icon{font-size:5rem}.file-preview.card-large .remove-btn{width:36px;height:36px}.file-preview.card-large .remove-btn .mat-icon{font-size:20px}.file-preview[style*=--eqp-card-width]{width:var(--eqp-card-width)}.file-preview[style*=--eqp-card-width] .preview-img-container{height:calc(var(--eqp-card-width) * .65)}@keyframes spin{to{transform:translate(-50%,-50%) rotate(360deg)}}.upload-stats{background-color:var(--background-card);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid var(--border-color);border-radius:var(--border-radius);padding:1.5rem;margin-top:2rem;animation:fadeIn .5s ease-out}.upload-stats .stats-info{display:flex;justify-content:space-around}.upload-stats .progress-bar{height:10px;background-color:var(--border-color);border-radius:5px;overflow:hidden;margin-top:1rem}.upload-stats .progress-fill{height:100%;background:linear-gradient(90deg,var(--primary-color),var(--success-color));transition:width var(--transition-speed) ease-out;border-radius:5px}.upload-notification{position:fixed;bottom:20px;left:50%;transform:translate(-50%,100px);padding:1rem 1.5rem;border-radius:var(--border-radius);color:#fff;font-weight:600;box-shadow:0 10px 30px #0003;transition:transform var(--transition-speed) cubic-bezier(.175,.885,.32,1.275)}.upload-notification.show{transform:translate(-50%)}.upload-notification.success{background-color:var(--success-color)}.upload-notification.error{background-color:var(--error-color)}::ng-deep .cdk-overlay-pane.eqp-crop-dialog{transform:translateY(-4vh)}@media (max-width: 600px){::ng-deep .cdk-overlay-pane.crop-dialog{width:auto!important}}::ng-deep .eqp-attachments-dialog .mat-mdc-dialog-container .mdc-dialog__surface{border-radius:20px!important;padding:0!important;overflow:hidden!important;width:fit-content!important;min-width:350px!important;max-width:95vw!important;margin:0 auto;background-color:#fff!important}::ng-deep .eqp-attachments-dialog .mat-mdc-dialog-content{padding:0!important;margin:0!important;max-height:none!important;display:flex;flex-direction:column}.modern-dialog-container{display:flex;flex-direction:column;width:100%;max-width:100%;height:100%;max-height:90vh;background:#fff;--preview-header-h: 66px;--preview-footer-h: 66px;--preview-padding-y: 30px}.modern-dialog-container .main-preview-media{display:block;max-width:100%;max-height:var(--media-max-h);width:auto;height:auto;object-fit:contain;border-radius:12px;background:#fff;padding:4px;box-shadow:0 10px 30px #00000014}.modern-dialog-container video.main-preview-media{width:min(100%,1100px);background:#000}.modern-dialog-container .iframe-container{width:min(100%,1100px);height:var(--media-max-h);display:flex;width:100%;max-width:1100px}.modern-dialog-container .preview-iframe-modern{width:100%;height:100%;border:0;border-radius:12px;background:#f8fafc}.modern-dialog-container .preview-header{flex:0 0 auto;flex-shrink:0;padding:14px 20px;display:flex;align-items:center;justify-content:flex-start;border-bottom:1px solid var(--border-color);background:#f8fafc;z-index:10;width:100%}.modern-dialog-container .preview-header .header-info{display:flex;align-items:center;justify-content:flex-start;gap:12px;flex:1 1 auto;min-width:0;text-align:left}.modern-dialog-container .preview-header .header-info .type-icon-wrapper{background:var(--primary-color);color:#fff;width:38px;height:38px;border-radius:10px;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 10px var(--shadow-color)}.modern-dialog-container .preview-header .header-info .title-group{display:flex;flex-direction:column;align-items:flex-start;text-align:left}.modern-dialog-container .preview-header .header-info .title-group .main-t{font-weight:800;color:var(--text-color);font-size:.95rem;line-height:1.2;text-align:left}.modern-dialog-container .preview-header .header-info .title-group .file-t{font-size:.75rem;color:var(--text-color-light);max-width:250px;overflow:hidden;text-overflow:ellipsis;text-align:left}.modern-dialog-container .preview-content-area{flex:1;background:#fff!important;display:flex;flex-direction:column;padding:15px 15px 22px!important;overflow:hidden!important;min-height:0;width:auto;--media-max-h: calc(90vh - var(--preview-header-h) - var(--preview-footer-h) - var(--preview-padding-y));width:100%}.modern-dialog-container .preview-content-area .crop-toolbar{flex-shrink:0;z-index:20;background:#0f172a14;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);padding:6px 16px;border-radius:30px;margin:0 auto 15px;display:flex;gap:8px;box-shadow:0 4px 12px #0000000d}.modern-dialog-container .preview-content-area .crop-toolbar button{color:var(--text-color)!important}.modern-dialog-container .preview-content-area .media-viewer-wrapper{flex:1;width:100%;display:flex;justify-content:center;align-items:center;min-height:0;overflow:visible}.modern-dialog-container .preview-content-area .media-viewer-wrapper .crop-container-modern{width:100%;height:100%;display:flex;justify-content:center;align-items:center;background:transparent;border-radius:12px;padding:0}.modern-dialog-container .preview-content-area .media-viewer-wrapper .crop-container-modern image-cropper{max-width:100%;max-height:100%;display:block}.modern-dialog-container .add-link-modern-content{flex:1;background:#fff;padding:24px!important}.modern-dialog-container .add-link-modern-content .add-link-form{display:flex;flex-direction:column;gap:10px}.modern-dialog-container .preview-actions{flex-shrink:0;padding:12px 24px;background:#f8fafc;border-top:1px solid #e2e8f0;margin:0}.modern-dialog-container .preview-actions .btn-download{border-radius:12px;font-weight:600;box-shadow:0 4px 12px var(--shadow-color)}@media (max-width: 600px){.modern-dialog-container{max-height:95vh}.modern-dialog-container .preview-content-area{padding:10px!important}.modern-dialog-container .preview-content-area .media-viewer-wrapper video.main-preview-media{width:100%!important;max-height:55vh!important;border-radius:4px;box-shadow:none}.modern-dialog-container .preview-content-area .media-viewer-wrapper .iframe-container{height:50vh}.modern-dialog-container .preview-content-area .crop-toolbar{width:calc(100% - 20px);max-width:400px;margin-bottom:10px;padding:4px 8px;gap:4px;overflow-x:auto;justify-content:center;-webkit-overflow-scrolling:touch;scrollbar-width:none}.modern-dialog-container .preview-content-area .crop-toolbar::-webkit-scrollbar{display:none}.modern-dialog-container .preview-content-area .crop-toolbar button{flex-shrink:0;transform:scale(.85);width:40px;height:40px}.modern-dialog-container .preview-actions{padding:10px 15px;flex-direction:column-reverse;gap:8px}.modern-dialog-container .preview-actions button{width:100%;margin:0!important}}@media (max-width: 360px){.modern-dialog-container .preview-content-area .crop-toolbar{gap:2px}.modern-dialog-container .preview-content-area .crop-toolbar button{transform:scale(.75)}}.control-icon{font-size:28px;cursor:pointer}image-cropper,image-cropper canvas,image-cropper img{max-width:100%!important;max-height:100%!important}.crop-large{display:flex}.crop-small{display:none}@media (max-width: 600px){.dialog-content{max-height:55vh}.crop-container{max-width:100%;aspect-ratio:auto}.crop-large{display:none}.crop-small{display:flex;justify-content:center;gap:14px}.control-icon{font-size:24px}}.stats-header{display:flex;justify-content:space-between;align-items:center;width:100%}.table-view-container{margin-top:1.5rem;background-color:var(--background-card);border-radius:var(--border-radius);border:1px solid var(--border-color);overflow:hidden;animation:fadeIn .5s ease-out}.table-header,.table-row{display:flex;align-items:center;padding:0 1rem;transition:background-color var(--transition-speed) ease}.table-header{background-color:#f8f9fa;font-weight:600;color:var(--text-color-light);font-size:.8rem;text-transform:uppercase;border-bottom:2px solid var(--border-color)}.table-row{border-bottom:1px solid var(--border-color)}.table-row:last-child{border-bottom:none}.table-row:hover{background-color:#00000005}.table-cell{padding:1rem .5rem;display:flex;align-items:center;gap:1rem}.name-col{flex:4}.size-col,.date-col{flex:2}.actions-col{flex:1;justify-content:flex-end;min-width:150px}.file-icon-small{font-size:1.5rem;color:var(--primary-color)}.file-info-text{display:flex;flex-direction:column}.file-name-table{font-weight:600;color:var(--text-color)}.file-type-table{font-size:.8rem;color:var(--text-color-light)}@media (max-width: 768px){.date-col{display:none}.name-col{flex:3}.size-col{flex:2}.actions-col{flex:2;min-width:120px}}.secondary-action-link{color:var(--primary-color);text-decoration:none;border-bottom:1px solid var(--primary-color);cursor:pointer;font-weight:500}.secondary-action-link:hover{color:var(--primary-color-dark);border-bottom-color:var(--primary-color-dark)}h2[mat-dialog-title]+mat-dialog-content.add-link-dialog-content{padding-top:20px}mat-dialog-content.add-link-dialog-content{padding-left:24px;padding-right:24px;padding-bottom:20px}.add-link-form{display:flex;flex-direction:column;gap:16px}::ng-deep .eqp-attachments-preview-dialog{--mdc-dialog-subhead-line-height: normal}::ng-deep .eqp-attachments-preview-dialog .mat-mdc-dialog-content{padding:0!important;margin:0!important;max-height:none!important;background:#fff!important}:host ::ng-deep .eqp-attachments-dialog .mat-mdc-dialog-container{--mdc-dialog-subhead-size: 1.25rem;--mdc-dialog-subhead-line-height: 1.5;--mdc-dialog-subhead-weight: 600;--mdc-dialog-supporting-text-size: 1rem}.empty-state{display:flex;flex-direction:column;justify-content:center;align-items:center;padding:1.5rem 1rem;margin-top:1.5rem;border:2px dashed var(--border-color);border-radius:var(--border-radius);background-color:#f8f9fa;text-align:center;color:var(--text-color-light);animation:fadeIn .5s ease-out}.empty-state:before{content:\"\\1f4ed\";font-size:2.5rem;margin-bottom:1rem;opacity:.7}.secondary-action-link.disabled-link{color:#adb5bd;cursor:not-allowed;border-bottom-color:transparent}.secondary-action-link.disabled-link:hover{color:#adb5bd;border-bottom-color:transparent}.table-cell{padding:1rem .5rem;display:flex;align-items:center;gap:1rem;min-width:0;overflow:hidden}.table-cell.col-actions{flex:0 0 150px;justify-content:flex-end;overflow:visible}.compact-uploader{display:flex;align-items:center;gap:16px;padding:16px;border:2px dashed var(--border-color);border-radius:var(--border-radius);background-color:#fcfdff;transition:all .3s ease}.compact-uploader .compact-text{flex:1 1 auto;min-width:0;cursor:pointer}.compact-uploader .compact-text:hover .compact-title{color:var(--primary-color)}.compact-uploader .compact-title{font-weight:600;color:var(--text-color);transition:color .3s ease}.compact-uploader .compact-subtitle{font-size:.8rem;color:var(--text-color-light);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.compact-uploader .compact-icon mat-icon{font-size:36px;width:36px;height:36px;color:var(--primary-color)}.compact-uploader .compact-actions{display:flex;gap:8px;flex-shrink:0}.compact-uploader.dragover{background-color:#f4f8ff;border-color:var(--primary-color);box-shadow:0 0 10px var(--shadow-color)}\n"] }]
|
|
1373
1540
|
}], ctorParameters: () => [{ type: i1.MatDialog }, { type: i2.FormBuilder }, { type: i3.DomSanitizer }, { type: i4.HttpClient }, { type: EqpAttachmentService }], propDecorators: { disableAction: [{
|
|
1374
1541
|
type: Input,
|
|
1375
1542
|
args: ["disableAction"]
|
|
@@ -1524,6 +1691,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
1524
1691
|
}], rotateLeftLabel: [{
|
|
1525
1692
|
type: Input,
|
|
1526
1693
|
args: ["rotateLeftLabel"]
|
|
1694
|
+
}], base64LimitMB: [{
|
|
1695
|
+
type: Input,
|
|
1696
|
+
args: ["base64LimitMB"]
|
|
1527
1697
|
}], uploadTitle: [{
|
|
1528
1698
|
type: Input
|
|
1529
1699
|
}], uploadSubtitle: [{
|
|
@@ -1556,6 +1726,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
1556
1726
|
}], showUploadTitle: [{
|
|
1557
1727
|
type: Input,
|
|
1558
1728
|
args: ["showUploadTitle"]
|
|
1729
|
+
}], showDropArea: [{
|
|
1730
|
+
type: Input,
|
|
1731
|
+
args: ["showDropArea"]
|
|
1732
|
+
}], hiddenColumns: [{
|
|
1733
|
+
type: Input
|
|
1734
|
+
}], hiddenActions: [{
|
|
1735
|
+
type: Input
|
|
1736
|
+
}], videoCompression: [{
|
|
1737
|
+
type: Input
|
|
1559
1738
|
}], customMenuActions: [{
|
|
1560
1739
|
type: Input
|
|
1561
1740
|
}], customColumns: [{
|