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.

InviteDialogView.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. /* global $, APP, JitsiMeetJS */
  2. /**
  3. * Substate for password
  4. * @type {{LOCKED: string, UNLOCKED: string}}
  5. */
  6. const States = {
  7. LOCKED: 'locked',
  8. UNLOCKED: 'unlocked'
  9. };
  10. /**
  11. * Class representing view for Invite dialog
  12. * @class InviteDialogView
  13. */
  14. export default class InviteDialogView {
  15. constructor(model) {
  16. let InviteAttributesKey = 'inviteUrlDefaultMsg';
  17. let title = APP.translation.translateString(InviteAttributesKey);
  18. this.unlockHint = "unlockHint";
  19. this.lockHint = "lockHint";
  20. this.model = model;
  21. if (this.model.inviteUrl === null) {
  22. this.inviteAttributes = (
  23. `data-i18n="[value]inviteUrlDefaultMsg" value="${title}"`
  24. );
  25. } else {
  26. let encodedInviteUrl = this.model.getEncodedInviteUrl();
  27. this.inviteAttributes = `value="${encodedInviteUrl}"`;
  28. }
  29. this.initDialog();
  30. }
  31. /**
  32. * Initialization of dialog property
  33. */
  34. initDialog() {
  35. let dialog = {};
  36. dialog.closeFunction = this.closeFunction.bind(this);
  37. dialog.submitFunction = this.submitFunction.bind(this);
  38. dialog.loadedFunction = this.loadedFunction.bind(this);
  39. let titleKey = "dialog.shareLink";
  40. let titleString = APP.translation.generateTranslationHTML(titleKey);
  41. dialog.titleKey = titleKey;
  42. dialog.titleString = titleString;
  43. this.dialog = dialog;
  44. this.dialog.states = this.getStates();
  45. }
  46. /**
  47. * Event handler for submitting dialog
  48. * @param e
  49. * @param v
  50. */
  51. submitFunction(e, v) {
  52. if (v && this.model.inviteUrl) {
  53. JitsiMeetJS.analytics.sendEvent('toolbar.invite.button');
  54. } else {
  55. JitsiMeetJS.analytics.sendEvent('toolbar.invite.cancel');
  56. }
  57. }
  58. /**
  59. * Event handler for load dialog
  60. * @param event
  61. */
  62. loadedFunction(event) {
  63. if (this.model.inviteUrl) {
  64. document.getElementById('inviteLinkRef').select();
  65. } else {
  66. if (event && event.target) {
  67. $(event.target).find('button[value=true]')
  68. .prop('disabled', true);
  69. }
  70. }
  71. }
  72. /**
  73. * Event handler for closing dialog
  74. * @param e
  75. * @param v
  76. * @param m
  77. * @param f
  78. */
  79. closeFunction(e, v, m, f) {
  80. $(document).off('click', '.copyInviteLink', this.copyToClipboard);
  81. if(!v && !m && !f)
  82. JitsiMeetJS.analytics.sendEvent('toolbar.invite.close');
  83. }
  84. /**
  85. * Returns all states of the dialog
  86. * @returns {{}}
  87. */
  88. getStates() {
  89. let {
  90. titleString
  91. } = this.dialog;
  92. let doneKey = 'dialog.done';
  93. let doneMsg = APP.translation.translateString(doneKey);
  94. let states = {};
  95. let buttons = {};
  96. buttons[`${doneMsg}`] = true;
  97. states[States.UNLOCKED] = {
  98. title: titleString,
  99. html: this.getShareLinkBlock() + this.getAddPasswordBlock(),
  100. buttons
  101. };
  102. states[States.LOCKED] = {
  103. title: titleString,
  104. html: this.getShareLinkBlock() + this.getPasswordBlock(),
  105. buttons
  106. };
  107. return states;
  108. }
  109. /**
  110. * Layout for invite link input
  111. * @returns {string}
  112. */
  113. getShareLinkBlock() {
  114. let copyKey = 'dialog.copy';
  115. let copyText = APP.translation.translateString(copyKey);
  116. let roomLockDescKey = 'dialog.roomLocked';
  117. let roomLockDesc = APP.translation.translateString(roomLockDescKey);
  118. let roomUnlockKey = 'roomUnlocked';
  119. let roomUnlock = APP.translation.translateString(roomUnlockKey);
  120. let classes = 'button-control button-control_light copyInviteLink';
  121. return (
  122. `<div class="input-control">
  123. <label class="input-control__label" for="inviteLinkRef">
  124. ${this.dialog.titleString}
  125. </label>
  126. <div class="input-control__container">
  127. <input class="input-control__input inviteLink"
  128. id="inviteLinkRef" type="text"
  129. ${this.inviteAttributes} readonly>
  130. <button data-i18n="${copyKey}"
  131. class="${classes}">
  132. ${copyText}
  133. </button>
  134. </div>
  135. <p class="input-control__hint ${this.lockHint}">
  136. <span class="icon-security-locked"></span>
  137. <span data-i18n="${roomLockDescKey}">${roomLockDesc}</span>
  138. </p>
  139. <p class="input-control__hint ${this.unlockHint}">
  140. <span class="icon-security"></span>
  141. <span data-i18n="${roomUnlockKey}">${roomUnlock}</span>
  142. </p>
  143. </div>`
  144. );
  145. }
  146. /**
  147. * Layout for adding password input
  148. * @returns {string}
  149. */
  150. getAddPasswordBlock() {
  151. let addPassKey = 'dialog.addPassword';
  152. let addPassText = APP.translation.translateString(addPassKey);
  153. let addKey = 'dialog.add';
  154. let addText = APP.translation.translateString(addKey);
  155. let hintKey = 'dialog.createPassword';
  156. let hintMsg = APP.translation.translateString(hintKey);
  157. let html;
  158. if (this.model.isModerator) {
  159. html = (`
  160. <div class="input-control">
  161. <label class="input-control__label
  162. for="newPasswordInput"
  163. data-i18n="${addPassKey}">${addPassText}</label>
  164. <div class="input-control__container">
  165. <input class="input-control__input" id="newPasswordInput"
  166. type="text" placeholder="${hintMsg}">
  167. <button id="addPasswordBtn" id="inviteDialogAddPassword"
  168. disabled data-i18n="${addKey}"
  169. class="button-control button-control_light">
  170. ${addText}
  171. </button>
  172. </div>
  173. </div>
  174. `);
  175. } else {
  176. html = '';
  177. }
  178. return html;
  179. }
  180. /**
  181. * Layout for password (when room is locked)
  182. * @returns {string}
  183. */
  184. getPasswordBlock() {
  185. let { password, isModerator } = this.model;
  186. let removePassKey = 'dialog.removePassword';
  187. let removePassText = APP.translation.translateString(removePassKey);
  188. let currentPassKey = 'dialog.currentPassword';
  189. let currentPassText = APP.translation.translateString(currentPassKey);
  190. let passwordKey = "dialog.passwordLabel";
  191. let passwordText = APP.translation.translateString(passwordKey);
  192. if (isModerator) {
  193. return (`
  194. <div class="input-control">
  195. <label class="input-control__label"
  196. data-i18n="${passwordKey}">${passwordText}</label>
  197. <div class="input-control__container">
  198. <p class="input-control__text"
  199. data-i18n="${currentPassKey}">
  200. ${currentPassText}
  201. <span id="inviteDialogPassword"
  202. class="input-control__em">
  203. ${password}
  204. </span>
  205. </p>
  206. <a class="link input-control__right"
  207. id="inviteDialogRemovePassword"
  208. data-i18n="${removePassKey}">
  209. ${removePassText}
  210. </a>
  211. </div>
  212. </div>
  213. `);
  214. } else {
  215. return (`
  216. <div class="input-control">
  217. <p>A participant protected this call with a password.</p>
  218. </div>
  219. `);
  220. }
  221. }
  222. /**
  223. * Opening the dialog
  224. */
  225. open() {
  226. let {
  227. submitFunction,
  228. loadedFunction,
  229. closeFunction
  230. } = this.dialog;
  231. let states = this.getStates();
  232. let initial = this.model.roomLocked ? States.LOCKED : States.UNLOCKED;
  233. APP.UI.messageHandler.openDialogWithStates(states, {
  234. submit: submitFunction,
  235. loaded: loadedFunction,
  236. close: closeFunction,
  237. size: 'medium'
  238. });
  239. $.prompt.goToState(initial);
  240. this.registerListeners();
  241. this.updateView();
  242. }
  243. /**
  244. * Setting event handlers
  245. * used in dialog
  246. */
  247. registerListeners() {
  248. const ENTER_KEY = 13;
  249. let addPasswordBtn = '#addPasswordBtn';
  250. let copyInviteLink = '.copyInviteLink';
  251. let newPasswordInput = '#newPasswordInput';
  252. let removePassword = '#inviteDialogRemovePassword';
  253. $(document).on('click', copyInviteLink, this.copyToClipboard);
  254. $(removePassword).on('click', () => {
  255. this.model.setRoomUnlocked();
  256. });
  257. let boundSetPassword = this.setPassword.bind(this);
  258. $(document).on('click', addPasswordBtn, boundSetPassword);
  259. let boundDisablePass = this.disableAddPassIfInputEmpty.bind(this);
  260. $(document).on('keypress', newPasswordInput, boundDisablePass);
  261. // We need to handle keydown event because impromptu
  262. // is listening to it too for closing the dialog
  263. $(newPasswordInput).on('keydown', (e) => {
  264. if (e.keyCode === ENTER_KEY) {
  265. e.stopPropagation();
  266. this.setPassword();
  267. }
  268. });
  269. }
  270. setPassword() {
  271. let $passInput = $('#newPasswordInput');
  272. let newPass = $passInput.val();
  273. if(newPass) {
  274. this.model.setRoomLocked(newPass);
  275. }
  276. }
  277. /**
  278. * Checking input and if it's empty then
  279. * disable add pass button
  280. */
  281. disableAddPassIfInputEmpty() {
  282. let $passInput = $('#newPasswordInput');
  283. let $addPassBtn = $('#addPasswordBtn');
  284. if(!$passInput.val()) {
  285. $addPassBtn.prop('disabled', true);
  286. } else {
  287. $addPassBtn.prop('disabled', false);
  288. }
  289. }
  290. /**
  291. * Copying text to clipboard
  292. */
  293. copyToClipboard() {
  294. $('.inviteLink').each(function () {
  295. let $el = $(this).closest('.jqistate');
  296. // TOFIX: We can select only visible elements
  297. if($el.css('display') === 'block') {
  298. this.select();
  299. try {
  300. document.execCommand('copy');
  301. this.blur();
  302. }
  303. catch (err) {
  304. console.error('error when copy the text');
  305. }
  306. }
  307. });
  308. }
  309. /**
  310. * Method syncing the view and the model
  311. */
  312. updateView() {
  313. let pass = this.model.getPassword();
  314. if (!pass)
  315. pass = APP.translation.translateString("passwordSetRemotely");
  316. $('#inviteDialogPassword').text(pass);
  317. $('#newPasswordInput').val('');
  318. this.disableAddPassIfInputEmpty();
  319. this.updateInviteLink();
  320. $.prompt.goToState(
  321. (this.model.isLocked())
  322. ? States.LOCKED
  323. : States.UNLOCKED);
  324. let roomLocked = `.${this.lockHint}`;
  325. let roomUnlocked = `.${this.unlockHint}`;
  326. let showDesc = this.model.isLocked() ? roomLocked : roomUnlocked;
  327. let hideDesc = !this.model.isLocked() ? roomLocked : roomUnlocked;
  328. $(showDesc).show();
  329. $(hideDesc).hide();
  330. }
  331. /**
  332. * Updates invite link
  333. */
  334. updateInviteLink() {
  335. // If the invite dialog has been already opened we update the
  336. // information.
  337. let inviteLink = document.querySelectorAll('.inviteLink');
  338. let list = Array.from(inviteLink);
  339. list.forEach((inviteLink) => {
  340. inviteLink.value = this.model.inviteUrl;
  341. inviteLink.select();
  342. });
  343. $('#inviteLinkRef').parent()
  344. .find('button[value=true]').prop('disabled', false);
  345. }
  346. }