Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support custom attachments #646

Merged
merged 1 commit into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docusaurus/docs/Angular/_common/supported-attachments.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import AttachmentsScreenshot from "../assets/attachments-screenshot.png";
import VoiceRecordingScreenshot from "../assets/voice-recording-screenshot.png";

- Images (including GIFs) are displayed inline
- Videos are displayed inline
- Voice recordings are displayed inline
- Other files can be downloaded
- Links in a message are enriched with built-in open graph URL scraping

**Example 1** - different type of attachments:

<img src={AttachmentsScreenshot} width="500" />

**Example 2** - voice recording:

<img src={VoiceRecordingScreenshot} width="500" />
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docusaurus/docs/Angular/assets/payment-link.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
251 changes: 251 additions & 0 deletions docusaurus/docs/Angular/code-examples/custom-attachments.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
---
id: custom-attachments
title: Custom attachments
---

import SupportedAttachments from "../_common/supported-attachments.mdx";
import PaymentLink from "../assets/payment-link.png";
import PaymentPreview from "../assets/payment-preview.png";
import PaymentAttachment from "../assets/payment-attachment.png";

The Stream API allows you to add any attachment to a message. The SDK supports some common types (such as images, videos, etc.) out-of-the-box, but you have to provide your own template to display others.

The Angular SDK has out-of-the-box support for the following types:

<SupportedAttachments />

This guide will show you how to create custom attachments. In the example, we'll allow users to make payment links to send money to each other, but the same logic works for any attachment.

## Creating the attachment

Let's add a button to the message input component to make a payment link. How we create the attachment doesn't matter; the important part is to create an `Attachment` object we can provide to the [`AttachmentService`](../../services/AttachmentService).

```html showLineNumbers
<!-- Each message input component has it's own instance of the AttachmentService -->
<stream-message-input #input>
<button
message-input-start
(click)="createPaymentLink(input.attachmentService)"
>
Payment link
</button>
</stream-message-input>
```

```typescript showLineNumbers {18-22}
// Optionally, you can define the shape of your custom attachments to get proper compile checks
type MyGenerics = DefaultStreamChatGenerics & {
attachmentType: {
type: 'custom';
subtype: 'payment';
value: string;
paymentLink: string;
};
};

createPaymentLink(attachmentService: AttachmentService) {
const attachment: Attachment<MyGenerics> = {
type: 'custom',
subtype: 'payment',
value: `${Math.ceil(Math.random() * 99)}$`,
paymentLink: 'pay/me/or/else',
};
// Insert the attachment to the list of custom attachments
attachmentService.customAttachments$.next([
...attachmentService.customAttachments$.value,
attachment,
]);
}
```

<img src={PaymentLink} width="500" />

Clicking the "Payment link" will add the attachment to the message, but we don't yet have any visual indicator of this.

## Custom attachment preview

Let's add a preview of the payment attachment.

To do this, we define the HTML template code for the preview that uses the `AttachmentService` to display the previews of the custom attachments:

```html showLineNumbers
<ng-template #customAttachmentPreviews let-service="service">
<div
style="padding: 8px; background-color: azure; border-radius: inherit"
class="custom-attachment-container"
*ngFor="let attachment of service.customAttachments$ | async"
>
<ng-container [ngSwitch]="attachment.subtype">
<div *ngSwitchCase="'payment'" class="payment-link">
🤑 {{ attachment.value }}
</div>
</ng-container>
</div>
</ng-template>
```

If you have multiple different types of custom attachments, you can display them all here.

Next, we register the template for the `customAttachmentPreviewListTemplate$` field of the [`CustomTemplatesService`](../../services/CustomTemplatesService):

```typescript showLineNumbers {8-10}
export class AppComponent implements AfterViewInit {
@ViewChild("customAttachmentPreviews")
customAttachmentPreviewsTemplate!: TemplateRef<CustomAttachmentPreviewListContext>;

constructor(private customTemplateService: CustomTemplatesService) {}

ngAfterViewInit(): void {
this.customTemplateService.customAttachmentPreviewListTemplate$.next(
this.customAttachmentPreviewsTemplate
);
}
}
```

If we click the "Payment link" button, the preview is now visible:

<img src={PaymentPreview} width="500" />

## Delete attachment preview

Let's allow deleting a payment link by extending the template of the preview:

```html showLineNumbers {10-11}
<ng-template #customAttachmentPreviews let-service="service">
<div
style="padding: 8px; background-color: azure; border-radius: inherit"
class="custom-attachment-container"
*ngFor="let attachment of service.customAttachments$ | async"
>
<ng-container [ngSwitch]="attachment.subtype">
<div *ngSwitchCase="'payment'" class="payment-link">
🤑 {{ attachment.value }}
<!-- Add a delete button -->
<button (click)="deletePaymentLink(attachment, service)">X</button>
</div>
</ng-container>
</div>
</ng-template>
```

This is the implementation of the delete payment attachment method:

```typescript showLineNumbers {5-8}
deletePaymentLink(
attachment: Attachment<MyGenerics>,
attachmentService: AttachmentService<MyGenerics>
) {
attachmentService.customAttachments$.next(
attachmentService.customAttachments$.value.filter(
(a) => a.paymentLink !== attachment.paymentLink
)
);
}
```

## Loading state

Sometimes, creating attachments happens asynchronously. If that's the case, you should disable message sending while the attachment is processing.

Here is how you can do that by extending the `createPaymentLink` method:

```typescript showLineNumbers {2-5,23-26}
async createPaymentLink(attachmentService: AttachmentService) {
// Increment the upload counter to disable message send
attachmentService.attachmentUploadInProgressCounter$.next(
attachmentService.attachmentUploadInProgressCounter$.value + 1
);
const attachment: Attachment<MyGenerics> = {
type: 'custom',
subtype: 'payment',
value: `${Math.ceil(Math.random() * 99)}$`,
paymentLink: '',
};
// simulate network call
await new Promise<void>((resolve) => {
setTimeout(() => {
attachment.paymentLink = 'pay/me/or/else';
resolve();
}, 2000);
});
attachmentService.customAttachments$.next([
...attachmentService.customAttachments$.value,
attachment,
]);
// Attachment is ready, decrease the upload counter
attachmentService.attachmentUploadInProgressCounter$.next(
attachmentService.attachmentUploadInProgressCounter$.value - 1
);
}
```

## Custom attachment inside the message list

The last missing step is to display the payment link inside the message list. This will be very similar to how we created the attachment preview.

First, let's define an HTML template:

```html showLineNumbers
<ng-template #customAttachments let-attachments="attachments">
<div
class="custom-attachment-container"
*ngFor="let attachment of attachments"
>
<ng-container [ngSwitch]="attachment.subtype">
<div
style="margin-inline: 16px; margin-top: 8px"
*ngSwitchCase="'payment'"
class="payment-link"
>
💵
<a [href]="attachment.link" target="_blank">{{ attachment.value }}</a>
</div>
</ng-container>
</div>
</ng-template>
```

The `attachments` template variable will contain the list of custom attachments.

:::note
By default the SDK will treat all `image`, `file`, `giphy`, `video` and `voiceRecording` attachments as built-in. All other type of attachments are treated as custom attachments.

If you want to change the filtering logic, provide your own implementation using the `filterCustomAttachment` method of the [`MessageService`](../../services/MessageService/#filtercustomattachment).
:::

Next, we register the template for the `customAttachmentListTemplate$` field of the [`CustomTemplatesService`](../../services/CustomTemplatesService):

```typescript showLineNumbers {8-10}
export class AppComponent implements AfterViewInit {
@ViewChild("customAttachments")
customAttachmentsTemplate!: TemplateRef<CustomAttachmentListContext>;

constructor(private customTemplateService: CustomTemplatesService) {}

ngAfterViewInit(): void {
this.customTemplateService.customAttachmentListTemplate$.next(
this.customAttachmentsTemplate
);
}
}
```

This is how the attachment looks like inside the message list:

<img src={PaymentAttachment} width="500" />

If you need reference to the message ID (and parent message ID for thread replies) the attachments belong to, this is how you can access them:

```html showLineNumbers {4-5}
<ng-template
#customAttachments
let-attachments="attachments"
let-messageId="messageId"
let-parentMessageId="parentMessageId"
>
<div *ngFor="let attachment of attachments">
{{ messageId }} {{ parentMessageId }} {{ attachment | json }}
</div>
</ng-template>
```
25 changes: 6 additions & 19 deletions docusaurus/docs/Angular/components/AttachmentListComponent.mdx
Original file line number Diff line number Diff line change
@@ -1,25 +1,12 @@
import AttachmentsScreenshot from "../assets/attachments-screenshot.png";
import VoiceRecordingScreenshot from "../assets/voice-recording-screenshot.png";
import ImageSizingScreenshot1 from "../assets/image-sizing-screenshot-1.png";
import ImageSizingScreenshot2 from "../assets/image-sizing-screenshot-2.png";
import ImageSizingScreenshot3 from "../assets/image-sizing-screenshot-3.png";
import AttachmentSizeWarning from "../assets/attachment-size-warning.png";
import SupportedAttachments from "../_common/supported-attachments.mdx";

The `AttachmentList` component displays the attachments of a message. The following attachments are supported:

- Images (including GIFs) are displayed inline
- Videos are displayed inline
- Voice recordings are displayed inline (the Angular SDK doesn't support recording, only playback)
- Other files can be downloaded
- Links in a message are enriched with built-in open graph URL scraping

**Example 1** - different type of attachments:

<img src={AttachmentsScreenshot} width="500" />

**Example 2** - voice recording:

<img src={VoiceRecordingScreenshot} width="500" />
<SupportedAttachments />

## Basic Usage

Expand Down Expand Up @@ -122,7 +109,7 @@ The id of the message the attachments belong to

#### Defined in

[projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.ts:39](https://github.com/GetStream/stream-chat-angular/blob/c4925a571484c046f73b9e63286a2e64380af0c6/projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.ts#L39)
[projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.ts:44](https://github.com/GetStream/stream-chat-angular/blob/233af9a28d1b6ecdfe793362d5f20b0e8b749c16/projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.ts#L44)

---

Expand All @@ -134,7 +121,7 @@ The parent id of the message the attachments belong to

#### Defined in

[projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.ts:43](https://github.com/GetStream/stream-chat-angular/blob/c4925a571484c046f73b9e63286a2e64380af0c6/projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.ts#L43)
[projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.ts:48](https://github.com/GetStream/stream-chat-angular/blob/233af9a28d1b6ecdfe793362d5f20b0e8b749c16/projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.ts#L48)

---

Expand All @@ -146,7 +133,7 @@ The attachments to display

#### Defined in

[projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.ts:47](https://github.com/GetStream/stream-chat-angular/blob/c4925a571484c046f73b9e63286a2e64380af0c6/projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.ts#L47)
[projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.ts:52](https://github.com/GetStream/stream-chat-angular/blob/233af9a28d1b6ecdfe793362d5f20b0e8b749c16/projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.ts#L52)

---

Expand All @@ -158,6 +145,6 @@ Emits the state of the image carousel window

#### Defined in

[projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.ts:51](https://github.com/GetStream/stream-chat-angular/blob/c4925a571484c046f73b9e63286a2e64380af0c6/projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.ts#L51)
[projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.ts:56](https://github.com/GetStream/stream-chat-angular/blob/233af9a28d1b6ecdfe793362d5f20b0e8b749c16/projects/stream-chat-angular/src/lib/attachment-list/attachment-list.component.ts#L56)

[//]: # "End of generated content"
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ A stream that emits the current file uploads and their states

#### Defined in

[projects/stream-chat-angular/src/lib/attachment-preview-list/attachment-preview-list.component.ts:17](https://github.com/GetStream/stream-chat-angular/blob/c4925a571484c046f73b9e63286a2e64380af0c6/projects/stream-chat-angular/src/lib/attachment-preview-list/attachment-preview-list.component.ts#L17)
[projects/stream-chat-angular/src/lib/attachment-preview-list/attachment-preview-list.component.ts:28](https://github.com/GetStream/stream-chat-angular/blob/233af9a28d1b6ecdfe793362d5f20b0e8b749c16/projects/stream-chat-angular/src/lib/attachment-preview-list/attachment-preview-list.component.ts#L28)

---

Expand All @@ -59,7 +59,7 @@ An output to notify the parent component if the user tries to retry a failed upl

#### Defined in

[projects/stream-chat-angular/src/lib/attachment-preview-list/attachment-preview-list.component.ts:21](https://github.com/GetStream/stream-chat-angular/blob/c4925a571484c046f73b9e63286a2e64380af0c6/projects/stream-chat-angular/src/lib/attachment-preview-list/attachment-preview-list.component.ts#L21)
[projects/stream-chat-angular/src/lib/attachment-preview-list/attachment-preview-list.component.ts:32](https://github.com/GetStream/stream-chat-angular/blob/233af9a28d1b6ecdfe793362d5f20b0e8b749c16/projects/stream-chat-angular/src/lib/attachment-preview-list/attachment-preview-list.component.ts#L32)

---

Expand All @@ -71,6 +71,6 @@ An output to notify the parent component if the user wants to delete a file

#### Defined in

[projects/stream-chat-angular/src/lib/attachment-preview-list/attachment-preview-list.component.ts:25](https://github.com/GetStream/stream-chat-angular/blob/c4925a571484c046f73b9e63286a2e64380af0c6/projects/stream-chat-angular/src/lib/attachment-preview-list/attachment-preview-list.component.ts#L25)
[projects/stream-chat-angular/src/lib/attachment-preview-list/attachment-preview-list.component.ts:36](https://github.com/GetStream/stream-chat-angular/blob/233af9a28d1b6ecdfe793362d5f20b0e8b749c16/projects/stream-chat-angular/src/lib/attachment-preview-list/attachment-preview-list.component.ts#L36)

[//]: # "End of generated content"
Loading
Loading