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.

MessageHandler.js 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. /* global $, APP, toastr */
  2. const logger = require("jitsi-meet-logger").getLogger(__filename);
  3. import UIUtil from './UIUtil';
  4. import jitsiLocalStorage from '../../util/JitsiLocalStorage';
  5. /**
  6. * Flag for enable/disable of the notifications.
  7. * @type {boolean}
  8. */
  9. let notificationsEnabled = true;
  10. /**
  11. * Flag for enabling/disabling popups.
  12. * @type {boolean}
  13. */
  14. let popupEnabled = true;
  15. /**
  16. * Currently displayed two button dialog.
  17. * @type {null}
  18. */
  19. let twoButtonDialog = null;
  20. /**
  21. * Generates html for dont show again checkbox.
  22. * @param {object} options options
  23. * @param {string} options.id the id of the checkbox.
  24. * @param {string} options.textKey the key for the text displayed next to
  25. * checkbox
  26. * @param {boolean} options.checked if true the checkbox is foing to be checked
  27. * by default.
  28. * @returns {string}
  29. */
  30. function generateDontShowCheckbox(options) {
  31. if(!isDontShowAgainEnabled(options)) {
  32. return "";
  33. }
  34. let checked
  35. = (options.checked === true) ? "checked" : "";
  36. return `<br />
  37. <label>
  38. <input type='checkbox' ${checked} id='${options.id}' />
  39. <span data-i18n='${options.textKey}'></span>
  40. </label>`;
  41. }
  42. /**
  43. * Checks whether the dont show again checkbox was checked before.
  44. * @param {object} options - options for dont show again checkbox.
  45. * @param {string} options.id the id of the checkbox.
  46. * @param {string} options.localStorageKey the key for the local storage. if
  47. * not provided options.id will be used.
  48. * @returns {boolean} true if the dialog mustn't be displayed and
  49. * false otherwise.
  50. */
  51. function dontShowTheDialog(options) {
  52. if(isDontShowAgainEnabled(options)) {
  53. if(jitsiLocalStorage.getItem(options.localStorageKey || options.id)
  54. === "true") {
  55. return true;
  56. }
  57. }
  58. return false;
  59. }
  60. /**
  61. * Wraps the submit function to process the dont show again status and store
  62. * it.
  63. * @param {object} options - options for dont show again checkbox.
  64. * @param {string} options.id the id of the checkbox.
  65. * @param {Array} options.buttonValues The button values that will trigger
  66. * storing he checkbox value
  67. * @param {string} options.localStorageKey the key for the local storage. if
  68. * not provided options.id will be used.
  69. * @param {Function} submitFunction the submit function to be wrapped
  70. * @returns {Function} wrapped function
  71. */
  72. function dontShowAgainSubmitFunctionWrapper(options, submitFunction) {
  73. if(isDontShowAgainEnabled(options)) {
  74. return (...args) => {
  75. logger.debug(args, options.buttonValues);
  76. //args[1] is the value associated with the pressed button
  77. if(!options.buttonValues || options.buttonValues.length === 0
  78. || options.buttonValues.indexOf(args[1]) !== -1 ) {
  79. let checkbox = $(`#${options.id}`);
  80. if (checkbox.length) {
  81. jitsiLocalStorage.setItem(
  82. options.localStorageKey || options.id,
  83. checkbox.prop("checked"));
  84. }
  85. }
  86. submitFunction(...args);
  87. };
  88. } else {
  89. return submitFunction;
  90. }
  91. }
  92. /**
  93. * Check whether dont show again checkbox is enabled or not.
  94. * @param {object} options - options for dont show again checkbox.
  95. * @returns {boolean} true if enabled and false if not.
  96. */
  97. function isDontShowAgainEnabled(options) {
  98. return typeof options === "object";
  99. }
  100. var messageHandler = {
  101. OK: "dialog.OK",
  102. CANCEL: "dialog.Cancel",
  103. /**
  104. * Shows a message to the user.
  105. *
  106. * @param titleKey the key used to find the translation of the title of the
  107. * message, if a message title is not provided.
  108. * @param messageKey the key used to find the translation of the message
  109. * @param i18nOptions the i18n options (optional)
  110. * @param closeFunction function to be called after
  111. * the prompt is closed (optional)
  112. * @return the prompt that was created, or null
  113. */
  114. openMessageDialog:
  115. function(titleKey, messageKey, i18nOptions, closeFunction) {
  116. if (!popupEnabled)
  117. return null;
  118. let dialog = $.prompt(
  119. APP.translation.generateTranslationHTML(messageKey, i18nOptions),
  120. {
  121. title: this._getFormattedTitleString(titleKey),
  122. persistent: false,
  123. promptspeed: 0,
  124. classes: this._getDialogClasses(),
  125. close: function (e, v, m, f) {
  126. if(closeFunction)
  127. closeFunction(e, v, m, f);
  128. }
  129. });
  130. APP.translation.translateElement(dialog, i18nOptions);
  131. return $.prompt.getApi();
  132. },
  133. /**
  134. * Shows a message to the user with two buttons: first is given as a
  135. * parameter and the second is Cancel.
  136. *
  137. * @param titleKey the key for the title of the message
  138. * @param msgKey the key for the message
  139. * @param msgString the text of the message
  140. * @param persistent boolean value which determines whether the message is
  141. * persistent or not
  142. * @param leftButton the fist button's text
  143. * @param submitFunction function to be called on submit
  144. * @param loadedFunction function to be called after the prompt is fully
  145. * loaded
  146. * @param closeFunction function to be called after the prompt is closed
  147. * @param focus optional focus selector or button index to be focused after
  148. * the dialog is opened
  149. * @param defaultButton index of default button which will be activated when
  150. * the user press 'enter'. Indexed from 0.
  151. * @param {object} dontShowAgain - options for dont show again checkbox.
  152. * @param {string} dontShowAgain.id the id of the checkbox.
  153. * @param {string} dontShowAgain.textKey the key for the text displayed
  154. * next to checkbox
  155. * @param {boolean} dontShowAgain.checked if true the checkbox is foing to
  156. * be checked
  157. * @param {Array} dontShowAgain.buttonValues The button values that will
  158. * trigger storing the checkbox value
  159. * @param {string} dontShowAgain.localStorageKey the key for the local
  160. * storage. if not provided dontShowAgain.id will be used.
  161. * @return the prompt that was created, or null
  162. */
  163. openTwoButtonDialog: function(options) {
  164. let {
  165. titleKey,
  166. msgKey,
  167. msgString,
  168. leftButtonKey,
  169. submitFunction,
  170. loadedFunction,
  171. closeFunction,
  172. focus,
  173. size,
  174. defaultButton,
  175. wrapperClass,
  176. classes,
  177. dontShowAgain
  178. } = options;
  179. if (!popupEnabled || twoButtonDialog)
  180. return null;
  181. if(dontShowTheDialog(dontShowAgain)) {
  182. // Maybe we should pass some parameters here? I'm not sure
  183. // and currently we don't need any parameters.
  184. submitFunction();
  185. return null;
  186. }
  187. var buttons = [];
  188. var leftButton = leftButtonKey ?
  189. APP.translation.generateTranslationHTML(leftButtonKey) :
  190. APP.translation.generateTranslationHTML('dialog.Submit');
  191. buttons.push({ title: leftButton, value: true});
  192. var cancelButton
  193. = APP.translation.generateTranslationHTML("dialog.Cancel");
  194. buttons.push({title: cancelButton, value: false});
  195. var message = msgString;
  196. if (msgKey) {
  197. message = APP.translation.generateTranslationHTML(msgKey);
  198. }
  199. message += generateDontShowCheckbox(dontShowAgain);
  200. classes = classes || this._getDialogClasses(size);
  201. if (wrapperClass) {
  202. classes.prompt += ` ${wrapperClass}`;
  203. }
  204. twoButtonDialog = $.prompt(message, {
  205. title: this._getFormattedTitleString(titleKey),
  206. persistent: false,
  207. buttons: buttons,
  208. defaultButton: defaultButton,
  209. focus: focus,
  210. loaded: loadedFunction,
  211. promptspeed: 0,
  212. classes,
  213. submit: dontShowAgainSubmitFunctionWrapper(dontShowAgain,
  214. function (e, v, m, f) {
  215. twoButtonDialog = null;
  216. if (v && submitFunction) {
  217. submitFunction(e, v, m, f);
  218. }
  219. }),
  220. close: function (e, v, m, f) {
  221. twoButtonDialog = null;
  222. if (closeFunction) {
  223. closeFunction(e, v, m, f);
  224. }
  225. }
  226. });
  227. APP.translation.translateElement(twoButtonDialog);
  228. return $.prompt.getApi();
  229. },
  230. /**
  231. * Shows a message to the user with two buttons: first is given as a
  232. * parameter and the second is Cancel.
  233. *
  234. * @param titleKey the key for the title of the message
  235. * @param msgString the text of the message
  236. * @param persistent boolean value which determines whether the message is
  237. * persistent or not
  238. * @param buttons object with the buttons. The keys must be the name of the
  239. * button and value is the value that will be passed to
  240. * submitFunction
  241. * @param submitFunction function to be called on submit
  242. * @param loadedFunction function to be called after the prompt is fully
  243. * loaded
  244. * @param closeFunction function to be called on dialog close
  245. * @param {object} dontShowAgain - options for dont show again checkbox.
  246. * @param {string} dontShowAgain.id the id of the checkbox.
  247. * @param {string} dontShowAgain.textKey the key for the text displayed
  248. * next to checkbox
  249. * @param {boolean} dontShowAgain.checked if true the checkbox is foing to
  250. * be checked
  251. * @param {Array} dontShowAgain.buttonValues The button values that will
  252. * trigger storing the checkbox value
  253. * @param {string} dontShowAgain.localStorageKey the key for the local
  254. * storage. if not provided dontShowAgain.id will be used.
  255. */
  256. openDialog: function (titleKey, msgString, persistent, buttons,
  257. submitFunction, loadedFunction, closeFunction, dontShowAgain) {
  258. if (!popupEnabled)
  259. return;
  260. if(dontShowTheDialog(dontShowAgain)) {
  261. // Maybe we should pass some parameters here? I'm not sure
  262. // and currently we don't need any parameters.
  263. submitFunction();
  264. return;
  265. }
  266. let args = {
  267. title: this._getFormattedTitleString(titleKey),
  268. persistent: persistent,
  269. buttons: buttons,
  270. defaultButton: 1,
  271. promptspeed: 0,
  272. loaded: function() {
  273. if (loadedFunction) {
  274. loadedFunction.apply(this, arguments);
  275. }
  276. // Hide the close button
  277. if (persistent) {
  278. $(".jqiclose", this).hide();
  279. }
  280. },
  281. submit: dontShowAgainSubmitFunctionWrapper(
  282. dontShowAgain, submitFunction),
  283. close: closeFunction,
  284. classes: this._getDialogClasses()
  285. };
  286. if (persistent) {
  287. args.closeText = '';
  288. }
  289. let dialog = $.prompt(
  290. msgString + generateDontShowCheckbox(dontShowAgain), args);
  291. APP.translation.translateElement(dialog);
  292. return $.prompt.getApi();
  293. },
  294. /**
  295. * Returns the formatted title string.
  296. *
  297. * @return the title string formatted as a div.
  298. */
  299. _getFormattedTitleString(titleKey) {
  300. let $titleString = $('<h2>');
  301. $titleString.addClass('aui-dialog2-header-main');
  302. $titleString.attr('data-i18n',titleKey);
  303. return $('<div>').append($titleString).html();
  304. },
  305. /**
  306. * Returns the dialog css classes.
  307. *
  308. * @return the dialog css classes
  309. */
  310. _getDialogClasses(size = 'small') {
  311. return {
  312. box: '',
  313. form: '',
  314. prompt: `dialog aui-layer aui-dialog2 aui-dialog2-${size}`,
  315. close: 'aui-icon aui-icon-small aui-iconfont-close-dialog',
  316. fade: 'aui-blanket',
  317. button: 'button-control',
  318. message: 'aui-dialog2-content',
  319. buttons: 'aui-dialog2-footer',
  320. defaultButton: 'button-control_primary',
  321. title: 'aui-dialog2-header'
  322. };
  323. },
  324. /**
  325. * Shows a dialog with different states to the user.
  326. *
  327. * @param statesObject object containing all the states of the dialog.
  328. * @param options impromptu options
  329. * @param translateOptions options passed to translation
  330. */
  331. openDialogWithStates: function (statesObject, options, translateOptions) {
  332. if (!popupEnabled)
  333. return;
  334. let { classes, size } = options;
  335. let defaultClasses = this._getDialogClasses(size);
  336. options.classes = Object.assign({}, defaultClasses, classes);
  337. options.promptspeed = options.promptspeed || 0;
  338. for (let state in statesObject) {
  339. let currentState = statesObject[state];
  340. if(currentState.titleKey) {
  341. currentState.title
  342. = this._getFormattedTitleString(currentState.titleKey);
  343. }
  344. }
  345. let dialog = $.prompt(statesObject, options);
  346. APP.translation.translateElement(dialog, translateOptions);
  347. return $.prompt.getApi();
  348. },
  349. /**
  350. * Opens new popup window for given <tt>url</tt> centered over current
  351. * window.
  352. *
  353. * @param url the URL to be displayed in the popup window
  354. * @param w the width of the popup window
  355. * @param h the height of the popup window
  356. * @param onPopupClosed optional callback function called when popup window
  357. * has been closed.
  358. *
  359. * @returns {object} popup window object if opened successfully or undefined
  360. * in case we failed to open it(popup blocked)
  361. */
  362. openCenteredPopup: function (url, w, h, onPopupClosed) {
  363. if (!popupEnabled)
  364. return;
  365. var l = window.screenX + (window.innerWidth / 2) - (w / 2);
  366. var t = window.screenY + (window.innerHeight / 2) - (h / 2);
  367. var popup = window.open(
  368. url, '_blank',
  369. 'top=' + t + ', left=' + l + ', width=' + w + ', height=' + h + '');
  370. if (popup && onPopupClosed) {
  371. var pollTimer = window.setInterval(function () {
  372. if (popup.closed !== false) {
  373. window.clearInterval(pollTimer);
  374. onPopupClosed();
  375. }
  376. }, 200);
  377. }
  378. return popup;
  379. },
  380. /**
  381. * Shows a dialog prompting the user to send an error report.
  382. *
  383. * @param titleKey the title of the message
  384. * @param msgKey the text of the message
  385. * @param error the error that is being reported
  386. */
  387. openReportDialog: function(titleKey, msgKey, error) {
  388. this.openMessageDialog(titleKey, msgKey);
  389. logger.log(error);
  390. //FIXME send the error to the server
  391. },
  392. /**
  393. * Shows an error dialog to the user.
  394. * @param titleKey the title of the message.
  395. * @param msgKey the text of the message.
  396. */
  397. showError: function(titleKey, msgKey) {
  398. if (!titleKey) {
  399. titleKey = "dialog.oops";
  400. }
  401. if (!msgKey) {
  402. msgKey = "dialog.defaultError";
  403. }
  404. messageHandler.openMessageDialog(titleKey, msgKey);
  405. },
  406. /**
  407. * Displays a notification.
  408. * @param displayName the display name of the participant that is
  409. * associated with the notification.
  410. * @param displayNameKey the key from the language file for the display
  411. * name. Only used if displayName i not provided.
  412. * @param cls css class for the notification
  413. * @param messageKey the key from the language file for the text of the
  414. * message.
  415. * @param messageArguments object with the arguments for the message.
  416. * @param options passed to toastr (e.g. timeOut)
  417. */
  418. notify: function(displayName, displayNameKey, cls, messageKey,
  419. messageArguments, options) {
  420. // If we're in ringing state we skip all toaster notifications.
  421. if(!notificationsEnabled || APP.UI.isOverlayVisible())
  422. return;
  423. var displayNameSpan = '<span class="nickname" ';
  424. if (displayName) {
  425. displayNameSpan += ">" + UIUtil.escapeHtml(displayName);
  426. } else {
  427. displayNameSpan += "data-i18n='" + displayNameKey + "'>";
  428. }
  429. displayNameSpan += "</span>";
  430. let element = toastr.info(
  431. displayNameSpan + '<br>' +
  432. '<span class=' + cls + ' data-i18n="' + messageKey + '"' +
  433. (messageArguments?
  434. " data-i18n-options='"
  435. + JSON.stringify(messageArguments) + "'"
  436. : "") + "></span>", null, options);
  437. APP.translation.translateElement(element);
  438. return element;
  439. },
  440. /**
  441. * Removes the toaster.
  442. * @param toasterElement
  443. */
  444. remove: function(toasterElement) {
  445. toasterElement.remove();
  446. },
  447. /**
  448. * Enables / disables notifications.
  449. */
  450. enableNotifications: function (enable) {
  451. notificationsEnabled = enable;
  452. },
  453. enablePopups: function (enable) {
  454. popupEnabled = enable;
  455. },
  456. /**
  457. * Returns true if dialog is opened
  458. * false otherwise
  459. * @returns {boolean} isOpened
  460. */
  461. isDialogOpened: function () {
  462. return !!$.prompt.getCurrentStateName();
  463. }
  464. };
  465. module.exports = messageHandler;