Skip to content

Commit 100468b

Browse files
committed
Option and comment adding
1 parent a79687f commit 100468b

30 files changed

+531
-36
lines changed

angular.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,8 @@
219219
"projectType": "application",
220220
"schematics": {
221221
"@schematics/angular:component": {
222-
"style": "scss"
222+
"style": "scss",
223+
"changeDetection": "OnPush"
223224
},
224225
"@schematics/angular:application": {
225226
"strict": true

projects/stream-chat-angular/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,16 @@
1919
"peerDependencies": {
2020
"@angular/common": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0",
2121
"@angular/core": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0",
22+
"@angular/forms": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0",
2223
"@breezystack/lamejs": "^1.2.7",
2324
"@ngx-translate/core": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0",
2425
"rxjs": "^7.4.0",
2526
"stream-chat": "^9.0.0"
2627
},
2728
"peerDependenciesMeta": {
29+
"@angular/forms": {
30+
"optional": true
31+
},
2832
"@breezystack/lamejs": {
2933
"optional": true
3034
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ export const en = {
142142
'Add a comment': 'Add a comment',
143143
'Update your comment': 'Update your comment',
144144
'View {{count}} comments': 'View {{count}} comments',
145+
'View {{count}} comment': 'View {{count}} comment',
145146
'View results': 'View results',
146147
'End vote': 'End vote',
147148
'After a poll is closed, no more votes can be cast':
@@ -153,5 +154,12 @@ export const en = {
153154
'Show all': 'Show all',
154155
'Poll results': 'Poll results',
155156
'Error loading votes': 'Error loading votes',
157+
'Poll comments': 'Poll comments',
158+
'Error loading answers': 'Error loading answers',
159+
Comment: 'Comment',
160+
'Failed to add comment': 'Failed to add comment',
161+
'Failed to add option ({{ message }})':
162+
'Failed to add option ({{ message }})',
163+
'Option already exists': 'Option already exists',
156164
},
157165
};

projects/stream-chat-angular/src/lib/modal/modal.component.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@
1010
>
1111
<stream-icon-placeholder icon="close"></stream-icon-placeholder>
1212
</div>
13-
<div #modalInner class="str-chat__modal__inner">
13+
<div
14+
#modalInner
15+
class="str-chat__modal__inner"
16+
(click)="$event.stopPropagation()"
17+
>
1418
<ng-container *ngIf="content; else elseContent">
1519
<ng-container *ngTemplateOutlet="content"></ng-container>
1620
</ng-container>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<div
2+
class="str-chat__dialog str-chat__dialog--form str-chat__prompt-dialog str-chat__modal__suggest-poll-option"
3+
>
4+
<div class="str-chat__dialog__body">
5+
<div class="str-chat__dialog__title">
6+
{{ "streamChat.Suggest an option" | translate }}
7+
</div>
8+
<form [formGroup]="formGroup" (ngSubmit)="addOption()">
9+
<div class="str-chat__dialog__field">
10+
<input formControlName="text" id="text" type="text" />
11+
<div class="str-chat__form-field-error">
12+
{{
13+
formGroup.get("text")?.errors?.duplicate
14+
? ("streamChat.Option already exists" | translate)
15+
: ""
16+
}}
17+
</div>
18+
</div>
19+
<div class="str-chat__dialog__controls">
20+
<button
21+
class="str-chat__dialog__controls-button str-chat__dialog__controls-button--cancel"
22+
onClick="{close}"
23+
type="button"
24+
translate
25+
(click)="closeModal()"
26+
>
27+
{{ "streamChat.Cancel" | translate }}
28+
</button>
29+
<button
30+
class="str-chat__dialog__controls-button str-chat__dialog__controls-button--submit"
31+
type="submit"
32+
translate
33+
[disabled]="formGroup.invalid"
34+
>
35+
{{ "streamChat.Send" | translate }}
36+
</button>
37+
</div>
38+
</form>
39+
</div>
40+
</div>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { AddOptionComponent } from './add-option.component';
4+
5+
describe('AddOptionComponent', () => {
6+
let component: AddOptionComponent;
7+
let fixture: ComponentFixture<AddOptionComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
declarations: [AddOptionComponent],
12+
}).compileComponents();
13+
14+
fixture = TestBed.createComponent(AddOptionComponent);
15+
component = fixture.componentInstance;
16+
fixture.detectChanges();
17+
});
18+
19+
it('should create', () => {
20+
expect(component).toBeTruthy();
21+
});
22+
});
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import {
2+
ChangeDetectionStrategy,
3+
Component,
4+
HostBinding,
5+
Input,
6+
} from '@angular/core';
7+
import { FormControl, FormGroup, Validators } from '@angular/forms';
8+
import { BasePollComponent } from '../../base-poll.component';
9+
import { Poll, PollOption } from 'stream-chat';
10+
import { createUniqueValidator } from '../../unique.validator';
11+
12+
@Component({
13+
selector: 'stream-add-option',
14+
templateUrl: './add-option.component.html',
15+
styles: [],
16+
changeDetection: ChangeDetectionStrategy.OnPush,
17+
})
18+
export class AddOptionComponent extends BasePollComponent {
19+
@HostBinding('class') class = 'str-chat__dialog';
20+
@Input() closeModal: () => void = () => {};
21+
formGroup = new FormGroup({
22+
text: new FormControl('', [
23+
Validators.required,
24+
createUniqueValidator((value) => {
25+
return !this.options.some(
26+
(option) =>
27+
option.text.trim().toLowerCase() === value.trim().toLowerCase()
28+
);
29+
}),
30+
]),
31+
});
32+
options: PollOption[] = [];
33+
34+
async addOption() {
35+
if (this.formGroup.invalid || !this.messageId) {
36+
return;
37+
}
38+
try {
39+
await this.poll?.createOption({
40+
text: this.formGroup.value.text!,
41+
});
42+
this.closeModal();
43+
this.markForCheck();
44+
} catch (error) {
45+
this.notificationService.addTemporaryNotification(
46+
'streamChat.Failed to add option ({{ message }})',
47+
'error',
48+
undefined,
49+
{ message: error }
50+
);
51+
this.markForCheck();
52+
throw error;
53+
}
54+
}
55+
56+
protected stateStoreSelector(
57+
poll: Poll,
58+
markForCheck: () => void
59+
): () => void {
60+
const unsubscribe = poll.state.subscribeWithSelector(
61+
(state) => ({
62+
options: state.options,
63+
}),
64+
(state) => {
65+
this.options = state.options;
66+
markForCheck();
67+
}
68+
);
69+
70+
return unsubscribe;
71+
}
72+
}

projects/stream-chat-angular/src/lib/polls/poll-actions/poll-actions.component.html

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
class="str-chat__poll-action"
2020
(click)="modalOpened('suggestOption')"
2121
>
22-
{{ "Suggest an option" | translate }}
22+
{{ "streamChat.Suggest an option" | translate }}
2323
</button>
2424
</ng-container>
2525
<ng-container *ngIf="allowAnswers && canVote && !isClosed">
@@ -34,7 +34,8 @@
3434
</ng-container>
3535
<ng-container *ngIf="answerCount > 0 && canQueryVotes">
3636
<button class="str-chat__poll-action" (click)="modalOpened('viewComments')">
37-
{{ "streamChat.View {{count}} comments" | translate:{count: answerCount}
37+
{{ (answerCount === 1 ? "streamChat.View {{count}} comment" :
38+
"streamChat.View {{ count }} comments") | translate:{count: answerCount}
3839
}}
3940
</button>
4041
</ng-container>
@@ -74,19 +75,14 @@
7475
</stream-modal>
7576
</ng-template>
7677

77-
<!-- The modal components click outside logic can't identify clicks from subcomponents, so we stop event propagation -->
7878
<ng-template #allOptions>
79-
<div
80-
class="str-chat__modal__poll-option-list"
81-
(click)="$event.stopPropagation()"
82-
>
79+
<div class="str-chat__modal__poll-option-list">
8380
<div class="str-chat__modal-header">
8481
<div class="str-chat__modal-header__title" translate>Poll options</div>
8582
</div>
8683
<div class="str-chat__modal__poll-option-list__body">
8784
<div class="str-chat__modal__poll-option-list__title">{{ name }}</div>
8885
<stream-poll-options-list
89-
(click)="$event.stopPropagation()"
9086
[pollId]="pollId"
9187
[messageId]="messageId"
9288
[maxOptionsDisplayed]="undefined"
@@ -95,24 +91,39 @@
9591
</div>
9692
</ng-template>
9793
<ng-template #suggestOption>
98-
<div>TODO: Suggest option</div>
94+
<stream-add-option
95+
[pollId]="pollId"
96+
[messageId]="messageId"
97+
[closeModal]="modalClosed"
98+
></stream-add-option>
99+
<stream-notification-list></stream-notification-list>
99100
</ng-template>
100101
<ng-template #addAnswer>
101-
<div>TODO: Add answer</div>
102+
<stream-upsert-answer
103+
[pollId]="pollId"
104+
[messageId]="messageId"
105+
[answer]="ownAnwer"
106+
[closeModal]="modalClosed"
107+
></stream-upsert-answer>
108+
<stream-notification-list></stream-notification-list>
102109
</ng-template>
103110
<ng-template #viewComments>
104-
<div>TODO: View comments</div>
111+
<stream-poll-answers-list
112+
(upsertOwnAnswer)="modalOpened('addAnswer')"
113+
[pollId]="pollId"
114+
[messageId]="messageId"
115+
></stream-poll-answers-list>
116+
<stream-notification-list></stream-notification-list>
105117
</ng-template>
106118
<ng-template #viewResults>
107119
<stream-poll-results-list
108-
(click)="$event.stopPropagation()"
109120
[messageId]="messageId"
110121
[pollId]="pollId"
111122
></stream-poll-results-list>
112123
<stream-notification-list></stream-notification-list>
113124
</ng-template>
114125
<ng-template #endVote>
115-
<div class="str-chat__dialog" (click)="$event.stopPropagation()">
126+
<div class="str-chat__dialog">
116127
<div class="str-chat__dialog__body str-chat-angular__dialog-body">
117128
<div class="str-chat__dialog__prompt" translate>
118129
After a poll is closed, no more votes can be cast

projects/stream-chat-angular/src/lib/polls/poll-actions/poll-actions.component.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
ViewChild,
88
} from '@angular/core';
99
import { BasePollComponent } from '../base-poll.component';
10-
import { Poll, PollOption, PollVote } from 'stream-chat';
10+
import { Poll, PollAnswer, PollOption } from 'stream-chat';
1111
import { ModalContext } from '../../types';
1212
import { CustomTemplatesService } from '../../custom-templates.service';
1313
import { ChatClientService } from '../../chat-client.service';
@@ -39,7 +39,7 @@ export class PollActionsComponent extends BasePollComponent {
3939
allowAnswers = false;
4040
answerCount = 0;
4141
isOwnPoll = false;
42-
ownAnwer: PollVote | undefined;
42+
ownAnwer: PollAnswer | undefined;
4343
selectedAction: Action | undefined = undefined;
4444
isModalOpen = false;
4545
@ViewChild('allOptions') allOptions!: TemplateRef<void>;
@@ -79,6 +79,7 @@ export class PollActionsComponent extends BasePollComponent {
7979
answer_count: state.answers_count,
8080
created_by: state.created_by,
8181
name: state.name,
82+
own_answer: state.ownAnswer,
8283
}),
8384
(state) => {
8485
this.options = state.options;
@@ -88,24 +89,29 @@ export class PollActionsComponent extends BasePollComponent {
8889
this.answerCount = state.answer_count ?? 0;
8990
this.isOwnPoll = state.created_by?.id === this.currentUser?.id;
9091
this.name = state.name;
92+
this.ownAnwer = state.own_answer ?? undefined;
9193
markForCheck();
9294
}
9395
);
9496

9597
return unsubscribe;
9698
}
9799

98-
modalOpened(action: Action) {
100+
modalOpened = (action: Action) => {
99101
this.selectedAction = action;
100-
this.isModalOpen = true;
101-
this.messageService.modalOpenedForMessage.next(this.messageId);
102-
}
102+
if (!this.isModalOpen) {
103+
this.isModalOpen = true;
104+
this.messageService.modalOpenedForMessage.next(this.messageId);
105+
}
106+
};
103107

104-
modalClosed() {
108+
modalClosed = () => {
105109
this.selectedAction = undefined;
106-
this.isModalOpen = false;
107-
this.messageService.modalOpenedForMessage.next(undefined);
108-
}
110+
if (this.isModalOpen) {
111+
this.isModalOpen = false;
112+
this.messageService.modalOpenedForMessage.next(undefined);
113+
}
114+
};
109115

110116
async closePoll() {
111117
try {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<div class="str-chat__modal__poll-answer-list">
2+
<div class="str-chat__modal-header">
3+
<div class="str-chat__modal-header__title" translate>Poll comments</div>
4+
</div>
5+
<div class="str-chat__modal__poll-answer-list__body">
6+
<div class="str-chat__poll-answer-list">
7+
<stream-paginated-list
8+
class="str-chat__poll-answer-list"
9+
[items]="answers"
10+
[hasMore]="next !== undefined"
11+
[isLoading]="isLoading"
12+
[trackBy]="trackByAnswerId"
13+
(loadMore)="queryAnswers()"
14+
>
15+
<ng-template let-answer="item">
16+
<div class="str-chat__poll-answer">
17+
<p *ngIf="answer.answer_text" class="str-chat__poll-answer__text">
18+
{{ answer.answer_text }}
19+
</p>
20+
<stream-poll-vote [vote]="answer"></stream-poll-vote>
21+
</div>
22+
</ng-template>
23+
</stream-paginated-list>
24+
<button
25+
*ngIf="!isClosed"
26+
class="str-chat__poll-action"
27+
(click)="upsertOwnAnswer.emit()"
28+
>
29+
{{
30+
(ownAnswer
31+
? "streamChat.Update your comment"
32+
: "streamChat.Add a comment"
33+
) | translate
34+
}}
35+
</button>
36+
</div>
37+
</div>
38+
</div>

0 commit comments

Comments
 (0)