@solcre-org/core-ui 2.12.14 → 2.12.16
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/assets/css/inc/components/chat-input.css +238 -0
- package/assets/css/inc/components/chat-layout.css +131 -0
- package/assets/css/main.css +2 -1
- package/fesm2022/solcre-org-core-ui.mjs +1295 -148
- package/fesm2022/solcre-org-core-ui.mjs.map +1 -1
- package/index.d.ts +388 -57
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { HostBinding, Directive, input, Component, inject, output, computed, signal, effect, Pipe, Injectable, ViewChild, ViewContainerRef, TemplateRef, InjectionToken, ContentChild, ChangeDetectionStrategy, ElementRef, Input, HostListener, viewChild, ChangeDetectorRef, importProvidersFrom } from '@angular/core';
|
|
2
|
+
import { HostBinding, Directive, input, Component, inject, output, computed, signal, effect, Pipe, Injectable, ViewChild, ViewContainerRef, TemplateRef, InjectionToken, ContentChild, ChangeDetectionStrategy, ElementRef, Input, HostListener, viewChild, ChangeDetectorRef, afterNextRender, importProvidersFrom } from '@angular/core';
|
|
3
3
|
import * as i2 from '@angular/common';
|
|
4
4
|
import { CommonModule, DatePipe } from '@angular/common';
|
|
5
5
|
import * as i3 from '@ngx-translate/core';
|
|
@@ -9,7 +9,7 @@ import { FormControl, Validators, FormsModule, ReactiveFormsModule, FormBuilder
|
|
|
9
9
|
import * as i5 from '@ng-select/ng-select';
|
|
10
10
|
import { NgSelectModule, NgSelectComponent } from '@ng-select/ng-select';
|
|
11
11
|
import { AuthService, ApiService } from '@solcre-org/core';
|
|
12
|
-
import { map, BehaviorSubject, Subject, throttleTime, takeUntil, debounceTime, distinctUntilChanged, tap, switchMap, of, catchError, finalize, throwError, Observable, forkJoin, zip, timeout, Subscription } from 'rxjs';
|
|
12
|
+
import { map, BehaviorSubject, Subject, throttleTime, takeUntil, debounceTime, distinctUntilChanged, tap, switchMap, of, catchError, finalize, throwError, Observable, forkJoin, zip, timeout, Subscription, from } from 'rxjs';
|
|
13
13
|
import * as i4 from '@angular/router';
|
|
14
14
|
import { RouterModule, Router, NavigationEnd, NavigationStart } from '@angular/router';
|
|
15
15
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
@@ -1341,11 +1341,11 @@ class ImagePreviewComponent {
|
|
|
1341
1341
|
}
|
|
1342
1342
|
}
|
|
1343
1343
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: ImagePreviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1344
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: ImagePreviewComponent, isStandalone: true, selector: "core-image-preview", inputs: { src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: true, transformFunction: null }, alt: { classPropertyName: "alt", publicName: "alt", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, objectFit: { classPropertyName: "objectFit", publicName: "objectFit", isSignal: true, isRequired: false, transformFunction: null }, borderRadius: { classPropertyName: "borderRadius", publicName: "borderRadius", isSignal: true, isRequired: false, transformFunction: null }, cursor: { classPropertyName: "cursor", publicName: "cursor", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, isRelative: { classPropertyName: "isRelative", publicName: "isRelative", isSignal: true, isRequired: false, transformFunction: null }, showSkeleton: { classPropertyName: "showSkeleton", publicName: "showSkeleton", isSignal: true, isRequired: false, transformFunction: null }, skeletonAnimation: { classPropertyName: "skeletonAnimation", publicName: "skeletonAnimation", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"c-img-preview\" \n [class.c-img-preview--loading]=\"isImageLoading() && showSkeleton()\"\n [class.c-img-preview--error]=\"hasImageError()\"\n [style.width]=\"width()\"\n [style.height]=\"height()\"\n [style.border-radius]=\"borderRadius()\"\n [style.position]=\"isRelative() ? 'relative' : 'static'\">\n \n @if (isImageLoading() && showSkeleton()) {\n <div class=\"c-img-preview__skeleton\"\n (click)=\"onSkeletonClick()\">\n <core-generic-skeleton \n [type]=\"SkeletonType.IMAGE\"\n [width]=\"width() || '100%'\"\n [height]=\"height() || '200px'\"\n [animation]=\"skeletonAnimation()\"\n [ariaLabel]=\"'Loading image: ' + (alt() || 'Image')\">\n </core-generic-skeleton>\n </div>\n }\n \n @if (!hasImageError()) {\n <img \n [src]=\"src()\" \n [alt]=\"alt()\"\n [title]=\"title()\"\n [loading]=\"loading()\"\n [style.object-fit]=\"objectFit()\"\n [style.border-radius]=\"borderRadius()\"\n [style.cursor]=\"cursor()\"\n
|
|
1344
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: ImagePreviewComponent, isStandalone: true, selector: "core-image-preview", inputs: { src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: true, transformFunction: null }, alt: { classPropertyName: "alt", publicName: "alt", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, objectFit: { classPropertyName: "objectFit", publicName: "objectFit", isSignal: true, isRequired: false, transformFunction: null }, borderRadius: { classPropertyName: "borderRadius", publicName: "borderRadius", isSignal: true, isRequired: false, transformFunction: null }, cursor: { classPropertyName: "cursor", publicName: "cursor", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, isRelative: { classPropertyName: "isRelative", publicName: "isRelative", isSignal: true, isRequired: false, transformFunction: null }, showSkeleton: { classPropertyName: "showSkeleton", publicName: "showSkeleton", isSignal: true, isRequired: false, transformFunction: null }, skeletonAnimation: { classPropertyName: "skeletonAnimation", publicName: "skeletonAnimation", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"c-img-preview\" \n [class.c-img-preview--loading]=\"isImageLoading() && showSkeleton()\"\n [class.c-img-preview--error]=\"hasImageError()\"\n [style.width]=\"width()\"\n [style.height]=\"height()\"\n [style.border-radius]=\"borderRadius()\"\n [style.position]=\"isRelative() ? 'relative' : 'static'\">\n \n @if (isImageLoading() && showSkeleton()) {\n <div class=\"c-img-preview__skeleton\"\n (click)=\"onSkeletonClick()\">\n <core-generic-skeleton \n [type]=\"SkeletonType.IMAGE\"\n [width]=\"width() || '100%'\"\n [height]=\"height() || '200px'\"\n [animation]=\"skeletonAnimation()\"\n [ariaLabel]=\"'Loading image: ' + (alt() || 'Image')\">\n </core-generic-skeleton>\n </div>\n }\n \n @if (!isImageLoading() && !hasImageError()) {\n <img \n [src]=\"src()\" \n [alt]=\"alt()\"\n [title]=\"title()\"\n [loading]=\"loading()\"\n [style.object-fit]=\"objectFit()\"\n [style.border-radius]=\"borderRadius()\"\n [style.cursor]=\"cursor()\"\n (click)=\"onImageClick()\">\n }\n \n @if (isImageLoading()) {\n <img \n [src]=\"src()\"\n [alt]=\"alt()\"\n style=\"display: none;\"\n (load)=\"onImageLoad()\"\n (error)=\"onImageError()\">\n }\n \n @if (hasImageError()) {\n <div class=\"c-img-preview__error\"\n [style.width]=\"width() || '100%'\"\n [style.height]=\"height() || '200px'\"\n [style.border-radius]=\"borderRadius()\">\n <div class=\"c-img-preview__error-content\">\n <span class=\"c-img-preview__error-icon\">\uD83D\uDCF7</span>\n <span class=\"c-img-preview__error-text\">Error al cargar imagen</span>\n </div>\n </div>\n }\n \n</div>", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: GenericSkeletonComponent, selector: "core-generic-skeleton", inputs: ["config", "items", "type", "size", "width", "height", "animated", "animation", "lines", "customClass", "ariaLabel"] }] });
|
|
1345
1345
|
}
|
|
1346
1346
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: ImagePreviewComponent, decorators: [{
|
|
1347
1347
|
type: Component,
|
|
1348
|
-
args: [{ selector: 'core-image-preview', standalone: true, imports: [CommonModule, GenericSkeletonComponent], template: "<div class=\"c-img-preview\" \n [class.c-img-preview--loading]=\"isImageLoading() && showSkeleton()\"\n [class.c-img-preview--error]=\"hasImageError()\"\n [style.width]=\"width()\"\n [style.height]=\"height()\"\n [style.border-radius]=\"borderRadius()\"\n [style.position]=\"isRelative() ? 'relative' : 'static'\">\n \n @if (isImageLoading() && showSkeleton()) {\n <div class=\"c-img-preview__skeleton\"\n (click)=\"onSkeletonClick()\">\n <core-generic-skeleton \n [type]=\"SkeletonType.IMAGE\"\n [width]=\"width() || '100%'\"\n [height]=\"height() || '200px'\"\n [animation]=\"skeletonAnimation()\"\n [ariaLabel]=\"'Loading image: ' + (alt() || 'Image')\">\n </core-generic-skeleton>\n </div>\n }\n \n @if (!hasImageError()) {\n <img \n [src]=\"src()\" \n [alt]=\"alt()\"\n [title]=\"title()\"\n [loading]=\"loading()\"\n [style.object-fit]=\"objectFit()\"\n [style.border-radius]=\"borderRadius()\"\n [style.cursor]=\"cursor()\"\n
|
|
1348
|
+
args: [{ selector: 'core-image-preview', standalone: true, imports: [CommonModule, GenericSkeletonComponent], template: "<div class=\"c-img-preview\" \n [class.c-img-preview--loading]=\"isImageLoading() && showSkeleton()\"\n [class.c-img-preview--error]=\"hasImageError()\"\n [style.width]=\"width()\"\n [style.height]=\"height()\"\n [style.border-radius]=\"borderRadius()\"\n [style.position]=\"isRelative() ? 'relative' : 'static'\">\n \n @if (isImageLoading() && showSkeleton()) {\n <div class=\"c-img-preview__skeleton\"\n (click)=\"onSkeletonClick()\">\n <core-generic-skeleton \n [type]=\"SkeletonType.IMAGE\"\n [width]=\"width() || '100%'\"\n [height]=\"height() || '200px'\"\n [animation]=\"skeletonAnimation()\"\n [ariaLabel]=\"'Loading image: ' + (alt() || 'Image')\">\n </core-generic-skeleton>\n </div>\n }\n \n @if (!isImageLoading() && !hasImageError()) {\n <img \n [src]=\"src()\" \n [alt]=\"alt()\"\n [title]=\"title()\"\n [loading]=\"loading()\"\n [style.object-fit]=\"objectFit()\"\n [style.border-radius]=\"borderRadius()\"\n [style.cursor]=\"cursor()\"\n (click)=\"onImageClick()\">\n }\n \n @if (isImageLoading()) {\n <img \n [src]=\"src()\"\n [alt]=\"alt()\"\n style=\"display: none;\"\n (load)=\"onImageLoad()\"\n (error)=\"onImageError()\">\n }\n \n @if (hasImageError()) {\n <div class=\"c-img-preview__error\"\n [style.width]=\"width() || '100%'\"\n [style.height]=\"height() || '200px'\"\n [style.border-radius]=\"borderRadius()\">\n <div class=\"c-img-preview__error-content\">\n <span class=\"c-img-preview__error-icon\">\uD83D\uDCF7</span>\n <span class=\"c-img-preview__error-text\">Error al cargar imagen</span>\n </div>\n </div>\n }\n \n</div>" }]
|
|
1349
1349
|
}] });
|
|
1350
1350
|
|
|
1351
1351
|
class FileFieldComponent extends BaseFieldComponent {
|
|
@@ -11985,11 +11985,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
|
|
|
11985
11985
|
// Este archivo es generado automáticamente por scripts/update-version.js
|
|
11986
11986
|
// No edites manualmente este archivo
|
|
11987
11987
|
const VERSION = {
|
|
11988
|
-
full: '2.12.
|
|
11988
|
+
full: '2.12.16',
|
|
11989
11989
|
major: 2,
|
|
11990
11990
|
minor: 12,
|
|
11991
|
-
patch:
|
|
11992
|
-
timestamp: '2025-09-
|
|
11991
|
+
patch: 16,
|
|
11992
|
+
timestamp: '2025-09-08T20:21:04.064Z',
|
|
11993
11993
|
buildDate: '8/9/2025'
|
|
11994
11994
|
};
|
|
11995
11995
|
|
|
@@ -13580,6 +13580,1293 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
|
|
|
13580
13580
|
args: ['keydown', ['$event']]
|
|
13581
13581
|
}] } });
|
|
13582
13582
|
|
|
13583
|
+
var ChatMessageType;
|
|
13584
|
+
(function (ChatMessageType) {
|
|
13585
|
+
ChatMessageType["TEXT"] = "text";
|
|
13586
|
+
ChatMessageType["IMAGE"] = "image";
|
|
13587
|
+
ChatMessageType["FILE"] = "file";
|
|
13588
|
+
ChatMessageType["SYSTEM"] = "system";
|
|
13589
|
+
ChatMessageType["NOTIFICATION"] = "notification";
|
|
13590
|
+
ChatMessageType["CUSTOM"] = "custom";
|
|
13591
|
+
})(ChatMessageType || (ChatMessageType = {}));
|
|
13592
|
+
|
|
13593
|
+
var ChatMessagePosition;
|
|
13594
|
+
(function (ChatMessagePosition) {
|
|
13595
|
+
ChatMessagePosition["LEFT"] = "left";
|
|
13596
|
+
ChatMessagePosition["RIGHT"] = "right";
|
|
13597
|
+
})(ChatMessagePosition || (ChatMessagePosition = {}));
|
|
13598
|
+
|
|
13599
|
+
var FilePreviewActionType;
|
|
13600
|
+
(function (FilePreviewActionType) {
|
|
13601
|
+
FilePreviewActionType["DOWNLOAD"] = "download";
|
|
13602
|
+
FilePreviewActionType["DELETE"] = "delete";
|
|
13603
|
+
FilePreviewActionType["PREVIEW"] = "preview";
|
|
13604
|
+
})(FilePreviewActionType || (FilePreviewActionType = {}));
|
|
13605
|
+
|
|
13606
|
+
class GenericChatService {
|
|
13607
|
+
constructor() { }
|
|
13608
|
+
formatTimestamp(timestamp, format) {
|
|
13609
|
+
if (!timestamp)
|
|
13610
|
+
return '';
|
|
13611
|
+
const timeFormat = format || 'dd/MM/yyyy HH:mm';
|
|
13612
|
+
if (timeFormat === 'dd/MM/yyyy HH:mm') {
|
|
13613
|
+
const day = timestamp.getDate().toString().padStart(2, '0');
|
|
13614
|
+
const month = (timestamp.getMonth() + 1).toString().padStart(2, '0');
|
|
13615
|
+
const year = timestamp.getFullYear();
|
|
13616
|
+
const hours = timestamp.getHours().toString().padStart(2, '0');
|
|
13617
|
+
const minutes = timestamp.getMinutes().toString().padStart(2, '0');
|
|
13618
|
+
return `${day}/${month}/${year} ${hours}:${minutes}`;
|
|
13619
|
+
}
|
|
13620
|
+
if (timeFormat === 'HH:mm') {
|
|
13621
|
+
const hours = timestamp.getHours().toString().padStart(2, '0');
|
|
13622
|
+
const minutes = timestamp.getMinutes().toString().padStart(2, '0');
|
|
13623
|
+
return `${hours}:${minutes}`;
|
|
13624
|
+
}
|
|
13625
|
+
return timestamp.toLocaleTimeString();
|
|
13626
|
+
}
|
|
13627
|
+
formatDate(timestamp, format) {
|
|
13628
|
+
if (!timestamp)
|
|
13629
|
+
return '';
|
|
13630
|
+
const dateFormat = format || 'dd/MM/yyyy';
|
|
13631
|
+
if (dateFormat === 'dd/MM/yyyy') {
|
|
13632
|
+
const day = timestamp.getDate().toString().padStart(2, '0');
|
|
13633
|
+
const month = (timestamp.getMonth() + 1).toString().padStart(2, '0');
|
|
13634
|
+
const year = timestamp.getFullYear();
|
|
13635
|
+
return `${day}/${month}/${year}`;
|
|
13636
|
+
}
|
|
13637
|
+
return timestamp.toLocaleDateString();
|
|
13638
|
+
}
|
|
13639
|
+
shouldUseCustomTemplate(message, config) {
|
|
13640
|
+
return message.type === ChatMessageType.CUSTOM &&
|
|
13641
|
+
!!config.customMessageTemplates &&
|
|
13642
|
+
!!message.customData?.templateKey &&
|
|
13643
|
+
!!config.customMessageTemplates.has(message.customData.templateKey);
|
|
13644
|
+
}
|
|
13645
|
+
getMessageClasses(message, config) {
|
|
13646
|
+
const baseClass = 'c-chat-bubble';
|
|
13647
|
+
const positionClass = message.position === 'right' ? 'c-chat-bubble--right' : '';
|
|
13648
|
+
const customClass = message.customClass || '';
|
|
13649
|
+
const configClass = config.customCssClasses?.messageItem || '';
|
|
13650
|
+
return [baseClass, positionClass, customClass, configClass]
|
|
13651
|
+
.filter(cls => cls.length > 0)
|
|
13652
|
+
.join(' ');
|
|
13653
|
+
}
|
|
13654
|
+
getContentClasses(message, config) {
|
|
13655
|
+
const baseClass = 'c-chat-bubble__content';
|
|
13656
|
+
const typeClass = message.type ? `c-chat-bubble__content--${message.type}` : '';
|
|
13657
|
+
const configClass = config.customCssClasses?.messageItem || '';
|
|
13658
|
+
return [baseClass, typeClass, configClass]
|
|
13659
|
+
.filter(cls => cls.length > 0)
|
|
13660
|
+
.join(' ');
|
|
13661
|
+
}
|
|
13662
|
+
getTimestampClasses(config) {
|
|
13663
|
+
const baseClass = 'c-chat-bubble__timestamp';
|
|
13664
|
+
const configClass = config.customCssClasses?.messageItem || '';
|
|
13665
|
+
return [baseClass, configClass]
|
|
13666
|
+
.filter(cls => cls.length > 0)
|
|
13667
|
+
.join(' ');
|
|
13668
|
+
}
|
|
13669
|
+
getImageClasses(config) {
|
|
13670
|
+
const baseClass = 'c-chat-bubble__image';
|
|
13671
|
+
const configClass = config.customCssClasses?.messageItem || '';
|
|
13672
|
+
return [baseClass, configClass]
|
|
13673
|
+
.filter(cls => cls.length > 0)
|
|
13674
|
+
.join(' ');
|
|
13675
|
+
}
|
|
13676
|
+
validateMessage(message) {
|
|
13677
|
+
if (!message)
|
|
13678
|
+
return false;
|
|
13679
|
+
if (!message.id || !message.content)
|
|
13680
|
+
return false;
|
|
13681
|
+
if (!message.timestamp)
|
|
13682
|
+
return false;
|
|
13683
|
+
if (!message.type)
|
|
13684
|
+
return false;
|
|
13685
|
+
return true;
|
|
13686
|
+
}
|
|
13687
|
+
getDefaultConfig() {
|
|
13688
|
+
return {
|
|
13689
|
+
maxHeight: '400px',
|
|
13690
|
+
placeholder: 'Escribe tu mensaje...',
|
|
13691
|
+
showTimestamps: true,
|
|
13692
|
+
showAvatars: true,
|
|
13693
|
+
showTypingIndicator: false,
|
|
13694
|
+
allowFileAttachments: false,
|
|
13695
|
+
allowEmojis: false,
|
|
13696
|
+
maxMessages: 100,
|
|
13697
|
+
readOnly: false,
|
|
13698
|
+
autoScroll: true,
|
|
13699
|
+
dateFormat: 'dd/MM/yyyy',
|
|
13700
|
+
timeFormat: 'HH:mm',
|
|
13701
|
+
groupByDate: false,
|
|
13702
|
+
theme: 'light'
|
|
13703
|
+
};
|
|
13704
|
+
}
|
|
13705
|
+
mergeConfig(userConfig) {
|
|
13706
|
+
return { ...this.getDefaultConfig(), ...userConfig };
|
|
13707
|
+
}
|
|
13708
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: GenericChatService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
13709
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: GenericChatService, providedIn: 'root' });
|
|
13710
|
+
}
|
|
13711
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: GenericChatService, decorators: [{
|
|
13712
|
+
type: Injectable,
|
|
13713
|
+
args: [{
|
|
13714
|
+
providedIn: 'root'
|
|
13715
|
+
}]
|
|
13716
|
+
}], ctorParameters: () => [] });
|
|
13717
|
+
|
|
13718
|
+
class CoreUiTranslateService {
|
|
13719
|
+
translateService = inject(TranslateService);
|
|
13720
|
+
initialize(defaultLang = 'es', currentLang) {
|
|
13721
|
+
this.translateService.setDefaultLang(defaultLang);
|
|
13722
|
+
this.translateService.use(currentLang || defaultLang);
|
|
13723
|
+
}
|
|
13724
|
+
changeLanguage(lang) {
|
|
13725
|
+
return this.translateService.use(lang);
|
|
13726
|
+
}
|
|
13727
|
+
instant(key, params) {
|
|
13728
|
+
return this.translateService.instant(key, params);
|
|
13729
|
+
}
|
|
13730
|
+
get(key, params) {
|
|
13731
|
+
return this.translateService.get(key, params);
|
|
13732
|
+
}
|
|
13733
|
+
getMany(keys, params) {
|
|
13734
|
+
return this.translateService.get(keys, params);
|
|
13735
|
+
}
|
|
13736
|
+
getCurrentLang() {
|
|
13737
|
+
return this.translateService.currentLang;
|
|
13738
|
+
}
|
|
13739
|
+
getDefaultLang() {
|
|
13740
|
+
return this.translateService.getDefaultLang();
|
|
13741
|
+
}
|
|
13742
|
+
getTranslateService() {
|
|
13743
|
+
return this.translateService;
|
|
13744
|
+
}
|
|
13745
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: CoreUiTranslateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
13746
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: CoreUiTranslateService, providedIn: 'root' });
|
|
13747
|
+
}
|
|
13748
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: CoreUiTranslateService, decorators: [{
|
|
13749
|
+
type: Injectable,
|
|
13750
|
+
args: [{
|
|
13751
|
+
providedIn: 'root'
|
|
13752
|
+
}]
|
|
13753
|
+
}] });
|
|
13754
|
+
|
|
13755
|
+
const PERMISSION_RESOURCES_PROVIDER = new InjectionToken('PERMISSION_RESOURCES_PROVIDER');
|
|
13756
|
+
const PERMISSION_ACTIONS_PROVIDER = new InjectionToken('PERMISSION_ACTIONS_PROVIDER');
|
|
13757
|
+
|
|
13758
|
+
class PermissionEnumsService {
|
|
13759
|
+
externalResources = inject(PERMISSION_RESOURCES_PROVIDER, { optional: true });
|
|
13760
|
+
externalActions = inject(PERMISSION_ACTIONS_PROVIDER, { optional: true });
|
|
13761
|
+
getResources() {
|
|
13762
|
+
return this.externalResources || PermissionsResources;
|
|
13763
|
+
}
|
|
13764
|
+
getActions() {
|
|
13765
|
+
return this.externalActions || PermissionsActions;
|
|
13766
|
+
}
|
|
13767
|
+
getResourceValue(key) {
|
|
13768
|
+
const resources = this.getResources();
|
|
13769
|
+
return resources[key];
|
|
13770
|
+
}
|
|
13771
|
+
getActionValue(key) {
|
|
13772
|
+
const actions = this.getActions();
|
|
13773
|
+
return actions[key];
|
|
13774
|
+
}
|
|
13775
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: PermissionEnumsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
13776
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: PermissionEnumsService, providedIn: 'root' });
|
|
13777
|
+
}
|
|
13778
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: PermissionEnumsService, decorators: [{
|
|
13779
|
+
type: Injectable,
|
|
13780
|
+
args: [{
|
|
13781
|
+
providedIn: 'root'
|
|
13782
|
+
}]
|
|
13783
|
+
}] });
|
|
13784
|
+
|
|
13785
|
+
class TranslationMergeService {
|
|
13786
|
+
http;
|
|
13787
|
+
constructor(http) {
|
|
13788
|
+
this.http = http;
|
|
13789
|
+
}
|
|
13790
|
+
loadCombinedTranslations(lang, projectPath = './assets/i18n/', projectFileName = 'main.json', corePath = './assets/i18n/', coreFileName = 'common.json', useNodeModules = false) {
|
|
13791
|
+
const projectUrl = `${projectPath}${lang}/${projectFileName}`;
|
|
13792
|
+
const projectTranslations$ = this.http.get(projectUrl).pipe(catchError$1(error => {
|
|
13793
|
+
console.warn(`No se pudieron cargar las traducciones del proyecto desde: ${projectUrl}`, error);
|
|
13794
|
+
return of({});
|
|
13795
|
+
}));
|
|
13796
|
+
const coreTranslations$ = useNodeModules
|
|
13797
|
+
? this.loadCoreTranslationsWithFallback(lang, coreFileName)
|
|
13798
|
+
: this.http.get(`${corePath}${lang}/${coreFileName}`).pipe(catchError$1(error => {
|
|
13799
|
+
console.warn(`No se pudieron cargar las traducciones del core-ui desde: ${corePath}${lang}/${coreFileName}`, error);
|
|
13800
|
+
return of({});
|
|
13801
|
+
}));
|
|
13802
|
+
return forkJoin({
|
|
13803
|
+
project: projectTranslations$,
|
|
13804
|
+
core: coreTranslations$
|
|
13805
|
+
}).pipe(map$1(({ project, core }) => {
|
|
13806
|
+
const merged = this.mergeWithProjectPriority(core, project);
|
|
13807
|
+
return merged;
|
|
13808
|
+
}));
|
|
13809
|
+
}
|
|
13810
|
+
loadCoreTranslationsWithFallback(lang, coreFileName) {
|
|
13811
|
+
const possiblePaths = [
|
|
13812
|
+
`./assets/i18n/${lang}/${coreFileName}`,
|
|
13813
|
+
`./node_modules/@solcre-org/core-ui/assets/i18n/${lang}/${coreFileName}`,
|
|
13814
|
+
`./i18n/${lang}/${coreFileName}`,
|
|
13815
|
+
];
|
|
13816
|
+
return this.tryLoadFromPaths(possiblePaths, 0);
|
|
13817
|
+
}
|
|
13818
|
+
tryLoadFromPaths(paths, index) {
|
|
13819
|
+
if (index >= paths.length) {
|
|
13820
|
+
console.warn('No se pudieron cargar las traducciones del core-ui desde ninguna ruta');
|
|
13821
|
+
return of({});
|
|
13822
|
+
}
|
|
13823
|
+
const currentPath = paths[index];
|
|
13824
|
+
return this.http.get(currentPath).pipe(catchError$1(error => {
|
|
13825
|
+
console.warn(`Fallo al cargar desde: ${currentPath}`, error);
|
|
13826
|
+
return this.tryLoadFromPaths(paths, index + 1);
|
|
13827
|
+
}));
|
|
13828
|
+
}
|
|
13829
|
+
mergeWithProjectPriority(coreTranslations, projectTranslations) {
|
|
13830
|
+
const result = { ...coreTranslations };
|
|
13831
|
+
for (const key in projectTranslations) {
|
|
13832
|
+
if (projectTranslations.hasOwnProperty(key)) {
|
|
13833
|
+
if (projectTranslations[key] &&
|
|
13834
|
+
typeof projectTranslations[key] === 'object' &&
|
|
13835
|
+
!Array.isArray(projectTranslations[key]) &&
|
|
13836
|
+
result[key] &&
|
|
13837
|
+
typeof result[key] === 'object' &&
|
|
13838
|
+
!Array.isArray(result[key])) {
|
|
13839
|
+
result[key] = this.mergeWithProjectPriority(result[key], projectTranslations[key]);
|
|
13840
|
+
}
|
|
13841
|
+
else {
|
|
13842
|
+
result[key] = projectTranslations[key];
|
|
13843
|
+
}
|
|
13844
|
+
}
|
|
13845
|
+
}
|
|
13846
|
+
return result;
|
|
13847
|
+
}
|
|
13848
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslationMergeService, deps: [{ token: i1$1.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
13849
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslationMergeService, providedIn: 'root' });
|
|
13850
|
+
}
|
|
13851
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslationMergeService, decorators: [{
|
|
13852
|
+
type: Injectable,
|
|
13853
|
+
args: [{
|
|
13854
|
+
providedIn: 'root'
|
|
13855
|
+
}]
|
|
13856
|
+
}], ctorParameters: () => [{ type: i1$1.HttpClient }] });
|
|
13857
|
+
|
|
13858
|
+
var FilePreviewType;
|
|
13859
|
+
(function (FilePreviewType) {
|
|
13860
|
+
FilePreviewType["IMAGE"] = "image";
|
|
13861
|
+
FilePreviewType["PDF"] = "pdf";
|
|
13862
|
+
FilePreviewType["DOCUMENT"] = "document";
|
|
13863
|
+
FilePreviewType["SPREADSHEET"] = "spreadsheet";
|
|
13864
|
+
FilePreviewType["VIDEO"] = "video";
|
|
13865
|
+
FilePreviewType["AUDIO"] = "audio";
|
|
13866
|
+
FilePreviewType["ARCHIVE"] = "archive";
|
|
13867
|
+
FilePreviewType["OTHER"] = "other";
|
|
13868
|
+
})(FilePreviewType || (FilePreviewType = {}));
|
|
13869
|
+
const FILE_TYPE_MAPPINGS = {
|
|
13870
|
+
'image/jpeg': FilePreviewType.IMAGE,
|
|
13871
|
+
'image/jpg': FilePreviewType.IMAGE,
|
|
13872
|
+
'image/png': FilePreviewType.IMAGE,
|
|
13873
|
+
'image/gif': FilePreviewType.IMAGE,
|
|
13874
|
+
'image/webp': FilePreviewType.IMAGE,
|
|
13875
|
+
'image/svg+xml': FilePreviewType.IMAGE,
|
|
13876
|
+
'application/pdf': FilePreviewType.PDF,
|
|
13877
|
+
'application/msword': FilePreviewType.DOCUMENT,
|
|
13878
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': FilePreviewType.DOCUMENT,
|
|
13879
|
+
'text/plain': FilePreviewType.DOCUMENT,
|
|
13880
|
+
'text/rtf': FilePreviewType.DOCUMENT,
|
|
13881
|
+
'application/vnd.ms-excel': FilePreviewType.SPREADSHEET,
|
|
13882
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': FilePreviewType.SPREADSHEET,
|
|
13883
|
+
'text/csv': FilePreviewType.SPREADSHEET,
|
|
13884
|
+
'video/mp4': FilePreviewType.VIDEO,
|
|
13885
|
+
'video/webm': FilePreviewType.VIDEO,
|
|
13886
|
+
'video/ogg': FilePreviewType.VIDEO,
|
|
13887
|
+
'video/avi': FilePreviewType.VIDEO,
|
|
13888
|
+
'video/mov': FilePreviewType.VIDEO,
|
|
13889
|
+
'audio/mp3': FilePreviewType.AUDIO,
|
|
13890
|
+
'audio/wav': FilePreviewType.AUDIO,
|
|
13891
|
+
'audio/ogg': FilePreviewType.AUDIO,
|
|
13892
|
+
'audio/mpeg': FilePreviewType.AUDIO,
|
|
13893
|
+
'application/zip': FilePreviewType.ARCHIVE,
|
|
13894
|
+
'application/x-rar-compressed': FilePreviewType.ARCHIVE,
|
|
13895
|
+
'application/x-tar': FilePreviewType.ARCHIVE,
|
|
13896
|
+
'application/gzip': FilePreviewType.ARCHIVE
|
|
13897
|
+
};
|
|
13898
|
+
function getFilePreviewType(contentType) {
|
|
13899
|
+
return FILE_TYPE_MAPPINGS[contentType] || FilePreviewType.OTHER;
|
|
13900
|
+
}
|
|
13901
|
+
|
|
13902
|
+
class FilePreviewService {
|
|
13903
|
+
fileUploadService = inject(FileUploadService);
|
|
13904
|
+
imageCache = new Map();
|
|
13905
|
+
convertFileModelToPreviewItem(fileModel) {
|
|
13906
|
+
let contentType = fileModel.type;
|
|
13907
|
+
if (!contentType && fileModel.content_type) {
|
|
13908
|
+
contentType = fileModel.content_type;
|
|
13909
|
+
}
|
|
13910
|
+
if (!contentType && fileModel.filename) {
|
|
13911
|
+
const extension = fileModel.filename.toLowerCase().split('.').pop();
|
|
13912
|
+
switch (extension) {
|
|
13913
|
+
case 'png':
|
|
13914
|
+
contentType = 'image/png';
|
|
13915
|
+
break;
|
|
13916
|
+
case 'jpg':
|
|
13917
|
+
case 'jpeg':
|
|
13918
|
+
contentType = 'image/jpeg';
|
|
13919
|
+
break;
|
|
13920
|
+
case 'gif':
|
|
13921
|
+
contentType = 'image/gif';
|
|
13922
|
+
break;
|
|
13923
|
+
case 'pdf':
|
|
13924
|
+
contentType = 'application/pdf';
|
|
13925
|
+
break;
|
|
13926
|
+
default:
|
|
13927
|
+
contentType = 'application/octet-stream';
|
|
13928
|
+
}
|
|
13929
|
+
}
|
|
13930
|
+
const previewItem = {
|
|
13931
|
+
id: fileModel.id,
|
|
13932
|
+
filename: fileModel.filename,
|
|
13933
|
+
size: fileModel.size,
|
|
13934
|
+
contentType: contentType,
|
|
13935
|
+
uploadedBy: fileModel.uploaded_by
|
|
13936
|
+
? `${fileModel.uploaded_by.first_name} ${fileModel.uploaded_by.last_name}`.trim()
|
|
13937
|
+
: 'Usuario desconocido',
|
|
13938
|
+
uploadedDate: fileModel.created_at ? new Date(fileModel.created_at) : new Date(),
|
|
13939
|
+
s3Key: fileModel.s3_key,
|
|
13940
|
+
canDownload: true,
|
|
13941
|
+
canDelete: false,
|
|
13942
|
+
fileModel: fileModel
|
|
13943
|
+
};
|
|
13944
|
+
return previewItem;
|
|
13945
|
+
}
|
|
13946
|
+
convertFileModelsToPreviewItems(fileModels) {
|
|
13947
|
+
return fileModels.map(file => this.convertFileModelToPreviewItem(file));
|
|
13948
|
+
}
|
|
13949
|
+
getFileIcon(contentType) {
|
|
13950
|
+
const fileType = getFilePreviewType(contentType);
|
|
13951
|
+
switch (fileType) {
|
|
13952
|
+
case FilePreviewType.IMAGE:
|
|
13953
|
+
return 'icon-image';
|
|
13954
|
+
default:
|
|
13955
|
+
return 'icon-file';
|
|
13956
|
+
}
|
|
13957
|
+
}
|
|
13958
|
+
formatFileSize(bytes) {
|
|
13959
|
+
if (bytes === 0)
|
|
13960
|
+
return '0 B';
|
|
13961
|
+
const k = 1024;
|
|
13962
|
+
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
13963
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
13964
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
13965
|
+
}
|
|
13966
|
+
truncateFilename(filename, maxLength = 30) {
|
|
13967
|
+
if (filename.length <= maxLength)
|
|
13968
|
+
return filename;
|
|
13969
|
+
const extension = filename.split('.').pop() || '';
|
|
13970
|
+
const nameWithoutExtension = filename.substring(0, filename.lastIndexOf('.')) || filename;
|
|
13971
|
+
const truncatedName = nameWithoutExtension.substring(0, maxLength - extension.length - 4) + '...';
|
|
13972
|
+
return extension ? `${truncatedName}.${extension}` : truncatedName;
|
|
13973
|
+
}
|
|
13974
|
+
isImage(contentType) {
|
|
13975
|
+
const result = getFilePreviewType(contentType) === FilePreviewType.IMAGE;
|
|
13976
|
+
return result;
|
|
13977
|
+
}
|
|
13978
|
+
generateThumbnailUrl(file) {
|
|
13979
|
+
if (!this.isImage(file.contentType))
|
|
13980
|
+
return null;
|
|
13981
|
+
return null;
|
|
13982
|
+
}
|
|
13983
|
+
/**
|
|
13984
|
+
* Genera una vista previa real de la imagen desde S3
|
|
13985
|
+
* @param file FilePreviewItem con los datos del archivo
|
|
13986
|
+
* @param maxWidth Ancho máximo de la vista previa (default: 300px)
|
|
13987
|
+
* @param maxHeight Alto máximo de la vista previa (default: 200px)
|
|
13988
|
+
* @returns Observable<string> con la imagen en formato base64 o null si hay error
|
|
13989
|
+
*/
|
|
13990
|
+
generateImagePreview(file, maxWidth = 300, maxHeight = 200) {
|
|
13991
|
+
if (!this.isImage(file.contentType) || !file.s3Key) {
|
|
13992
|
+
return of(null);
|
|
13993
|
+
}
|
|
13994
|
+
const cacheKey = `${file.s3Key}_${maxWidth}x${maxHeight}`;
|
|
13995
|
+
const cachedImage = this.imageCache.get(cacheKey);
|
|
13996
|
+
if (cachedImage) {
|
|
13997
|
+
return of(cachedImage);
|
|
13998
|
+
}
|
|
13999
|
+
return from(this.downloadAndProcessImage(file, maxWidth, maxHeight, cacheKey)).pipe(catchError(error => {
|
|
14000
|
+
console.error('Error generando vista previa de imagen:', error);
|
|
14001
|
+
return of(null);
|
|
14002
|
+
}));
|
|
14003
|
+
}
|
|
14004
|
+
async downloadAndProcessImage(file, maxWidth, maxHeight, cacheKey) {
|
|
14005
|
+
try {
|
|
14006
|
+
const downloadUrlObservable = this.fileUploadService.getPresignedDownloadUrl(file.s3Key);
|
|
14007
|
+
const downloadUrl = await downloadUrlObservable.toPromise();
|
|
14008
|
+
if (!downloadUrl) {
|
|
14009
|
+
console.warn('No se pudo obtener URL de descarga para:', file.s3Key);
|
|
14010
|
+
return null;
|
|
14011
|
+
}
|
|
14012
|
+
const response = await fetch(downloadUrl);
|
|
14013
|
+
if (!response.ok) {
|
|
14014
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
14015
|
+
}
|
|
14016
|
+
const blob = await response.blob();
|
|
14017
|
+
const processedImage = await this.resizeImage(blob, maxWidth, maxHeight);
|
|
14018
|
+
this.imageCache.set(cacheKey, processedImage);
|
|
14019
|
+
return processedImage;
|
|
14020
|
+
}
|
|
14021
|
+
catch (error) {
|
|
14022
|
+
console.error('Error descargando y procesando imagen:', error);
|
|
14023
|
+
return null;
|
|
14024
|
+
}
|
|
14025
|
+
}
|
|
14026
|
+
resizeImage(blob, maxWidth, maxHeight) {
|
|
14027
|
+
return new Promise((resolve, reject) => {
|
|
14028
|
+
const img = new Image();
|
|
14029
|
+
const canvas = document.createElement('canvas');
|
|
14030
|
+
const ctx = canvas.getContext('2d');
|
|
14031
|
+
if (!ctx) {
|
|
14032
|
+
reject(new Error('No se pudo obtener contexto del canvas'));
|
|
14033
|
+
return;
|
|
14034
|
+
}
|
|
14035
|
+
img.onload = () => {
|
|
14036
|
+
let { width, height } = img;
|
|
14037
|
+
const aspectRatio = width / height;
|
|
14038
|
+
if (width > height) {
|
|
14039
|
+
if (width > maxWidth) {
|
|
14040
|
+
width = maxWidth;
|
|
14041
|
+
height = width / aspectRatio;
|
|
14042
|
+
}
|
|
14043
|
+
}
|
|
14044
|
+
else {
|
|
14045
|
+
if (height > maxHeight) {
|
|
14046
|
+
height = maxHeight;
|
|
14047
|
+
width = height * aspectRatio;
|
|
14048
|
+
}
|
|
14049
|
+
}
|
|
14050
|
+
if (width > maxWidth) {
|
|
14051
|
+
width = maxWidth;
|
|
14052
|
+
height = width / aspectRatio;
|
|
14053
|
+
}
|
|
14054
|
+
if (height > maxHeight) {
|
|
14055
|
+
height = maxHeight;
|
|
14056
|
+
width = height * aspectRatio;
|
|
14057
|
+
}
|
|
14058
|
+
canvas.width = width;
|
|
14059
|
+
canvas.height = height;
|
|
14060
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
14061
|
+
const dataUrl = canvas.toDataURL('image/jpeg', 0.8);
|
|
14062
|
+
resolve(dataUrl);
|
|
14063
|
+
};
|
|
14064
|
+
img.onerror = () => {
|
|
14065
|
+
reject(new Error('Error cargando imagen'));
|
|
14066
|
+
};
|
|
14067
|
+
img.src = URL.createObjectURL(blob);
|
|
14068
|
+
});
|
|
14069
|
+
}
|
|
14070
|
+
clearImageCache() {
|
|
14071
|
+
this.imageCache.clear();
|
|
14072
|
+
}
|
|
14073
|
+
generateMultipleImagePreviews(files, maxWidth = 300, maxHeight = 200) {
|
|
14074
|
+
const results = new Map();
|
|
14075
|
+
const imageFiles = files.filter(file => this.isImage(file.contentType));
|
|
14076
|
+
if (imageFiles.length === 0) {
|
|
14077
|
+
return of(results);
|
|
14078
|
+
}
|
|
14079
|
+
return from(Promise.all(imageFiles.map(async (file) => {
|
|
14080
|
+
const preview = await this.generateImagePreview(file, maxWidth, maxHeight).toPromise();
|
|
14081
|
+
results.set(file.id.toString(), preview || null);
|
|
14082
|
+
return { id: file.id, preview };
|
|
14083
|
+
}))).pipe(catchError(error => {
|
|
14084
|
+
console.error('Error generando vistas previas múltiples:', error);
|
|
14085
|
+
return of(results);
|
|
14086
|
+
}), () => of(results));
|
|
14087
|
+
}
|
|
14088
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: FilePreviewService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
14089
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: FilePreviewService, providedIn: 'root' });
|
|
14090
|
+
}
|
|
14091
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: FilePreviewService, decorators: [{
|
|
14092
|
+
type: Injectable,
|
|
14093
|
+
args: [{
|
|
14094
|
+
providedIn: 'root'
|
|
14095
|
+
}]
|
|
14096
|
+
}] });
|
|
14097
|
+
|
|
14098
|
+
class FilePreviewComponent {
|
|
14099
|
+
filePreviewService = inject(FilePreviewService);
|
|
14100
|
+
fileModels = input([]);
|
|
14101
|
+
files = input([]);
|
|
14102
|
+
config = input({
|
|
14103
|
+
showUploadDate: true,
|
|
14104
|
+
showUploadedBy: true,
|
|
14105
|
+
showFileSize: true,
|
|
14106
|
+
allowDownload: true,
|
|
14107
|
+
allowDelete: false,
|
|
14108
|
+
maxFilenameLength: 30,
|
|
14109
|
+
dateFormat: 'dd/MM/yyyy HH:mm',
|
|
14110
|
+
emptyStateMessage: 'file_preview.no_files',
|
|
14111
|
+
emptyStateIcon: 'icon-file-not-found',
|
|
14112
|
+
gridColumns: 1,
|
|
14113
|
+
showThumbnails: true
|
|
14114
|
+
});
|
|
14115
|
+
actionTriggered = output();
|
|
14116
|
+
isModalVisible = signal(false);
|
|
14117
|
+
modalImageSrc = signal('');
|
|
14118
|
+
// modalImageAlt = signal<string>('');
|
|
14119
|
+
getDeleteButtonConfig(file) {
|
|
14120
|
+
return {
|
|
14121
|
+
type: ButtonType.ICON,
|
|
14122
|
+
context: ButtonContext.ERROR,
|
|
14123
|
+
size: ButtonSize.SMALL,
|
|
14124
|
+
text: '',
|
|
14125
|
+
icon: 'icon-trash',
|
|
14126
|
+
iconPosition: 'left',
|
|
14127
|
+
customClass: 'delete-btn'
|
|
14128
|
+
};
|
|
14129
|
+
}
|
|
14130
|
+
processedFiles = computed(() => {
|
|
14131
|
+
const directFiles = this.files();
|
|
14132
|
+
if (directFiles.length > 0) {
|
|
14133
|
+
return directFiles;
|
|
14134
|
+
}
|
|
14135
|
+
const models = this.fileModels();
|
|
14136
|
+
return this.filePreviewService.convertFileModelsToPreviewItems(models);
|
|
14137
|
+
});
|
|
14138
|
+
onAction(actionType, file) {
|
|
14139
|
+
const action = {
|
|
14140
|
+
type: actionType,
|
|
14141
|
+
file: file
|
|
14142
|
+
};
|
|
14143
|
+
this.actionTriggered.emit(action);
|
|
14144
|
+
}
|
|
14145
|
+
openImageModal(imageSrc, imageAlt) {
|
|
14146
|
+
this.modalImageSrc.set(imageSrc);
|
|
14147
|
+
// this.modalImageAlt.set(imageAlt);
|
|
14148
|
+
this.isModalVisible.set(true);
|
|
14149
|
+
}
|
|
14150
|
+
closeImageModal() {
|
|
14151
|
+
this.isModalVisible.set(false);
|
|
14152
|
+
this.modalImageSrc.set('');
|
|
14153
|
+
// this.modalImageAlt.set('');
|
|
14154
|
+
}
|
|
14155
|
+
formatDate(date) {
|
|
14156
|
+
const config = this.config();
|
|
14157
|
+
return date.toLocaleDateString('es-ES', {
|
|
14158
|
+
day: '2-digit',
|
|
14159
|
+
month: '2-digit',
|
|
14160
|
+
year: 'numeric',
|
|
14161
|
+
hour: '2-digit',
|
|
14162
|
+
minute: '2-digit'
|
|
14163
|
+
});
|
|
14164
|
+
}
|
|
14165
|
+
getGridColumns() {
|
|
14166
|
+
const columns = this.config().gridColumns || 1;
|
|
14167
|
+
return `repeat(${columns}, 1fr)`;
|
|
14168
|
+
}
|
|
14169
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: FilePreviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14170
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: FilePreviewComponent, isStandalone: true, selector: "core-file-preview", inputs: { fileModels: { classPropertyName: "fileModels", publicName: "fileModels", isSignal: true, isRequired: false, transformFunction: null }, files: { classPropertyName: "files", publicName: "files", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { actionTriggered: "actionTriggered" }, ngImport: i0, template: "<div class=\"c-attachments\">\n @if (processedFiles().length === 0) {\n <p class=\"c-placeholder u-push-t\">\n <span [class]=\"config().emptyStateIcon || 'icon-file-not-found'\"></span>\n {{ config().emptyStateMessage || 'file_preview.no_files' | translate }}\n </p>\n } @else {\n <ul class=\"c-attachments__list\">\n @for (file of processedFiles(); track file.id) {\n\n\n <li class=\"c-attachments__item\">\n\n <div class=\"c-bulleted-text\">\n @if (config().showUploadDate) {\n <time>\n {{ formatDate(file.uploadedDate) }}\n </time>\n }\n @if (config().showUploadedBy) {\n <span>\n {{ file.uploadedBy }}\n </span>\n }\n </div>\n\n <div class=\"c-attachments__holder\">\n <div class=\"c-attachments__content\">\n @if (filePreviewService.isImage(file.contentType)) {\n @if (file.thumbnailUrl) {\n <div class=\"c-attachments__pic\">\n <picture class=\"c-img-preview c-pic\">\n <img \n [src]=\"file.thumbnailUrl\" \n [alt]=\"file.filename\" \n class=\"js-img-preview\"\n (click)=\"openImageModal(file.thumbnailUrl, file.filename)\">\n </picture>\n </div>\n } @else {\n <span [class]=\"filePreviewService.getFileIcon(file.contentType)\" class=\"file-icon\"></span>\n }\n } @else {\n <span [class]=\"filePreviewService.getFileIcon(file.contentType)\" class=\"file-icon\"></span>\n }\n\n <div class=\"c-attachments__text\">\n @if (config().showFilename !== false && (!filePreviewService.isImage(file.contentType) || config().showFilename === true)) {\n @if (config().allowDownload && file.canDownload) {\n <a class=\"c-attachments__name-file c-attachments__name-file--clickable\" \n [title]=\"'Haz clic para descargar: ' + file.filename\"\n (click)=\"onAction('download', file)\">\n {{ filePreviewService.truncateFilename(file.filename, config().maxFilenameLength || 30) }}\n </a>\n } @else {\n <span class=\"c-attachments__name-file\" [title]=\"file.filename\">\n {{ filePreviewService.truncateFilename(file.filename, config().maxFilenameLength || 30) }}\n </span>\n }\n }\n @if (config().showFileSize) {\n <span class=\"c-attachments__file-size\">\n {{ filePreviewService.formatFileSize(file.size) }}\n </span>\n }\n </div>\n </div>\n\n \n\n <div class=\"c-attachments__actions u-flex u-flex--wrap\">\n @if (config().allowDelete && file.canDelete) {\n <core-generic-button \n [config]=\"getDeleteButtonConfig(file)\"\n (buttonClick)=\"onAction('delete', file)\">\n </core-generic-button>\n }\n </div>\n </div>\n </li>\n }\n </ul>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i3.TranslatePipe, name: "translate" }, { kind: "component", type: GenericButtonComponent, selector: "core-generic-button", inputs: ["config", "data"], outputs: ["buttonClick"] }] });
|
|
14171
|
+
}
|
|
14172
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: FilePreviewComponent, decorators: [{
|
|
14173
|
+
type: Component,
|
|
14174
|
+
args: [{ selector: 'core-file-preview', standalone: true, imports: [CommonModule, TranslateModule, GenericButtonComponent], template: "<div class=\"c-attachments\">\n @if (processedFiles().length === 0) {\n <p class=\"c-placeholder u-push-t\">\n <span [class]=\"config().emptyStateIcon || 'icon-file-not-found'\"></span>\n {{ config().emptyStateMessage || 'file_preview.no_files' | translate }}\n </p>\n } @else {\n <ul class=\"c-attachments__list\">\n @for (file of processedFiles(); track file.id) {\n\n\n <li class=\"c-attachments__item\">\n\n <div class=\"c-bulleted-text\">\n @if (config().showUploadDate) {\n <time>\n {{ formatDate(file.uploadedDate) }}\n </time>\n }\n @if (config().showUploadedBy) {\n <span>\n {{ file.uploadedBy }}\n </span>\n }\n </div>\n\n <div class=\"c-attachments__holder\">\n <div class=\"c-attachments__content\">\n @if (filePreviewService.isImage(file.contentType)) {\n @if (file.thumbnailUrl) {\n <div class=\"c-attachments__pic\">\n <picture class=\"c-img-preview c-pic\">\n <img \n [src]=\"file.thumbnailUrl\" \n [alt]=\"file.filename\" \n class=\"js-img-preview\"\n (click)=\"openImageModal(file.thumbnailUrl, file.filename)\">\n </picture>\n </div>\n } @else {\n <span [class]=\"filePreviewService.getFileIcon(file.contentType)\" class=\"file-icon\"></span>\n }\n } @else {\n <span [class]=\"filePreviewService.getFileIcon(file.contentType)\" class=\"file-icon\"></span>\n }\n\n <div class=\"c-attachments__text\">\n @if (config().showFilename !== false && (!filePreviewService.isImage(file.contentType) || config().showFilename === true)) {\n @if (config().allowDownload && file.canDownload) {\n <a class=\"c-attachments__name-file c-attachments__name-file--clickable\" \n [title]=\"'Haz clic para descargar: ' + file.filename\"\n (click)=\"onAction('download', file)\">\n {{ filePreviewService.truncateFilename(file.filename, config().maxFilenameLength || 30) }}\n </a>\n } @else {\n <span class=\"c-attachments__name-file\" [title]=\"file.filename\">\n {{ filePreviewService.truncateFilename(file.filename, config().maxFilenameLength || 30) }}\n </span>\n }\n }\n @if (config().showFileSize) {\n <span class=\"c-attachments__file-size\">\n {{ filePreviewService.formatFileSize(file.size) }}\n </span>\n }\n </div>\n </div>\n\n \n\n <div class=\"c-attachments__actions u-flex u-flex--wrap\">\n @if (config().allowDelete && file.canDelete) {\n <core-generic-button \n [config]=\"getDeleteButtonConfig(file)\"\n (buttonClick)=\"onAction('delete', file)\">\n </core-generic-button>\n }\n </div>\n </div>\n </li>\n }\n </ul>\n }\n</div>\n" }]
|
|
14175
|
+
}] });
|
|
14176
|
+
|
|
14177
|
+
class GenericChatComponent {
|
|
14178
|
+
chatService;
|
|
14179
|
+
elementRef = inject(ElementRef);
|
|
14180
|
+
sanitizer = inject(DomSanitizer);
|
|
14181
|
+
authService = inject(AuthService);
|
|
14182
|
+
messages = input([]);
|
|
14183
|
+
config = input({});
|
|
14184
|
+
isTyping = input(false);
|
|
14185
|
+
customTemplates = input(new Map());
|
|
14186
|
+
messagesSent = output();
|
|
14187
|
+
imageClicked = output();
|
|
14188
|
+
fileClicked = output();
|
|
14189
|
+
fileSelected = output();
|
|
14190
|
+
filesSelected = output();
|
|
14191
|
+
typingStatusChanged = output();
|
|
14192
|
+
modalClosed = output();
|
|
14193
|
+
attachmentAction = output();
|
|
14194
|
+
sendingStatusChanged = output();
|
|
14195
|
+
loadMoreMessages = output();
|
|
14196
|
+
messagesContainer = viewChild('messagesContainer');
|
|
14197
|
+
modalMessagesContainer = viewChild('modalMessagesContainer');
|
|
14198
|
+
messageInput = viewChild('messageInput');
|
|
14199
|
+
fileInput = viewChild('fileInput');
|
|
14200
|
+
currentMessage = signal('');
|
|
14201
|
+
showImageModal = signal(false);
|
|
14202
|
+
selectedImage = signal('');
|
|
14203
|
+
selectedFile = signal(null);
|
|
14204
|
+
selectedFiles = signal([]);
|
|
14205
|
+
isClosing = signal(false);
|
|
14206
|
+
isSending = signal(false);
|
|
14207
|
+
messageTypes = ChatMessageType;
|
|
14208
|
+
isLoadingMore = signal(false);
|
|
14209
|
+
hasMoreMessages = signal(true);
|
|
14210
|
+
currentPage = signal(1);
|
|
14211
|
+
hasInitialLoad = signal(false);
|
|
14212
|
+
mergedConfig = computed(() => {
|
|
14213
|
+
const config = this.chatService.mergeConfig(this.config());
|
|
14214
|
+
if (config.enableInfiniteScroll === undefined) {
|
|
14215
|
+
config.enableInfiniteScroll = true;
|
|
14216
|
+
}
|
|
14217
|
+
if (config.pageSize === undefined) {
|
|
14218
|
+
config.pageSize = 20;
|
|
14219
|
+
}
|
|
14220
|
+
if (config.loadMoreThreshold === undefined) {
|
|
14221
|
+
config.loadMoreThreshold = 100;
|
|
14222
|
+
}
|
|
14223
|
+
return config;
|
|
14224
|
+
});
|
|
14225
|
+
chronologicalMessages = computed(() => {
|
|
14226
|
+
const originalMessages = this.messages();
|
|
14227
|
+
if (originalMessages.length === 0)
|
|
14228
|
+
return originalMessages;
|
|
14229
|
+
let isChronological = true;
|
|
14230
|
+
if (originalMessages.length > 1) {
|
|
14231
|
+
const firstDate = this.parseTimestamp(originalMessages[0].timestamp);
|
|
14232
|
+
const secondDate = this.parseTimestamp(originalMessages[1].timestamp);
|
|
14233
|
+
isChronological = firstDate <= secondDate;
|
|
14234
|
+
}
|
|
14235
|
+
return isChronological ? originalMessages : [...originalMessages].reverse();
|
|
14236
|
+
});
|
|
14237
|
+
shouldShowDateSeparator = computed(() => {
|
|
14238
|
+
const messages = this.chronologicalMessages();
|
|
14239
|
+
const shouldShow = new Map();
|
|
14240
|
+
for (let i = 0; i < messages.length; i++) {
|
|
14241
|
+
const currentMessage = messages[i];
|
|
14242
|
+
const messageId = currentMessage.id;
|
|
14243
|
+
if (i === 0) {
|
|
14244
|
+
shouldShow.set(messageId, true);
|
|
14245
|
+
}
|
|
14246
|
+
else {
|
|
14247
|
+
const previousMessage = messages[i - 1];
|
|
14248
|
+
const currentDate = this.parseTimestamp(currentMessage.timestamp);
|
|
14249
|
+
const previousDate = this.parseTimestamp(previousMessage.timestamp);
|
|
14250
|
+
const isDifferentDay = currentDate.toDateString() !== previousDate.toDateString();
|
|
14251
|
+
shouldShow.set(messageId, isDifferentDay);
|
|
14252
|
+
}
|
|
14253
|
+
}
|
|
14254
|
+
return shouldShow;
|
|
14255
|
+
});
|
|
14256
|
+
trackByMessage = (index, message) => {
|
|
14257
|
+
return `${message.id}_${index}`;
|
|
14258
|
+
};
|
|
14259
|
+
trackByChronologicalMessage = (index, message) => {
|
|
14260
|
+
return `chrono_${message.id}_${index}_${message.timestamp}`;
|
|
14261
|
+
};
|
|
14262
|
+
parseTimestamp(timestamp) {
|
|
14263
|
+
if (timestamp instanceof Date) {
|
|
14264
|
+
return timestamp;
|
|
14265
|
+
}
|
|
14266
|
+
const timestampStr = timestamp.toString();
|
|
14267
|
+
const ddmmyyyyRegex = /^(\d{2})-(\d{2})-(\d{4})(\s+\d{2}:\d{2})?/;
|
|
14268
|
+
const match = timestampStr.match(ddmmyyyyRegex);
|
|
14269
|
+
if (match) {
|
|
14270
|
+
const day = parseInt(match[1], 10);
|
|
14271
|
+
const month = parseInt(match[2], 10) - 1;
|
|
14272
|
+
const year = parseInt(match[3], 10);
|
|
14273
|
+
const timeStr = match[4] || '';
|
|
14274
|
+
const date = new Date(year, month, day);
|
|
14275
|
+
if (timeStr.trim()) {
|
|
14276
|
+
const timeMatch = timeStr.trim().match(/(\d{2}):(\d{2})/);
|
|
14277
|
+
if (timeMatch) {
|
|
14278
|
+
const hours = parseInt(timeMatch[1], 10);
|
|
14279
|
+
const minutes = parseInt(timeMatch[2], 10);
|
|
14280
|
+
date.setHours(hours, minutes);
|
|
14281
|
+
}
|
|
14282
|
+
}
|
|
14283
|
+
return date;
|
|
14284
|
+
}
|
|
14285
|
+
return new Date(timestampStr);
|
|
14286
|
+
}
|
|
14287
|
+
getDateSeparatorText(timestamp) {
|
|
14288
|
+
const today = new Date();
|
|
14289
|
+
const messageDate = this.parseTimestamp(timestamp);
|
|
14290
|
+
const todayStr = today.toDateString();
|
|
14291
|
+
const messageDateStr = messageDate.toDateString();
|
|
14292
|
+
if (messageDateStr === todayStr) {
|
|
14293
|
+
return 'Hoy';
|
|
14294
|
+
}
|
|
14295
|
+
const yesterday = new Date(today);
|
|
14296
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
14297
|
+
if (messageDateStr === yesterday.toDateString()) {
|
|
14298
|
+
return 'Ayer';
|
|
14299
|
+
}
|
|
14300
|
+
return messageDate.toLocaleDateString('es-ES', {
|
|
14301
|
+
weekday: 'long',
|
|
14302
|
+
year: 'numeric',
|
|
14303
|
+
month: 'long',
|
|
14304
|
+
day: 'numeric'
|
|
14305
|
+
});
|
|
14306
|
+
}
|
|
14307
|
+
closeButtonConfig = computed(() => ({
|
|
14308
|
+
type: ButtonType.ICON,
|
|
14309
|
+
icon: 'icon-cross',
|
|
14310
|
+
ariaLabel: 'modal.close',
|
|
14311
|
+
customClass: 'c-modal__close'
|
|
14312
|
+
}));
|
|
14313
|
+
getFilePreviewConfig(attachments) {
|
|
14314
|
+
if (!attachments || attachments.length === 0) {
|
|
14315
|
+
return {};
|
|
14316
|
+
}
|
|
14317
|
+
const hasNonImageFiles = attachments.some(file => !file.contentType || !file.contentType.startsWith('image/'));
|
|
14318
|
+
return {
|
|
14319
|
+
showUploadDate: false,
|
|
14320
|
+
showUploadedBy: false,
|
|
14321
|
+
showFileSize: hasNonImageFiles,
|
|
14322
|
+
allowDownload: hasNonImageFiles,
|
|
14323
|
+
allowDelete: false,
|
|
14324
|
+
maxFilenameLength: 25,
|
|
14325
|
+
emptyStateMessage: '',
|
|
14326
|
+
emptyStateIcon: '',
|
|
14327
|
+
showThumbnails: true,
|
|
14328
|
+
gridColumns: 1,
|
|
14329
|
+
showFilename: hasNonImageFiles
|
|
14330
|
+
};
|
|
14331
|
+
}
|
|
14332
|
+
separateFilesByType(attachments) {
|
|
14333
|
+
if (!attachments || attachments.length === 0) {
|
|
14334
|
+
return { images: [], files: [] };
|
|
14335
|
+
}
|
|
14336
|
+
const images = attachments.filter(file => file.contentType && file.contentType.startsWith('image/'));
|
|
14337
|
+
const files = attachments.filter(file => !file.contentType || !file.contentType.startsWith('image/'));
|
|
14338
|
+
return { images, files };
|
|
14339
|
+
}
|
|
14340
|
+
getImagePreviewConfig(images) {
|
|
14341
|
+
return {
|
|
14342
|
+
showUploadDate: false,
|
|
14343
|
+
showUploadedBy: false,
|
|
14344
|
+
showFileSize: false,
|
|
14345
|
+
allowDownload: true,
|
|
14346
|
+
allowDelete: false,
|
|
14347
|
+
maxFilenameLength: 25,
|
|
14348
|
+
emptyStateMessage: '',
|
|
14349
|
+
emptyStateIcon: '',
|
|
14350
|
+
showThumbnails: true,
|
|
14351
|
+
gridColumns: images.length,
|
|
14352
|
+
showFilename: false,
|
|
14353
|
+
containerClass: 'file-preview-images-inline'
|
|
14354
|
+
};
|
|
14355
|
+
}
|
|
14356
|
+
getFileOnlyPreviewConfig(files) {
|
|
14357
|
+
return {
|
|
14358
|
+
showUploadDate: false,
|
|
14359
|
+
showUploadedBy: false,
|
|
14360
|
+
showFileSize: true,
|
|
14361
|
+
allowDownload: true,
|
|
14362
|
+
allowDelete: false,
|
|
14363
|
+
maxFilenameLength: 25,
|
|
14364
|
+
emptyStateMessage: '',
|
|
14365
|
+
emptyStateIcon: '',
|
|
14366
|
+
showThumbnails: false,
|
|
14367
|
+
gridColumns: 1,
|
|
14368
|
+
showFilename: true,
|
|
14369
|
+
containerClass: 'file-preview-files-column'
|
|
14370
|
+
};
|
|
14371
|
+
}
|
|
14372
|
+
isImageFile(file) {
|
|
14373
|
+
const imageTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml'];
|
|
14374
|
+
const isImage = imageTypes.includes(file.type);
|
|
14375
|
+
return isImage;
|
|
14376
|
+
}
|
|
14377
|
+
isImageFilePreview(file) {
|
|
14378
|
+
const imageTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml'];
|
|
14379
|
+
return imageTypes.includes(file.contentType);
|
|
14380
|
+
}
|
|
14381
|
+
filePreviewUrls = new Map();
|
|
14382
|
+
createImagePreview(file) {
|
|
14383
|
+
if (this.filePreviewUrls.has(file)) {
|
|
14384
|
+
const existingUrl = this.filePreviewUrls.get(file);
|
|
14385
|
+
return existingUrl;
|
|
14386
|
+
}
|
|
14387
|
+
try {
|
|
14388
|
+
const url = URL.createObjectURL(file);
|
|
14389
|
+
this.filePreviewUrls.set(file, url);
|
|
14390
|
+
this.imagePreviewUrls.add(url);
|
|
14391
|
+
return url;
|
|
14392
|
+
}
|
|
14393
|
+
catch (error) {
|
|
14394
|
+
console.error('Error creating image preview:', error);
|
|
14395
|
+
return '/images/static/img-placeholder.svg';
|
|
14396
|
+
}
|
|
14397
|
+
}
|
|
14398
|
+
getImagePreviewUrl(file) {
|
|
14399
|
+
if (!file || !this.isImageFile(file)) {
|
|
14400
|
+
return '/images/static/img-placeholder.svg';
|
|
14401
|
+
}
|
|
14402
|
+
return this.createImagePreview(file);
|
|
14403
|
+
}
|
|
14404
|
+
getAttachmentPreviewUrl(file) {
|
|
14405
|
+
if (!file) {
|
|
14406
|
+
return null;
|
|
14407
|
+
}
|
|
14408
|
+
if (this.isImageFilePreview(file) && file.thumbnailUrl) {
|
|
14409
|
+
return file.thumbnailUrl;
|
|
14410
|
+
}
|
|
14411
|
+
return file.url || file.src || null;
|
|
14412
|
+
}
|
|
14413
|
+
getAttachmentPlaceholderUrl(file) {
|
|
14414
|
+
if (!file) {
|
|
14415
|
+
return '/images/static/img-placeholder.svg';
|
|
14416
|
+
}
|
|
14417
|
+
return '/images/static/img-placeholder.svg';
|
|
14418
|
+
}
|
|
14419
|
+
trackByFile(index, file) {
|
|
14420
|
+
return file.name + file.size + file.lastModified;
|
|
14421
|
+
}
|
|
14422
|
+
onImageLoad(fileName) {
|
|
14423
|
+
// console.log('Image loaded successfully:', fileName);
|
|
14424
|
+
}
|
|
14425
|
+
onImageError(fileName, event) {
|
|
14426
|
+
console.error('Image failed to load:', fileName, event);
|
|
14427
|
+
}
|
|
14428
|
+
openImageModal(imageUrl, filename) {
|
|
14429
|
+
this.selectedImage.set(imageUrl);
|
|
14430
|
+
this.showImageModal.set(true);
|
|
14431
|
+
}
|
|
14432
|
+
convertAttachmentsToFilePreviewItems(attachments) {
|
|
14433
|
+
return attachments.map((file, index) => ({
|
|
14434
|
+
id: file.id || `attachment_${index}`,
|
|
14435
|
+
filename: file.filename || `archivo_${index}`,
|
|
14436
|
+
size: file.size || 0,
|
|
14437
|
+
contentType: file.contentType || 'application/octet-stream',
|
|
14438
|
+
uploadedBy: file.uploadedBy || 'Usuario',
|
|
14439
|
+
uploadedDate: file.uploadedDate ? new Date(file.uploadedDate) : new Date(),
|
|
14440
|
+
s3Key: file.s3Key,
|
|
14441
|
+
thumbnailUrl: this.isImageFilePreview(file) ? file.thumbnailUrl : undefined,
|
|
14442
|
+
canDownload: true,
|
|
14443
|
+
canDelete: false,
|
|
14444
|
+
fileModel: file
|
|
14445
|
+
}));
|
|
14446
|
+
}
|
|
14447
|
+
getChatFilePreviewConfig() {
|
|
14448
|
+
return {
|
|
14449
|
+
showUploadDate: false,
|
|
14450
|
+
showUploadedBy: false,
|
|
14451
|
+
showFileSize: false,
|
|
14452
|
+
allowDownload: true,
|
|
14453
|
+
allowDelete: false,
|
|
14454
|
+
maxFilenameLength: 20,
|
|
14455
|
+
emptyStateMessage: '',
|
|
14456
|
+
emptyStateIcon: '',
|
|
14457
|
+
showThumbnails: true,
|
|
14458
|
+
gridColumns: 1,
|
|
14459
|
+
showFilename: true
|
|
14460
|
+
};
|
|
14461
|
+
}
|
|
14462
|
+
getImageAttachments(attachments) {
|
|
14463
|
+
return attachments.filter(file => this.isImageFilePreview(file));
|
|
14464
|
+
}
|
|
14465
|
+
getNonImageAttachments(attachments) {
|
|
14466
|
+
return attachments.filter(file => !this.isImageFilePreview(file));
|
|
14467
|
+
}
|
|
14468
|
+
getSelectedImageFiles() {
|
|
14469
|
+
return this.selectedFiles().filter(file => this.isImageFile(file));
|
|
14470
|
+
}
|
|
14471
|
+
getSelectedNonImageFiles() {
|
|
14472
|
+
return this.selectedFiles().filter(file => !this.isImageFile(file));
|
|
14473
|
+
}
|
|
14474
|
+
getSelectedFileIndex(file) {
|
|
14475
|
+
return this.selectedFiles().indexOf(file);
|
|
14476
|
+
}
|
|
14477
|
+
convertSelectedFilesToPreviewItems(files) {
|
|
14478
|
+
return files.map((file, index) => ({
|
|
14479
|
+
id: `selected_${index}_${file.name}`,
|
|
14480
|
+
filename: file.name,
|
|
14481
|
+
size: file.size,
|
|
14482
|
+
contentType: file.type || 'application/octet-stream',
|
|
14483
|
+
uploadedBy: 'Usuario',
|
|
14484
|
+
uploadedDate: new Date(),
|
|
14485
|
+
thumbnailUrl: this.isImageFile(file) ? this.createImagePreview(file) : undefined,
|
|
14486
|
+
canDownload: false,
|
|
14487
|
+
canDelete: true,
|
|
14488
|
+
fileModel: file
|
|
14489
|
+
}));
|
|
14490
|
+
}
|
|
14491
|
+
getChatSelectedFilesConfig() {
|
|
14492
|
+
return {
|
|
14493
|
+
showUploadDate: false,
|
|
14494
|
+
showUploadedBy: false,
|
|
14495
|
+
showFileSize: true,
|
|
14496
|
+
allowDownload: false,
|
|
14497
|
+
allowDelete: true,
|
|
14498
|
+
maxFilenameLength: 15,
|
|
14499
|
+
emptyStateMessage: '',
|
|
14500
|
+
emptyStateIcon: '',
|
|
14501
|
+
showThumbnails: false,
|
|
14502
|
+
gridColumns: 1,
|
|
14503
|
+
showFilename: true
|
|
14504
|
+
};
|
|
14505
|
+
}
|
|
14506
|
+
onSelectedFileAction(action) {
|
|
14507
|
+
if (action.type === FilePreviewActionType.DELETE) {
|
|
14508
|
+
const files = this.selectedFiles();
|
|
14509
|
+
const fileIndex = files.findIndex(file => file.name === action.file.filename && file.size === action.file.size);
|
|
14510
|
+
if (fileIndex !== -1) {
|
|
14511
|
+
this.removeSelectedFile(fileIndex);
|
|
14512
|
+
}
|
|
14513
|
+
}
|
|
14514
|
+
}
|
|
14515
|
+
onFilePreviewAction(action, message) {
|
|
14516
|
+
this.onAttachmentAction(action.type, action.file, message);
|
|
14517
|
+
}
|
|
14518
|
+
imagePreviewUrls = new Set();
|
|
14519
|
+
addImagePreviewUrl(url) {
|
|
14520
|
+
this.imagePreviewUrls.add(url);
|
|
14521
|
+
}
|
|
14522
|
+
cleanupImagePreviewUrls() {
|
|
14523
|
+
this.imagePreviewUrls.forEach(url => URL.revokeObjectURL(url));
|
|
14524
|
+
this.imagePreviewUrls.clear();
|
|
14525
|
+
this.filePreviewUrls.clear();
|
|
14526
|
+
}
|
|
14527
|
+
typingTimer;
|
|
14528
|
+
shouldScrollToBottom = true;
|
|
14529
|
+
ghostTextarea = null;
|
|
14530
|
+
scrollSentinel = null;
|
|
14531
|
+
shouldInitializeInfiniteScroll = signal(false);
|
|
14532
|
+
shouldAutoScroll = signal(true);
|
|
14533
|
+
isLoadingMoreMessages = signal(false);
|
|
14534
|
+
scrollSentinelElement;
|
|
14535
|
+
constructor(chatService) {
|
|
14536
|
+
this.chatService = chatService;
|
|
14537
|
+
afterNextRender(() => {
|
|
14538
|
+
if (!this.hasInitialLoad() && this.messages().length > 0) {
|
|
14539
|
+
this.hasInitialLoad.set(true);
|
|
14540
|
+
setTimeout(() => {
|
|
14541
|
+
this.scrollToBottom();
|
|
14542
|
+
}, 300);
|
|
14543
|
+
}
|
|
14544
|
+
setTimeout(() => {
|
|
14545
|
+
if (this.mergedConfig().enableInfiniteScroll) {
|
|
14546
|
+
this.initializeInfiniteScroll();
|
|
14547
|
+
}
|
|
14548
|
+
}, 100);
|
|
14549
|
+
});
|
|
14550
|
+
effect(() => {
|
|
14551
|
+
const messages = this.messages();
|
|
14552
|
+
if (messages.length > 0 && !this.hasInitialLoad()) {
|
|
14553
|
+
this.hasInitialLoad.set(true);
|
|
14554
|
+
setTimeout(() => {
|
|
14555
|
+
this.scrollToBottom();
|
|
14556
|
+
}, 300);
|
|
14557
|
+
}
|
|
14558
|
+
else if (messages.length > 0 && this.shouldAutoScroll() && !this.isLoadingMoreMessages()) {
|
|
14559
|
+
const hasSendingMessage = messages.some(msg => msg.isSending === true);
|
|
14560
|
+
if (hasSendingMessage) {
|
|
14561
|
+
setTimeout(() => {
|
|
14562
|
+
this.scrollToBottom();
|
|
14563
|
+
}, 300);
|
|
14564
|
+
}
|
|
14565
|
+
}
|
|
14566
|
+
});
|
|
14567
|
+
effect(() => {
|
|
14568
|
+
this.shouldInitializeInfiniteScroll.set(!!this.mergedConfig().enableInfiniteScroll);
|
|
14569
|
+
});
|
|
14570
|
+
}
|
|
14571
|
+
initializeInfiniteScroll() {
|
|
14572
|
+
if (!this.scrollSentinelElement?.nativeElement || !this.mergedConfig().enableInfiniteScroll) {
|
|
14573
|
+
return;
|
|
14574
|
+
}
|
|
14575
|
+
if (this.scrollSentinel) {
|
|
14576
|
+
this.scrollSentinel.disconnect();
|
|
14577
|
+
}
|
|
14578
|
+
const options = {
|
|
14579
|
+
root: this.mergedConfig().modalMode ? this.modalMessagesContainer()?.nativeElement : this.messagesContainer()?.nativeElement,
|
|
14580
|
+
rootMargin: `${this.mergedConfig().loadMoreThreshold}px 0px 0px 0px`,
|
|
14581
|
+
threshold: 0
|
|
14582
|
+
};
|
|
14583
|
+
this.scrollSentinel = new IntersectionObserver((entries) => {
|
|
14584
|
+
entries.forEach(entry => {
|
|
14585
|
+
if (entry.isIntersecting && !this.isLoadingMore() && this.hasMoreMessages()) {
|
|
14586
|
+
this.loadMoreMessagesHandler();
|
|
14587
|
+
}
|
|
14588
|
+
});
|
|
14589
|
+
}, options);
|
|
14590
|
+
this.scrollSentinel.observe(this.scrollSentinelElement.nativeElement);
|
|
14591
|
+
}
|
|
14592
|
+
loadMoreMessagesHandler() {
|
|
14593
|
+
if (this.isLoadingMore() || !this.hasMoreMessages()) {
|
|
14594
|
+
return;
|
|
14595
|
+
}
|
|
14596
|
+
this.isLoadingMore.set(true);
|
|
14597
|
+
this.isLoadingMoreMessages.set(true);
|
|
14598
|
+
this.shouldAutoScroll.set(false);
|
|
14599
|
+
this.loadMoreMessages.emit();
|
|
14600
|
+
}
|
|
14601
|
+
onMessagesLoaded(paginationInfo) {
|
|
14602
|
+
this.currentPage.set(paginationInfo.page);
|
|
14603
|
+
this.hasMoreMessages.set(paginationInfo.page < paginationInfo.page_count);
|
|
14604
|
+
this.isLoadingMore.set(false);
|
|
14605
|
+
this.isLoadingMoreMessages.set(false);
|
|
14606
|
+
this.shouldAutoScroll.set(true);
|
|
14607
|
+
const container = this.mergedConfig().modalMode ? this.modalMessagesContainer() : this.messagesContainer();
|
|
14608
|
+
if (container) {
|
|
14609
|
+
const element = container.nativeElement;
|
|
14610
|
+
const previousScrollHeight = element.scrollHeight;
|
|
14611
|
+
setTimeout(() => {
|
|
14612
|
+
const newScrollHeight = element.scrollHeight;
|
|
14613
|
+
const scrollDiff = newScrollHeight - previousScrollHeight;
|
|
14614
|
+
element.scrollTop = element.scrollTop + scrollDiff;
|
|
14615
|
+
}, 0);
|
|
14616
|
+
}
|
|
14617
|
+
}
|
|
14618
|
+
ngOnDestroy() {
|
|
14619
|
+
if (this.typingTimer) {
|
|
14620
|
+
clearTimeout(this.typingTimer);
|
|
14621
|
+
}
|
|
14622
|
+
if (this.ghostTextarea && this.ghostTextarea.parentNode) {
|
|
14623
|
+
this.ghostTextarea.parentNode.removeChild(this.ghostTextarea);
|
|
14624
|
+
this.ghostTextarea = null;
|
|
14625
|
+
}
|
|
14626
|
+
if (this.scrollSentinel) {
|
|
14627
|
+
this.scrollSentinel.disconnect();
|
|
14628
|
+
this.scrollSentinel = null;
|
|
14629
|
+
}
|
|
14630
|
+
this.cleanupImagePreviewUrls();
|
|
14631
|
+
}
|
|
14632
|
+
sendMessage() {
|
|
14633
|
+
const message = this.currentMessage().trim();
|
|
14634
|
+
const hasFiles = this.selectedFiles().length > 0;
|
|
14635
|
+
if (!message && !hasFiles)
|
|
14636
|
+
return;
|
|
14637
|
+
this.isSending.set(true);
|
|
14638
|
+
this.sendingStatusChanged.emit(true);
|
|
14639
|
+
const newMessage = {
|
|
14640
|
+
id: this.generateMessageId(),
|
|
14641
|
+
content: message || '',
|
|
14642
|
+
type: ChatMessageType.TEXT,
|
|
14643
|
+
position: ChatMessagePosition.RIGHT,
|
|
14644
|
+
timestamp: DateUtility.getCurrentUruguayTime('YYYY-MM-DD HH:mm'),
|
|
14645
|
+
sender: {
|
|
14646
|
+
id: this.mergedConfig().currentUserId || 'current-user',
|
|
14647
|
+
name: this.authService.getLoggedStartData()?.name || 'You'
|
|
14648
|
+
},
|
|
14649
|
+
isSending: true,
|
|
14650
|
+
hasError: false
|
|
14651
|
+
};
|
|
14652
|
+
if (hasFiles) {
|
|
14653
|
+
newMessage.attachmentIds = this.selectedFiles().map(file => file.name);
|
|
14654
|
+
}
|
|
14655
|
+
this.messagesSent.emit(newMessage);
|
|
14656
|
+
this.currentMessage.set('');
|
|
14657
|
+
this.clearSelectedFiles();
|
|
14658
|
+
this.shouldScrollToBottom = true;
|
|
14659
|
+
this.shouldAutoScroll.set(true);
|
|
14660
|
+
setTimeout(() => {
|
|
14661
|
+
this.scrollToBottom();
|
|
14662
|
+
}, 100);
|
|
14663
|
+
}
|
|
14664
|
+
onImageClick(message) {
|
|
14665
|
+
if (message.imageData?.url) {
|
|
14666
|
+
this.selectedImage.set(message.imageData.url);
|
|
14667
|
+
this.showImageModal.set(true);
|
|
14668
|
+
}
|
|
14669
|
+
this.imageClicked.emit(message);
|
|
14670
|
+
}
|
|
14671
|
+
onMultipleImageClick(imageData, message) {
|
|
14672
|
+
this.selectedImage.set(imageData.url);
|
|
14673
|
+
this.showImageModal.set(true);
|
|
14674
|
+
this.imageClicked.emit(message);
|
|
14675
|
+
}
|
|
14676
|
+
closeImageModal() {
|
|
14677
|
+
this.showImageModal.set(false);
|
|
14678
|
+
this.selectedImage.set('');
|
|
14679
|
+
}
|
|
14680
|
+
closeModal() {
|
|
14681
|
+
this.isClosing.set(true);
|
|
14682
|
+
const overlay = this.elementRef.nativeElement.querySelector('.c-modal__overlay');
|
|
14683
|
+
const onAnimationEnd = (e) => {
|
|
14684
|
+
if (e.target === overlay && this.isClosing()) {
|
|
14685
|
+
this.isClosing.set(false);
|
|
14686
|
+
this.modalClosed.emit();
|
|
14687
|
+
overlay.removeEventListener('animationend', onAnimationEnd);
|
|
14688
|
+
}
|
|
14689
|
+
};
|
|
14690
|
+
overlay.addEventListener('animationend', onAnimationEnd);
|
|
14691
|
+
}
|
|
14692
|
+
handleKeyboardEvent(event) {
|
|
14693
|
+
if (event.key === 'Escape' && this.showImageModal()) {
|
|
14694
|
+
this.closeImageModal();
|
|
14695
|
+
}
|
|
14696
|
+
}
|
|
14697
|
+
onFileClick(message) {
|
|
14698
|
+
this.fileClicked.emit(message);
|
|
14699
|
+
}
|
|
14700
|
+
onTyping() {
|
|
14701
|
+
if (!this.mergedConfig().showTypingIndicator)
|
|
14702
|
+
return;
|
|
14703
|
+
this.typingStatusChanged.emit(true);
|
|
14704
|
+
clearTimeout(this.typingTimer);
|
|
14705
|
+
this.typingTimer = setTimeout(() => {
|
|
14706
|
+
this.typingStatusChanged.emit(false);
|
|
14707
|
+
}, 1000);
|
|
14708
|
+
}
|
|
14709
|
+
updateCurrentMessage(value) {
|
|
14710
|
+
this.currentMessage.set(value);
|
|
14711
|
+
}
|
|
14712
|
+
onTextareaInput(event) {
|
|
14713
|
+
const textarea = event.target;
|
|
14714
|
+
this.autoResizeTextarea(textarea);
|
|
14715
|
+
this.updateCurrentMessage(textarea.value);
|
|
14716
|
+
this.onTyping();
|
|
14717
|
+
}
|
|
14718
|
+
autoResizeTextarea(textarea) {
|
|
14719
|
+
if (!this.ghostTextarea) {
|
|
14720
|
+
this.ghostTextarea = document.createElement('textarea');
|
|
14721
|
+
this.ghostTextarea.style.position = 'absolute';
|
|
14722
|
+
this.ghostTextarea.style.visibility = 'hidden';
|
|
14723
|
+
this.ghostTextarea.style.height = 'auto';
|
|
14724
|
+
this.ghostTextarea.style.top = '-9999px';
|
|
14725
|
+
document.body.appendChild(this.ghostTextarea);
|
|
14726
|
+
}
|
|
14727
|
+
if (textarea.value.trim() === '') {
|
|
14728
|
+
textarea.style.height = 'inherit';
|
|
14729
|
+
return;
|
|
14730
|
+
}
|
|
14731
|
+
const style = window.getComputedStyle(textarea);
|
|
14732
|
+
const properties = [
|
|
14733
|
+
'width', 'fontSize', 'fontFamily', 'fontWeight',
|
|
14734
|
+
'lineHeight', 'letterSpacing', 'padding',
|
|
14735
|
+
'border', 'boxSizing', 'whiteSpace',
|
|
14736
|
+
'wordWrap', 'overflowWrap'
|
|
14737
|
+
];
|
|
14738
|
+
properties.forEach(prop => {
|
|
14739
|
+
this.ghostTextarea.style[prop] = style.getPropertyValue(prop);
|
|
14740
|
+
});
|
|
14741
|
+
this.ghostTextarea.value = textarea.value;
|
|
14742
|
+
this.ghostTextarea.style.height = 'auto';
|
|
14743
|
+
textarea.style.height = 'auto';
|
|
14744
|
+
const minHeight = parseFloat(style.minHeight) || 0;
|
|
14745
|
+
const height = Math.max(this.ghostTextarea.scrollHeight, minHeight);
|
|
14746
|
+
textarea.style.height = height + 'px';
|
|
14747
|
+
}
|
|
14748
|
+
openFileSelector() {
|
|
14749
|
+
const fileInput = this.fileInput();
|
|
14750
|
+
if (fileInput) {
|
|
14751
|
+
fileInput.nativeElement.click();
|
|
14752
|
+
}
|
|
14753
|
+
}
|
|
14754
|
+
onFileSelected(event) {
|
|
14755
|
+
const files = Array.from(event.target.files);
|
|
14756
|
+
if (files && files.length > 0) {
|
|
14757
|
+
this.selectedFiles.set(files);
|
|
14758
|
+
this.filesSelected.emit(files);
|
|
14759
|
+
if (files.length === 1) {
|
|
14760
|
+
this.selectedFile.set(files[0]);
|
|
14761
|
+
this.fileSelected.emit(files[0]);
|
|
14762
|
+
}
|
|
14763
|
+
}
|
|
14764
|
+
}
|
|
14765
|
+
clearSelectedFile() {
|
|
14766
|
+
this.selectedFile.set(null);
|
|
14767
|
+
const fileInput = this.fileInput();
|
|
14768
|
+
if (fileInput) {
|
|
14769
|
+
fileInput.nativeElement.value = '';
|
|
14770
|
+
}
|
|
14771
|
+
}
|
|
14772
|
+
clearSelectedFiles() {
|
|
14773
|
+
this.cleanupImagePreviewUrls();
|
|
14774
|
+
this.selectedFiles.set([]);
|
|
14775
|
+
this.clearSelectedFile();
|
|
14776
|
+
}
|
|
14777
|
+
removeSelectedFile(index) {
|
|
14778
|
+
const currentFiles = this.selectedFiles();
|
|
14779
|
+
const fileToRemove = currentFiles[index];
|
|
14780
|
+
if (fileToRemove && this.isImageFile(fileToRemove)) {
|
|
14781
|
+
const previewUrl = this.filePreviewUrls.get(fileToRemove);
|
|
14782
|
+
if (previewUrl) {
|
|
14783
|
+
URL.revokeObjectURL(previewUrl);
|
|
14784
|
+
this.imagePreviewUrls.delete(previewUrl);
|
|
14785
|
+
this.filePreviewUrls.delete(fileToRemove);
|
|
14786
|
+
}
|
|
14787
|
+
}
|
|
14788
|
+
const newFiles = currentFiles.filter((_, i) => i !== index);
|
|
14789
|
+
this.selectedFiles.set(newFiles);
|
|
14790
|
+
if (newFiles.length === 0) {
|
|
14791
|
+
this.clearSelectedFile();
|
|
14792
|
+
}
|
|
14793
|
+
else if (newFiles.length === 1) {
|
|
14794
|
+
this.selectedFile.set(newFiles[0]);
|
|
14795
|
+
}
|
|
14796
|
+
}
|
|
14797
|
+
toggleEmojiPicker() {
|
|
14798
|
+
console.log('Toggle emoji picker');
|
|
14799
|
+
}
|
|
14800
|
+
getCustomTemplate(message) {
|
|
14801
|
+
if (message.customTemplate) {
|
|
14802
|
+
return message.customTemplate;
|
|
14803
|
+
}
|
|
14804
|
+
const templates = this.customTemplates();
|
|
14805
|
+
if (message.customData?.templateKey && templates.has(message.customData.templateKey)) {
|
|
14806
|
+
return templates.get(message.customData.templateKey) || null;
|
|
14807
|
+
}
|
|
14808
|
+
return null;
|
|
14809
|
+
}
|
|
14810
|
+
formatFileSize(bytes) {
|
|
14811
|
+
if (bytes === 0)
|
|
14812
|
+
return '0 Bytes';
|
|
14813
|
+
const k = 1024;
|
|
14814
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
14815
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
14816
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
14817
|
+
}
|
|
14818
|
+
scrollToBottom() {
|
|
14819
|
+
try {
|
|
14820
|
+
const isModal = this.mergedConfig().modalMode && this.mergedConfig().isModalOpen;
|
|
14821
|
+
const container = isModal ? this.modalMessagesContainer() : this.messagesContainer();
|
|
14822
|
+
if (container && this.mergedConfig().autoScroll) {
|
|
14823
|
+
const element = container.nativeElement;
|
|
14824
|
+
element.scrollTop = element.scrollHeight - element.clientHeight;
|
|
14825
|
+
}
|
|
14826
|
+
}
|
|
14827
|
+
catch (err) {
|
|
14828
|
+
console.error('Error scrolling to bottom:', err);
|
|
14829
|
+
}
|
|
14830
|
+
}
|
|
14831
|
+
generateMessageId() {
|
|
14832
|
+
return 'msg_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
|
14833
|
+
}
|
|
14834
|
+
onAttachmentAction(action, file, message) {
|
|
14835
|
+
this.attachmentAction.emit({ action, file, message });
|
|
14836
|
+
}
|
|
14837
|
+
onMessageSentSuccess() {
|
|
14838
|
+
this.isSending.set(false);
|
|
14839
|
+
this.sendingStatusChanged.emit(false);
|
|
14840
|
+
this.shouldAutoScroll.set(true);
|
|
14841
|
+
setTimeout(() => {
|
|
14842
|
+
this.scrollToBottom();
|
|
14843
|
+
}, 100);
|
|
14844
|
+
}
|
|
14845
|
+
onMessageSentError() {
|
|
14846
|
+
this.isSending.set(false);
|
|
14847
|
+
this.sendingStatusChanged.emit(false);
|
|
14848
|
+
this.shouldAutoScroll.set(true);
|
|
14849
|
+
setTimeout(() => {
|
|
14850
|
+
this.scrollToBottom();
|
|
14851
|
+
}, 100);
|
|
14852
|
+
}
|
|
14853
|
+
get isCurrentlySending() {
|
|
14854
|
+
return this.isSending();
|
|
14855
|
+
}
|
|
14856
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: GenericChatComponent, deps: [{ token: GenericChatService }], target: i0.ɵɵFactoryTarget.Component });
|
|
14857
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: GenericChatComponent, isStandalone: true, selector: "app-generic-chat", inputs: { messages: { classPropertyName: "messages", publicName: "messages", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, isTyping: { classPropertyName: "isTyping", publicName: "isTyping", isSignal: true, isRequired: false, transformFunction: null }, customTemplates: { classPropertyName: "customTemplates", publicName: "customTemplates", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { messagesSent: "messagesSent", imageClicked: "imageClicked", fileClicked: "fileClicked", fileSelected: "fileSelected", filesSelected: "filesSelected", typingStatusChanged: "typingStatusChanged", modalClosed: "modalClosed", attachmentAction: "attachmentAction", sendingStatusChanged: "sendingStatusChanged", loadMoreMessages: "loadMoreMessages" }, host: { listeners: { "document:keydown": "handleKeyboardEvent($event)" } }, viewQueries: [{ propertyName: "messagesContainer", first: true, predicate: ["messagesContainer"], descendants: true, isSignal: true }, { propertyName: "modalMessagesContainer", first: true, predicate: ["modalMessagesContainer"], descendants: true, isSignal: true }, { propertyName: "messageInput", first: true, predicate: ["messageInput"], descendants: true, isSignal: true }, { propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }, { propertyName: "scrollSentinelElement", first: true, predicate: ["scrollSentinel"], descendants: true, read: ElementRef }], ngImport: i0, template: "@if (mergedConfig().modalMode && mergedConfig().isModalOpen) {\n<div class=\"c-services-chat c-modal is-visible\" [class.is-closing]=\"isClosing()\">\n <div class=\"c-modal__overlay\" (click)=\"closeModal()\"></div>\n <div class=\"c-modal__holder\">\n <div class=\"c-modal__header\">\n <p>\n @if (mergedConfig().modalTitle) {\n <span>{{ mergedConfig().modalTitle }}</span>\n }\n @if (mergedConfig().modalSubtitle) {\n <time>{{ mergedConfig().modalSubtitle }}</time>\n }\n </p>\n <core-generic-button [config]=\"closeButtonConfig()\" (buttonClick)=\"closeModal()\">\n </core-generic-button>\n </div>\n\n <div class=\"c-modal__body c-chat-body\" #modalMessagesContainer>\n <!-- Sentinel para infinite scroll -->\n @if (mergedConfig().enableInfiniteScroll && hasMoreMessages()) {\n <div #scrollSentinel class=\"c-chat-scroll-sentinel\" style=\"height: 1px; margin-top: 10px;\">\n @if (isLoadingMore()) {\n <div class=\"c-chat-loading-more\">\n <span class=\"c-chat-dots\">\n <span></span> <span></span> <span></span>\n </span>\n </div>\n }\n </div>\n }\n \n <!-- <div class=\"c-chat-body__holder\"> -->\n @for (message of chronologicalMessages(); track trackByChronologicalMessage($index, message)) {\n\n @if (shouldShowDateSeparator().get(message.id)) {\n <time class=\"c-chat-date\" [attr.datetime]=\"message.timestamp\">\n {{ getDateSeparatorText(message.timestamp) }}\n </time>\n }\n\n <div class=\"c-chat-bubble\" [ngClass]=\"message.position === 'right' ? 'c-chat-bubble--right' : ''\">\n <!-- @if (mergedConfig().showSenderNames && message.sender?.name) {\n <div class=\"c-chat-bubble__sender\">{{ message.sender?.name }}</div>\n } -->\n\n <div class=\"c-chat-bubble__content\">\n @if (chatService.shouldUseCustomTemplate(message, mergedConfig())) {\n <ng-container *ngTemplateOutlet=\"\n getCustomTemplate(message);\n context: { $implicit: message }\n \">\n </ng-container>\n }\n @else if (message.type === messageTypes.TEXT) {\n @for (paragraph of message.content.split('\\n\\n'); track $index) {\n @if (paragraph.trim()) {\n <p [innerHTML]=\"paragraph.trim()\"></p>\n }\n }\n }\n @else if (message.type === messageTypes.SYSTEM || message.type === messageTypes.NOTIFICATION) {\n <p>{{ message.content }}</p>\n }\n\n @if (message.isSending) {\n <div class=\"c-chat-sending-indicator\">\n <span class=\"c-chat-dots\">\n <span></span> <span></span> <span></span>\n </span>\n </div>\n }\n @if (message.hasError) {\n <div class=\"c-chat-error-indicator\">\u274C Error al enviar</div>\n }\n </div>\n\n @if (message.attachments && message.attachments.length > 0) {\n @if (getImageAttachments(message.attachments).length > 0) {\n <div class=\"c-chat-bubble__images\">\n <div class=\"c-chat-bubble__image\">\n @for (file of getImageAttachments(message.attachments); track $index) {\n @if (getAttachmentPreviewUrl(file)) {\n <core-image-preview \n [src]=\"getAttachmentPreviewUrl(file)\"\n [alt]=\"file.filename || 'Imagen'\"\n [showSkeleton]=\"true\"\n (click)=\"onImageClick(message)\">\n </core-image-preview>\n } @else {\n <div class=\"c-chat-image-placeholder\" (click)=\"onImageClick(message)\">\n <div class=\"c-chat-image-placeholder__content\">\n <span class=\"c-chat-image-placeholder__icon\">\uD83D\uDCF7</span>\n <span class=\"c-chat-image-placeholder__text\">{{ file.filename || 'Imagen no disponible' }}</span>\n </div>\n </div>\n }\n }\n </div>\n </div>\n }\n\n @if (getNonImageAttachments(message.attachments).length > 0) {\n <div class=\"c-chat-bubble__files\">\n <core-file-preview\n [files]=\"convertAttachmentsToFilePreviewItems(getNonImageAttachments(message.attachments))\"\n [config]=\"getChatFilePreviewConfig()\" (actionTriggered)=\"onFilePreviewAction($event, message)\">\n </core-file-preview>\n </div>\n }\n }\n\n <!-- @if (message.type === messageTypes.IMAGE && (message.imageData || message.imagesData)) {\n <div class=\"c-chat-bubble__images\">\n @if (message.imageData) {\n <div class=\"c-chat-bubble__image\">\n <div class=\"c-img-preview\">\n <img \n class=\"js-img-preview\" \n [src]=\"message.imageData.url\"\n [alt]=\"message.imageData.alt || 'Image'\"\n (click)=\"onImageClick(message)\"\n />\n </div>\n </div>\n }\n @if (message.imagesData) {\n @for (image of message.imagesData; track image.url) {\n <div class=\"c-chat-bubble__image\">\n <picture class=\"c-img-preview\">\n <img \n class=\"js-img-preview\" \n [src]=\"image.url\"\n [alt]=\"image.alt || 'Image'\"\n (click)=\"onMultipleImageClick(image, message)\"\n />\n </picture>\n </div>\n }\n }\n </div>\n } -->\n\n <!-- @if (message.type === messageTypes.FILE && message.fileData) {\n <div class=\"c-chat-file\" (click)=\"onFileClick(message)\">\n <div class=\"c-chat-file-icon\">\n @if (message.fileData.icon) {\n <img [src]=\"message.fileData.icon\" [alt]=\"message.fileData.type\" />\n } @else { <span>\uD83D\uDCC4</span> }\n </div>\n <div class=\"c-chat-file-info\">\n <div class=\"c-chat-file-name\">{{ message.fileData.name }}</div>\n <div class=\"c-chat-file-size\">{{ formatFileSize(message.fileData.size) }}</div>\n </div>\n </div>\n } -->\n\n <div class=\"c-chat-bubble__info\">\n @if (mergedConfig().showSenderNames && message.sender?.name) {\n <span class=\"c-chat-bubble__sender\">{{ message.sender?.name }}</span>\n }\n @if (mergedConfig().showTimestamps) {\n <time class=\"c-chat-bubble__date\" [attr.datetime]=\"message.timestamp\">\n {{ message.timestamp }}\n </time>\n }\n </div>\n\n\n </div>\n }\n\n @if (mergedConfig().showTypingIndicator && isTyping()) {\n <div class=\"c-chat-typing-indicator\">\n <div class=\"c-chat-bubble\">\n <div class=\"c-chat-bubble__content\">\n <div class=\"c-chat-typing-dots\">\n <span></span> <span></span> <span></span>\n </div>\n </div>\n </div>\n </div>\n }\n <!-- </div> .c-chat-body__holder -->\n @if (selectedFiles().length > 0) {\n <div class=\"c-chat-files is-active\">\n <div class=\"c-chat-files__list\">\n @if (getSelectedImageFiles().length > 0) {\n @for (file of getSelectedImageFiles(); track trackByFile($index, file)) {\n <div class=\"c-chat-files__item\">\n <div class=\"c-chat-files__preview\">\n <core-image-preview [src]=\"getImagePreviewUrl(file)\" [alt]=\"file.name\" (load)=\"onImageLoad(file.name)\"\n (error)=\"onImageError(file.name, $event)\" [width]=\"'50px'\" [height]=\"'50px'\"></core-image-preview>\n </div>\n <p class=\"c-chat-files__text\">{{ file.name }}</p>\n <button type=\"button\" class=\"c-chat-files__remove icon-cross-thin\"\n (click)=\"removeSelectedFile(getSelectedFileIndex(file))\" aria-label=\"Eliminar archivo\">\n </button>\n </div>\n }\n }\n @if (getSelectedNonImageFiles().length > 0) {\n @for (file of getSelectedNonImageFiles(); track trackByFile($index, file)) {\n <div class=\"c-chat-files__item\">\n <div class=\"c-chat-files__preview\">\n <img src=\"/images/static/file-placeholder.svg\" [alt]=\"file.name\" width=\"50px\" height=\"50px\">\n </div>\n <p class=\"c-chat-files__text\">{{ file.name }}</p>\n <button type=\"button\" class=\"c-chat-files__remove icon-cross-thin\"\n (click)=\"removeSelectedFile(getSelectedFileIndex(file))\" aria-label=\"Eliminar archivo\">\n </button>\n </div>\n }\n }\n </div>\n\n </div>\n }\n </div>\n\n @if (!mergedConfig().readOnly) {\n <div class=\"c-modal__bottom\">\n <div class=\"c-chat-input\">\n <div class=\"c-chat-input__textarea\">\n <label>\n <textarea #messageInput rows=\"1\" class=\"js-chat-textarea\" [value]=\"currentMessage()\"\n (input)=\"onTextareaInput($event)\" [placeholder]=\"mergedConfig().placeholder\"\n (keydown.enter)=\"sendMessage()\"></textarea>\n </label>\n <div class=\"c-chat-input__actions\">\n @if (mergedConfig().allowFileAttachments) {\n <button type=\"button\" class=\"icon-attach\" (click)=\"openFileSelector()\"></button>\n }\n </div>\n </div>\n <button type=\"button\" class=\"c-chat-input__submit\"\n [disabled]=\"!currentMessage().trim() && selectedFiles().length === 0\" (click)=\"sendMessage()\">\n <span class=\"icon-chat-send\"></span>\n </button>\n </div>\n @if (mergedConfig().allowFileAttachments) {\n <input #fileInput type=\"file\" multiple style=\"display: none\" (change)=\"onFileSelected($event)\" />\n }\n </div>\n }\n </div>\n</div>\n} @else {\n<div class=\"c-chat-layout c-services-chat\" [ngClass]=\"mergedConfig().customCssClasses?.container\">\n <div #messagesContainer class=\"c-chat-layout__body c-chat-body\"\n [ngClass]=\"mergedConfig().customCssClasses?.messageList\">\n <!-- Sentinel para infinite scroll -->\n @if (mergedConfig().enableInfiniteScroll && hasMoreMessages()) {\n <div #scrollSentinel class=\"c-chat-scroll-sentinel\" style=\"height: 1px; margin-top: 10px;\">\n @if (isLoadingMore()) {\n <div class=\"c-chat-loading-more\">\n <span class=\"c-chat-dots\">\n <span></span> <span></span> <span></span>\n </span>\n </div>\n }\n </div>\n }\n \n <!-- <div class=\"c-chat-body__holder\"> -->\n\n @for (message of messages(); track trackByMessage($index, message)) {\n\n @if (shouldShowDateSeparator().get(message.id)) {\n <time class=\"c-chat-date\" [attr.datetime]=\"message.timestamp\">\n {{ getDateSeparatorText(message.timestamp) }}\n </time>\n }\n \n <div class=\"c-chat-bubble\" [ngClass]=\"message.position === 'right' ? 'c-chat-bubble--right' : ''\">\n <!-- @if (mergedConfig().showSenderNames && message.sender?.name) {\n <div class=\"c-chat-bubble__sender\">{{ message.sender?.name }}</div>\n } -->\n\n <div class=\"c-chat-bubble__content\">\n @if (chatService.shouldUseCustomTemplate(message, mergedConfig())) {\n <ng-container *ngTemplateOutlet=\"\n getCustomTemplate(message);\n context: { $implicit: message }\n \">\n </ng-container>\n }\n @else if (message.type === messageTypes.TEXT) {\n @for (paragraph of message.content.split('\\n\\n'); track $index) {\n @if (paragraph.trim()) {\n <p [innerHTML]=\"paragraph.trim()\"></p>\n }\n }\n }\n @else if (message.type === messageTypes.SYSTEM || message.type === messageTypes.NOTIFICATION) {\n <p>{{ message.content }}</p>\n }\n\n @if (message.isSending) {\n <div class=\"c-chat-sending-indicator\">\n <span class=\"c-chat-dots\">\n <span></span> <span></span> <span></span>\n </span>\n </div>\n }\n @if (message.hasError) {\n <div class=\"c-chat-error-indicator\">\u274C Error al enviar</div>\n }\n </div>\n\n @if (message.attachments && message.attachments.length > 0) {\n @if (getImageAttachments(message.attachments).length > 0) {\n <div class=\"c-chat-bubble__images\">\n @for (file of getImageAttachments(message.attachments); track $index) {\n <div class=\"c-chat-bubble__image\">\n <core-image-preview [src]=\"getAttachmentPreviewUrl(file)\"\n [alt]=\"file.filename || 'Imagen'\"></core-image-preview>\n </div>\n }\n </div>\n }\n\n @if (getNonImageAttachments(message.attachments).length > 0) {\n <div class=\"c-chat-bubble__files\">\n <core-file-preview [files]=\"convertAttachmentsToFilePreviewItems(getNonImageAttachments(message.attachments))\"\n [config]=\"getChatFilePreviewConfig()\" (actionTriggered)=\"onFilePreviewAction($event, message)\">\n </core-file-preview>\n </div>\n }\n }\n\n <!-- @if (message.type === messageTypes.IMAGE && (message.imageData || message.imagesData)) {\n <div class=\"c-chat-bubble__images\">\n @if (message.imageData) {\n <div class=\"c-img-preview\">\n <img \n class=\"js-img-preview\" \n [src]=\"message.imageData.url\"\n [alt]=\"message.imageData.alt || 'Image'\"\n (click)=\"onImageClick(message)\"\n />\n </div>\n }\n @if (message.imagesData) {\n @for (image of message.imagesData; track image.url) {\n <div class=\"c-img-preview\">\n <img \n class=\"js-img-preview\" \n [src]=\"image.url\"\n [alt]=\"image.alt || 'Image'\"\n (click)=\"onMultipleImageClick(image, message)\"\n />\n </div>\n }\n }\n </div>\n } -->\n\n <!-- @if (message.type === messageTypes.FILE && message.fileData) {\n <div class=\"c-chat-file\" (click)=\"onFileClick(message)\">\n <div class=\"c-chat-file-icon\">\n @if (message.fileData.icon) {\n <img [src]=\"message.fileData.icon\" [alt]=\"message.fileData.type\" />\n } @else { <span>\uD83D\uDCC4</span> }\n </div>\n <div class=\"c-chat-file-info\">\n <div class=\"c-chat-file-name\">{{ message.fileData.name }}</div>\n <div class=\"c-chat-file-size\">{{ formatFileSize(message.fileData.size) }}</div>\n </div>\n </div>\n } -->\n\n <div class=\"c-chat-bubble__info\">\n @if (mergedConfig().showSenderNames && message.sender?.name) {\n <span class=\"c-chat-bubble__sender\">{{ message.sender?.name }}</span>\n }\n @if (mergedConfig().showTimestamps) {\n <time class=\"c-chat-bubble__date\" [attr.datetime]=\"message.timestamp\">\n {{ message.timestamp }}\n </time>\n }\n </div>\n\n </div>\n }\n\n @if (mergedConfig().showTypingIndicator && isTyping()) {\n <div class=\"c-chat-typing-indicator\">\n <div class=\"c-chat-bubble\">\n <div class=\"c-chat-bubble__content\">\n <div class=\"c-chat-typing-dots\">\n <span></span> <span></span> <span></span>\n </div>\n </div>\n </div>\n </div>\n }\n <!-- </div> c-chat-body__holder -->\n @if (selectedFiles().length > 0) {\n <div class=\"c-chat-files is-active\">\n <div class=\"c-chat-files__list\">\n @if (getSelectedImageFiles().length > 0) {\n @for (file of getSelectedImageFiles(); track trackByFile($index, file)) {\n <div class=\"c-chat-files__item\">\n <div class=\"c-chat-files__preview\">\n <core-image-preview [src]=\"getImagePreviewUrl(file)\" [alt]=\"file.name\" (load)=\"onImageLoad(file.name)\"\n (error)=\"onImageError(file.name, $event)\" [width]=\"'50px'\" [height]=\"'50px'\"></core-image-preview>\n </div>\n <p class=\"c-chat-files__text\">{{ file.name }}</p>\n <button type=\"button\" class=\"c-chat-files__remove icon-cross-thin\"\n (click)=\"removeSelectedFile(getSelectedFileIndex(file))\" aria-label=\"Eliminar archivo\">\n </button>\n </div>\n }\n }\n @if (getSelectedNonImageFiles().length > 0) {\n @for (file of getSelectedNonImageFiles(); track trackByFile($index, file)) {\n <div class=\"c-chat-files__item\">\n <div class=\"c-chat-files__preview\">\n <img src=\"/images/static/file-placeholder.svg\" [alt]=\"file.name\" width=\"50px\" height=\"50px\">\n </div>\n <p class=\"c-chat-files__text\">{{ file.name }}</p>\n <button type=\"button\" class=\"c-chat-files__remove icon-cross-thin\"\n (click)=\"removeSelectedFile(getSelectedFileIndex(file))\" aria-label=\"Eliminar archivo\">\n </button>\n </div>\n }\n }\n </div>\n </div>\n }\n </div>\n\n @if (!mergedConfig().readOnly) {\n <div class=\"c-chat-layout__bottom\" [ngClass]=\"mergedConfig().customCssClasses?.inputContainer\">\n <div class=\"c-chat-input\">\n <div class=\"c-chat-input__textarea\">\n <label>\n <textarea #messageInput rows=\"1\" class=\"js-chat-textarea\" [value]=\"currentMessage()\"\n (input)=\"onTextareaInput($event)\" [placeholder]=\"mergedConfig().placeholder\"\n (keydown.enter)=\"sendMessage()\"></textarea>\n </label>\n <div class=\"c-chat-input__actions\">\n @if (mergedConfig().allowFileAttachments) {\n <button type=\"button\" class=\"icon-attach\" (click)=\"openFileSelector()\"></button>\n }\n </div>\n </div>\n <button type=\"button\" class=\"c-chat-input__submit\"\n [disabled]=\"!currentMessage().trim() && selectedFiles().length === 0\" (click)=\"sendMessage()\">\n <span class=\"icon-chat-send\"></span>\n </button>\n </div>\n @if (mergedConfig().allowFileAttachments) {\n <input #fileInput type=\"file\" multiple style=\"display: none\" (change)=\"onFileSelected($event)\" />\n }\n </div>\n }\n</div>\n}\n\n<!-- @if (showImageModal()) {\n<div class=\"c-img-modal js-img-modal is-visible\">\n <span \n class=\"c-img-modal__close icon-cross-thin js-gallery-modal-close\"\n (click)=\"closeImageModal()\"\n ></span>\n <div \n class=\"c-img-modal__overlay js-img-modal-close\"\n (click)=\"closeImageModal()\"\n ></div>\n <div class=\"c-img-modal__holder\">\n <img \n class=\"js-img-modal-img\" \n [src]=\"selectedImage()\" \n alt=\"Imagen Ampliada\"\n />\n </div>\n</div>\n} -->", styles: [".c-img-preview{position:relative;width:100%;height:100%}.c-img-preview__placeholder{position:absolute;bottom:.5rem;left:.5rem;right:.5rem;background-color:#000000b3;color:#fff;padding:.25rem .5rem;border-radius:.25rem;font-size:.75rem;text-align:center}.c-chat-image-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem 1rem;background-color:#f8fafc;border:2px dashed #e2e8f0;border-radius:.5rem;min-height:8rem}.c-chat-image-placeholder .icon-image{font-size:2rem;color:#9ca3af;margin-bottom:.5rem}.c-chat-image-placeholder p{margin:0;color:#6b7280;font-size:.875rem;text-align:center}.c-chat-files__preview{width:50px;height:50px;display:flex;align-items:center;justify-content:center;overflow:hidden;margin-bottom:.25rem}.c-chat-loading-spinner{display:inline-block;width:16px;height:16px;border:2px solid #f3f3f3;border-top:2px solid #3498db;border-radius:50%;animation:spin 1s linear infinite}.c-chat-input__submit.is-sending{opacity:.7;cursor:not-allowed}.c-chat-sending-global-indicator{margin-top:.5rem}.c-chat-sending-global-indicator .c-chat-bubble{background-color:#f8f9fa;border:1px solid #e9ecef}.c-chat-sending-global-indicator .c-chat-bubble .c-chat-bubble__content{padding:.5rem .75rem}.c-chat-sending-message{display:flex;align-items:center;gap:.5rem;font-size:.875rem;color:#6c757d}.c-chat-sending-spinner{display:inline-block;width:12px;height:12px;border:2px solid #f3f3f3;border-top:2px solid #007bff;border-radius:50%;animation:spin 1s linear infinite}.c-chat-sending-text{font-style:italic}.c-chat-sending-indicator{display:flex;align-items:center;gap:.5rem;margin-top:.25rem;font-size:.75rem;color:#6c757d}.c-chat-sending-indicator .c-chat-dots{display:inline-flex;gap:2px}.c-chat-sending-indicator .c-chat-dots span{display:inline-block;width:4px;height:4px;background-color:#007bff;border-radius:50%;animation:pulse 1.4s ease-in-out infinite both}.c-chat-sending-indicator .c-chat-dots span:nth-child(1){animation-delay:-.32s}.c-chat-sending-indicator .c-chat-dots span:nth-child(2){animation-delay:-.16s}.c-chat-sending-indicator .c-chat-dots span:nth-child(3){animation-delay:0s}.c-chat-error-indicator{display:flex;align-items:center;gap:.25rem;margin-top:.25rem;font-size:.75rem;color:#dc3545}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes pulse{0%,80%,to{transform:scale(0)}40%{transform:scale(1)}}.c-chat-scroll-sentinel{width:100%;min-height:1px;position:relative}.c-chat-loading-more{display:flex;justify-content:center;align-items:center;padding:1rem}.c-chat-loading-more .c-chat-dots{display:inline-flex;gap:4px}.c-chat-loading-more .c-chat-dots span{display:inline-block;width:8px;height:8px;background-color:#007bff;border-radius:50%;animation:pulse 1.4s ease-in-out infinite both}.c-chat-loading-more .c-chat-dots span:nth-child(1){animation-delay:-.32s}.c-chat-loading-more .c-chat-dots span:nth-child(2){animation-delay:-.16s}.c-chat-loading-more .c-chat-dots span:nth-child(3){animation-delay:0s}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: GenericButtonComponent, selector: "core-generic-button", inputs: ["config", "data"], outputs: ["buttonClick"] }, { kind: "component", type: ImagePreviewComponent, selector: "core-image-preview", inputs: ["src", "alt", "title", "width", "height", "objectFit", "borderRadius", "cursor", "loading", "isRelative", "showSkeleton", "skeletonAnimation"] }, { kind: "component", type: FilePreviewComponent, selector: "core-file-preview", inputs: ["fileModels", "files", "config"], outputs: ["actionTriggered"] }] });
|
|
14858
|
+
}
|
|
14859
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: GenericChatComponent, decorators: [{
|
|
14860
|
+
type: Component,
|
|
14861
|
+
args: [{ selector: 'app-generic-chat', standalone: true, imports: [CommonModule, FormsModule, GenericButtonComponent, ImagePreviewComponent, FilePreviewComponent], template: "@if (mergedConfig().modalMode && mergedConfig().isModalOpen) {\n<div class=\"c-services-chat c-modal is-visible\" [class.is-closing]=\"isClosing()\">\n <div class=\"c-modal__overlay\" (click)=\"closeModal()\"></div>\n <div class=\"c-modal__holder\">\n <div class=\"c-modal__header\">\n <p>\n @if (mergedConfig().modalTitle) {\n <span>{{ mergedConfig().modalTitle }}</span>\n }\n @if (mergedConfig().modalSubtitle) {\n <time>{{ mergedConfig().modalSubtitle }}</time>\n }\n </p>\n <core-generic-button [config]=\"closeButtonConfig()\" (buttonClick)=\"closeModal()\">\n </core-generic-button>\n </div>\n\n <div class=\"c-modal__body c-chat-body\" #modalMessagesContainer>\n <!-- Sentinel para infinite scroll -->\n @if (mergedConfig().enableInfiniteScroll && hasMoreMessages()) {\n <div #scrollSentinel class=\"c-chat-scroll-sentinel\" style=\"height: 1px; margin-top: 10px;\">\n @if (isLoadingMore()) {\n <div class=\"c-chat-loading-more\">\n <span class=\"c-chat-dots\">\n <span></span> <span></span> <span></span>\n </span>\n </div>\n }\n </div>\n }\n \n <!-- <div class=\"c-chat-body__holder\"> -->\n @for (message of chronologicalMessages(); track trackByChronologicalMessage($index, message)) {\n\n @if (shouldShowDateSeparator().get(message.id)) {\n <time class=\"c-chat-date\" [attr.datetime]=\"message.timestamp\">\n {{ getDateSeparatorText(message.timestamp) }}\n </time>\n }\n\n <div class=\"c-chat-bubble\" [ngClass]=\"message.position === 'right' ? 'c-chat-bubble--right' : ''\">\n <!-- @if (mergedConfig().showSenderNames && message.sender?.name) {\n <div class=\"c-chat-bubble__sender\">{{ message.sender?.name }}</div>\n } -->\n\n <div class=\"c-chat-bubble__content\">\n @if (chatService.shouldUseCustomTemplate(message, mergedConfig())) {\n <ng-container *ngTemplateOutlet=\"\n getCustomTemplate(message);\n context: { $implicit: message }\n \">\n </ng-container>\n }\n @else if (message.type === messageTypes.TEXT) {\n @for (paragraph of message.content.split('\\n\\n'); track $index) {\n @if (paragraph.trim()) {\n <p [innerHTML]=\"paragraph.trim()\"></p>\n }\n }\n }\n @else if (message.type === messageTypes.SYSTEM || message.type === messageTypes.NOTIFICATION) {\n <p>{{ message.content }}</p>\n }\n\n @if (message.isSending) {\n <div class=\"c-chat-sending-indicator\">\n <span class=\"c-chat-dots\">\n <span></span> <span></span> <span></span>\n </span>\n </div>\n }\n @if (message.hasError) {\n <div class=\"c-chat-error-indicator\">\u274C Error al enviar</div>\n }\n </div>\n\n @if (message.attachments && message.attachments.length > 0) {\n @if (getImageAttachments(message.attachments).length > 0) {\n <div class=\"c-chat-bubble__images\">\n <div class=\"c-chat-bubble__image\">\n @for (file of getImageAttachments(message.attachments); track $index) {\n @if (getAttachmentPreviewUrl(file)) {\n <core-image-preview \n [src]=\"getAttachmentPreviewUrl(file)\"\n [alt]=\"file.filename || 'Imagen'\"\n [showSkeleton]=\"true\"\n (click)=\"onImageClick(message)\">\n </core-image-preview>\n } @else {\n <div class=\"c-chat-image-placeholder\" (click)=\"onImageClick(message)\">\n <div class=\"c-chat-image-placeholder__content\">\n <span class=\"c-chat-image-placeholder__icon\">\uD83D\uDCF7</span>\n <span class=\"c-chat-image-placeholder__text\">{{ file.filename || 'Imagen no disponible' }}</span>\n </div>\n </div>\n }\n }\n </div>\n </div>\n }\n\n @if (getNonImageAttachments(message.attachments).length > 0) {\n <div class=\"c-chat-bubble__files\">\n <core-file-preview\n [files]=\"convertAttachmentsToFilePreviewItems(getNonImageAttachments(message.attachments))\"\n [config]=\"getChatFilePreviewConfig()\" (actionTriggered)=\"onFilePreviewAction($event, message)\">\n </core-file-preview>\n </div>\n }\n }\n\n <!-- @if (message.type === messageTypes.IMAGE && (message.imageData || message.imagesData)) {\n <div class=\"c-chat-bubble__images\">\n @if (message.imageData) {\n <div class=\"c-chat-bubble__image\">\n <div class=\"c-img-preview\">\n <img \n class=\"js-img-preview\" \n [src]=\"message.imageData.url\"\n [alt]=\"message.imageData.alt || 'Image'\"\n (click)=\"onImageClick(message)\"\n />\n </div>\n </div>\n }\n @if (message.imagesData) {\n @for (image of message.imagesData; track image.url) {\n <div class=\"c-chat-bubble__image\">\n <picture class=\"c-img-preview\">\n <img \n class=\"js-img-preview\" \n [src]=\"image.url\"\n [alt]=\"image.alt || 'Image'\"\n (click)=\"onMultipleImageClick(image, message)\"\n />\n </picture>\n </div>\n }\n }\n </div>\n } -->\n\n <!-- @if (message.type === messageTypes.FILE && message.fileData) {\n <div class=\"c-chat-file\" (click)=\"onFileClick(message)\">\n <div class=\"c-chat-file-icon\">\n @if (message.fileData.icon) {\n <img [src]=\"message.fileData.icon\" [alt]=\"message.fileData.type\" />\n } @else { <span>\uD83D\uDCC4</span> }\n </div>\n <div class=\"c-chat-file-info\">\n <div class=\"c-chat-file-name\">{{ message.fileData.name }}</div>\n <div class=\"c-chat-file-size\">{{ formatFileSize(message.fileData.size) }}</div>\n </div>\n </div>\n } -->\n\n <div class=\"c-chat-bubble__info\">\n @if (mergedConfig().showSenderNames && message.sender?.name) {\n <span class=\"c-chat-bubble__sender\">{{ message.sender?.name }}</span>\n }\n @if (mergedConfig().showTimestamps) {\n <time class=\"c-chat-bubble__date\" [attr.datetime]=\"message.timestamp\">\n {{ message.timestamp }}\n </time>\n }\n </div>\n\n\n </div>\n }\n\n @if (mergedConfig().showTypingIndicator && isTyping()) {\n <div class=\"c-chat-typing-indicator\">\n <div class=\"c-chat-bubble\">\n <div class=\"c-chat-bubble__content\">\n <div class=\"c-chat-typing-dots\">\n <span></span> <span></span> <span></span>\n </div>\n </div>\n </div>\n </div>\n }\n <!-- </div> .c-chat-body__holder -->\n @if (selectedFiles().length > 0) {\n <div class=\"c-chat-files is-active\">\n <div class=\"c-chat-files__list\">\n @if (getSelectedImageFiles().length > 0) {\n @for (file of getSelectedImageFiles(); track trackByFile($index, file)) {\n <div class=\"c-chat-files__item\">\n <div class=\"c-chat-files__preview\">\n <core-image-preview [src]=\"getImagePreviewUrl(file)\" [alt]=\"file.name\" (load)=\"onImageLoad(file.name)\"\n (error)=\"onImageError(file.name, $event)\" [width]=\"'50px'\" [height]=\"'50px'\"></core-image-preview>\n </div>\n <p class=\"c-chat-files__text\">{{ file.name }}</p>\n <button type=\"button\" class=\"c-chat-files__remove icon-cross-thin\"\n (click)=\"removeSelectedFile(getSelectedFileIndex(file))\" aria-label=\"Eliminar archivo\">\n </button>\n </div>\n }\n }\n @if (getSelectedNonImageFiles().length > 0) {\n @for (file of getSelectedNonImageFiles(); track trackByFile($index, file)) {\n <div class=\"c-chat-files__item\">\n <div class=\"c-chat-files__preview\">\n <img src=\"/images/static/file-placeholder.svg\" [alt]=\"file.name\" width=\"50px\" height=\"50px\">\n </div>\n <p class=\"c-chat-files__text\">{{ file.name }}</p>\n <button type=\"button\" class=\"c-chat-files__remove icon-cross-thin\"\n (click)=\"removeSelectedFile(getSelectedFileIndex(file))\" aria-label=\"Eliminar archivo\">\n </button>\n </div>\n }\n }\n </div>\n\n </div>\n }\n </div>\n\n @if (!mergedConfig().readOnly) {\n <div class=\"c-modal__bottom\">\n <div class=\"c-chat-input\">\n <div class=\"c-chat-input__textarea\">\n <label>\n <textarea #messageInput rows=\"1\" class=\"js-chat-textarea\" [value]=\"currentMessage()\"\n (input)=\"onTextareaInput($event)\" [placeholder]=\"mergedConfig().placeholder\"\n (keydown.enter)=\"sendMessage()\"></textarea>\n </label>\n <div class=\"c-chat-input__actions\">\n @if (mergedConfig().allowFileAttachments) {\n <button type=\"button\" class=\"icon-attach\" (click)=\"openFileSelector()\"></button>\n }\n </div>\n </div>\n <button type=\"button\" class=\"c-chat-input__submit\"\n [disabled]=\"!currentMessage().trim() && selectedFiles().length === 0\" (click)=\"sendMessage()\">\n <span class=\"icon-chat-send\"></span>\n </button>\n </div>\n @if (mergedConfig().allowFileAttachments) {\n <input #fileInput type=\"file\" multiple style=\"display: none\" (change)=\"onFileSelected($event)\" />\n }\n </div>\n }\n </div>\n</div>\n} @else {\n<div class=\"c-chat-layout c-services-chat\" [ngClass]=\"mergedConfig().customCssClasses?.container\">\n <div #messagesContainer class=\"c-chat-layout__body c-chat-body\"\n [ngClass]=\"mergedConfig().customCssClasses?.messageList\">\n <!-- Sentinel para infinite scroll -->\n @if (mergedConfig().enableInfiniteScroll && hasMoreMessages()) {\n <div #scrollSentinel class=\"c-chat-scroll-sentinel\" style=\"height: 1px; margin-top: 10px;\">\n @if (isLoadingMore()) {\n <div class=\"c-chat-loading-more\">\n <span class=\"c-chat-dots\">\n <span></span> <span></span> <span></span>\n </span>\n </div>\n }\n </div>\n }\n \n <!-- <div class=\"c-chat-body__holder\"> -->\n\n @for (message of messages(); track trackByMessage($index, message)) {\n\n @if (shouldShowDateSeparator().get(message.id)) {\n <time class=\"c-chat-date\" [attr.datetime]=\"message.timestamp\">\n {{ getDateSeparatorText(message.timestamp) }}\n </time>\n }\n \n <div class=\"c-chat-bubble\" [ngClass]=\"message.position === 'right' ? 'c-chat-bubble--right' : ''\">\n <!-- @if (mergedConfig().showSenderNames && message.sender?.name) {\n <div class=\"c-chat-bubble__sender\">{{ message.sender?.name }}</div>\n } -->\n\n <div class=\"c-chat-bubble__content\">\n @if (chatService.shouldUseCustomTemplate(message, mergedConfig())) {\n <ng-container *ngTemplateOutlet=\"\n getCustomTemplate(message);\n context: { $implicit: message }\n \">\n </ng-container>\n }\n @else if (message.type === messageTypes.TEXT) {\n @for (paragraph of message.content.split('\\n\\n'); track $index) {\n @if (paragraph.trim()) {\n <p [innerHTML]=\"paragraph.trim()\"></p>\n }\n }\n }\n @else if (message.type === messageTypes.SYSTEM || message.type === messageTypes.NOTIFICATION) {\n <p>{{ message.content }}</p>\n }\n\n @if (message.isSending) {\n <div class=\"c-chat-sending-indicator\">\n <span class=\"c-chat-dots\">\n <span></span> <span></span> <span></span>\n </span>\n </div>\n }\n @if (message.hasError) {\n <div class=\"c-chat-error-indicator\">\u274C Error al enviar</div>\n }\n </div>\n\n @if (message.attachments && message.attachments.length > 0) {\n @if (getImageAttachments(message.attachments).length > 0) {\n <div class=\"c-chat-bubble__images\">\n @for (file of getImageAttachments(message.attachments); track $index) {\n <div class=\"c-chat-bubble__image\">\n <core-image-preview [src]=\"getAttachmentPreviewUrl(file)\"\n [alt]=\"file.filename || 'Imagen'\"></core-image-preview>\n </div>\n }\n </div>\n }\n\n @if (getNonImageAttachments(message.attachments).length > 0) {\n <div class=\"c-chat-bubble__files\">\n <core-file-preview [files]=\"convertAttachmentsToFilePreviewItems(getNonImageAttachments(message.attachments))\"\n [config]=\"getChatFilePreviewConfig()\" (actionTriggered)=\"onFilePreviewAction($event, message)\">\n </core-file-preview>\n </div>\n }\n }\n\n <!-- @if (message.type === messageTypes.IMAGE && (message.imageData || message.imagesData)) {\n <div class=\"c-chat-bubble__images\">\n @if (message.imageData) {\n <div class=\"c-img-preview\">\n <img \n class=\"js-img-preview\" \n [src]=\"message.imageData.url\"\n [alt]=\"message.imageData.alt || 'Image'\"\n (click)=\"onImageClick(message)\"\n />\n </div>\n }\n @if (message.imagesData) {\n @for (image of message.imagesData; track image.url) {\n <div class=\"c-img-preview\">\n <img \n class=\"js-img-preview\" \n [src]=\"image.url\"\n [alt]=\"image.alt || 'Image'\"\n (click)=\"onMultipleImageClick(image, message)\"\n />\n </div>\n }\n }\n </div>\n } -->\n\n <!-- @if (message.type === messageTypes.FILE && message.fileData) {\n <div class=\"c-chat-file\" (click)=\"onFileClick(message)\">\n <div class=\"c-chat-file-icon\">\n @if (message.fileData.icon) {\n <img [src]=\"message.fileData.icon\" [alt]=\"message.fileData.type\" />\n } @else { <span>\uD83D\uDCC4</span> }\n </div>\n <div class=\"c-chat-file-info\">\n <div class=\"c-chat-file-name\">{{ message.fileData.name }}</div>\n <div class=\"c-chat-file-size\">{{ formatFileSize(message.fileData.size) }}</div>\n </div>\n </div>\n } -->\n\n <div class=\"c-chat-bubble__info\">\n @if (mergedConfig().showSenderNames && message.sender?.name) {\n <span class=\"c-chat-bubble__sender\">{{ message.sender?.name }}</span>\n }\n @if (mergedConfig().showTimestamps) {\n <time class=\"c-chat-bubble__date\" [attr.datetime]=\"message.timestamp\">\n {{ message.timestamp }}\n </time>\n }\n </div>\n\n </div>\n }\n\n @if (mergedConfig().showTypingIndicator && isTyping()) {\n <div class=\"c-chat-typing-indicator\">\n <div class=\"c-chat-bubble\">\n <div class=\"c-chat-bubble__content\">\n <div class=\"c-chat-typing-dots\">\n <span></span> <span></span> <span></span>\n </div>\n </div>\n </div>\n </div>\n }\n <!-- </div> c-chat-body__holder -->\n @if (selectedFiles().length > 0) {\n <div class=\"c-chat-files is-active\">\n <div class=\"c-chat-files__list\">\n @if (getSelectedImageFiles().length > 0) {\n @for (file of getSelectedImageFiles(); track trackByFile($index, file)) {\n <div class=\"c-chat-files__item\">\n <div class=\"c-chat-files__preview\">\n <core-image-preview [src]=\"getImagePreviewUrl(file)\" [alt]=\"file.name\" (load)=\"onImageLoad(file.name)\"\n (error)=\"onImageError(file.name, $event)\" [width]=\"'50px'\" [height]=\"'50px'\"></core-image-preview>\n </div>\n <p class=\"c-chat-files__text\">{{ file.name }}</p>\n <button type=\"button\" class=\"c-chat-files__remove icon-cross-thin\"\n (click)=\"removeSelectedFile(getSelectedFileIndex(file))\" aria-label=\"Eliminar archivo\">\n </button>\n </div>\n }\n }\n @if (getSelectedNonImageFiles().length > 0) {\n @for (file of getSelectedNonImageFiles(); track trackByFile($index, file)) {\n <div class=\"c-chat-files__item\">\n <div class=\"c-chat-files__preview\">\n <img src=\"/images/static/file-placeholder.svg\" [alt]=\"file.name\" width=\"50px\" height=\"50px\">\n </div>\n <p class=\"c-chat-files__text\">{{ file.name }}</p>\n <button type=\"button\" class=\"c-chat-files__remove icon-cross-thin\"\n (click)=\"removeSelectedFile(getSelectedFileIndex(file))\" aria-label=\"Eliminar archivo\">\n </button>\n </div>\n }\n }\n </div>\n </div>\n }\n </div>\n\n @if (!mergedConfig().readOnly) {\n <div class=\"c-chat-layout__bottom\" [ngClass]=\"mergedConfig().customCssClasses?.inputContainer\">\n <div class=\"c-chat-input\">\n <div class=\"c-chat-input__textarea\">\n <label>\n <textarea #messageInput rows=\"1\" class=\"js-chat-textarea\" [value]=\"currentMessage()\"\n (input)=\"onTextareaInput($event)\" [placeholder]=\"mergedConfig().placeholder\"\n (keydown.enter)=\"sendMessage()\"></textarea>\n </label>\n <div class=\"c-chat-input__actions\">\n @if (mergedConfig().allowFileAttachments) {\n <button type=\"button\" class=\"icon-attach\" (click)=\"openFileSelector()\"></button>\n }\n </div>\n </div>\n <button type=\"button\" class=\"c-chat-input__submit\"\n [disabled]=\"!currentMessage().trim() && selectedFiles().length === 0\" (click)=\"sendMessage()\">\n <span class=\"icon-chat-send\"></span>\n </button>\n </div>\n @if (mergedConfig().allowFileAttachments) {\n <input #fileInput type=\"file\" multiple style=\"display: none\" (change)=\"onFileSelected($event)\" />\n }\n </div>\n }\n</div>\n}\n\n<!-- @if (showImageModal()) {\n<div class=\"c-img-modal js-img-modal is-visible\">\n <span \n class=\"c-img-modal__close icon-cross-thin js-gallery-modal-close\"\n (click)=\"closeImageModal()\"\n ></span>\n <div \n class=\"c-img-modal__overlay js-img-modal-close\"\n (click)=\"closeImageModal()\"\n ></div>\n <div class=\"c-img-modal__holder\">\n <img \n class=\"js-img-modal-img\" \n [src]=\"selectedImage()\" \n alt=\"Imagen Ampliada\"\n />\n </div>\n</div>\n} -->", styles: [".c-img-preview{position:relative;width:100%;height:100%}.c-img-preview__placeholder{position:absolute;bottom:.5rem;left:.5rem;right:.5rem;background-color:#000000b3;color:#fff;padding:.25rem .5rem;border-radius:.25rem;font-size:.75rem;text-align:center}.c-chat-image-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem 1rem;background-color:#f8fafc;border:2px dashed #e2e8f0;border-radius:.5rem;min-height:8rem}.c-chat-image-placeholder .icon-image{font-size:2rem;color:#9ca3af;margin-bottom:.5rem}.c-chat-image-placeholder p{margin:0;color:#6b7280;font-size:.875rem;text-align:center}.c-chat-files__preview{width:50px;height:50px;display:flex;align-items:center;justify-content:center;overflow:hidden;margin-bottom:.25rem}.c-chat-loading-spinner{display:inline-block;width:16px;height:16px;border:2px solid #f3f3f3;border-top:2px solid #3498db;border-radius:50%;animation:spin 1s linear infinite}.c-chat-input__submit.is-sending{opacity:.7;cursor:not-allowed}.c-chat-sending-global-indicator{margin-top:.5rem}.c-chat-sending-global-indicator .c-chat-bubble{background-color:#f8f9fa;border:1px solid #e9ecef}.c-chat-sending-global-indicator .c-chat-bubble .c-chat-bubble__content{padding:.5rem .75rem}.c-chat-sending-message{display:flex;align-items:center;gap:.5rem;font-size:.875rem;color:#6c757d}.c-chat-sending-spinner{display:inline-block;width:12px;height:12px;border:2px solid #f3f3f3;border-top:2px solid #007bff;border-radius:50%;animation:spin 1s linear infinite}.c-chat-sending-text{font-style:italic}.c-chat-sending-indicator{display:flex;align-items:center;gap:.5rem;margin-top:.25rem;font-size:.75rem;color:#6c757d}.c-chat-sending-indicator .c-chat-dots{display:inline-flex;gap:2px}.c-chat-sending-indicator .c-chat-dots span{display:inline-block;width:4px;height:4px;background-color:#007bff;border-radius:50%;animation:pulse 1.4s ease-in-out infinite both}.c-chat-sending-indicator .c-chat-dots span:nth-child(1){animation-delay:-.32s}.c-chat-sending-indicator .c-chat-dots span:nth-child(2){animation-delay:-.16s}.c-chat-sending-indicator .c-chat-dots span:nth-child(3){animation-delay:0s}.c-chat-error-indicator{display:flex;align-items:center;gap:.25rem;margin-top:.25rem;font-size:.75rem;color:#dc3545}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes pulse{0%,80%,to{transform:scale(0)}40%{transform:scale(1)}}.c-chat-scroll-sentinel{width:100%;min-height:1px;position:relative}.c-chat-loading-more{display:flex;justify-content:center;align-items:center;padding:1rem}.c-chat-loading-more .c-chat-dots{display:inline-flex;gap:4px}.c-chat-loading-more .c-chat-dots span{display:inline-block;width:8px;height:8px;background-color:#007bff;border-radius:50%;animation:pulse 1.4s ease-in-out infinite both}.c-chat-loading-more .c-chat-dots span:nth-child(1){animation-delay:-.32s}.c-chat-loading-more .c-chat-dots span:nth-child(2){animation-delay:-.16s}.c-chat-loading-more .c-chat-dots span:nth-child(3){animation-delay:0s}\n"] }]
|
|
14862
|
+
}], ctorParameters: () => [{ type: GenericChatService }], propDecorators: { scrollSentinelElement: [{
|
|
14863
|
+
type: ViewChild,
|
|
14864
|
+
args: ['scrollSentinel', { read: ElementRef }]
|
|
14865
|
+
}], handleKeyboardEvent: [{
|
|
14866
|
+
type: HostListener,
|
|
14867
|
+
args: ['document:keydown', ['$event']]
|
|
14868
|
+
}] } });
|
|
14869
|
+
|
|
13583
14870
|
class CacheBustingInterceptor {
|
|
13584
14871
|
intercept(req, next) {
|
|
13585
14872
|
if (req.url.includes('/assets/i18n/') && req.url.endsWith('.json')) {
|
|
@@ -13627,79 +14914,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
|
|
|
13627
14914
|
|
|
13628
14915
|
;
|
|
13629
14916
|
|
|
13630
|
-
class TranslationMergeService {
|
|
13631
|
-
http;
|
|
13632
|
-
constructor(http) {
|
|
13633
|
-
this.http = http;
|
|
13634
|
-
}
|
|
13635
|
-
loadCombinedTranslations(lang, projectPath = './assets/i18n/', projectFileName = 'main.json', corePath = './assets/i18n/', coreFileName = 'common.json', useNodeModules = false) {
|
|
13636
|
-
const projectUrl = `${projectPath}${lang}/${projectFileName}`;
|
|
13637
|
-
const projectTranslations$ = this.http.get(projectUrl).pipe(catchError$1(error => {
|
|
13638
|
-
console.warn(`No se pudieron cargar las traducciones del proyecto desde: ${projectUrl}`, error);
|
|
13639
|
-
return of({});
|
|
13640
|
-
}));
|
|
13641
|
-
const coreTranslations$ = useNodeModules
|
|
13642
|
-
? this.loadCoreTranslationsWithFallback(lang, coreFileName)
|
|
13643
|
-
: this.http.get(`${corePath}${lang}/${coreFileName}`).pipe(catchError$1(error => {
|
|
13644
|
-
console.warn(`No se pudieron cargar las traducciones del core-ui desde: ${corePath}${lang}/${coreFileName}`, error);
|
|
13645
|
-
return of({});
|
|
13646
|
-
}));
|
|
13647
|
-
return forkJoin({
|
|
13648
|
-
project: projectTranslations$,
|
|
13649
|
-
core: coreTranslations$
|
|
13650
|
-
}).pipe(map$1(({ project, core }) => {
|
|
13651
|
-
const merged = this.mergeWithProjectPriority(core, project);
|
|
13652
|
-
return merged;
|
|
13653
|
-
}));
|
|
13654
|
-
}
|
|
13655
|
-
loadCoreTranslationsWithFallback(lang, coreFileName) {
|
|
13656
|
-
const possiblePaths = [
|
|
13657
|
-
`./assets/i18n/${lang}/${coreFileName}`,
|
|
13658
|
-
`./node_modules/@solcre-org/core-ui/assets/i18n/${lang}/${coreFileName}`,
|
|
13659
|
-
`./i18n/${lang}/${coreFileName}`,
|
|
13660
|
-
];
|
|
13661
|
-
return this.tryLoadFromPaths(possiblePaths, 0);
|
|
13662
|
-
}
|
|
13663
|
-
tryLoadFromPaths(paths, index) {
|
|
13664
|
-
if (index >= paths.length) {
|
|
13665
|
-
console.warn('No se pudieron cargar las traducciones del core-ui desde ninguna ruta');
|
|
13666
|
-
return of({});
|
|
13667
|
-
}
|
|
13668
|
-
const currentPath = paths[index];
|
|
13669
|
-
return this.http.get(currentPath).pipe(catchError$1(error => {
|
|
13670
|
-
console.warn(`Fallo al cargar desde: ${currentPath}`, error);
|
|
13671
|
-
return this.tryLoadFromPaths(paths, index + 1);
|
|
13672
|
-
}));
|
|
13673
|
-
}
|
|
13674
|
-
mergeWithProjectPriority(coreTranslations, projectTranslations) {
|
|
13675
|
-
const result = { ...coreTranslations };
|
|
13676
|
-
for (const key in projectTranslations) {
|
|
13677
|
-
if (projectTranslations.hasOwnProperty(key)) {
|
|
13678
|
-
if (projectTranslations[key] &&
|
|
13679
|
-
typeof projectTranslations[key] === 'object' &&
|
|
13680
|
-
!Array.isArray(projectTranslations[key]) &&
|
|
13681
|
-
result[key] &&
|
|
13682
|
-
typeof result[key] === 'object' &&
|
|
13683
|
-
!Array.isArray(result[key])) {
|
|
13684
|
-
result[key] = this.mergeWithProjectPriority(result[key], projectTranslations[key]);
|
|
13685
|
-
}
|
|
13686
|
-
else {
|
|
13687
|
-
result[key] = projectTranslations[key];
|
|
13688
|
-
}
|
|
13689
|
-
}
|
|
13690
|
-
}
|
|
13691
|
-
return result;
|
|
13692
|
-
}
|
|
13693
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslationMergeService, deps: [{ token: i1$1.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
13694
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslationMergeService, providedIn: 'root' });
|
|
13695
|
-
}
|
|
13696
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: TranslationMergeService, decorators: [{
|
|
13697
|
-
type: Injectable,
|
|
13698
|
-
args: [{
|
|
13699
|
-
providedIn: 'root'
|
|
13700
|
-
}]
|
|
13701
|
-
}], ctorParameters: () => [{ type: i1$1.HttpClient }] });
|
|
13702
|
-
|
|
13703
14917
|
class CoreUiTranslateLoader {
|
|
13704
14918
|
projectPath;
|
|
13705
14919
|
projectFileName;
|
|
@@ -13762,9 +14976,6 @@ function provideCoreUiTranslateLoader(config) {
|
|
|
13762
14976
|
}));
|
|
13763
14977
|
}
|
|
13764
14978
|
|
|
13765
|
-
const PERMISSION_RESOURCES_PROVIDER = new InjectionToken('PERMISSION_RESOURCES_PROVIDER');
|
|
13766
|
-
const PERMISSION_ACTIONS_PROVIDER = new InjectionToken('PERMISSION_ACTIONS_PROVIDER');
|
|
13767
|
-
|
|
13768
14979
|
function providePermissionService(permissionService) {
|
|
13769
14980
|
return {
|
|
13770
14981
|
provide: PERMISSION_PROVIDER,
|
|
@@ -13835,70 +15046,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
|
|
|
13835
15046
|
}]
|
|
13836
15047
|
}] });
|
|
13837
15048
|
|
|
13838
|
-
class CoreUiTranslateService {
|
|
13839
|
-
translateService = inject(TranslateService);
|
|
13840
|
-
initialize(defaultLang = 'es', currentLang) {
|
|
13841
|
-
this.translateService.setDefaultLang(defaultLang);
|
|
13842
|
-
this.translateService.use(currentLang || defaultLang);
|
|
13843
|
-
}
|
|
13844
|
-
changeLanguage(lang) {
|
|
13845
|
-
return this.translateService.use(lang);
|
|
13846
|
-
}
|
|
13847
|
-
instant(key, params) {
|
|
13848
|
-
return this.translateService.instant(key, params);
|
|
13849
|
-
}
|
|
13850
|
-
get(key, params) {
|
|
13851
|
-
return this.translateService.get(key, params);
|
|
13852
|
-
}
|
|
13853
|
-
getMany(keys, params) {
|
|
13854
|
-
return this.translateService.get(keys, params);
|
|
13855
|
-
}
|
|
13856
|
-
getCurrentLang() {
|
|
13857
|
-
return this.translateService.currentLang;
|
|
13858
|
-
}
|
|
13859
|
-
getDefaultLang() {
|
|
13860
|
-
return this.translateService.getDefaultLang();
|
|
13861
|
-
}
|
|
13862
|
-
getTranslateService() {
|
|
13863
|
-
return this.translateService;
|
|
13864
|
-
}
|
|
13865
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: CoreUiTranslateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
13866
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: CoreUiTranslateService, providedIn: 'root' });
|
|
13867
|
-
}
|
|
13868
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: CoreUiTranslateService, decorators: [{
|
|
13869
|
-
type: Injectable,
|
|
13870
|
-
args: [{
|
|
13871
|
-
providedIn: 'root'
|
|
13872
|
-
}]
|
|
13873
|
-
}] });
|
|
13874
|
-
|
|
13875
|
-
class PermissionEnumsService {
|
|
13876
|
-
externalResources = inject(PERMISSION_RESOURCES_PROVIDER, { optional: true });
|
|
13877
|
-
externalActions = inject(PERMISSION_ACTIONS_PROVIDER, { optional: true });
|
|
13878
|
-
getResources() {
|
|
13879
|
-
return this.externalResources || PermissionsResources;
|
|
13880
|
-
}
|
|
13881
|
-
getActions() {
|
|
13882
|
-
return this.externalActions || PermissionsActions;
|
|
13883
|
-
}
|
|
13884
|
-
getResourceValue(key) {
|
|
13885
|
-
const resources = this.getResources();
|
|
13886
|
-
return resources[key];
|
|
13887
|
-
}
|
|
13888
|
-
getActionValue(key) {
|
|
13889
|
-
const actions = this.getActions();
|
|
13890
|
-
return actions[key];
|
|
13891
|
-
}
|
|
13892
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: PermissionEnumsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
13893
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: PermissionEnumsService, providedIn: 'root' });
|
|
13894
|
-
}
|
|
13895
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: PermissionEnumsService, decorators: [{
|
|
13896
|
-
type: Injectable,
|
|
13897
|
-
args: [{
|
|
13898
|
-
providedIn: 'root'
|
|
13899
|
-
}]
|
|
13900
|
-
}] });
|
|
13901
|
-
|
|
13902
15049
|
/*
|
|
13903
15050
|
* Public API Surface of core-ui-library
|
|
13904
15051
|
*/
|
|
@@ -13907,5 +15054,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
|
|
|
13907
15054
|
* Generated bundle index. Do not edit.
|
|
13908
15055
|
*/
|
|
13909
15056
|
|
|
13910
|
-
export { ActiveFiltersComponent, AlertComponent, AlertContainerComponent, AlertService, AlertType, ApiConfigurationProvider, BaseFieldComponent, ButtonContext, ButtonSize, ButtonType, CacheBustingInterceptor, CardComponent, CarouselComponent, CheckboxFieldComponent, ConfigurationModel, ConfirmationDialogComponent, ConfirmationDialogService, CoreHostDirective, CoreUiHttpLoaderFactory, CoreUiTranslateLoader, CoreUiTranslateService, DataListComponent, DataListItemComponent, DateFieldComponent, DateUtility, DatetimeFieldComponent, DialogActions, DocumentAction, DocumentDisplayMode, DropdownComponent, DropdownDirection, DropdownService, DynamicFieldDirective, DynamicFieldsHelper, FieldErrorsComponent, FieldType, FileFieldComponent, FileModel, FileTemplateModel, FileTemplateType, FileType, FileTypeModel, FileUploadService, FilterModalComponent, FilterService, FilterType, GenericButtonComponent, GenericDocumentationComponent, GenericModalComponent, GenericPaginationComponent, GenericRatingComponent, GenericSidebarComponent, GenericSkeletonComponent, GenericStepsComponent, GenericTableComponent, GenericTabsComponent, GenericTimelineComponent, GlobalApiConfigService, HeaderComponent, HeaderConfigurationService, HeaderElementType, HeaderService, HttpLoaderFactory, ImageModalComponent, ImageModalService, ImagePreviewComponent, LayoutAuth, LayoutBreakpoint, LayoutComponent, LayoutService, LayoutStateService, LayoutType, LoaderComponent, LoaderService, MainNavComponent, MainNavService, ModalMode, ModelApiService, MultiEntryFieldComponent, MultiEntryOutputFormat, NumberFieldComponent, NumberFieldConfigType, NumberFieldType, NumberRange, PERMISSION_ACTIONS_PROVIDER, PERMISSION_PROVIDER, PERMISSION_RESOURCES_PROVIDER, PaginationService, PasswordFieldComponent, PermissionEnumsService, PermissionModel, PermissionService, PermissionWrapperService, PermissionsActions, PermissionsInterceptor, PermissionsResources, ProgressBarComponent, ProgressBarSize, RatingService, RatingSize, RatingType, ResetPasswordModel, RoleModel, SelectFieldComponent, ServerSelectFieldComponent, ServerSelectService, SidebarCustomModalComponent, SidebarCustomModalService, SidebarHeight, SidebarMobileModalService, SidebarMobileType, SidebarPosition, SidebarService, SidebarState, SidebarTemplateRegistryService, SidebarVisibility, SidebarWidth, SkeletonAnimation, SkeletonService, SkeletonSize, SkeletonType, SmartFieldComponent, SortDirection, SortMode, StepSize, StepStatus, StepType, StepsService, SwitchFieldComponent, TableAction, TableActionService, TableDataService, TableSortService, TextAreaFieldComponent, TextFieldComponent, TimeFieldComponent, TimeInterval, TimelineService, TimelineStatus, TimelineType, TranslationMergeService, UsersModel, VERSION, equalToValidator, isSameDate, provideCoreUiTranslateLoader, providePermissionActions, providePermissionEnums, providePermissionResources, providePermissionService, providePermissionServiceFactory, provideTranslateLoader };
|
|
15057
|
+
export { ActiveFiltersComponent, AlertComponent, AlertContainerComponent, AlertService, AlertType, ApiConfigurationProvider, BaseFieldComponent, ButtonContext, ButtonSize, ButtonType, CacheBustingInterceptor, CardComponent, CarouselComponent, ChatMessagePosition, ChatMessageType, CheckboxFieldComponent, ConfigurationModel, ConfirmationDialogComponent, ConfirmationDialogService, CoreHostDirective, CoreUiHttpLoaderFactory, CoreUiTranslateLoader, CoreUiTranslateService, DataListComponent, DataListItemComponent, DateFieldComponent, DateUtility, DatetimeFieldComponent, DialogActions, DocumentAction, DocumentDisplayMode, DropdownComponent, DropdownDirection, DropdownService, DynamicFieldDirective, DynamicFieldsHelper, FieldErrorsComponent, FieldType, FileFieldComponent, FileModel, FilePreviewActionType, FileTemplateModel, FileTemplateType, FileType, FileTypeModel, FileUploadService, FilterModalComponent, FilterService, FilterType, GenericButtonComponent, GenericChatComponent, GenericChatService, GenericDocumentationComponent, GenericModalComponent, GenericPaginationComponent, GenericRatingComponent, GenericSidebarComponent, GenericSkeletonComponent, GenericStepsComponent, GenericTableComponent, GenericTabsComponent, GenericTimelineComponent, GlobalApiConfigService, HeaderComponent, HeaderConfigurationService, HeaderElementType, HeaderService, HttpLoaderFactory, ImageModalComponent, ImageModalService, ImagePreviewComponent, LayoutAuth, LayoutBreakpoint, LayoutComponent, LayoutService, LayoutStateService, LayoutType, LoaderComponent, LoaderService, MainNavComponent, MainNavService, ModalMode, ModelApiService, MultiEntryFieldComponent, MultiEntryOutputFormat, NumberFieldComponent, NumberFieldConfigType, NumberFieldType, NumberRange, PERMISSION_ACTIONS_PROVIDER, PERMISSION_PROVIDER, PERMISSION_RESOURCES_PROVIDER, PaginationService, PasswordFieldComponent, PermissionEnumsService, PermissionModel, PermissionService, PermissionWrapperService, PermissionsActions, PermissionsInterceptor, PermissionsResources, ProgressBarComponent, ProgressBarSize, RatingService, RatingSize, RatingType, ResetPasswordModel, RoleModel, SelectFieldComponent, ServerSelectFieldComponent, ServerSelectService, SidebarCustomModalComponent, SidebarCustomModalService, SidebarHeight, SidebarMobileModalService, SidebarMobileType, SidebarPosition, SidebarService, SidebarState, SidebarTemplateRegistryService, SidebarVisibility, SidebarWidth, SkeletonAnimation, SkeletonService, SkeletonSize, SkeletonType, SmartFieldComponent, SortDirection, SortMode, StepSize, StepStatus, StepType, StepsService, SwitchFieldComponent, TableAction, TableActionService, TableDataService, TableSortService, TextAreaFieldComponent, TextFieldComponent, TimeFieldComponent, TimeInterval, TimelineService, TimelineStatus, TimelineType, TranslationMergeService, UsersModel, VERSION, equalToValidator, isSameDate, provideCoreUiTranslateLoader, providePermissionActions, providePermissionEnums, providePermissionResources, providePermissionService, providePermissionServiceFactory, provideTranslateLoader };
|
|
13911
15058
|
//# sourceMappingURL=solcre-org-core-ui.mjs.map
|