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 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  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 states = {};
  93. states[States.UNLOCKED] = {
  94. title: titleString,
  95. html: this.getShareLinkBlock() + this.getAddPasswordBlock()
  96. };
  97. states[States.LOCKED] = {
  98. title: titleString,
  99. html: this.getShareLinkBlock() + this.getPasswordBlock()
  100. };
  101. return states;
  102. }
  103. /**
  104. * Layout for invite link input
  105. * @returns {string}
  106. */
  107. getShareLinkBlock() {
  108. let copyKey = 'dialog.copy';
  109. let copyText = APP.translation.translateString(copyKey);
  110. let roomLockDescKey = 'roomLocked';
  111. let roomLockDesc = APP.translation.translateString(roomLockDescKey);
  112. let roomUnlockKey = 'roomUnlocked';
  113. let roomUnlock = APP.translation.translateString(roomUnlockKey);
  114. let classes = 'button-control button-control_light copyInviteLink';
  115. return (
  116. `<div class="input-control">
  117. <label class="input-control__label" for="inviteLinkRef">
  118. ${this.dialog.titleString}
  119. </label>
  120. <div class="input-control__container">
  121. <input class="input-control__input inviteLink"
  122. id="inviteLinkRef" type="text"
  123. ${this.inviteAttributes} readonly>
  124. <button data-i18n="${copyKey}"
  125. class="${classes}">
  126. ${copyText}
  127. </button>
  128. </div>
  129. <p class="input-control__hint ${this.lockHint}">
  130. <span class="icon-security-locked"></span>
  131. <span data-i18n="${roomLockDescKey}">${roomLockDesc}</span>
  132. </p>
  133. <p class="input-control__hint ${this.unlockHint}">
  134. <span class="icon-security"></span>
  135. <span data-i18n="${roomUnlockKey}">${roomUnlock}</span>
  136. </p>
  137. </div>`
  138. );
  139. }
  140. /**
  141. * Layout for adding password input
  142. * @returns {string}
  143. */
  144. getAddPasswordBlock() {
  145. let addPassKey = 'dialog.addPassword';
  146. let addPassText = APP.translation.translateString(addPassKey);
  147. let addKey = 'dialog.add';
  148. let addText = APP.translation.translateString(addKey);
  149. let html;
  150. if (this.model.isModerator) {
  151. html = (`
  152. <div class="input-control">
  153. <label class="input-control__label
  154. for="newPasswordInput"
  155. data-i18n="${addPassKey}">${addPassText}</label>
  156. <div class="input-control__container">
  157. <input class="input-control__input" id="newPasswordInput"
  158. type="text">
  159. <button id="addPasswordBtn" id="inviteDialogAddPassword"
  160. disabled data-i18n="${addKey}"
  161. class="button-control button-control_light">
  162. ${addText}
  163. </button>
  164. </div>
  165. </div>
  166. `);
  167. } else {
  168. html = '';
  169. }
  170. return html;
  171. }
  172. /**
  173. * Layout for password (when room is locked)
  174. * @returns {string}
  175. */
  176. getPasswordBlock() {
  177. let { password, isModerator } = this.model;
  178. let removePassKey = 'dialog.removePassword';
  179. let removePassText = APP.translation.translateString(removePassKey);
  180. let currentPassKey = 'dialog.currentPassword';
  181. let currentPassText = APP.translation.translateString(currentPassKey);
  182. let passwordKey = "dialog.passwordLabel";
  183. let passwordText = APP.translation.translateString(passwordKey);
  184. if (isModerator) {
  185. return (`
  186. <div class="input-control">
  187. <label class="input-control__label"
  188. data-i18n="${passwordKey}">${passwordText}</label>
  189. <div class="input-control__container">
  190. <p class="input-control__text"
  191. data-i18n="${currentPassKey}">
  192. ${currentPassText}
  193. <span id="inviteDialogPassword"
  194. class="input-control__em">
  195. ${password}
  196. </span>
  197. </p>
  198. <a class="link input-control__right"
  199. id="inviteDialogRemovePassword"
  200. href="#" data-i18n="${removePassKey}">
  201. ${removePassText}
  202. </a>
  203. </div>
  204. </div>
  205. `);
  206. } else {
  207. return (`
  208. <div class="input-control">
  209. <p>A participant protected this call with a password.</p>
  210. </div>
  211. `);
  212. }
  213. }
  214. /**
  215. * Opening the dialog
  216. */
  217. open() {
  218. let leftButton;
  219. let {
  220. submitFunction,
  221. loadedFunction,
  222. closeFunction
  223. } = this.dialog;
  224. let states = this.getStates();
  225. let buttons = [];
  226. let leftButtonKey = "dialog.Invite";
  227. let cancelButton
  228. = APP.translation.generateTranslationHTML("dialog.Cancel");
  229. buttons.push({title: cancelButton, value: false});
  230. leftButton = APP.translation.generateTranslationHTML(leftButtonKey);
  231. buttons.push({ title: leftButton, value: true});
  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. buttons,
  238. size: 'medium'
  239. });
  240. $.prompt.goToState(initial);
  241. this.registerListeners();
  242. this.updateView();
  243. }
  244. /**
  245. * Setting event handlers
  246. * used in dialog
  247. */
  248. registerListeners() {
  249. let $passInput = $('#newPasswordInput');
  250. let $addPassBtn = $('#addPasswordBtn');
  251. $(document).on('click', '.copyInviteLink', this.copyToClipboard);
  252. $addPassBtn.on('click', () => {
  253. let newPass = $passInput.val();
  254. if(newPass) {
  255. this.model.setRoomLocked(newPass);
  256. }
  257. });
  258. $('#inviteDialogRemovePassword').on('click', () => {
  259. this.model.setRoomUnlocked();
  260. });
  261. $passInput.keyup(this.disableAddPassIfInputEmpty.bind(this));
  262. }
  263. /**
  264. * Checking input and if it's empty then
  265. * disable add pass button
  266. */
  267. disableAddPassIfInputEmpty() {
  268. let $passInput = $('#newPasswordInput');
  269. let $addPassBtn = $('#addPasswordBtn');
  270. if(!$passInput.val()) {
  271. $addPassBtn.prop('disabled', true);
  272. } else {
  273. $addPassBtn.prop('disabled', false);
  274. }
  275. }
  276. /**
  277. * Copying text to clipboard
  278. */
  279. copyToClipboard() {
  280. $('.inviteLink').each(function () {
  281. let $el = $(this).closest('.jqistate');
  282. // TOFIX: We can select only visible elements
  283. if($el.css('display') === 'block') {
  284. this.select();
  285. try {
  286. document.execCommand('copy');
  287. this.blur();
  288. }
  289. catch (err) {
  290. console.error('error when copy the text');
  291. }
  292. }
  293. });
  294. }
  295. /**
  296. * Method syncing the view and the model
  297. */
  298. updateView() {
  299. let pass = this.model.getPassword();
  300. if (!pass)
  301. pass = APP.translation.translateString("passwordSetRemotely");
  302. $('#inviteDialogPassword').text(pass);
  303. $('#newPasswordInput').val('');
  304. this.disableAddPassIfInputEmpty();
  305. this.updateInviteLink();
  306. $.prompt.goToState(
  307. (this.model.isLocked())
  308. ? States.LOCKED
  309. : States.UNLOCKED);
  310. let roomLocked = `.${this.lockHint}`;
  311. let roomUnlocked = `.${this.unlockHint}`;
  312. let showDesc = this.model.isLocked() ? roomLocked : roomUnlocked;
  313. let hideDesc = !this.model.isLocked() ? roomLocked : roomUnlocked;
  314. $(showDesc).show();
  315. $(hideDesc).hide();
  316. }
  317. /**
  318. * Updates invite link
  319. */
  320. updateInviteLink() {
  321. // If the invite dialog has been already opened we update the
  322. // information.
  323. let inviteLink = document.querySelectorAll('.inviteLink');
  324. let list = Array.from(inviteLink);
  325. list.forEach((inviteLink) => {
  326. inviteLink.value = this.model.inviteUrl;
  327. inviteLink.select();
  328. });
  329. $('#inviteLinkRef').parent()
  330. .find('button[value=true]').prop('disabled', false);
  331. }
  332. }