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

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