Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(polls/web/native): refactoring #14778

Merged
merged 6 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion react/features/polls/components/AbstractPollAnswer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ const AbstractPollAnswer = (Component: ComponentType<AbstractProps>) => (props:
type: COMMAND_NEW_POLL,
pollId,
question,
answers
answers: answers.map(answer => answer.name)
});

dispatch(editPoll(pollId, false));
Expand Down
36 changes: 25 additions & 11 deletions react/features/polls/components/AbstractPollCreate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { IReduxState } from '../../app/types';
import { getLocalParticipant } from '../../base/participants/functions';
import { savePoll } from '../actions';
import { hasIdenticalAnswers } from '../functions';
import { IPoll } from '../types';
import { IAnswerData, IPoll } from '../types';

/**
* The type of the React {@code Component} props of inheriting component.
Expand All @@ -25,14 +25,14 @@ type InputProps = {
**/
export type AbstractProps = InputProps & {
addAnswer: (index?: number) => void;
answers: Array<string>;
answers: Array<IAnswerData>;
editingPoll: IPoll | undefined;
editingPollId: string | undefined;
isSubmitDisabled: boolean;
onSubmit: (event?: FormEvent<HTMLFormElement>) => void;
question: string;
removeAnswer: (index: number) => void;
setAnswer: (index: number, value: string) => void;
setAnswer: (index: number, value: IAnswerData) => void;
setQuestion: (question: string) => void;
t: Function;
};
Expand Down Expand Up @@ -67,7 +67,17 @@ const AbstractPollCreate = (Component: ComponentType<AbstractProps>) => (props:
}, [ pollState ]);

const answerResults = useMemo(() => {
return editingPoll ? editingPoll[1].answers as Array<string> : [ '', '' ];
return editingPoll
? editingPoll[1].answers
: [
{
name: '',
voters: []
},
{
name: '',
voters: []
} ];
}, [ editingPoll ]);

const questionResult = useMemo(() => {
Expand All @@ -78,7 +88,7 @@ const AbstractPollCreate = (Component: ComponentType<AbstractProps>) => (props:

const [ answers, setAnswers ] = useState(answerResults);

const setAnswer = useCallback((i, answer) => {
const setAnswer = useCallback((i: number, answer: IAnswerData) => {
setAnswers(currentAnswers => {
const newAnswers = [ ...currentAnswers ];

Expand All @@ -89,11 +99,15 @@ const AbstractPollCreate = (Component: ComponentType<AbstractProps>) => (props:
}, [ answers ]);

const addAnswer = useCallback((i?: number) => {
const newAnswers: (string | { name: string; voters: Array<string>; })[] = [ ...answers ];
const newAnswers: Array<IAnswerData> = [ ...answers ];

sendAnalytics(createPollEvent('option.added'));
newAnswers.splice(typeof i === 'number' ? i : answers.length, 0, '');
setAnswers(newAnswers as string[]);
newAnswers.splice(typeof i === 'number'
? i : answers.length, 0, {
name: '',
voters: []
});
setAnswers(newAnswers);
}, [ answers ]);

const removeAnswer = useCallback(i => {
Expand All @@ -104,7 +118,7 @@ const AbstractPollCreate = (Component: ComponentType<AbstractProps>) => (props:

sendAnalytics(createPollEvent('option.removed'));
newAnswers.splice(i, 1);
setAnswers(newAnswers as string[]);
setAnswers(newAnswers);
}, [ answers ]);

const conference = useSelector((state: IReduxState) => state['features/base/conference'].conference);
Expand All @@ -120,7 +134,7 @@ const AbstractPollCreate = (Component: ComponentType<AbstractProps>) => (props:
ev.preventDefault();
}

const filteredAnswers = answers.filter((answer: string) => answer.trim().length > 0);
const filteredAnswers = answers.filter(answer => answer.name.trim().length > 0);

if (filteredAnswers.length < 2) {
return;
Expand Down Expand Up @@ -152,7 +166,7 @@ const AbstractPollCreate = (Component: ComponentType<AbstractProps>) => (props:
// Check if the poll create form can be submitted i.e. if the send button should be disabled.
const isSubmitDisabled
= question.trim().length <= 0 // If no question is provided
|| answers.filter((answer: string) => answer.trim().length > 0).length < 2 // If not enough options are provided
|| answers.filter(answer => answer.name.trim().length > 0).length < 2 // If not enough options are provided
|| hasIdenticalAnswers(answers); // If duplicate options are provided

const { t } = useTranslation();
Expand Down
5 changes: 2 additions & 3 deletions react/features/polls/components/AbstractPollResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,16 @@ const AbstractPollResults = (Component: ComponentType<AbstractProps>) => (props:

const answers: Array<AnswerInfo> = useMemo(() => {
const allVoters = new Set();
const pollDetailsAnswers = pollDetails.answers as { name: string; voters: string[]; }[];

// Getting every voters ID that participates to the poll
for (const answer of pollDetailsAnswers) {
for (const answer of pollDetails.answers) {
// checking if the voters is an array for supporting old structure model
const voters: string[] = answer.voters.length ? answer.voters : Object.keys(answer.voters);

voters.forEach((voter: string) => allVoters.add(voter));
}

return pollDetailsAnswers.map(answer => {
return pollDetails.answers.map(answer => {
const nrOfVotersPerAnswer = answer.voters ? Object.keys(answer.voters).length : 0;
const percentage = allVoters.size > 0 ? Math.round(nrOfVotersPerAnswer / allVoters.size * 100) : 0;

Expand Down
6 changes: 2 additions & 4 deletions react/features/polls/components/native/PollAnswer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import AbstractPollAnswer, { AbstractProps } from '../AbstractPollAnswer';

import { chatStyles, dialogStyles } from './styles';


const PollAnswer = (props: AbstractProps) => {
const {
checkBoxStates,
Expand All @@ -32,7 +31,6 @@ const PollAnswer = (props: AbstractProps) => {
const dispatch = useDispatch();
const localParticipant = useSelector(getLocalParticipant);
const { PRIMARY, SECONDARY } = BUTTON_TYPES;
const pollAnswers = poll.answers as { name: string; voters: string[]; }[];

return (
<>
Expand All @@ -43,7 +41,7 @@ const PollAnswer = (props: AbstractProps) => {
</Text>
<View style = { chatStyles.answerContent as ViewStyle }>
{
pollAnswers.map((answer, index: number) => (
poll.answers.map((answer, index: number) => (
<View
key = { index }
style = { chatStyles.switchRow as ViewStyle } >
Expand All @@ -52,7 +50,7 @@ const PollAnswer = (props: AbstractProps) => {
disabled = { poll.saved }
onChange = { state => setCheckbox(index, state) } />
<Text style = { chatStyles.switchLabel as TextStyle }>
{ poll.saved ? answer : answer.name }
{ answer.name }
</Text>
</View>
))
Expand Down
10 changes: 7 additions & 3 deletions react/features/polls/components/native/PollCreate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ const PollCreate = (props: AbstractProps) => {
// Called on keypress in answer fields
const onAnswerKeyDown = useCallback((index: number, ev) => {
const { key } = ev.nativeEvent;
const currentText = answers[index];
const currentText = answers[index].name;

if (key === 'Backspace' && currentText === '' && answers.length > 1) {
removeAnswer(index);
Expand Down Expand Up @@ -110,14 +110,18 @@ const PollCreate = (props: AbstractProps) => {
label = { t('polls.create.pollOption', { index: index + 1 }) }
maxLength = { CHAR_LIMIT }
multiline = { true }
onChange = { text => setAnswer(index, text) }
onChange = { name => setAnswer(index,
{
name,
voters: []
}) }
onKeyPress = { ev => onAnswerKeyDown(index, ev) }
placeholder = { t('polls.create.answerPlaceholder', { index: index + 1 }) }

// This is set to help the touch event not be propagated to any subviews.
pointerEvents = { 'auto' }
ref = { input => registerFieldRef(index, input) }
value = { answers[index] } />
value = { answers[index].name } />
{
answers.length > 2
&& createRemoveOptionButton(() => removeAnswer(index))
Expand Down
4 changes: 2 additions & 2 deletions react/features/polls/components/web/PollAnswer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,15 @@ const PollAnswer = ({
</div>
<ul className = { classes.answerList }>
{
poll.answers.map((answer: any, index: number) => (
poll.answers.map((answer, index: number) => (
<li
className = { classes.answer }
key = { index }>
<Checkbox
checked = { checkBoxStates[index] }
disabled = { poll.saved }
key = { index }
label = { poll.saved ? answer : answer.name }
label = { answer.name }
onChange = { ev => setCheckbox(index, ev.target.checked) } />
</li>
))
Expand Down
14 changes: 9 additions & 5 deletions react/features/polls/components/web/PollCreate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,11 +206,12 @@ const PollCreate = ({
value = { question } />
</div>
<ol className = { classes.answerList }>
{answers.map((answer: string, i: number) => {
{answers.map((answer, i: number) => {

const isIdenticalAnswer = answers.slice(0, i).length === 0 ? false
: answers.slice(0, i).some((prevAnswer: string) =>
prevAnswer === answer && prevAnswer !== '' && answer !== '');
: answers.slice(0, i).some(prevAnswer =>
prevAnswer.name === answer.name
&& prevAnswer.name !== '' && answer.name !== '');

return (<li
className = { classes.answer }
Expand All @@ -222,12 +223,15 @@ const PollCreate = ({
id = { `polls-answer-input-${i}` }
label = { t('polls.create.pollOption', { index: i + 1 }) }
maxLength = { CHAR_LIMIT }
onChange = { val => setAnswer(i, val) }
onChange = { name => setAnswer(i, {
name,
voters: []
}) }
onKeyPress = { ev => onAnswerKeyDown(i, ev) }
placeholder = { t('polls.create.answerPlaceholder', { index: i + 1 }) }
ref = { r => registerFieldRef(i, r) }
textarea = { true }
value = { answer } />
value = { answer.name } />

{ answers.length > 2
&& <button
Expand Down
8 changes: 5 additions & 3 deletions react/features/polls/functions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { IReduxState } from '../app/types';

import { IAnswerData } from './types';

/**
* Selector creator for determining if poll results should be displayed or not.
*
Expand Down Expand Up @@ -49,12 +51,12 @@ export function isSubmitAnswerDisabled(checkBoxStates: Array<boolean>) {
/**
* Check if the input array has identical answers.
*
* @param {Array<string>} currentAnswers - The array of current answers to compare.
* @param {Array<IAnswerData>} currentAnswers - The array of current answers to compare.
* @returns {boolean} - Returns true if the answers are identical.
*/
export function hasIdenticalAnswers(currentAnswers: Array<string>): boolean {
export function hasIdenticalAnswers(currentAnswers: Array<IAnswerData>): boolean {

const nonEmptyCurrentAnswers = currentAnswers.filter((answer: string): boolean => answer !== '');
const nonEmptyCurrentAnswers = currentAnswers.filter((answer): boolean => answer.name !== '');

const currentAnswersSet = new Set(nonEmptyCurrentAnswers);

Expand Down
4 changes: 1 addition & 3 deletions react/features/polls/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ ReducerRegistry.register<IPollsState>('features/polls', (state = INITIAL_STATE,
...state,
polls: {
...state.polls,

[action.pollId]: action.poll
},
nbUnreadPolls: state.nbUnreadPolls + 1
Expand All @@ -81,8 +80,7 @@ ReducerRegistry.register<IPollsState>('features/polls', (state = INITIAL_STATE,
}

// if the poll exists, we update it with the incoming answer
const statePollsAnswers = state.polls[pollId].answers as { name: string; voters: string[]; }[];
const newAnswers = statePollsAnswers
const newAnswers = state.polls[pollId].answers
.map(_answer => {
// checking if the voters is an array for supporting old structure model
const answerVoters = _answer.voters
Expand Down
15 changes: 14 additions & 1 deletion react/features/polls/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export interface IPoll {
* An array of answers:
* the name of the answer name and a map of ids and names of voters voting for this option.
*/
answers: Array<{ name: string; voters: string[]; }> | string[];
answers: Array<IAnswerData>;

/**
* Whether the poll vote is being edited/changed.
Expand Down Expand Up @@ -69,3 +69,16 @@ export interface IPoll {
export interface IPollData extends IPoll {
id: string;
}

export interface IAnswerData {

/**
* The answer name chosen for the poll.
*/
name: string;

/**
* An array of voters.
*/
voters: Array<string>;
}