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

Commit 431b69e

Browse files
authored
Display started polls in timeline (without votes) (behind labs setting) (#7088)
* Display started polls in timeline (without votes) * Update i18n info * Keep original background colour of poll options, even on hover * Show full avatar above a poll message
1 parent 8ea551f commit 431b69e

File tree

9 files changed

+229
-5
lines changed

9 files changed

+229
-5
lines changed

res/css/_components.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@
182182
@import "./views/messages/_MImageReplyBody.scss";
183183
@import "./views/messages/_MJitsiWidgetEvent.scss";
184184
@import "./views/messages/_MNoticeBody.scss";
185+
@import "./views/messages/_MPollBody.scss";
185186
@import "./views/messages/_MStickerBody.scss";
186187
@import "./views/messages/_MTextBody.scss";
187188
@import "./views/messages/_MVideoBody.scss";
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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+
.mx_MPollBody {
18+
margin-top: 8px;
19+
20+
h2 {
21+
font-weight: 600;
22+
font-size: $font-15px;
23+
line-height: $font-24px;
24+
margin-top: 0;
25+
margin-bottom: 8px;
26+
}
27+
28+
h2::before {
29+
content: '';
30+
position: relative;
31+
display: inline-block;
32+
margin-right: 12px;
33+
top: 3px;
34+
left: 3px;
35+
height: 20px;
36+
width: 20px;
37+
background-color: $secondary-content;
38+
mask-repeat: no-repeat;
39+
mask-size: contain;
40+
mask-position: center;
41+
mask-image: url('$(res)/img/element-icons/room/composer/poll.svg');
42+
}
43+
44+
.mx_MPollBody_option {
45+
border: 1px solid $quinary-content;
46+
border-radius: 8px;
47+
margin-bottom: 16px;
48+
padding: 6px;
49+
max-width: 550px;
50+
background-color: $background;
51+
52+
.mx_StyledRadioButton {
53+
margin-bottom: 8px;
54+
}
55+
56+
.mx_StyledRadioButton_content {
57+
padding-top: 2px;
58+
}
59+
60+
.mx_MPollBody_optionVoteCount {
61+
position: absolute;
62+
color: $secondary-content;
63+
right: 4px;
64+
font-size: $font-12px;
65+
}
66+
67+
.mx_MPollBody_popularityBackground {
68+
width: calc(100% - 4px);
69+
height: 8px;
70+
margin-right: 12px;
71+
border-radius: 8px;
72+
background-color: $system;
73+
74+
.mx_MPollBody_popularityAmount {
75+
width: 0%;
76+
height: 8px;
77+
border-radius: 8px;
78+
background-color: $quaternary-content;
79+
}
80+
}
81+
}
82+
83+
.mx_MPollBody_option:last-child {
84+
margin-bottom: 8px;
85+
}
86+
87+
.mx_MPollBody_option_checked {
88+
border-color: $accent-color;
89+
}
90+
91+
.mx_StyledRadioButton_checked input[type="radio"] + div {
92+
border-width: 2px;
93+
border-color: $accent-color;
94+
background-color: $accent-color;
95+
background-image: url('$(res)/img/element-icons/check-white.svg');
96+
background-size: 12px;
97+
background-repeat: no-repeat;
98+
background-position: center;
99+
100+
div {
101+
visibility: hidden;
102+
}
103+
}
104+
105+
.mx_MPollBody_totalVotes {
106+
color: $secondary-content;
107+
font-size: $font-12px;
108+
}
109+
}
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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 { _t } from '../../../languageHandler';
19+
import { replaceableComponent } from "../../../utils/replaceableComponent";
20+
import { IBodyProps } from "./IBodyProps";
21+
import { IPollAnswer, IPollContent, POLL_START_EVENT_TYPE } from '../../../polls/consts';
22+
import StyledRadioButton from '../elements/StyledRadioButton';
23+
24+
// TODO: [andyb] Use extensible events library when ready
25+
const TEXT_NODE_TYPE = "org.matrix.msc1767.text";
26+
27+
interface IState {
28+
selected?: string;
29+
}
30+
31+
@replaceableComponent("views.messages.MPollBody")
32+
export default class MPollBody extends React.Component<IBodyProps, IState> {
33+
constructor(props: IBodyProps) {
34+
super(props);
35+
36+
this.state = {
37+
selected: null,
38+
};
39+
}
40+
41+
private selectOption(answerId: string) {
42+
this.setState({ selected: answerId });
43+
}
44+
45+
private onOptionSelected = (e: React.FormEvent<HTMLInputElement>): void => {
46+
this.selectOption(e.currentTarget.value);
47+
};
48+
49+
render() {
50+
const pollStart: IPollContent =
51+
this.props.mxEvent.getContent()[POLL_START_EVENT_TYPE.name];
52+
const pollId = this.props.mxEvent.getId();
53+
54+
return <div className="mx_MPollBody">
55+
<h2>{ pollStart.question[TEXT_NODE_TYPE] }</h2>
56+
<div className="mx_MPollBody_allOptions">
57+
{
58+
pollStart.answers.map((answer: IPollAnswer) => {
59+
const checked = this.state.selected === answer.id;
60+
const classNames = `mx_MPollBody_option${
61+
checked ? " mx_MPollBody_option_checked": ""
62+
}`;
63+
return <div
64+
key={answer.id}
65+
className={classNames}
66+
onClick={() => this.selectOption(answer.id)}
67+
>
68+
<StyledRadioButton
69+
name={`poll_answer_select-${pollId}`}
70+
value={answer.id}
71+
checked={this.state.selected === answer.id}
72+
onChange={this.onOptionSelected}
73+
>
74+
<div className="mx_MPollBody_optionVoteCount">
75+
{ _t("%(number)s votes", { number: 0 }) }
76+
</div>
77+
<div className="mx_MPollBody_optionText">
78+
{ answer[TEXT_NODE_TYPE] }
79+
</div>
80+
</StyledRadioButton>
81+
<div className="mx_MPollBody_popularityBackground">
82+
<div className="mx_MPollBody_popularityAmount" />
83+
</div>
84+
</div>;
85+
})
86+
}
87+
</div>
88+
<div className="mx_MPollBody_totalVotes">
89+
{ _t( "Based on %(total)s votes", { total: 0 } ) }
90+
</div>
91+
</div>;
92+
}
93+
}

src/components/views/messages/MessageEvent.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { MediaEventHelper } from "../../../utils/MediaEventHelper";
2727
import { ReactAnyComponent } from "../../../@types/common";
2828
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
2929
import { IBodyProps } from "./IBodyProps";
30+
import { POLL_START_EVENT_TYPE } from '../../../polls/consts';
3031

3132
// onMessageAllowed is handled internally
3233
interface IProps extends Omit<IBodyProps, "onMessageAllowed"> {
@@ -111,6 +112,15 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
111112
// Fallback to UnknownBody otherwise if not redacted
112113
BodyType = UnknownBody;
113114
}
115+
116+
if (type && type === POLL_START_EVENT_TYPE.name) {
117+
// TODO: this can all disappear when Polls comes out of labs -
118+
// instead, add something like this into this.evTypes:
119+
// [EventType.Poll]: "messages.MPollBody"
120+
if (SettingsStore.getValue("feature_polls")) {
121+
BodyType = sdk.getComponent('messages.MPollBody');
122+
}
123+
}
114124
}
115125

116126
if (SettingsStore.getValue("feature_mjolnir")) {

src/components/views/rooms/EventTile.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,14 @@ import { logger } from "matrix-js-sdk/src/logger";
6565
import { TimelineRenderingType } from "../../../contexts/RoomContext";
6666
import { MediaEventHelper } from "../../../utils/MediaEventHelper";
6767
import Toolbar from '../../../accessibility/Toolbar';
68+
import { POLL_START_EVENT_TYPE } from '../../../polls/consts';
6869
import { RovingAccessibleTooltipButton } from '../../../accessibility/roving/RovingAccessibleTooltipButton';
6970
import { ThreadListContextMenu } from '../context_menus/ThreadListContextMenu';
7071

7172
const eventTileTypes = {
7273
[EventType.RoomMessage]: 'messages.MessageEvent',
7374
[EventType.Sticker]: 'messages.MessageEvent',
75+
[POLL_START_EVENT_TYPE.name]: 'messages.MessageEvent',
7476
[EventType.KeyVerificationCancel]: 'messages.MKeyVerificationConclusion',
7577
[EventType.KeyVerificationDone]: 'messages.MKeyVerificationConclusion',
7678
[EventType.CallInvite]: 'messages.CallEvent',

src/i18n/strings/en_EN.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2021,6 +2021,8 @@
20212021
"Declining …": "Declining …",
20222022
"%(name)s wants to verify": "%(name)s wants to verify",
20232023
"You sent a verification request": "You sent a verification request",
2024+
"%(number)s votes": "%(number)s votes",
2025+
"Based on %(total)s votes": "Based on %(total)s votes",
20242026
"Error decrypting video": "Error decrypting video",
20252027
"Error processing voice message": "Error processing voice message",
20262028
"Add reaction": "Add reaction",

src/polls/consts.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,18 @@ export const POLL_KIND_UNDISCLOSED = new UnstableValue("m.poll.undisclosed", "or
2424
// TODO: [TravisR] Use extensible events library when ready
2525
const TEXT_NODE_TYPE = "org.matrix.msc1767.text";
2626

27+
export interface IPollAnswer extends IContent {
28+
id: string;
29+
[TEXT_NODE_TYPE]: string;
30+
}
31+
2732
export interface IPollContent extends IContent {
2833
[POLL_START_EVENT_TYPE.name]: {
2934
kind: string; // disclosed or undisclosed (untypeable for now)
3035
question: {
3136
[TEXT_NODE_TYPE]: string;
3237
};
33-
answers: {
34-
id: string;
35-
[TEXT_NODE_TYPE]: string;
36-
}[];
38+
answers: IPollAnswer[];
3739
};
3840
[TEXT_NODE_TYPE]: string;
3941
}

src/utils/EventUtils.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { EventType } from "matrix-js-sdk/src/@types/event";
2424
import { MatrixClient } from 'matrix-js-sdk/src/client';
2525
import { Thread } from 'matrix-js-sdk/src/models/thread';
2626
import { logger } from 'matrix-js-sdk/src/logger';
27+
import { POLL_START_EVENT_TYPE } from '../polls/consts';
2728

2829
/**
2930
* Returns whether an event should allow actions like reply, reactions, edit, etc.
@@ -136,7 +137,8 @@ export function getEventDisplayInfo(mxEvent: MatrixEvent): {
136137
!isLeftAlignedBubbleMessage &&
137138
eventType !== EventType.RoomMessage &&
138139
eventType !== EventType.Sticker &&
139-
eventType !== EventType.RoomCreate
140+
eventType !== EventType.RoomCreate &&
141+
eventType !== POLL_START_EVENT_TYPE.name
140142
);
141143

142144
// If we're showing hidden events in the timeline, we should use the

0 commit comments

Comments
 (0)