Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

JitsiPopover.js 9.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. /* global $ */
  2. /* eslint-disable no-unused-vars */
  3. import React, { Component } from 'react';
  4. import ReactDOM from 'react-dom';
  5. import { I18nextProvider } from 'react-i18next';
  6. import { i18next } from '../../../react/features/base/i18n';
  7. /* eslint-enable no-unused-vars */
  8. const positionConfigurations = {
  9. left: {
  10. // Align the popover's right side to the target element.
  11. my: 'right',
  12. // Align the popover to the left side of the target element.
  13. at: 'left',
  14. // Force the popover to fit within the viewport.
  15. collision: 'fit',
  16. /**
  17. * Callback invoked by jQuery UI tooltip.
  18. *
  19. * @param {Object} position - The top and bottom position the popover
  20. * element should be set at.
  21. * @param {Object} element. - Additional size and position information
  22. * about the popover element and target.
  23. * @param {Object} elements.element - Has position and size related data
  24. * for the popover element itself.
  25. * @param {Object} elements.target - Has position and size related data
  26. * for the target element the popover displays from.
  27. */
  28. using: function setPositionLeft(position, elements) {
  29. const { element, target } = elements;
  30. $('.jitsipopover').css({
  31. display: 'table',
  32. left: position.left,
  33. top: position.top
  34. });
  35. // Move additional padding to the right edge of the popover and
  36. // allow css to take care of width. The padding is used to maintain
  37. // a hover state between the target and the popover.
  38. $('.jitsipopover > .jitsipopover__menu-padding').css({
  39. left: element.width
  40. });
  41. // Find the distance from the top of the popover to the center of
  42. // the target and use that value to position the arrow to point to
  43. // it.
  44. const verticalCenterOfTarget = target.height / 2;
  45. const verticalDistanceFromTops = target.top - element.top;
  46. const verticalPositionOfTargetCenter
  47. = verticalDistanceFromTops + verticalCenterOfTarget;
  48. $('.jitsipopover > .arrow').css({
  49. left: element.width,
  50. top: verticalPositionOfTargetCenter
  51. });
  52. }
  53. },
  54. top: {
  55. my: "bottom",
  56. at: "top",
  57. collision: "fit",
  58. using: function setPositionTop(position, elements) {
  59. var calcLeft = elements.target.left - elements.element.left +
  60. elements.target.width/2;
  61. $(".jitsipopover").css(
  62. {top: position.top, left: position.left, display: "table"});
  63. $(".jitsipopover > .arrow").css({left: calcLeft});
  64. $(".jitsipopover > .jitsipopover__menu-padding").css(
  65. {left: calcLeft - 50});
  66. }
  67. }
  68. };
  69. var JitsiPopover = (function () {
  70. /**
  71. * The default options
  72. */
  73. const defaultOptions = {
  74. skin: 'white',
  75. content: '',
  76. hasArrow: true,
  77. onBeforePosition: undefined,
  78. position: 'top'
  79. };
  80. /**
  81. * Constructs new JitsiPopover and attaches it to the element
  82. * @param element jquery selector
  83. * @param options the options for the popover.
  84. * - {Function} onBeforePosition - function executed just before
  85. * positioning the popover. Useful for translation.
  86. * @constructor
  87. */
  88. function JitsiPopover(element, options)
  89. {
  90. this.options = Object.assign({}, defaultOptions, options);
  91. this.elementIsHovered = false;
  92. this.popoverIsHovered = false;
  93. this.popoverShown = false;
  94. element.data("jitsi_popover", this);
  95. this.element = element;
  96. this.template = this.getTemplate();
  97. var self = this;
  98. this.element.on("mouseenter", function () {
  99. self.elementIsHovered = true;
  100. self.show();
  101. }).on("mouseleave", function () {
  102. self.elementIsHovered = false;
  103. setTimeout(function () {
  104. self.hide();
  105. }, 10);
  106. });
  107. }
  108. /**
  109. * Returns template for popover
  110. */
  111. JitsiPopover.prototype.getTemplate = function () {
  112. const { hasArrow, position, skin } = this.options;
  113. let arrow = '';
  114. if (hasArrow) {
  115. arrow = '<div class="arrow"></div>';
  116. }
  117. return (
  118. `<div class="jitsipopover ${skin} ${position}">
  119. ${arrow}
  120. <div class="jitsipopover__content"></div>
  121. <div class="jitsipopover__menu-padding"></div>
  122. </div>`
  123. );
  124. };
  125. /**
  126. * Shows the popover
  127. */
  128. JitsiPopover.prototype.show = function () {
  129. if(!JitsiPopover.enabled)
  130. return;
  131. this.createPopover();
  132. this.popoverShown = true;
  133. };
  134. /**
  135. * Hides the popover if not hovered or popover is not shown.
  136. */
  137. JitsiPopover.prototype.hide = function () {
  138. if(!this.elementIsHovered && !this.popoverIsHovered &&
  139. this.popoverShown) {
  140. this.forceHide();
  141. }
  142. };
  143. /**
  144. * Hides the popover and clears the document elements added by popover.
  145. */
  146. JitsiPopover.prototype.forceHide = function () {
  147. this.remove();
  148. this.popoverShown = false;
  149. if(this.popoverIsHovered) { //the browser is not firing hover events
  150. //when the element was on hover if got removed.
  151. this.popoverIsHovered = false;
  152. this.onHoverPopover(this.popoverIsHovered);
  153. }
  154. };
  155. /**
  156. * Creates the popover html.
  157. */
  158. JitsiPopover.prototype.createPopover = function () {
  159. $("body").append(this.template);
  160. let popoverElem = $(".jitsipopover > .jitsipopover__content");
  161. const { content } = this.options;
  162. if (React.isValidElement(content)) {
  163. /* jshint ignore:start */
  164. ReactDOM.render(
  165. <I18nextProvider i18n = { i18next }>
  166. { content }
  167. </I18nextProvider>,
  168. popoverElem.get(0),
  169. () => {
  170. // FIXME There seems to be odd timing interaction when a
  171. // React Component is manually removed from the DOM and then
  172. // created again, as the ReactDOM callback will fire before
  173. // render is called on the React Component. Using a timeout
  174. // looks to bypass this behavior, maybe by creating
  175. // different execution context. JitsiPopover should be
  176. // rewritten into react soon anyway or at least rewritten
  177. // so the html isn't completely torn down with each update.
  178. setTimeout(() => this._popoverCreated());
  179. });
  180. /* jshint ignore:end */
  181. return;
  182. }
  183. popoverElem.html(content);
  184. this._popoverCreated();
  185. };
  186. /**
  187. * Adds listeners and executes callbacks after the popover has been created
  188. * and displayed.
  189. *
  190. * @private
  191. * @returns {void}
  192. */
  193. JitsiPopover.prototype._popoverCreated = function () {
  194. const { onBeforePosition } = this.options;
  195. if (typeof onBeforePosition === 'function') {
  196. onBeforePosition($(".jitsipopover"));
  197. }
  198. $('.jitsipopover').on('mouseenter', () => {
  199. this.popoverIsHovered = true;
  200. if (typeof this.onHoverPopover === 'function') {
  201. this.onHoverPopover(this.popoverIsHovered);
  202. }
  203. }).on('mouseleave', () => {
  204. this.popoverIsHovered = false;
  205. this.hide();
  206. if (typeof this.onHoverPopover === 'function') {
  207. this.onHoverPopover(this.popoverIsHovered);
  208. }
  209. });
  210. this.refreshPosition();
  211. };
  212. /**
  213. * Adds a hover listener to the popover.
  214. */
  215. JitsiPopover.prototype.addOnHoverPopover = function (listener) {
  216. this.onHoverPopover = listener;
  217. };
  218. /**
  219. * Refreshes the position of the popover.
  220. */
  221. JitsiPopover.prototype.refreshPosition = function () {
  222. const positionOptions = Object.assign(
  223. {},
  224. positionConfigurations[this.options.position],
  225. {
  226. of: this.element
  227. }
  228. );
  229. $(".jitsipopover").position(positionOptions);
  230. };
  231. /**
  232. * Updates the content of popover.
  233. * @param content new content
  234. */
  235. JitsiPopover.prototype.updateContent = function (content) {
  236. this.options.content = content;
  237. if(!this.popoverShown)
  238. return;
  239. this.remove();
  240. this.createPopover();
  241. };
  242. /**
  243. * Unmounts any present child React Component and removes the popover itself
  244. * from the DOM.
  245. *
  246. * @returns {void}
  247. */
  248. JitsiPopover.prototype.remove = function () {
  249. const $popover = $('.jitsipopover');
  250. const $popoverContent = $popover.find('.jitsipopover__content');
  251. const attachedComponent = $popoverContent.get(0);
  252. if (attachedComponent) {
  253. // ReactDOM will no-op if no React Component is found.
  254. ReactDOM.unmountComponentAtNode(attachedComponent);
  255. }
  256. $popover.off();
  257. $popover.remove();
  258. };
  259. JitsiPopover.enabled = true;
  260. return JitsiPopover;
  261. })();
  262. module.exports = JitsiPopover;