fix(polls): Fix 'Skip' button functionality
This commit is contained in:
parent
09efaa0d0d
commit
70af0d6b78
|
@ -1,5 +1,16 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the action which signals that a Poll will be changed
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: CHANGE_VOTE,
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const CHANGE_VOTE = 'CHANGE_VOTE';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the action which signals that a new Poll was received.
|
* The type of the action which signals that a new Poll was received.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
CHANGE_VOTE,
|
||||||
RESET_NB_UNREAD_POLLS,
|
RESET_NB_UNREAD_POLLS,
|
||||||
RECEIVE_ANSWER,
|
RECEIVE_ANSWER,
|
||||||
RECEIVE_POLL,
|
RECEIVE_POLL,
|
||||||
|
@ -9,6 +10,26 @@ import {
|
||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
import type { Answer, Poll } from './types';
|
import type { Answer, Poll } from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action to signal that a poll's vote will be changed.
|
||||||
|
*
|
||||||
|
* @param {string} pollId - The id of the incoming poll.
|
||||||
|
* @param {boolean} value - The value of the 'changing' state.
|
||||||
|
|
||||||
|
* @returns {{
|
||||||
|
* type: CHANGE_VOTE,
|
||||||
|
* pollId: string,
|
||||||
|
* value: boolean
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export const setVoteChanging = (pollId: string, value: boolean) => {
|
||||||
|
return {
|
||||||
|
type: CHANGE_VOTE,
|
||||||
|
pollId,
|
||||||
|
value
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action to signal that a new poll was received.
|
* Action to signal that a new poll was received.
|
||||||
*
|
*
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { sendAnalytics, createPollEvent } from '../../analytics';
|
import { sendAnalytics, createPollEvent } from '../../analytics';
|
||||||
import { getLocalParticipant, getParticipantById } from '../../base/participants';
|
import { getLocalParticipant, getParticipantById } from '../../base/participants';
|
||||||
import { registerVote } from '../actions';
|
import { registerVote, setVoteChanging } from '../actions';
|
||||||
import { COMMAND_ANSWER_POLL } from '../constants';
|
import { COMMAND_ANSWER_POLL } from '../constants';
|
||||||
import type { Poll } from '../types';
|
import type { Poll } from '../types';
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ export type AbstractProps = {
|
||||||
poll: Poll,
|
poll: Poll,
|
||||||
setCheckbox: Function,
|
setCheckbox: Function,
|
||||||
skipAnswer: Function,
|
skipAnswer: Function,
|
||||||
|
skipChangeVote: Function,
|
||||||
submitAnswer: Function,
|
submitAnswer: Function,
|
||||||
t: Function,
|
t: Function,
|
||||||
};
|
};
|
||||||
|
@ -90,6 +91,10 @@ const AbstractPollAnswer = (Component: AbstractComponent<AbstractProps>) => (pro
|
||||||
|
|
||||||
}, [ pollId ]);
|
}, [ pollId ]);
|
||||||
|
|
||||||
|
const skipChangeVote = useCallback(() => {
|
||||||
|
dispatch(setVoteChanging(pollId, false));
|
||||||
|
}, [ dispatch, pollId ]);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (<Component
|
return (<Component
|
||||||
|
@ -97,6 +102,7 @@ const AbstractPollAnswer = (Component: AbstractComponent<AbstractProps>) => (pro
|
||||||
poll = { poll }
|
poll = { poll }
|
||||||
setCheckbox = { setCheckbox }
|
setCheckbox = { setCheckbox }
|
||||||
skipAnswer = { skipAnswer }
|
skipAnswer = { skipAnswer }
|
||||||
|
skipChangeVote = { skipChangeVote }
|
||||||
submitAnswer = { submitAnswer }
|
submitAnswer = { submitAnswer }
|
||||||
t = { t } />);
|
t = { t } />);
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,8 @@ import { useTranslation } from 'react-i18next';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { sendAnalytics, createPollEvent } from '../../analytics';
|
import { sendAnalytics, createPollEvent } from '../../analytics';
|
||||||
import { getLocalParticipant, getParticipantById } from '../../base/participants/functions';
|
import { setVoteChanging } from '../actions';
|
||||||
import { retractVote } from '../actions';
|
import { getPoll } from '../functions';
|
||||||
import { COMMAND_ANSWER_POLL } from '../constants';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the React {@code Component} props of inheriting component.
|
* The type of the React {@code Component} props of inheriting component.
|
||||||
|
@ -51,7 +50,7 @@ export type AbstractProps = {
|
||||||
const AbstractPollResults = (Component: AbstractComponent<AbstractProps>) => (props: InputProps) => {
|
const AbstractPollResults = (Component: AbstractComponent<AbstractProps>) => (props: InputProps) => {
|
||||||
const { pollId } = props;
|
const { pollId } = props;
|
||||||
|
|
||||||
const pollDetails = useSelector(state => state['features/polls'].polls[pollId]);
|
const pollDetails = useSelector(getPoll(pollId));
|
||||||
|
|
||||||
const [ showDetails, setShowDetails ] = useState(false);
|
const [ showDetails, setShowDetails ] = useState(false);
|
||||||
const toggleIsDetailed = useCallback(() => {
|
const toggleIsDetailed = useCallback(() => {
|
||||||
|
@ -95,33 +94,23 @@ const AbstractPollResults = (Component: AbstractComponent<AbstractProps>) => (pr
|
||||||
}, [ pollDetails.answers, showDetails ]);
|
}, [ pollDetails.answers, showDetails ]);
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const conference: Object = useSelector(state => state['features/base/conference'].conference);
|
|
||||||
const localId = useSelector(state => getLocalParticipant(state).id);
|
|
||||||
const localParticipant = useSelector(state => getParticipantById(state, localId));
|
|
||||||
const localName: string = localParticipant ? localParticipant.name : 'Fellow Jitster';
|
|
||||||
const changeVote = useCallback(() => {
|
const changeVote = useCallback(() => {
|
||||||
conference.sendMessage({
|
dispatch(setVoteChanging(pollId, true));
|
||||||
type: COMMAND_ANSWER_POLL,
|
|
||||||
pollId,
|
|
||||||
voterId: localId,
|
|
||||||
voterName: localName,
|
|
||||||
answers: new Array(pollDetails.answers.length).fill(false)
|
|
||||||
});
|
|
||||||
dispatch(retractVote(pollId));
|
|
||||||
sendAnalytics(createPollEvent('vote.changed'));
|
sendAnalytics(createPollEvent('vote.changed'));
|
||||||
}, [ pollId, localId, localName, pollDetails ]);
|
}, [ dispatch, pollId ]);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (<Component
|
return (
|
||||||
|
<Component
|
||||||
answers = { answers }
|
answers = { answers }
|
||||||
changeVote = { changeVote }
|
changeVote = { changeVote }
|
||||||
haveVoted = { pollDetails.lastVote !== null }
|
haveVoted = { pollDetails.lastVote !== null }
|
||||||
question = { pollDetails.question }
|
question = { pollDetails.question }
|
||||||
showDetails = { showDetails }
|
showDetails = { showDetails }
|
||||||
t = { t }
|
t = { t }
|
||||||
toggleIsDetailed = { toggleIsDetailed } />);
|
toggleIsDetailed = { toggleIsDetailed } />
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AbstractPollResults;
|
export default AbstractPollResults;
|
||||||
|
|
|
@ -18,9 +18,11 @@ const PollAnswer = (props: AbstractProps) => {
|
||||||
poll,
|
poll,
|
||||||
setCheckbox,
|
setCheckbox,
|
||||||
skipAnswer,
|
skipAnswer,
|
||||||
|
skipChangeVote,
|
||||||
submitAnswer,
|
submitAnswer,
|
||||||
t
|
t
|
||||||
} = props;
|
} = props;
|
||||||
|
const { changingVote } = poll;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
|
@ -44,7 +46,7 @@ const PollAnswer = (props: AbstractProps) => {
|
||||||
<Button
|
<Button
|
||||||
color = '#3D3D3D'
|
color = '#3D3D3D'
|
||||||
mode = { BUTTON_MODES.CONTAINED }
|
mode = { BUTTON_MODES.CONTAINED }
|
||||||
onPress = { skipAnswer }
|
onPress = { changingVote ? skipChangeVote : skipAnswer }
|
||||||
style = { chatStyles.pollCreateButton } >
|
style = { chatStyles.pollCreateButton } >
|
||||||
{t('polls.answer.skip')}
|
{t('polls.answer.skip')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -20,7 +20,7 @@ type Props = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const PollItem = ({ pollId }: Props) => {
|
const PollItem = ({ pollId }: Props) => {
|
||||||
const showResults = useSelector(state => shouldShowResults(state, pollId));
|
const showResults = useSelector(shouldShowResults(pollId));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
|
|
|
@ -13,9 +13,11 @@ const PollAnswer = (props: AbstractProps) => {
|
||||||
poll,
|
poll,
|
||||||
setCheckbox,
|
setCheckbox,
|
||||||
skipAnswer,
|
skipAnswer,
|
||||||
|
skipChangeVote,
|
||||||
submitAnswer,
|
submitAnswer,
|
||||||
t
|
t
|
||||||
} = props;
|
} = props;
|
||||||
|
const { changingVote } = poll;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className = 'poll-answer'>
|
<div className = 'poll-answer'>
|
||||||
|
@ -45,7 +47,7 @@ const PollAnswer = (props: AbstractProps) => {
|
||||||
<button
|
<button
|
||||||
aria-label = { t('polls.answer.skip') }
|
aria-label = { t('polls.answer.skip') }
|
||||||
className = 'poll-button poll-button-secondary poll-button-shortest'
|
className = 'poll-button poll-button-secondary poll-button-shortest'
|
||||||
onClick = { skipAnswer } >
|
onClick = { changingVote ? skipChangeVote : skipAnswer } >
|
||||||
<span>{t('polls.answer.skip')}</span>
|
<span>{t('polls.answer.skip')}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -17,7 +17,7 @@ type Props = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const PollItem = React.forwardRef<Props, HTMLElement>(({ pollId }, ref) => {
|
const PollItem = React.forwardRef<Props, HTMLElement>(({ pollId }, ref) => {
|
||||||
const showResults = useSelector(state => shouldShowResults(state, pollId));
|
const showResults = useSelector(shouldShowResults(pollId));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref = { ref }>
|
<div ref = { ref }>
|
||||||
|
|
|
@ -1,14 +1,28 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should poll results be shown.
|
* Selector creator for determining if poll results should be displayed or not.
|
||||||
*
|
*
|
||||||
* @param {Object} state - Global state.
|
|
||||||
* @param {string} id - Id of the poll.
|
* @param {string} id - Id of the poll.
|
||||||
* @returns {boolean} Should poll results be shown.
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
export const shouldShowResults = (state: Object, id: string) => Boolean(state['features/polls']?.polls[id].showResults);
|
export function shouldShowResults(id: string) {
|
||||||
|
return function(state: Object) {
|
||||||
|
return Boolean(state['features/polls']?.polls[id].showResults);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selector creator for polls.
|
||||||
|
*
|
||||||
|
* @param {string} pollId - Id of the poll to get.
|
||||||
|
* @returns {Function}
|
||||||
|
*/
|
||||||
|
export function getPoll(pollId: string) {
|
||||||
|
return function(state: Object) {
|
||||||
|
return state['features/polls'].polls[pollId];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selector for calculating the number of unread poll messages.
|
* Selector for calculating the number of unread poll messages.
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import { ReducerRegistry } from '../base/redux';
|
import { ReducerRegistry } from '../base/redux';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
CHANGE_VOTE,
|
||||||
RECEIVE_POLL,
|
RECEIVE_POLL,
|
||||||
RECEIVE_ANSWER,
|
RECEIVE_ANSWER,
|
||||||
REGISTER_VOTE,
|
REGISTER_VOTE,
|
||||||
|
@ -21,6 +22,22 @@ const INITIAL_STATE = {
|
||||||
ReducerRegistry.register('features/polls', (state = INITIAL_STATE, action) => {
|
ReducerRegistry.register('features/polls', (state = INITIAL_STATE, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
|
||||||
|
case CHANGE_VOTE: {
|
||||||
|
const { pollId, value } = action;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
polls: {
|
||||||
|
...state.polls,
|
||||||
|
[pollId]: {
|
||||||
|
...state.polls[pollId],
|
||||||
|
changingVote: value,
|
||||||
|
showResults: !value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Reducer triggered when a poll is received
|
// Reducer triggered when a poll is received
|
||||||
case RECEIVE_POLL: {
|
case RECEIVE_POLL: {
|
||||||
const newState = {
|
const newState = {
|
||||||
|
@ -93,6 +110,7 @@ ReducerRegistry.register('features/polls', (state = INITIAL_STATE, action) => {
|
||||||
...state.polls,
|
...state.polls,
|
||||||
[pollId]: {
|
[pollId]: {
|
||||||
...state.polls[pollId],
|
...state.polls[pollId],
|
||||||
|
changingVote: false,
|
||||||
lastVote: answers,
|
lastVote: answers,
|
||||||
showResults: true
|
showResults: true
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ const parsePollData = (pollData): Poll | null => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
changingVote: false,
|
||||||
senderId,
|
senderId,
|
||||||
senderName,
|
senderName,
|
||||||
question,
|
question,
|
||||||
|
@ -63,6 +64,7 @@ StateListenerRegistry.register(
|
||||||
const { question, answers, pollId, senderId, senderName } = data;
|
const { question, answers, pollId, senderId, senderName } = data;
|
||||||
|
|
||||||
const poll = {
|
const poll = {
|
||||||
|
changingVote: false,
|
||||||
senderId,
|
senderId,
|
||||||
senderName,
|
senderName,
|
||||||
showResults: false,
|
showResults: false,
|
||||||
|
|
|
@ -25,6 +25,11 @@ export type Answer = {
|
||||||
|
|
||||||
export type Poll = {
|
export type Poll = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the poll vote is being edited/changed.
|
||||||
|
*/
|
||||||
|
changingVote: boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ID of the sender of this poll
|
* ID of the sender of this poll
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue