Skip to content

Commit 422fda4

Browse files
committed
Poll composer added
1 parent d3d6206 commit 422fda4

File tree

16 files changed

+710
-128
lines changed

16 files changed

+710
-128
lines changed

package-lock.json

Lines changed: 221 additions & 117 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@
129129
"replace": "^1.2.2",
130130
"rxjs": "~7.4.0",
131131
"starwars-names": "^1.6.0",
132-
"stream-chat": "9.0.0",
132+
"stream-chat": "9.20.0",
133133
"ts-node": "^10.9.2",
134134
"tslib": "^2.3.0",
135135
"uuid": "^9.0.1",

projects/sample-app/src/app/app.component.html

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@
2626
</stream-channel-header>
2727
<stream-message-list></stream-message-list>
2828
<stream-notification-list></stream-notification-list>
29-
<stream-message-input [displayVoiceRecordingButton]="true">
29+
<stream-message-input
30+
[displayPollCreateButton]="true"
31+
[displayVoiceRecordingButton]="true"
32+
>
3033
<ng-template voice-recorder let-service="service">
3134
<stream-voice-recorder
3235
[voiceRecorderService]="service"
@@ -71,3 +74,14 @@
7174
<stream-poll [pollId]="pollId" [messageId]="messageId"> </stream-poll>
7275
</ng-template>
7376
</ng-template>
77+
78+
<ng-template
79+
#pollComposerTemplate
80+
let-pollCompose="pollCompose"
81+
let-cancel="cancel"
82+
>
83+
<stream-poll-composer
84+
(pollCompose)="pollCompose($event)"
85+
(cancel)="cancel()"
86+
></stream-poll-composer>
87+
</ng-template>

projects/sample-app/src/app/app.component.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ export class AppComponent implements AfterViewInit {
3434
pollId: string;
3535
messageId: string;
3636
}>;
37+
@ViewChild('pollComposerTemplate')
38+
pollComposerTemplate!: TemplateRef<{
39+
pollCompose: (pollId: string) => void;
40+
cancel: () => void;
41+
}>;
3742
theme$: Observable<string>;
3843
counter = 0;
3944

@@ -84,6 +89,9 @@ export class AppComponent implements AfterViewInit {
8489
this.emojiPickerTemplate
8590
);
8691
this.customTemplateService.pollTemplate$.next(this.pollTemplate);
92+
this.customTemplateService.pollComposerTemplate$.next(
93+
this.pollComposerTemplate
94+
);
8795
}
8896

8997
closeMenu(event: Event) {

projects/stream-chat-angular/src/assets/i18n/en.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,5 +163,21 @@ export const en = {
163163
'Option already exists': 'Option already exists',
164164
'You have reached the maximum number of votes allowed':
165165
'You have reached the maximum number of votes allowed',
166+
'Create poll': 'Create poll',
167+
Create: 'Create',
168+
Question: 'Question',
169+
'Ask a question': 'Ask a question',
170+
'Question is required': 'Question is required',
171+
Options: 'Options',
172+
'Add an option': 'Add an option',
173+
'Multiple answers': 'Multiple answers',
174+
'Maximum number of votes': 'Maximum number of votes',
175+
'Provide a value between {{ min }} and {{ max }}':
176+
'Provide a value between {{ min }} and {{ max }}',
177+
'Anonymous poll': 'Anonymous poll',
178+
'Allow option suggestions': 'Allow option suggestions',
179+
'Allow comments': 'Allow comments',
180+
'Failed to create poll': 'Failed to create poll',
181+
'Provide at least one option': 'Provide at least one option',
166182
},
167183
};

projects/stream-chat-angular/src/lib/channel.service.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -838,7 +838,8 @@ export class ChannelService {
838838
mentionedUsers: UserResponse[] = [],
839839
parentId: string | undefined = undefined,
840840
quotedMessageId: string | undefined = undefined,
841-
customData: undefined | CustomMessageData = undefined
841+
customData: undefined | CustomMessageData = undefined,
842+
pollId: string | undefined = undefined
842843
) {
843844
let input: MessageInput = {
844845
text,
@@ -847,6 +848,7 @@ export class ChannelService {
847848
parentId,
848849
quotedMessageId,
849850
customData,
851+
pollId,
850852
};
851853
if (this.beforeSendMessage) {
852854
input = await this.beforeSendMessage(input);
@@ -858,7 +860,8 @@ export class ChannelService {
858860
input.mentionedUsers,
859861
input.parentId,
860862
input.quotedMessageId,
861-
input.customData
863+
input.customData,
864+
input.pollId
862865
);
863866
const channel = this.activeChannelSubject.getValue()!;
864867
channel.state.addMessageSorted(preview, true);
@@ -1206,6 +1209,7 @@ export class ChannelService {
12061209
mentioned_users: preview.mentioned_users?.map((u) => u.id),
12071210
parent_id: preview.parent_id,
12081211
quoted_message_id: preview.quoted_message_id,
1212+
poll_id: preview.poll_id,
12091213
...customData,
12101214
} as Message); // TODO: find out why we need typecast here
12111215
channel.state.addMessageSorted(

projects/stream-chat-angular/src/lib/custom-templates.service.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,13 @@ export class CustomTemplatesService {
379379
pollTemplate$ = new BehaviorSubject<
380380
TemplateRef<{ pollId: string; messageId: string }> | undefined
381381
>(undefined);
382+
/**
383+
* Template to display poll composer inside [message input component](/chat/docs/sdk/angular/components/MessageInputComponent/). There is no default template, but the `PollsModule` contains default components.
384+
*/
385+
pollComposerTemplate$ = new BehaviorSubject<
386+
| TemplateRef<{ pollCompose: (pollId: string) => void; cancel: () => void }>
387+
| undefined
388+
>(undefined);
382389

383390
constructor() {}
384391
}

projects/stream-chat-angular/src/lib/icon/icon.component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ export type Icon =
2222
| 'play'
2323
| 'pause'
2424
| 'mic'
25-
| 'bin';
25+
| 'bin'
26+
| 'poll';
2627

2728
/**
2829
* The `Icon` component can be used to display different icons (i. e. message delivered icon).

projects/stream-chat-angular/src/lib/message-input/message-input.component.html

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@
6666
</div>
6767
</ng-template>
6868
</ng-container>
69+
<ng-container *ngIf="canSendPolls && displayPollCreateButton">
70+
<button
71+
class="str-chat-angular__create-poll"
72+
(click)="openPollComposer()"
73+
>
74+
<stream-icon-placeholder icon="poll"></stream-icon-placeholder>
75+
</button>
76+
</ng-container>
6977
<div class="str-chat__message-textarea-container">
7078
<div
7179
*ngIf="quotedMessage"
@@ -232,3 +240,13 @@
232240
[ngTemplateOutlet]="voiceRecorderRef"
233241
[ngTemplateOutletContext]="{ service: voiceRecorderService }"
234242
></ng-template>
243+
<ng-template
244+
*ngIf="
245+
isComposerOpen && (customTemplatesService.pollComposerTemplate$ | async)
246+
"
247+
[ngTemplateOutlet]="(customTemplatesService.pollComposerTemplate$ | async)!"
248+
[ngTemplateOutletContext]="{
249+
pollCompose: addPoll,
250+
cancel: closePollComposer
251+
}"
252+
></ng-template>

projects/stream-chat-angular/src/lib/message-input/message-input.component.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ export class MessageInputComponent
108108
* You can enable/disable voice recordings with this input
109109
*/
110110
@Input() displayVoiceRecordingButton = false;
111+
/**
112+
* You can enable/disable polls with this input
113+
*/
114+
@Input() displayPollCreateButton = false;
111115
/**
112116
* Emits when a message was successfuly sent or updated
113117
*/
@@ -122,6 +126,7 @@ export class MessageInputComponent
122126
isFileUploadAuthorized: boolean | undefined;
123127
canSendLinks: boolean | undefined;
124128
canSendMessages: boolean | undefined;
129+
canSendPolls: boolean | undefined;
125130
attachmentUploads$: Observable<AttachmentUpload[]>;
126131
customAttachments$: Observable<Attachment[]>;
127132
attachmentUploadInProgressCounter$: Observable<number>;
@@ -141,6 +146,7 @@ export class MessageInputComponent
141146
| undefined;
142147
textareaPlaceholder: string;
143148
fileInputId = uuidv4();
149+
isComposerOpen = false;
144150
@ViewChild('fileInput') private fileInput!: ElementRef<HTMLInputElement>;
145151
@ViewChild(TextareaDirective, { static: false })
146152
private textareaAnchor!: TextareaDirective;
@@ -152,6 +158,7 @@ export class MessageInputComponent
152158
private readonly defaultTextareaPlaceholder = 'streamChat.Type your message';
153159
private readonly slowModeTextareaPlaceholder = 'streamChat.Slow Mode ON';
154160
private messageToEdit?: StreamMessage;
161+
private pollId: string | undefined;
155162

156163
constructor(
157164
private channelService: ChannelService,
@@ -184,13 +191,15 @@ export class MessageInputComponent
184191
if (channel && this.channel && channel.id !== this.channel.id) {
185192
this.textareaValue = '';
186193
this.attachmentService.resetAttachmentUploads();
194+
this.pollId = undefined;
187195
this.voiceRecorderService.isRecorderVisible$.next(false);
188196
}
189197
const capabilities = channel?.data?.own_capabilities as string[];
190198
if (capabilities) {
191199
this.isFileUploadAuthorized =
192200
capabilities.indexOf('upload-file') !== -1;
193201
this.canSendLinks = capabilities.indexOf('send-links') !== -1;
202+
this.canSendPolls = capabilities.indexOf('send-poll') !== -1;
194203
this.channel = channel;
195204
this.setCanSendMessages();
196205
}
@@ -377,7 +386,8 @@ export class MessageInputComponent
377386
const textContainsOnlySpaceChars = !text.replace(/ /g, ''); //spcae
378387
if (
379388
(!text || textContainsOnlySpaceChars) &&
380-
(!attachments || attachments.length === 0)
389+
(!attachments || attachments.length === 0) &&
390+
!this.pollId
381391
) {
382392
return;
383393
}
@@ -390,8 +400,10 @@ export class MessageInputComponent
390400
);
391401
return;
392402
}
403+
const pollId = this.pollId;
393404
if (!this.isUpdate) {
394405
this.textareaValue = '';
406+
this.pollId = undefined;
395407
}
396408
try {
397409
const message = await (this.isUpdate
@@ -405,7 +417,9 @@ export class MessageInputComponent
405417
attachments,
406418
this.mentionedUsers,
407419
this.parentMessageId,
408-
this.quotedMessage?.id
420+
this.quotedMessage?.id,
421+
undefined,
422+
pollId
409423
));
410424
this.messageUpdate.emit({ message });
411425
if (this.isUpdate) {
@@ -509,6 +523,20 @@ export class MessageInputComponent
509523
}
510524
}
511525

526+
openPollComposer() {
527+
this.isComposerOpen = true;
528+
}
529+
530+
closePollComposer = () => {
531+
this.isComposerOpen = false;
532+
};
533+
534+
addPoll = (pollId: string) => {
535+
this.isComposerOpen = false;
536+
this.pollId = pollId;
537+
this.messageSent();
538+
};
539+
512540
async voiceRecordingReady(recording: AudioRecording) {
513541
try {
514542
await this.attachmentService.uploadVoiceRecording(recording);
@@ -641,8 +669,10 @@ export class MessageInputComponent
641669
this.message!.attachments || []
642670
);
643671
this.textareaValue = this.message!.text || '';
672+
this.pollId = this.message!.poll_id;
644673
} else {
645674
this.textareaValue = '';
675+
this.pollId = undefined;
646676
}
647677
}
648678
}

0 commit comments

Comments
 (0)