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

Allow ending polls #7305

Merged
merged 20 commits into from
Dec 8, 2021
Merged
Show file tree
Hide file tree
Changes from 8 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
5 changes: 3 additions & 2 deletions res/css/views/messages/_MPollBody.scss
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ limitations under the License.
border: 1px solid $quinary-content;
border-radius: 8px;
margin-bottom: 16px;
padding: 6px;
padding: 6px 12px;
max-width: 550px;
background-color: $background;

Expand All @@ -55,6 +55,7 @@ limitations under the License.

.mx_StyledRadioButton_content, .mx_MPollBody_endedOption {
padding-top: 2px;
margin-right: 0px;
}

.mx_StyledRadioButton_spacer {
Expand All @@ -73,7 +74,7 @@ limitations under the License.
}

.mx_MPollBody_popularityBackground {
width: calc(100% - 6px);
width: 100%;
height: 8px;
margin-right: 12px;
border-radius: 8px;
Expand Down
10 changes: 5 additions & 5 deletions src/components/views/dialogs/EndPollDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
import React from "react";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { Relations } from "matrix-js-sdk/src/models/relations";

import { _t } from "../../../languageHandler";
import { IDialogProps } from "./IDialogProps";
Expand All @@ -25,17 +26,16 @@ import { IPollEndContent, POLL_END_EVENT_TYPE, TEXT_NODE_TYPE } from "../../../p
import { findTopAnswer } from "../messages/MPollBody";
import Modal from "../../../Modal";
import ErrorDialog from "./ErrorDialog";
import { Relations } from "matrix-js-sdk/src/models/relations";

interface IProps extends IDialogProps {
matrixClient: MatrixClient;
event: MatrixEvent;
onFinished: (success: boolean) => void;
getRelationsForEvent?: (
eventId: string,
relationType: string,
eventType: string
) => Relations;
eventId: string,
relationType: string,
eventType: string
) => Relations;
}

export default class EndPollDialog extends React.Component<IProps> {
Expand Down
71 changes: 39 additions & 32 deletions src/components/views/messages/MPollBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -413,14 +413,16 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
>
{ (
ended
? renderEndedOption(answer, checked, votesText)
: renderOption(
pollId,
answer,
checked,
votesText,
this.onOptionSelected,
)
? <EndedPollOption
answer={answer}
checked={checked}
votesText={votesText} />
: <LivePollOption
pollId={pollId}
answer={answer}
checked={checked}
votesText={votesText}
onOptionSelected={this.onOptionSelected} />
) }
<div className="mx_MPollBody_popularityBackground">
<div
Expand All @@ -439,46 +441,50 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
}
}

function renderEndedOption(
answer: IPollAnswer,
checked: boolean,
votesText: string,
) {
interface IEndedPollOptionProps {
answer: IPollAnswer;
checked: boolean;
votesText: string;
}

function EndedPollOption(props: IEndedPollOptionProps) {
const classNames = `mx_MPollBody_endedOption${
checked ? " mx_MPollBody_endedOptionWinner": ""
props.checked ? " mx_MPollBody_endedOptionWinner": ""
}`;
andybalaam marked this conversation as resolved.
Show resolved Hide resolved

return <div className={classNames} data-value={answer.id}>
return <div className={classNames} data-value={props.answer.id}>
<div className="mx_MPollBody_optionDescription">
<div className="mx_MPollBody_optionText">
{ answer[TEXT_NODE_TYPE.name] }
{ props.answer[TEXT_NODE_TYPE.name] }
</div>
<div className="mx_MPollBody_optionVoteCount">
{ votesText }
{ props.votesText }
</div>
</div>
</div>;
}

function renderOption(
pollId: string,
answer: IPollAnswer,
checked: boolean,
votesText: string,
onOptionSelected: (e: React.FormEvent<HTMLInputElement>) => void,
) {
interface ILivePollOptionProps {
pollId: string;
answer: IPollAnswer;
checked: boolean;
votesText: string;
onOptionSelected: (e: React.FormEvent<HTMLInputElement>) => void;
}

function LivePollOption(props: ILivePollOptionProps) {
return <StyledRadioButton
name={`poll_answer_select-${pollId}`}
value={answer.id}
checked={checked}
onChange={onOptionSelected}
name={`poll_answer_select-${props.pollId}`}
value={props.answer.id}
checked={props.checked}
onChange={props.onOptionSelected}
>
<div className="mx_MPollBody_optionDescription">
<div className="mx_MPollBody_optionText">
{ answer[TEXT_NODE_TYPE.name] }
{ props.answer[TEXT_NODE_TYPE.name] }
</div>
<div className="mx_MPollBody_optionVoteCount">
{ votesText }
{ props.votesText }
</div>
</div>
</StyledRadioButton>;
Expand Down Expand Up @@ -528,13 +534,14 @@ export function allVotes(
}

/**
* Returns the earliest timestamp from the supplied list of end_poll events.
* Returns the earliest timestamp from the supplied list of end_poll events
* or null if there are no authorised events.
*/
export function pollEndTs(
pollEvent: MatrixEvent,
matrixClient: MatrixClient,
endRelations: Relations,
) {
): number | null {
if (!endRelations) {
return null;
}
Expand Down
99 changes: 49 additions & 50 deletions src/components/views/messages/MessageActionBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import React, { useEffect } from 'react';
import React, { ReactElement, useEffect } from 'react';
import { EventStatus, MatrixEvent } from 'matrix-js-sdk/src/models/event';
import type { Relations } from 'matrix-js-sdk/src/models/relations';

Expand Down Expand Up @@ -52,58 +52,57 @@ interface IOptionsButtonProps {
permalinkCreator: RoomPermalinkCreator;
onFocusChange: (menuDisplayed: boolean) => void;
getRelationsForEvent?: (
eventId: string,
relationType: string,
eventType: string
) => Relations;
eventId: string,
relationType: string,
eventType: string
) => Relations;
}

const OptionsButton: React.FC<IOptionsButtonProps> =
({
mxEvent,
getTile,
getReplyChain,
permalinkCreator,
onFocusChange,
getRelationsForEvent,
}) => {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
const [onFocus, isActive, ref] = useRovingTabIndex(button);
useEffect(() => {
onFocusChange(menuDisplayed);
}, [onFocusChange, menuDisplayed]);

let contextMenu;
if (menuDisplayed) {
const tile = getTile && getTile();
const replyChain = getReplyChain && getReplyChain();

const buttonRect = button.current.getBoundingClientRect();
contextMenu = <MessageContextMenu
{...aboveLeftOf(buttonRect)}
mxEvent={mxEvent}
permalinkCreator={permalinkCreator}
eventTileOps={tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined}
collapseReplyChain={replyChain && replyChain.canCollapse() ? replyChain.collapse : undefined}
onFinished={closeMenu}
getRelationsForEvent={getRelationsForEvent}
/>;
}
const OptionsButton: React.FC<IOptionsButtonProps> = ({
mxEvent,
getTile,
getReplyChain,
permalinkCreator,
onFocusChange,
getRelationsForEvent,
}) => {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
const [onFocus, isActive, ref] = useRovingTabIndex(button);
useEffect(() => {
onFocusChange(menuDisplayed);
}, [onFocusChange, menuDisplayed]);

return <React.Fragment>
<ContextMenuTooltipButton
className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
title={_t("Options")}
onClick={openMenu}
isExpanded={menuDisplayed}
inputRef={ref}
onFocus={onFocus}
tabIndex={isActive ? 0 : -1}
/>

{ contextMenu }
</React.Fragment>;
};
let contextMenu: ReactElement | null;
if (menuDisplayed) {
const tile = getTile && getTile();
const replyChain = getReplyChain && getReplyChain();

const buttonRect = button.current.getBoundingClientRect();
contextMenu = <MessageContextMenu
{...aboveLeftOf(buttonRect)}
mxEvent={mxEvent}
permalinkCreator={permalinkCreator}
eventTileOps={tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined}
collapseReplyChain={replyChain && replyChain.canCollapse() ? replyChain.collapse : undefined}
onFinished={closeMenu}
getRelationsForEvent={getRelationsForEvent}
/>;
}

return <React.Fragment>
<ContextMenuTooltipButton
className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
title={_t("Options")}
onClick={openMenu}
isExpanded={menuDisplayed}
inputRef={ref}
onFocus={onFocus}
tabIndex={isActive ? 0 : -1}
/>

{ contextMenu }
</React.Fragment>;
};

interface IReactButtonProps {
mxEvent: MatrixEvent;
Expand Down
2 changes: 1 addition & 1 deletion test/components/views/messages/MPollBody-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -988,7 +988,7 @@ function votesCount(wrapper: ReactWrapper, value: string): string {

function endedVoteChecked(wrapper: ReactWrapper, value: string): boolean {
return endedVoteDiv(wrapper, value)
.parent()
.closest(".mx_MPollBody_option")
.hasClass("mx_MPollBody_option_checked");
}

Expand Down
Loading