jiti-meet/react/features/polls/components/web/PollCreate.js

249 lines
8.2 KiB
JavaScript

// @flow
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { Icon, IconMenu } from '../../../base/icons';
import { Tooltip } from '../../../base/tooltip';
import AbstractPollCreate from '../AbstractPollCreate';
import type { AbstractProps } from '../AbstractPollCreate';
const PollCreate = (props: AbstractProps) => {
const {
addAnswer,
answers,
isSubmitDisabled,
moveAnswer,
onSubmit,
question,
removeAnswer,
setAnswer,
setCreateMode,
setQuestion,
t
} = props;
/*
* This ref stores the Array of answer input fields, allowing us to focus on them.
* This array is maintained by registerfieldRef and the useEffect below.
*/
const answerInputs = useRef([]);
const registerFieldRef = useCallback((i, r) => {
if (r === null) {
return;
}
answerInputs.current[i] = r;
}, [ answerInputs ]);
useEffect(() => {
answerInputs.current = answerInputs.current.slice(0, answers.length);
}, [ answers ]);
/*
* This state allows us to requestFocus asynchronously, without having to worry
* about whether a newly created input field has been rendered yet or not.
*/
const [ lastFocus, requestFocus ] = useState(null);
useEffect(() => {
if (lastFocus === null) {
return;
}
const input = answerInputs.current[lastFocus];
if (input === undefined) {
return;
}
input.focus();
}, [ lastFocus ]);
const checkModifiers = useCallback(ev => {
// Because this isn't done automatically on MacOS
if (ev.key === 'Enter' && ev.metaKey) {
ev.preventDefault();
onSubmit();
return;
}
if (ev.ctrlKey || ev.metaKey || ev.altKey || ev.shiftKey) {
return;
}
});
const onQuestionKeyDown = useCallback(ev => {
if (checkModifiers(ev)) {
return;
}
if (ev.key === 'Enter') {
requestFocus(0);
ev.preventDefault();
}
});
// Called on keypress in answer fields
const onAnswerKeyDown = useCallback((i, ev) => {
if (checkModifiers(ev)) {
return;
}
if (ev.key === 'Enter') {
addAnswer(i + 1);
requestFocus(i + 1);
ev.preventDefault();
} else if (ev.key === 'Backspace' && ev.target.value === '' && answers.length > 1) {
removeAnswer(i);
requestFocus(i > 0 ? i - 1 : 0);
ev.preventDefault();
} else if (ev.key === 'ArrowDown') {
if (i === answers.length - 1) {
addAnswer();
}
requestFocus(i + 1);
ev.preventDefault();
} else if (ev.key === 'ArrowUp') {
if (i === 0) {
addAnswer(0);
requestFocus(0);
} else {
requestFocus(i - 1);
}
ev.preventDefault();
}
}, [ answers, addAnswer, removeAnswer, requestFocus ]);
const [ grabbing, setGrabbing ] = useState(null);
const onGrab = useCallback((i, ev) => {
if (ev.button !== 0) {
return;
}
setGrabbing(i);
window.addEventListener('mouseup', () => {
setGrabbing(_grabbing => {
requestFocus(_grabbing);
return null;
});
}, { once: true });
});
const onMouseOver = useCallback(i => {
if (grabbing !== null && grabbing !== i) {
moveAnswer(grabbing, i);
setGrabbing(i);
}
});
const autogrow = ev => {
const el = ev.target;
el.style.height = '1px';
el.style.height = `${el.scrollHeight + 2}px`;
};
/* eslint-disable react/jsx-no-bind */
return (<form
className = 'polls-pane-content'
onSubmit = { onSubmit }>
<div className = 'poll-create-container poll-container'>
<div className = 'poll-create-header'>
{ t('polls.create.create') }
</div>
<div className = 'poll-question-field'>
<span className = 'poll-create-label'>
{ t('polls.create.pollQuestion') }
</span>
<textarea
autoFocus = { true }
className = 'expandable-input'
onChange = { ev => setQuestion(ev.target.value) }
onInput = { autogrow }
onKeyDown = { onQuestionKeyDown }
placeholder = { t('polls.create.questionPlaceholder') }
required = { true }
row = '1'
value = { question } />
</div>
<ol className = 'poll-answer-field-list'>
{answers.map((answer, i) =>
(<li
className = { `poll-answer-field${grabbing === i ? ' poll-dragged' : ''}` }
key = { i }
onMouseOver = { () => onMouseOver(i) }>
<span className = 'poll-create-label'>
{ t('polls.create.pollOption', { index: i + 1 })}
</span>
<div className = 'poll-create-option-row'>
<textarea
className = 'expandable-input'
onChange = { ev => setAnswer(i, ev.target.value) }
onInput = { autogrow }
onKeyDown = { ev => onAnswerKeyDown(i, ev) }
placeholder = { t('polls.create.answerPlaceholder', { index: i + 1 }) }
ref = { r => registerFieldRef(i, r) }
required = { true }
row = { 1 }
value = { answer } />
<button
className = 'poll-drag-handle'
onMouseDown = { ev => onGrab(i, ev) }
tabIndex = '-1'
type = 'button'>
<Icon src = { IconMenu } />
</button>
</div>
{ answers.length > 2
&& <Tooltip content = { t('polls.create.removeOption') }>
<button
className = 'poll-remove-option-button'
onClick = { () => removeAnswer(i) }
type = 'button'>
{ t('polls.create.removeOption') }
</button>
</Tooltip>}
</li>)
)}
</ol>
<div className = 'poll-add-button'>
<button
aria-label = { 'Add option' }
className = { 'poll-secondary-button' }
onClick = { () => {
addAnswer();
requestFocus(answers.length);
} }
type = 'button' >
<span>{t('polls.create.addOption')}</span>
</button>
</div>
</div>
<div className = 'poll-footer'>
<button
aria-label = { t('polls.create.cancel') }
className = 'poll-small-secondary-button'
onClick = { () => setCreateMode(false) }
type = 'button' >
<span>{t('polls.create.cancel')}</span>
</button>
<button
aria-label = { t('polls.create.send') }
className = 'poll-small-primary-button'
disabled = { isSubmitDisabled }
type = 'submit' >
<span>{t('polls.create.send')}</span>
</button>
</div>
</form>);
};
/*
* We apply AbstractPollCreate to fill in the AbstractProps common
* to both the web and native implementations.
*/
// eslint-disable-next-line new-cap
export default AbstractPollCreate(PollCreate);