"Fossies" - the Fresh Open Source Software Archive

Member "jitsi-meet-7329/react/features/polls/components/web/PollCreate.tsx" (9 Jun 2023, 8899 Bytes) of package /linux/misc/jitsi-meet-7329.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) TSX (TypeScript with React) source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file.

    1 import React, { useCallback, useEffect, useRef, useState } from 'react';
    2 import { makeStyles } from 'tss-react/mui';
    3 
    4 import { withPixelLineHeight } from '../../../base/styles/functions.web';
    5 import Button from '../../../base/ui/components/web/Button';
    6 import Input from '../../../base/ui/components/web/Input';
    7 import { BUTTON_TYPES } from '../../../base/ui/constants.web';
    8 import { ANSWERS_LIMIT, CHAR_LIMIT } from '../../constants';
    9 import AbstractPollCreate, { AbstractProps } from '../AbstractPollCreate';
   10 
   11 const useStyles = makeStyles()(theme => {
   12     return {
   13         container: {
   14             height: '100%',
   15             position: 'relative'
   16         },
   17         createContainer: {
   18             padding: '0 24px',
   19             height: 'calc(100% - 88px)',
   20             overflowY: 'auto'
   21         },
   22         header: {
   23             ...withPixelLineHeight(theme.typography.heading6),
   24             color: theme.palette.text01,
   25             margin: '24px 0 16px'
   26         },
   27         questionContainer: {
   28             paddingBottom: '24px',
   29             borderBottom: `1px solid ${theme.palette.ui03}`
   30         },
   31         answerList: {
   32             listStyleType: 'none',
   33             margin: 0,
   34             padding: 0
   35         },
   36         answer: {
   37             marginBottom: '24px'
   38         },
   39         removeOption: {
   40             ...withPixelLineHeight(theme.typography.bodyShortRegular),
   41             color: theme.palette.link01,
   42             marginTop: '8px',
   43             border: 0,
   44             background: 'transparent'
   45         },
   46         addButtonContainer: {
   47             display: 'flex'
   48         },
   49         footer: {
   50             position: 'absolute',
   51             bottom: 0,
   52             display: 'flex',
   53             justifyContent: 'flex-end',
   54             padding: '24px',
   55             width: '100%',
   56             boxSizing: 'border-box'
   57         },
   58         buttonMargin: {
   59             marginRight: theme.spacing(3)
   60         }
   61     };
   62 });
   63 
   64 const PollCreate = ({
   65     addAnswer,
   66     answers,
   67     isSubmitDisabled,
   68     onSubmit,
   69     question,
   70     removeAnswer,
   71     setAnswer,
   72     setCreateMode,
   73     setQuestion,
   74     t
   75 }: AbstractProps) => {
   76     const { classes } = useStyles();
   77 
   78     /*
   79      * This ref stores the Array of answer input fields, allowing us to focus on them.
   80      * This array is maintained by registerfieldRef and the useEffect below.
   81      */
   82     const answerInputs = useRef<Array<HTMLInputElement>>([]);
   83     const registerFieldRef = useCallback((i, r) => {
   84         if (r === null) {
   85             return;
   86         }
   87         answerInputs.current[i] = r;
   88     }, [ answerInputs ]);
   89 
   90     useEffect(() => {
   91         answerInputs.current = answerInputs.current.slice(0, answers.length);
   92     }, [ answers ]);
   93 
   94     /*
   95      * This state allows us to requestFocus asynchronously, without having to worry
   96      * about whether a newly created input field has been rendered yet or not.
   97      */
   98     const [ lastFocus, requestFocus ] = useState<number | null>(null);
   99 
  100     useEffect(() => {
  101         if (lastFocus === null) {
  102             return;
  103         }
  104         const input = answerInputs.current[lastFocus];
  105 
  106         if (input === undefined) {
  107             return;
  108         }
  109         input.focus();
  110     }, [ lastFocus ]);
  111 
  112     const checkModifiers = useCallback(ev => {
  113         // Composition events used to add accents to characters
  114         // despite their absence from standard US keyboards,
  115         // to build up logograms of many Asian languages
  116         // from their base components or categories and so on.
  117         if (ev.isComposing || ev.keyCode === 229) {
  118             // keyCode 229 means that user pressed some button,
  119             // but input method is still processing that.
  120             // This is a standard behavior for some input methods
  121             // like entering japanese or сhinese hieroglyphs.
  122             return true;
  123         }
  124 
  125         // Because this isn't done automatically on MacOS
  126         if (ev.key === 'Enter' && ev.metaKey) {
  127             ev.preventDefault();
  128             onSubmit();
  129 
  130             return;
  131         }
  132         if (ev.ctrlKey || ev.metaKey || ev.altKey || ev.shiftKey) {
  133             return;
  134         }
  135     }, []);
  136 
  137     const onQuestionKeyDown = useCallback(ev => {
  138         if (checkModifiers(ev)) {
  139             return;
  140         }
  141 
  142         if (ev.key === 'Enter') {
  143             requestFocus(0);
  144             ev.preventDefault();
  145         }
  146     }, []);
  147 
  148     // Called on keypress in answer fields
  149     const onAnswerKeyDown = useCallback((i, ev) => {
  150         if (checkModifiers(ev)) {
  151             return;
  152         }
  153 
  154         if (ev.key === 'Enter') {
  155             // We add a new option input
  156             // only if we are on the last option input
  157             if (i === answers.length - 1) {
  158                 addAnswer(i + 1);
  159             }
  160             requestFocus(i + 1);
  161             ev.preventDefault();
  162         } else if (ev.key === 'Backspace' && ev.target.value === '' && answers.length > 1) {
  163             removeAnswer(i);
  164             requestFocus(i > 0 ? i - 1 : 0);
  165             ev.preventDefault();
  166         } else if (ev.key === 'ArrowDown') {
  167             if (i === answers.length - 1) {
  168                 addAnswer();
  169             }
  170             requestFocus(i + 1);
  171             ev.preventDefault();
  172         } else if (ev.key === 'ArrowUp') {
  173             if (i === 0) {
  174                 addAnswer(0);
  175                 requestFocus(0);
  176             } else {
  177                 requestFocus(i - 1);
  178             }
  179             ev.preventDefault();
  180         }
  181     }, [ answers, addAnswer, removeAnswer, requestFocus ]);
  182 
  183     /* eslint-disable react/jsx-no-bind */
  184     return (<form
  185         className = { classes.container }
  186         onSubmit = { onSubmit }>
  187         <div className = { classes.createContainer }>
  188             <div className = { classes.header }>
  189                 { t('polls.create.create') }
  190             </div>
  191             <div className = { classes.questionContainer }>
  192                 <Input
  193                     autoFocus = { true }
  194                     label = { t('polls.create.pollQuestion') }
  195                     maxLength = { CHAR_LIMIT }
  196                     onChange = { setQuestion }
  197                     onKeyPress = { onQuestionKeyDown }
  198                     placeholder = { t('polls.create.questionPlaceholder') }
  199                     textarea = { true }
  200                     value = { question } />
  201             </div>
  202             <ol className = { classes.answerList }>
  203                 {answers.map((answer: any, i: number) =>
  204                     (<li
  205                         className = { classes.answer }
  206                         key = { i }>
  207                         <Input
  208                             label = { t('polls.create.pollOption', { index: i + 1 }) }
  209                             maxLength = { CHAR_LIMIT }
  210                             onChange = { val => setAnswer(i, val) }
  211                             onKeyPress = { ev => onAnswerKeyDown(i, ev) }
  212                             placeholder = { t('polls.create.answerPlaceholder', { index: i + 1 }) }
  213                             ref = { r => registerFieldRef(i, r) }
  214                             textarea = { true }
  215                             value = { answer } />
  216 
  217                         { answers.length > 2
  218                         && <button
  219                             className = { classes.removeOption }
  220                             onClick = { () => removeAnswer(i) }
  221                             type = 'button'>
  222                             { t('polls.create.removeOption') }
  223                         </button>}
  224                     </li>)
  225                 )}
  226             </ol>
  227             <div className = { classes.addButtonContainer }>
  228                 <Button
  229                     accessibilityLabel = { t('polls.create.addOption') }
  230                     disabled = { answers.length >= ANSWERS_LIMIT }
  231                     labelKey = { 'polls.create.addOption' }
  232                     onClick = { () => {
  233                         addAnswer();
  234                         requestFocus(answers.length);
  235                     } }
  236                     type = { BUTTON_TYPES.SECONDARY } />
  237             </div>
  238         </div>
  239         <div className = { classes.footer }>
  240             <Button
  241                 accessibilityLabel = { t('polls.create.cancel') }
  242                 className = { classes.buttonMargin }
  243                 labelKey = { 'polls.create.cancel' }
  244                 onClick = { () => setCreateMode(false) }
  245                 type = { BUTTON_TYPES.SECONDARY } />
  246             <Button
  247                 accessibilityLabel = { t('polls.create.send') }
  248                 disabled = { isSubmitDisabled }
  249                 isSubmit = { true }
  250                 labelKey = { 'polls.create.send' } />
  251         </div>
  252     </form>);
  253 };
  254 
  255 /*
  256  * We apply AbstractPollCreate to fill in the AbstractProps common
  257  * to both the web and native implementations.
  258  */
  259 // eslint-disable-next-line new-cap
  260 export default AbstractPollCreate(PollCreate);