diff --git a/projects/igniteui-angular/src/lib/wrappers/chat-wrapper.component.html b/projects/igniteui-angular/src/lib/wrappers/chat-wrapper.component.html new file mode 100644 index 00000000000..02d8aba82fd --- /dev/null +++ b/projects/igniteui-angular/src/lib/wrappers/chat-wrapper.component.html @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/projects/igniteui-angular/src/lib/wrappers/chat-wrapper.component.ts b/projects/igniteui-angular/src/lib/wrappers/chat-wrapper.component.ts new file mode 100644 index 00000000000..b47e77691d3 --- /dev/null +++ b/projects/igniteui-angular/src/lib/wrappers/chat-wrapper.component.ts @@ -0,0 +1,78 @@ +import { + Component, + CUSTOM_ELEMENTS_SCHEMA, + EmbeddedViewRef, + inject, + ViewContainerRef, + input, + effect, + model +} from '@angular/core'; + +@Component({ + selector: 'igx-chat-wrapper', + templateUrl: './chat-wrapper.component.html', + schemas: [CUSTOM_ELEMENTS_SCHEMA], + standalone: true +}) +export class IgxChatWrapperComponent { + protected readonly viewContainer = inject(ViewContainerRef); + + messages = input([]); + options = model({}); + + constructor() { + effect(() => { + const templateKeys = [ + 'attachmentTemplate', + 'attachmentHeaderTemplate', + 'attachmentActionsTemplate', + 'attachmentContentTemplate', + 'messageTemplate', + 'messageActionsTemplate', + 'composingIndicatorTemplate', + 'textInputTemplate', + 'textAreaActionsTemplate', + 'textAreaAttachmentsTemplate' + ]; + const templates = this.options().templates ?? {}; + const newTemplates: any = {}; + + templateKeys.forEach(key => { + const currentTemplate = templates[key]; + if (currentTemplate) { + const mapKey = `${key}Map`; + const lastKey = `last${key.charAt(0).toUpperCase() + key.slice(1)}`; + if (!this[mapKey]) this[mapKey] = new Map>(); + if (!this[lastKey]) this[lastKey] = undefined; + + newTemplates[key] = (item: any) => { + if (this[lastKey] !== currentTemplate) { + this[mapKey].forEach((view: EmbeddedViewRef) => view.destroy()); + this[mapKey].clear(); + this[lastKey] = currentTemplate; + } + + const cacheKey = item.id; // will not set cacheKey for arrays (attachments, etc.) + if (this[mapKey].has(cacheKey)) { + return this[mapKey].get(cacheKey)!.rootNodes; + } + + const context = { $implicit: item }; + if (currentTemplate === null) return; + + const view = this.viewContainer.createEmbeddedView(currentTemplate, context); + + if (cacheKey) { + this[mapKey].set(cacheKey, view); + } + + return view.rootNodes; + }; + } + }); + + this.options().templates = { ...templates, ...newTemplates }; + }); + } +} diff --git a/projects/igniteui-angular/src/lib/wrappers/public_api.ts b/projects/igniteui-angular/src/lib/wrappers/public_api.ts new file mode 100644 index 00000000000..44c16fc5a36 --- /dev/null +++ b/projects/igniteui-angular/src/lib/wrappers/public_api.ts @@ -0,0 +1 @@ +export * from './chat-wrapper.component'; diff --git a/projects/igniteui-angular/src/public_api.ts b/projects/igniteui-angular/src/public_api.ts index 0e844761ba2..38d16574c07 100644 --- a/projects/igniteui-angular/src/public_api.ts +++ b/projects/igniteui-angular/src/public_api.ts @@ -108,6 +108,7 @@ export * from './lib/date-range-picker/public_api'; export * from './lib/date-common/public_api'; export * from './lib/tree/public_api'; export * from './lib/query-builder/public_api'; +export * from './lib/wrappers/public_api'; /** * Exporter services, classes, interfaces and enums diff --git a/src/app/app.component.ts b/src/app/app.component.ts index c4637552c6b..9c8ab1fd214 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -127,6 +127,11 @@ export class AppComponent implements OnInit { icon: 'view_carousel', name: 'Carousel' }, + { + link: '/chat-wrapper', + icon: 'view_column', + name: 'Chat Wrapper' + }, { link: '/chip', icon: 'android', diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 992516f1574..91c2710ecdd 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -147,6 +147,7 @@ import { HoundComponent } from './hound/hound.component'; import { LabelSampleComponent } from "./label/label.sample"; import { GridRecreateSampleComponent } from './grid-re-create/grid-re-create.sample'; import { HierarchicalGridAdvancedFilteringSampleComponent } from './hierarchical-grid-advanced-filtering/hierarchical-grid-advanced-filtering.sample'; +import { ChatWrapperSampleComponent } from './chat-wrapper/chat-wrapper.sample'; export const appRoutes: Routes = [ { @@ -198,6 +199,10 @@ export const appRoutes: Routes = [ path: 'carousel', component: CarouselSampleComponent }, + { + path: 'chat-wrapper', + component: ChatWrapperSampleComponent + }, { path: 'input-controls', component: InputControlsSampleComponent diff --git a/src/app/chat-wrapper/chat-wrapper.sample.html b/src/app/chat-wrapper/chat-wrapper.sample.html new file mode 100644 index 00000000000..640ebb58092 --- /dev/null +++ b/src/app/chat-wrapper/chat-wrapper.sample.html @@ -0,0 +1,61 @@ +
+
+ +
+ +
+ + + + +
+ @for (attachment of attachments; track attachment) { + {{ attachment.name }} - {{ attachment.file.size }} KB + } +
+
+ + + @if (message.attachments && message.attachments.length > 0) { +
+ Attachments: + +
+ } + + @if (message.text && message.text.includes('red')) { + + } + @else if (message.text && message.text.includes('blue')) { + + } + @else { + + } +
+ + + @if (message.attachments && message.attachments.length > 0) { +
+ Attachments: + +
+ } + +
+ Other template: {{message.text}} +
+
+
+
diff --git a/src/app/chat-wrapper/chat-wrapper.sample.scss b/src/app/chat-wrapper/chat-wrapper.sample.scss new file mode 100644 index 00000000000..bf3d708a928 --- /dev/null +++ b/src/app/chat-wrapper/chat-wrapper.sample.scss @@ -0,0 +1,3 @@ +.button-wrapper { + margin-bottom: 20px; +} \ No newline at end of file diff --git a/src/app/chat-wrapper/chat-wrapper.sample.ts b/src/app/chat-wrapper/chat-wrapper.sample.ts new file mode 100644 index 00000000000..351f909da70 --- /dev/null +++ b/src/app/chat-wrapper/chat-wrapper.sample.ts @@ -0,0 +1,91 @@ +import { NgTemplateOutlet } from '@angular/common'; +import { + AfterViewInit, + Component, + CUSTOM_ELEMENTS_SCHEMA, + TemplateRef, + ViewChild, + ViewEncapsulation, +} from '@angular/core'; +import { + IgxButtonDirective, + IgxChatWrapperComponent, +} from 'igniteui-angular'; +import { + defineComponents, + IgcChatComponent +} from 'igniteui-webcomponents'; + + +defineComponents( + IgcChatComponent +); + +@Component({ + encapsulation: ViewEncapsulation.None, + selector: 'app-chat-wrapper-sample', + styleUrls: ['chat-wrapper.sample.scss'], + templateUrl: 'chat-wrapper.sample.html', + schemas: [CUSTOM_ELEMENTS_SCHEMA], + imports: [ + IgxButtonDirective, + IgxChatWrapperComponent, + NgTemplateOutlet + ] +}) +export class ChatWrapperSampleComponent implements AfterViewInit { + private msgTemplate?: TemplateRef; + + @ViewChild('chatWrapper', { read: IgxChatWrapperComponent }) + public chatWrapper!: IgxChatWrapperComponent; + + @ViewChild('messageTemplate1', { static: true }) + public messageTemplate1!: TemplateRef; + + @ViewChild('messageTemplate2', { static: true }) + public messageTemplate2!: TemplateRef; + + @ViewChild('attachmentsTemplate', { static: true }) + public attachmentsTemplate!: TemplateRef; + + public messages = [ + { + id: '1', + text: 'Hello! How can I help you today?', + sender: 'bot', + timestamp: new Date(Date.now() - 3600000), + } + ]; + + public options = { + templates: {} + }; + + ngAfterViewInit() { + this.options.templates = { + messageTemplate: this.messageTemplate1, + textAreaAttachmentsTemplate: this.attachmentsTemplate + }; + this.chatWrapper.options.set({ ...this.options, templates: this.options.templates }); + } + + public onClick(context: any) { + console.log('Context: ' + context); + } + + public switchMessageTemplate() { + this.msgTemplate = this.msgTemplate === this.messageTemplate2 ? this.messageTemplate1 : this.messageTemplate2; + this.options.templates = { + ...this.options.templates, + messageTemplate: this.msgTemplate, + textAreaAttachmentsTemplate: this.attachmentsTemplate + }; + this.chatWrapper.options.set({ ...this.options, templates: this.options.templates }); + this.messages = [...this.chatWrapper.messages()]; + } + + public onMessageCreated($event: any) { + const newMessage = $event.detail; + this.messages = [...this.messages, newMessage]; + } +} \ No newline at end of file