You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PasswordSection.js 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. // @flow
  2. /* eslint-disable react/no-multi-comp, react/jsx-no-bind */
  3. import React, { useRef } from 'react';
  4. import { translate } from '../../../../base/i18n';
  5. import { copyText } from '../../../../base/util';
  6. import { NOTIFY_CLICK_MODE } from '../../../../toolbox/constants';
  7. import PasswordForm from './PasswordForm';
  8. const DIGITS_ONLY = /^\d+$/;
  9. const KEY = 'add-passcode';
  10. type Props = {
  11. /**
  12. * Toolbar buttons which have their click exposed through the API.
  13. */
  14. buttonsWithNotifyClick: Array<string | Object>,
  15. /**
  16. * Whether or not the current user can modify the current password.
  17. */
  18. canEditPassword: boolean,
  19. /**
  20. * The JitsiConference for which to display a lock state and change the
  21. * password.
  22. */
  23. conference: Object,
  24. /**
  25. * The value for how the conference is locked (or undefined if not locked)
  26. * as defined by room-lock constants.
  27. */
  28. locked: string,
  29. /**
  30. * The current known password for the JitsiConference.
  31. */
  32. password: string,
  33. /**
  34. * Whether or not to show the password in editing mode.
  35. */
  36. passwordEditEnabled: boolean,
  37. /**
  38. * The number of digits to be used in the password.
  39. */
  40. passwordNumberOfDigits: ?number,
  41. /**
  42. * Action that sets the conference password.
  43. */
  44. setPassword: Function,
  45. /**
  46. * Method that sets whether the password editing is enabled or not.
  47. */
  48. setPasswordEditEnabled: Function,
  49. /**
  50. * Invoked to obtain translated strings.
  51. */
  52. t: Function
  53. };
  54. declare var APP: Object;
  55. /**
  56. * Component that handles the password manipulation from the invite dialog.
  57. *
  58. * @returns {React$Element<any>}
  59. */
  60. function PasswordSection({
  61. buttonsWithNotifyClick,
  62. canEditPassword,
  63. conference,
  64. locked,
  65. password,
  66. passwordEditEnabled,
  67. passwordNumberOfDigits,
  68. setPassword,
  69. setPasswordEditEnabled,
  70. t }: Props) {
  71. const formRef: Object = useRef(null);
  72. /**
  73. * Callback invoked to set a password on the current JitsiConference.
  74. *
  75. * @param {string} enteredPassword - The new password to be used to lock the
  76. * current JitsiConference.
  77. * @private
  78. * @returns {void}
  79. */
  80. function onPasswordSubmit(enteredPassword) {
  81. if (enteredPassword && passwordNumberOfDigits && !DIGITS_ONLY.test(enteredPassword)) {
  82. // Don't set the password.
  83. return;
  84. }
  85. setPassword(conference, conference.lock, enteredPassword);
  86. }
  87. /**
  88. * Toggles whether or not the password should currently be shown as being
  89. * edited locally.
  90. *
  91. * @private
  92. * @returns {void}
  93. */
  94. function onTogglePasswordEditState() {
  95. if (typeof APP === 'undefined' || !buttonsWithNotifyClick?.length) {
  96. setPasswordEditEnabled(!passwordEditEnabled);
  97. return;
  98. }
  99. let notifyMode;
  100. const notify = buttonsWithNotifyClick.find(
  101. (btn: string | Object) =>
  102. (typeof btn === 'string' && btn === KEY)
  103. || (typeof btn === 'object' && btn.key === KEY)
  104. );
  105. if (notify) {
  106. notifyMode = typeof notify === 'string' || notify.preventExecution
  107. ? NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
  108. : NOTIFY_CLICK_MODE.ONLY_NOTIFY;
  109. APP.API.notifyToolbarButtonClicked(
  110. KEY, notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
  111. );
  112. }
  113. if (notifyMode === NOTIFY_CLICK_MODE.ONLY_NOTIFY) {
  114. setPasswordEditEnabled(!passwordEditEnabled);
  115. }
  116. }
  117. /**
  118. * Method to remotely submit the password from outside of the password form.
  119. *
  120. * @returns {void}
  121. */
  122. function onPasswordSave() {
  123. if (formRef.current) {
  124. const { value } = formRef.current.querySelector('div > input');
  125. if (value) {
  126. onPasswordSubmit(value);
  127. }
  128. }
  129. }
  130. /**
  131. * Callback invoked to unlock the current JitsiConference.
  132. *
  133. * @returns {void}
  134. */
  135. function onPasswordRemove() {
  136. onPasswordSubmit('');
  137. }
  138. /**
  139. * Copies the password to the clipboard.
  140. *
  141. * @returns {void}
  142. */
  143. function onPasswordCopy() {
  144. copyText(password);
  145. }
  146. /**
  147. * Toggles whether or not the password should currently be shown as being
  148. * edited locally.
  149. *
  150. * @param {Object} e - The key event to handle.
  151. *
  152. * @private
  153. * @returns {void}
  154. */
  155. function onTogglePasswordEditStateKeyPressHandler(e) {
  156. if (e.key === ' ' || e.key === 'Enter') {
  157. e.preventDefault();
  158. onTogglePasswordEditState();
  159. }
  160. }
  161. /**
  162. * Method to remotely submit the password from outside of the password form.
  163. *
  164. * @param {Object} e - The key event to handle.
  165. *
  166. * @private
  167. * @returns {void}
  168. */
  169. function onPasswordSaveKeyPressHandler(e) {
  170. if (e.key === ' ' || e.key === 'Enter') {
  171. e.preventDefault();
  172. onPasswordSave();
  173. }
  174. }
  175. /**
  176. * Callback invoked to unlock the current JitsiConference.
  177. *
  178. * @param {Object} e - The key event to handle.
  179. *
  180. * @private
  181. * @returns {void}
  182. */
  183. function onPasswordRemoveKeyPressHandler(e) {
  184. if (e.key === ' ' || e.key === 'Enter') {
  185. e.preventDefault();
  186. onPasswordRemove();
  187. }
  188. }
  189. /**
  190. * Copies the password to the clipboard.
  191. *
  192. * @param {Object} e - The key event to handle.
  193. *
  194. * @private
  195. * @returns {void}
  196. */
  197. function onPasswordCopyKeyPressHandler(e) {
  198. if (e.key === ' ' || e.key === 'Enter') {
  199. e.preventDefault();
  200. onPasswordCopy();
  201. }
  202. }
  203. /**
  204. * Method that renders the password action(s) based on the current
  205. * locked-status of the conference.
  206. *
  207. * @returns {React$Element<any>}
  208. */
  209. function renderPasswordActions() {
  210. if (!canEditPassword) {
  211. return null;
  212. }
  213. if (passwordEditEnabled) {
  214. return (
  215. <>
  216. <a
  217. aria-label = { t('dialog.Cancel') }
  218. onClick = { onTogglePasswordEditState }
  219. onKeyPress = { onTogglePasswordEditStateKeyPressHandler }
  220. role = 'button'
  221. tabIndex = { 0 }>{ t('dialog.Cancel') }</a>
  222. <a
  223. aria-label = { t('dialog.add') }
  224. onClick = { onPasswordSave }
  225. onKeyPress = { onPasswordSaveKeyPressHandler }
  226. role = 'button'
  227. tabIndex = { 0 }>{ t('dialog.add') }</a>
  228. </>
  229. );
  230. }
  231. if (locked) {
  232. return (
  233. <>
  234. <a
  235. aria-label = { t('dialog.Remove') }
  236. className = 'remove-password'
  237. onClick = { onPasswordRemove }
  238. onKeyPress = { onPasswordRemoveKeyPressHandler }
  239. role = 'button'
  240. tabIndex = { 0 }>{ t('dialog.Remove') }</a>
  241. {
  242. // There are cases like lobby and grant moderator when password is not available
  243. password ? <>
  244. <a
  245. aria-label = { t('dialog.copy') }
  246. className = 'copy-password'
  247. onClick = { onPasswordCopy }
  248. onKeyPress = { onPasswordCopyKeyPressHandler }
  249. role = 'button'
  250. tabIndex = { 0 }>{ t('dialog.copy') }</a>
  251. </> : null
  252. }
  253. </>
  254. );
  255. }
  256. return (
  257. <a
  258. aria-label = { t('info.addPassword') }
  259. className = 'add-password'
  260. onClick = { onTogglePasswordEditState }
  261. onKeyPress = { onTogglePasswordEditStateKeyPressHandler }
  262. role = 'button'
  263. tabIndex = { 0 }>{ t('info.addPassword') }</a>
  264. );
  265. }
  266. return (
  267. <div className = 'security-dialog password-section'>
  268. <p className = 'description'>
  269. { t(canEditPassword ? 'security.about' : 'security.aboutReadOnly') }
  270. </p>
  271. <div className = 'security-dialog password'>
  272. <div
  273. className = 'info-dialog info-dialog-column info-dialog-password'
  274. ref = { formRef }>
  275. <PasswordForm
  276. editEnabled = { passwordEditEnabled }
  277. locked = { locked }
  278. onSubmit = { onPasswordSubmit }
  279. password = { password }
  280. passwordNumberOfDigits = { passwordNumberOfDigits } />
  281. </div>
  282. <div className = 'security-dialog password-actions'>
  283. { renderPasswordActions() }
  284. </div>
  285. </div>
  286. </div>
  287. );
  288. }
  289. export default translate(PasswordSection);