| 
									
										
										
										
											2021-06-25 12:08:16 -04:00
										 |  |  | // Copyright 2018-2021 Signal Messenger, LLC
 | 
					
						
							|  |  |  | // SPDX-License-Identifier: AGPL-3.0-only
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-26 14:15:33 -05:00
										 |  |  | import type { KeyboardEvent } from 'react'; | 
					
						
							|  |  |  | import React, { useCallback, useEffect, useState } from 'react'; | 
					
						
							| 
									
										
										
										
											2021-06-25 12:08:16 -04:00
										 |  |  | import { noop } from 'lodash'; | 
					
						
							|  |  |  | import { createPortal } from 'react-dom'; | 
					
						
							|  |  |  | import classNames from 'classnames'; | 
					
						
							|  |  |  | import { Manager, Popper, Reference } from 'react-popper'; | 
					
						
							| 
									
										
										
										
											2021-10-26 14:15:33 -05:00
										 |  |  | import type { LocalizerType } from '../types/Util'; | 
					
						
							| 
									
										
										
										
											2022-06-15 10:53:08 -07:00
										 |  |  | import { useRefMerger } from '../hooks/useRefMerger'; | 
					
						
							| 
									
										
										
										
											2022-09-14 18:58:35 -07:00
										 |  |  | import { handleOutsideClick } from '../util/handleOutsideClick'; | 
					
						
							| 
									
										
										
										
											2021-06-25 12:08:16 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | export type PropsType = { | 
					
						
							|  |  |  |   i18n: LocalizerType; | 
					
						
							|  |  |  |   isHighQuality: boolean; | 
					
						
							|  |  |  |   onSelectQuality: (isHQ: boolean) => unknown; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-17 16:45:19 -08:00
										 |  |  | export function MediaQualitySelector({ | 
					
						
							| 
									
										
										
										
											2021-06-25 12:08:16 -04:00
										 |  |  |   i18n, | 
					
						
							|  |  |  |   isHighQuality, | 
					
						
							|  |  |  |   onSelectQuality, | 
					
						
							| 
									
										
										
										
											2022-11-17 16:45:19 -08:00
										 |  |  | }: PropsType): JSX.Element { | 
					
						
							| 
									
										
										
										
											2021-06-25 12:08:16 -04:00
										 |  |  |   const [menuShowing, setMenuShowing] = useState(false); | 
					
						
							|  |  |  |   const [popperRoot, setPopperRoot] = useState<HTMLElement | null>(null); | 
					
						
							| 
									
										
										
										
											2021-07-07 13:05:03 -04:00
										 |  |  |   const [focusedOption, setFocusedOption] = useState<0 | 1 | undefined>( | 
					
						
							|  |  |  |     undefined | 
					
						
							|  |  |  |   ); | 
					
						
							| 
									
										
										
										
											2021-06-25 12:08:16 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-15 10:53:08 -07:00
										 |  |  |   const buttonRef = React.useRef<HTMLButtonElement | null>(null); | 
					
						
							|  |  |  |   const refMerger = useRefMerger(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const handleClick = () => { | 
					
						
							| 
									
										
										
										
											2021-06-25 12:08:16 -04:00
										 |  |  |     setMenuShowing(true); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-07 13:05:03 -04:00
										 |  |  |   const handleKeyDown = (ev: KeyboardEvent) => { | 
					
						
							|  |  |  |     if (!popperRoot) { | 
					
						
							|  |  |  |       if (ev.key === 'Enter') { | 
					
						
							|  |  |  |         setFocusedOption(isHighQuality ? 1 : 0); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (ev.key === 'ArrowDown' || ev.key === 'ArrowUp') { | 
					
						
							|  |  |  |       setFocusedOption(oldFocusedOption => (oldFocusedOption === 1 ? 0 : 1)); | 
					
						
							|  |  |  |       ev.stopPropagation(); | 
					
						
							|  |  |  |       ev.preventDefault(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (ev.key === 'Enter') { | 
					
						
							|  |  |  |       onSelectQuality(Boolean(focusedOption)); | 
					
						
							|  |  |  |       setMenuShowing(false); | 
					
						
							|  |  |  |       ev.stopPropagation(); | 
					
						
							|  |  |  |       ev.preventDefault(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-25 12:08:16 -04:00
										 |  |  |   const handleClose = useCallback(() => { | 
					
						
							|  |  |  |     setMenuShowing(false); | 
					
						
							| 
									
										
										
										
											2021-07-07 13:05:03 -04:00
										 |  |  |     setFocusedOption(undefined); | 
					
						
							| 
									
										
										
										
											2021-06-25 12:08:16 -04:00
										 |  |  |   }, [setMenuShowing]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   useEffect(() => { | 
					
						
							|  |  |  |     if (menuShowing) { | 
					
						
							|  |  |  |       const root = document.createElement('div'); | 
					
						
							|  |  |  |       setPopperRoot(root); | 
					
						
							|  |  |  |       document.body.appendChild(root); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return () => { | 
					
						
							|  |  |  |         document.body.removeChild(root); | 
					
						
							|  |  |  |         setPopperRoot(null); | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return noop; | 
					
						
							|  |  |  |   }, [menuShowing, setPopperRoot, handleClose]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-14 18:58:35 -07:00
										 |  |  |   useEffect(() => { | 
					
						
							|  |  |  |     if (!menuShowing) { | 
					
						
							|  |  |  |       return noop; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return handleOutsideClick( | 
					
						
							|  |  |  |       () => { | 
					
						
							|  |  |  |         handleClose(); | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2022-09-27 13:24:21 -07:00
										 |  |  |       { | 
					
						
							|  |  |  |         containerElements: [popperRoot, buttonRef], | 
					
						
							|  |  |  |         name: 'MediaQualitySelector', | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2022-09-14 18:58:35 -07:00
										 |  |  |     ); | 
					
						
							|  |  |  |   }, [menuShowing, popperRoot, handleClose]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-25 12:08:16 -04:00
										 |  |  |   return ( | 
					
						
							|  |  |  |     <Manager> | 
					
						
							|  |  |  |       <Reference> | 
					
						
							|  |  |  |         {({ ref }) => ( | 
					
						
							|  |  |  |           <button | 
					
						
							|  |  |  |             aria-label={i18n('MediaQualitySelector--button')} | 
					
						
							|  |  |  |             className={classNames({ | 
					
						
							|  |  |  |               MediaQualitySelector__button: true, | 
					
						
							|  |  |  |               'MediaQualitySelector__button--hq': isHighQuality, | 
					
						
							|  |  |  |               'MediaQualitySelector__button--active': menuShowing, | 
					
						
							|  |  |  |             })} | 
					
						
							|  |  |  |             onClick={handleClick} | 
					
						
							| 
									
										
										
										
											2021-07-07 13:05:03 -04:00
										 |  |  |             onKeyDown={handleKeyDown} | 
					
						
							| 
									
										
										
										
											2022-06-15 10:53:08 -07:00
										 |  |  |             ref={refMerger(buttonRef, ref)} | 
					
						
							| 
									
										
										
										
											2021-06-25 12:08:16 -04:00
										 |  |  |             type="button" | 
					
						
							|  |  |  |           /> | 
					
						
							|  |  |  |         )} | 
					
						
							|  |  |  |       </Reference> | 
					
						
							|  |  |  |       {menuShowing && popperRoot | 
					
						
							|  |  |  |         ? createPortal( | 
					
						
							| 
									
										
										
										
											2021-08-03 12:04:49 -05:00
										 |  |  |             <Popper placement="top-start" strategy="fixed"> | 
					
						
							| 
									
										
										
										
											2021-06-25 12:08:16 -04:00
										 |  |  |               {({ ref, style, placement }) => ( | 
					
						
							|  |  |  |                 <div | 
					
						
							|  |  |  |                   className="MediaQualitySelector__popper" | 
					
						
							|  |  |  |                   data-placement={placement} | 
					
						
							|  |  |  |                   ref={ref} | 
					
						
							|  |  |  |                   style={style} | 
					
						
							|  |  |  |                 > | 
					
						
							|  |  |  |                   <div className="MediaQualitySelector__title"> | 
					
						
							|  |  |  |                     {i18n('MediaQualitySelector--title')} | 
					
						
							|  |  |  |                   </div> | 
					
						
							|  |  |  |                   <button | 
					
						
							|  |  |  |                     aria-label={i18n( | 
					
						
							|  |  |  |                       'MediaQualitySelector--standard-quality-title' | 
					
						
							|  |  |  |                     )} | 
					
						
							| 
									
										
										
										
											2021-07-07 13:05:03 -04:00
										 |  |  |                     className={classNames({ | 
					
						
							|  |  |  |                       MediaQualitySelector__option: true, | 
					
						
							|  |  |  |                       'MediaQualitySelector__option--focused': | 
					
						
							|  |  |  |                         focusedOption === 0, | 
					
						
							|  |  |  |                     })} | 
					
						
							| 
									
										
										
										
											2021-06-25 12:08:16 -04:00
										 |  |  |                     type="button" | 
					
						
							|  |  |  |                     onClick={() => { | 
					
						
							|  |  |  |                       onSelectQuality(false); | 
					
						
							|  |  |  |                       setMenuShowing(false); | 
					
						
							|  |  |  |                     }} | 
					
						
							|  |  |  |                   > | 
					
						
							|  |  |  |                     <div | 
					
						
							|  |  |  |                       className={classNames({ | 
					
						
							|  |  |  |                         'MediaQualitySelector__option--checkmark': true, | 
					
						
							| 
									
										
										
										
											2021-11-11 16:43:05 -06:00
										 |  |  |                         'MediaQualitySelector__option--selected': | 
					
						
							|  |  |  |                           !isHighQuality, | 
					
						
							| 
									
										
										
										
											2021-06-25 12:08:16 -04:00
										 |  |  |                       })} | 
					
						
							|  |  |  |                     /> | 
					
						
							|  |  |  |                     <div> | 
					
						
							|  |  |  |                       <div className="MediaQualitySelector__option--title"> | 
					
						
							|  |  |  |                         {i18n('MediaQualitySelector--standard-quality-title')} | 
					
						
							|  |  |  |                       </div> | 
					
						
							|  |  |  |                       <div className="MediaQualitySelector__option--description"> | 
					
						
							|  |  |  |                         {i18n( | 
					
						
							|  |  |  |                           'MediaQualitySelector--standard-quality-description' | 
					
						
							|  |  |  |                         )} | 
					
						
							|  |  |  |                       </div> | 
					
						
							|  |  |  |                     </div> | 
					
						
							|  |  |  |                   </button> | 
					
						
							|  |  |  |                   <button | 
					
						
							|  |  |  |                     aria-label={i18n( | 
					
						
							|  |  |  |                       'MediaQualitySelector--high-quality-title' | 
					
						
							|  |  |  |                     )} | 
					
						
							| 
									
										
										
										
											2021-07-07 13:05:03 -04:00
										 |  |  |                     className={classNames({ | 
					
						
							|  |  |  |                       MediaQualitySelector__option: true, | 
					
						
							|  |  |  |                       'MediaQualitySelector__option--focused': | 
					
						
							|  |  |  |                         focusedOption === 1, | 
					
						
							|  |  |  |                     })} | 
					
						
							| 
									
										
										
										
											2021-06-25 12:08:16 -04:00
										 |  |  |                     type="button" | 
					
						
							|  |  |  |                     onClick={() => { | 
					
						
							|  |  |  |                       onSelectQuality(true); | 
					
						
							|  |  |  |                       setMenuShowing(false); | 
					
						
							|  |  |  |                     }} | 
					
						
							|  |  |  |                   > | 
					
						
							|  |  |  |                     <div | 
					
						
							|  |  |  |                       className={classNames({ | 
					
						
							|  |  |  |                         'MediaQualitySelector__option--checkmark': true, | 
					
						
							|  |  |  |                         'MediaQualitySelector__option--selected': isHighQuality, | 
					
						
							|  |  |  |                       })} | 
					
						
							|  |  |  |                     /> | 
					
						
							|  |  |  |                     <div> | 
					
						
							|  |  |  |                       <div className="MediaQualitySelector__option--title"> | 
					
						
							|  |  |  |                         {i18n('MediaQualitySelector--high-quality-title')} | 
					
						
							|  |  |  |                       </div> | 
					
						
							|  |  |  |                       <div className="MediaQualitySelector__option--description"> | 
					
						
							|  |  |  |                         {i18n('MediaQualitySelector--high-quality-description')} | 
					
						
							|  |  |  |                       </div> | 
					
						
							|  |  |  |                     </div> | 
					
						
							|  |  |  |                   </button> | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |               )} | 
					
						
							|  |  |  |             </Popper>, | 
					
						
							|  |  |  |             popperRoot | 
					
						
							|  |  |  |           ) | 
					
						
							|  |  |  |         : null} | 
					
						
							|  |  |  |     </Manager> | 
					
						
							|  |  |  |   ); | 
					
						
							| 
									
										
										
										
											2022-11-17 16:45:19 -08:00
										 |  |  | } |