Skip to content

Commit 6bfe499

Browse files
committed
feat(chat): add attachments templates
1 parent b4661b9 commit 6bfe499

File tree

5 files changed

+148
-67
lines changed

5 files changed

+148
-67
lines changed

src/components/chat/chat-message-list.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { repeat } from 'lit/directives/repeat.js';
44
import { registerComponent } from '../common/definitions/register.js';
55
import IgcChatMessageComponent from './chat-message.js';
66
import { styles } from './themes/message-list.base.css.js';
7-
import type { IgcMessage } from './types.js';
7+
import type { AttachmentTemplate, IgcMessage } from './types.js';
88

99
/**
1010
*
@@ -31,6 +31,18 @@ export default class IgcChatMessageListComponent extends LitElement {
3131
@property({ type: Boolean, attribute: 'disable-auto-scroll' })
3232
public disableAutoScroll = false;
3333

34+
@property({ type: Function })
35+
public attachmentTemplate?: AttachmentTemplate;
36+
37+
@property({ type: Function })
38+
public attachmentHeaderTemplate?: AttachmentTemplate;
39+
40+
@property({ type: Function })
41+
public attachmentActionsTemplate?: AttachmentTemplate;
42+
43+
@property({ type: Function })
44+
public attachmentContentTemplate?: AttachmentTemplate;
45+
3446
private formatDate(date: Date): string {
3547
const today = new Date();
3648
const yesterday = new Date(today);
@@ -109,6 +121,10 @@ export default class IgcChatMessageListComponent extends LitElement {
109121
<igc-chat-message
110122
.message=${message}
111123
.isResponse=${message.isResponse}
124+
.attachmentTemplate=${this.attachmentTemplate}
125+
.attachmentHeaderTemplate=${this.attachmentHeaderTemplate}
126+
.attachmentActionsTemplate=${this.attachmentActionsTemplate}
127+
.attachmentContentTemplate=${this.attachmentContentTemplate}
112128
></igc-chat-message>
113129
`
114130
)}

src/components/chat/chat-message.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { registerComponent } from '../common/definitions/register.js';
55
import { renderMarkdown } from './markdown-util.js';
66
import { IgcMessageAttachmentsComponent } from './message-attachments.js';
77
import { styles } from './themes/message.base.css.js';
8-
import type { IgcMessage } from './types.js';
8+
import type { AttachmentTemplate, IgcMessage } from './types.js';
99

1010
/**
1111
*
@@ -33,9 +33,17 @@ export default class IgcChatMessageComponent extends LitElement {
3333
@property({ reflect: true, attribute: false })
3434
public isResponse = false;
3535

36-
private formatTime(date: Date | undefined): string | undefined {
37-
return date?.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
38-
}
36+
@property({ type: Function })
37+
public attachmentTemplate?: AttachmentTemplate;
38+
39+
@property({ type: Function })
40+
public attachmentHeaderTemplate?: AttachmentTemplate;
41+
42+
@property({ type: Function })
43+
public attachmentActionsTemplate?: AttachmentTemplate;
44+
45+
@property({ type: Function })
46+
public attachmentContentTemplate?: AttachmentTemplate;
3947

4048
protected override render() {
4149
const containerClass = `message-container ${!this.isResponse ? 'sent' : ''}`;
@@ -50,6 +58,10 @@ export default class IgcChatMessageComponent extends LitElement {
5058
${this.message?.attachments && this.message?.attachments.length > 0
5159
? html`<igc-message-attachments
5260
.attachments=${this.message?.attachments}
61+
.attachmentTemplate=${this.attachmentTemplate}
62+
.attachmentHeaderTemplate=${this.attachmentHeaderTemplate}
63+
.attachmentActionsTemplate=${this.attachmentActionsTemplate}
64+
.attachmentContentTemplate=${this.attachmentContentTemplate}
5365
>
5466
</igc-message-attachments>`
5567
: ''}

src/components/chat/chat.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import { EventEmitterMixin } from '../common/mixins/event-emitter.js';
77
import IgcChatInputComponent from './chat-input.js';
88
import IgcChatMessageListComponent from './chat-message-list.js';
99
import { styles } from './themes/chat.base.css.js';
10-
import type { IgcMessage, IgcMessageAttachment } from './types.js';
10+
import type {
11+
AttachmentTemplate,
12+
IgcMessage,
13+
IgcMessageAttachment,
14+
} from './types.js';
1115

1216
export interface IgcChatComponentEventMap {
1317
igcMessageSend: CustomEvent<IgcMessage>;
@@ -59,6 +63,18 @@ export default class IgcChatComponent extends EventEmitterMixin<
5963
@property({ type: String, attribute: 'header-text', reflect: true })
6064
public headerText = '';
6165

66+
@property({ type: Function })
67+
public attachmentTemplate?: AttachmentTemplate;
68+
69+
@property({ type: Function })
70+
public attachmentHeaderTemplate?: AttachmentTemplate;
71+
72+
@property({ type: Function })
73+
public attachmentActionsTemplate?: AttachmentTemplate;
74+
75+
@property({ type: Function })
76+
public attachmentContentTemplate?: AttachmentTemplate;
77+
6278
public override connectedCallback() {
6379
super.connectedCallback();
6480
this.addEventListener(
@@ -122,6 +138,10 @@ export default class IgcChatComponent extends EventEmitterMixin<
122138
.messages=${this.messages}
123139
.disableAutoScroll=${this.disableAutoScroll}
124140
.isAiResponding=${this.isAiResponding}
141+
.attachmentTemplate=${this.attachmentTemplate}
142+
.attachmentHeaderTemplate=${this.attachmentHeaderTemplate}
143+
.attachmentActionsTemplate=${this.attachmentActionsTemplate}
144+
.attachmentContentTemplate=${this.attachmentContentTemplate}
125145
>
126146
</igc-chat-message-list>
127147
<igc-chat-input

src/components/chat/message-attachments.ts

Lines changed: 89 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import IgcIconComponent from '../icon/icon.js';
77
import { registerIconFromText } from '../icon/icon.registry.js';
88
import { styles } from './themes/message-attachments.base.css';
99
import {
10+
type AttachmentTemplate,
1011
type IgcMessageAttachment,
1112
closeIcon,
1213
fileIcon,
@@ -41,6 +42,18 @@ export class IgcMessageAttachmentsComponent extends LitElement {
4142
@property({ type: String })
4243
previewImage = '';
4344

45+
@property({ type: Function })
46+
attachmentTemplate: AttachmentTemplate | undefined;
47+
48+
@property({ type: Function })
49+
attachmentHeaderTemplate: AttachmentTemplate | undefined;
50+
51+
@property({ type: Function })
52+
attachmentActionsTemplate: AttachmentTemplate | undefined;
53+
54+
@property({ type: Function })
55+
attachmentContentTemplate: AttachmentTemplate | undefined;
56+
4457
constructor() {
4558
super();
4659
registerIconFromText('close', closeIcon, 'material');
@@ -50,12 +63,6 @@ export class IgcMessageAttachmentsComponent extends LitElement {
5063
registerIconFromText('more', moreIcon, 'material');
5164
}
5265

53-
private formatFileSize(bytes = 0): string {
54-
if (bytes < 1024) return `${bytes} B`;
55-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
56-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
57-
}
58-
5966
private openImagePreview(url: string) {
6067
this.previewImage = url;
6168
}
@@ -82,60 +89,82 @@ export class IgcMessageAttachmentsComponent extends LitElement {
8289
protected override render() {
8390
return html`
8491
<div class="attachments-container">
85-
${this.attachments.map(
86-
(attachment) => html`
87-
<igc-expansion-panel
88-
indicator-position="none"
89-
.open=${attachment.type === 'image'}
90-
@igcClosing=${(ev: CustomEvent) =>
91-
this.handleToggle(ev, attachment)}
92-
@igcOpening=${(ev: CustomEvent) =>
93-
this.handleToggle(ev, attachment)}
94-
>
95-
<div slot="title" class="attachment">
96-
<div class="details">
97-
${attachment.type === 'image'
98-
? html`<igc-icon
99-
name="image"
100-
collection="material"
101-
class="medium"
102-
></igc-icon>`
103-
: html`<igc-icon
104-
name="file"
105-
collection="material"
106-
class="medium"
107-
></igc-icon>`}
108-
<span class="file-name">${attachment.name}</span>
109-
</div>
110-
<div class="actions">
111-
${attachment.type === 'image'
112-
? html` <igc-icon-button
113-
name="preview"
114-
collection="material"
115-
variant="flat"
116-
class="small"
117-
@click=${() => this.openImagePreview(attachment.url)}
118-
></igc-icon-button>`
119-
: ''}
120-
<igc-icon-button
121-
name="more"
122-
collection="material"
123-
variant="flat"
124-
class="small"
125-
></igc-icon-button>
126-
</div>
127-
</div>
128-
129-
${attachment.type === 'image'
130-
? html` <img
131-
class="image-attachment"
132-
src=${attachment.url}
133-
alt=${attachment.name}
134-
/>`
135-
: ''}
136-
</igc-expansion-panel>
137-
`
138-
)}
92+
${this.attachmentTemplate
93+
? this.attachmentTemplate(this.attachments)
94+
: html` ${this.attachments.map(
95+
(attachment) =>
96+
html` <igc-expansion-panel
97+
indicator-position="none"
98+
.open=${attachment.type === 'image'}
99+
@igcClosing=${(ev: CustomEvent) =>
100+
this.handleToggle(ev, attachment)}
101+
@igcOpening=${(ev: CustomEvent) =>
102+
this.handleToggle(ev, attachment)}
103+
>
104+
<div slot="title" class="attachment">
105+
<div class="details">
106+
${this.attachmentHeaderTemplate
107+
? this.attachmentHeaderTemplate(this.attachments)
108+
: html`
109+
<slot name="attachment-icon">
110+
${attachment.type === 'image'
111+
? html`<igc-icon
112+
name="image"
113+
collection="material"
114+
class="medium"
115+
></igc-icon>`
116+
: html`<igc-icon
117+
name="file"
118+
collection="material"
119+
class="medium"
120+
></igc-icon>`}
121+
</slot>
122+
<slot name="attachment-name">
123+
<span class="file-name">${attachment.name}</span>
124+
</slot>
125+
`}
126+
</div>
127+
<div class="actions">
128+
${this.attachmentActionsTemplate
129+
? this.attachmentActionsTemplate(this.attachments)
130+
: html`
131+
<slot name="attachment-actions">
132+
${attachment.type === 'image'
133+
? html` <igc-icon-button
134+
name="preview"
135+
collection="material"
136+
variant="flat"
137+
class="small"
138+
@click=${() =>
139+
this.openImagePreview(attachment.url)}
140+
></igc-icon-button>`
141+
: ''}
142+
<igc-icon-button
143+
name="more"
144+
collection="material"
145+
variant="flat"
146+
class="small"
147+
></igc-icon-button>
148+
</slot>
149+
`}
150+
</div>
151+
</div>
152+
153+
${this.attachmentContentTemplate
154+
? this.attachmentContentTemplate(this.attachments)
155+
: html`
156+
<slot name="attachment-content">
157+
${attachment.type === 'image'
158+
? html` <img
159+
class="image-attachment"
160+
src=${attachment.url}
161+
alt=${attachment.name}
162+
/>`
163+
: ''}
164+
</slot>
165+
`}
166+
</igc-expansion-panel>`
167+
)}`}
139168
</div>
140169
141170
${this.previewImage

src/components/chat/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { TemplateResult } from 'lit';
2+
13
export type IgcMessageAttachmentType = 'image' | 'file';
24

35
export interface IgcMessage {
@@ -16,7 +18,9 @@ export interface IgcMessageAttachment {
1618
size?: number;
1719
thumbnail?: string;
1820
}
19-
21+
export type AttachmentTemplate = (
22+
attachments: IgcMessageAttachment[]
23+
) => TemplateResult;
2024
export const attachmentIcon =
2125
'<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M720-330q0 104-73 177T470-80q-104 0-177-73t-73-177v-370q0-75 52.5-127.5T400-880q75 0 127.5 52.5T580-700v350q0 46-32 78t-78 32q-46 0-78-32t-32-78v-370h80v370q0 13 8.5 21.5T470-320q13 0 21.5-8.5T500-350v-350q-1-42-29.5-71T400-800q-42 0-71 29t-29 71v370q-1 71 49 120.5T470-160q70 0 119-49.5T640-330v-390h80v390Z"/></svg>';
2226
export const sendButtonIcon =

0 commit comments

Comments
 (0)