Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 2b52e17

Browse files
andybalaamt3chguy
andauthored
Allow ending polls (#7305)
Co-authored-by: Michael Telatynski <[email protected]>
1 parent 697b5d2 commit 2b52e17

File tree

12 files changed

+2817
-683
lines changed

12 files changed

+2817
-683
lines changed

res/css/views/context_menus/_MessageContextMenu.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ limitations under the License.
5454
mask-image: url('$(res)/img/element-icons/settings/appearance.svg');
5555
}
5656

57+
.mx_MessageContextMenu_iconEndPoll::before {
58+
mask-image: url('$(res)/img/element-icons/check-white.svg');
59+
}
60+
5761
.mx_MessageContextMenu_iconForward::before {
5862
mask-image: url('$(res)/img/element-icons/message/fwd.svg');
5963
}

res/css/views/messages/_MPollBody.scss

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,17 @@ limitations under the License.
4545
border: 1px solid $quinary-content;
4646
border-radius: 8px;
4747
margin-bottom: 16px;
48-
padding: 6px;
48+
padding: 6px 12px;
4949
max-width: 550px;
5050
background-color: $background;
5151

52-
.mx_StyledRadioButton {
52+
.mx_StyledRadioButton, .mx_MPollBody_endedOption {
5353
margin-bottom: 8px;
5454
}
5555

56-
.mx_StyledRadioButton_content {
56+
.mx_StyledRadioButton_content, .mx_MPollBody_endedOption {
5757
padding-top: 2px;
58+
margin-right: 0px;
5859
}
5960

6061
.mx_StyledRadioButton_spacer {
@@ -73,7 +74,7 @@ limitations under the License.
7374
}
7475

7576
.mx_MPollBody_popularityBackground {
76-
width: calc(100% - 6px);
77+
width: 100%;
7778
height: 8px;
7879
margin-right: 12px;
7980
border-radius: 8px;
@@ -102,20 +103,37 @@ limitations under the License.
102103
}
103104
}
104105

105-
.mx_StyledRadioButton_checked input[type="radio"] + div {
106-
border-width: 2px;
107-
border-color: $accent;
108-
background-color: $accent;
109-
background-image: url('$(res)/img/element-icons/check-white.svg');
110-
background-size: 12px;
111-
background-repeat: no-repeat;
112-
background-position: center;
113-
114-
div {
115-
visibility: hidden;
106+
.mx_StyledRadioButton_checked, .mx_MPollBody_endedOptionWinner {
107+
input[type="radio"] + div {
108+
border-width: 2px;
109+
border-color: $accent;
110+
background-color: $accent;
111+
background-image: url('$(res)/img/element-icons/check-white.svg');
112+
background-size: 12px;
113+
background-repeat: no-repeat;
114+
background-position: center;
115+
116+
div {
117+
visibility: hidden;
118+
}
116119
}
117120
}
118121

122+
.mx_MPollBody_endedOptionWinner .mx_MPollBody_optionDescription .mx_MPollBody_optionVoteCount::before {
123+
content: '';
124+
position: relative;
125+
display: inline-block;
126+
margin-right: 4px;
127+
top: 2px;
128+
height: 12px;
129+
width: 12px;
130+
background-color: $accent;
131+
mask-repeat: no-repeat;
132+
mask-size: contain;
133+
mask-position: center;
134+
mask-image: url('$(res)/img/element-icons/trophy.svg');
135+
}
136+
119137
.mx_MPollBody_totalVotes {
120138
color: $secondary-content;
121139
font-size: $font-12px;

res/img/element-icons/trophy.svg

Lines changed: 3 additions & 0 deletions
Loading

src/components/views/context_menus/MessageContextMenu.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ import { IPosition, ChevronFace } from '../../structures/ContextMenu';
4141
import RoomContext, { TimelineRenderingType } from '../../../contexts/RoomContext';
4242
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
4343
import { WidgetLayoutStore } from '../../../stores/widgets/WidgetLayoutStore';
44+
import { POLL_START_EVENT_TYPE } from '../../../polls/consts';
45+
import EndPollDialog from '../dialogs/EndPollDialog';
46+
import { Relations } from 'matrix-js-sdk/src/models/relations';
47+
import { isPollEnded } from '../messages/MPollBody';
4448

4549
export function canCancel(eventStatus: EventStatus): boolean {
4650
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
@@ -68,6 +72,11 @@ interface IProps extends IPosition {
6872
onFinished(): void;
6973
/* if the menu is inside a dialog, we sometimes need to close that dialog after click (forwarding) */
7074
onCloseDialog?(): void;
75+
getRelationsForEvent?: (
76+
eventId: string,
77+
relationType: string,
78+
eventType: string
79+
) => Relations;
7180
}
7281

7382
interface IState {
@@ -123,6 +132,14 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
123132
return content.pinned && Array.isArray(content.pinned) && content.pinned.includes(this.props.mxEvent.getId());
124133
}
125134

135+
private canEndPoll(mxEvent: MatrixEvent): boolean {
136+
return (
137+
mxEvent.getType() === POLL_START_EVENT_TYPE.name &&
138+
this.state.canRedact &&
139+
!isPollEnded(mxEvent, MatrixClientPeg.get(), this.props.getRelationsForEvent)
140+
);
141+
}
142+
126143
private onResendReactionsClick = (): void => {
127144
for (const reaction of this.getUnsentReactions()) {
128145
Resend.resend(reaction);
@@ -215,6 +232,16 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
215232
this.closeMenu();
216233
};
217234

235+
private onEndPollClick = (): void => {
236+
const matrixClient = MatrixClientPeg.get();
237+
Modal.createTrackedDialog('End Poll', '', EndPollDialog, {
238+
matrixClient,
239+
event: this.props.mxEvent,
240+
getRelationsForEvent: this.props.getRelationsForEvent,
241+
}, 'mx_Dialog_endPoll');
242+
this.closeMenu();
243+
};
244+
218245
private getReactions(filter: (e: MatrixEvent) => boolean): MatrixEvent[] {
219246
const cli = MatrixClientPeg.get();
220247
const room = cli.getRoom(this.props.mxEvent.getRoomId());
@@ -250,6 +277,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
250277
const eventStatus = mxEvent.status;
251278
const unsentReactionsCount = this.getUnsentReactions().length;
252279

280+
let endPollButton: JSX.Element;
253281
let resendReactionsButton: JSX.Element;
254282
let redactButton: JSX.Element;
255283
let forwardButton: JSX.Element;
@@ -345,6 +373,16 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
345373
/>
346374
);
347375

376+
if (this.canEndPoll(mxEvent)) {
377+
endPollButton = (
378+
<IconizedContextMenuOption
379+
iconClassName="mx_MessageContextMenu_iconEndPoll"
380+
label={_t("End Poll")}
381+
onClick={this.onEndPollClick}
382+
/>
383+
);
384+
}
385+
348386
if (this.props.eventTileOps) { // this event is rendered using TextualBody
349387
quoteButton = (
350388
<IconizedContextMenuOption
@@ -415,6 +453,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
415453
label={_t("View in room")}
416454
onClick={this.viewInRoom}
417455
/> }
456+
{ endPollButton }
418457
{ quoteButton }
419458
{ forwardButton }
420459
{ pinButton }
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
Copyright 2021 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import React from "react";
18+
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
19+
import { MatrixClient } from "matrix-js-sdk/src/client";
20+
import { Relations } from "matrix-js-sdk/src/models/relations";
21+
22+
import { _t } from "../../../languageHandler";
23+
import { IDialogProps } from "./IDialogProps";
24+
import QuestionDialog from "./QuestionDialog";
25+
import { IPollEndContent, POLL_END_EVENT_TYPE, TEXT_NODE_TYPE } from "../../../polls/consts";
26+
import { findTopAnswer } from "../messages/MPollBody";
27+
import Modal from "../../../Modal";
28+
import ErrorDialog from "./ErrorDialog";
29+
30+
interface IProps extends IDialogProps {
31+
matrixClient: MatrixClient;
32+
event: MatrixEvent;
33+
onFinished: (success: boolean) => void;
34+
getRelationsForEvent?: (
35+
eventId: string,
36+
relationType: string,
37+
eventType: string
38+
) => Relations;
39+
}
40+
41+
export default class EndPollDialog extends React.Component<IProps> {
42+
private onFinished = (endPoll: boolean) => {
43+
const topAnswer = findTopAnswer(
44+
this.props.event,
45+
this.props.matrixClient,
46+
this.props.getRelationsForEvent,
47+
);
48+
49+
const message = (
50+
(topAnswer === "")
51+
? _t("The poll has ended. No votes were cast.")
52+
: _t(
53+
"The poll has ended. Top answer: %(topAnswer)s",
54+
{ topAnswer },
55+
)
56+
);
57+
58+
if (endPoll) {
59+
const endContent: IPollEndContent = {
60+
[POLL_END_EVENT_TYPE.name]: {},
61+
"m.relates_to": {
62+
"event_id": this.props.event.getId(),
63+
"rel_type": "m.reference",
64+
},
65+
[TEXT_NODE_TYPE.name]: message,
66+
};
67+
68+
this.props.matrixClient.sendEvent(
69+
this.props.event.getRoomId(), POLL_END_EVENT_TYPE.name, endContent,
70+
).catch((e: any) => {
71+
console.error("Failed to submit poll response event:", e);
72+
Modal.createTrackedDialog(
73+
'Failed to end poll',
74+
'',
75+
ErrorDialog,
76+
{
77+
title: _t("Failed to end poll"),
78+
description: _t(
79+
"Sorry, the poll did not end. Please try again."),
80+
},
81+
);
82+
});
83+
}
84+
this.props.onFinished(endPoll);
85+
};
86+
87+
render() {
88+
return (
89+
<QuestionDialog
90+
title={_t("End Poll")}
91+
description={
92+
_t(
93+
"Are you sure you want to end this poll? " +
94+
"This will show the final results of the poll and " +
95+
"stop people from being able to vote.",
96+
)
97+
}
98+
button={_t("End Poll")}
99+
onFinished={(endPoll: boolean) => this.onFinished(endPoll)}
100+
/>
101+
);
102+
}
103+
}

0 commit comments

Comments
 (0)