Quellcode durchsuchen

feat(popover): do not remove the popover on every update

With popover usage now only passing in React Components, the
logic of removing the popover and recreating its html with
every update is not necessary. Instead allow React to update
the popover contents.

Because of this change, mouse event handlers are not recreated
on each update, so it is possible for mouseleave to fire after
the size of the popover shrinks when collapsing to hide more stats,
forcing the mouse out of the popover. To prevent this, padding has
been added to the top of the popover so on resize the mouse will
still be over the popover. The padding has the added bonus of
fixing an issue where the popover would not close until mouseenter
was triggered after size collapse, but it adds the drawback of
requiring more upward mouse travel to close the popover.
master
Leonard Kim vor 7 Jahren
Ursprung
Commit
0a1bd5a0c7

+ 23
- 3
css/_jitsi_popover.scss Datei anzeigen

@@ -16,11 +16,31 @@
16 16
     white-space: normal;
17 17
     margin-top: -$popoverMenuPadding;
18 18
 
19
-    &__menu-padding {
20
-        height: $popoverMenuPadding;
21
-        width: 100px;
19
+
20
+    &__menu-padding,
21
+    &__menu-padding-top {
22 22
         position: absolute;
23
+        width: 100px;
24
+    }
25
+
26
+    /**
27
+     * Invisible padding is added to the bottom of the popover to extend its
28
+     * height so it does not close when moving the mouse from the trigger
29
+     * element towards the popover itself.
30
+     */
31
+    &__menu-padding {
23 32
         bottom: -$popoverMenuPadding;
33
+        height: $popoverMenuPadding;
34
+    }
35
+
36
+    /**
37
+     * Invisible padding is added to the top of the popover to extend its height
38
+     * so it does not close automatically when its height is shrunk from showing
39
+     * less video statistics.
40
+     */
41
+    &__menu-padding-top {
42
+        height: 20px;
43
+        top: -20px;
24 44
     }
25 45
 
26 46
     &__showmore {

+ 53
- 73
modules/UI/util/JitsiPopover.js Datei anzeigen

@@ -37,16 +37,15 @@ const positionConfigurations = {
37 37
 
38 38
             $('.jitsipopover').css({
39 39
                 display: 'table',
40
-                left: position.left,
41
-                top: position.top
40
+                left: element.left,
41
+                top: element.top
42 42
             });
43 43
 
44 44
             // Move additional padding to the right edge of the popover and
45 45
             // allow css to take care of width. The padding is used to maintain
46 46
             // a hover state between the target and the popover.
47
-            $('.jitsipopover > .jitsipopover__menu-padding').css({
48
-                left: element.width
49
-            });
47
+            $('.jitsipopover > .jitsipopover__menu-padding')
48
+                .css({ left: element.width });
50 49
 
51 50
             // Find the distance from the top of the popover to the center of
52 51
             // the target and use that value to position the arrow to point to
@@ -67,13 +66,21 @@ const positionConfigurations = {
67 66
         at: "top",
68 67
         collision: "fit",
69 68
         using: function setPositionTop(position, elements) {
70
-            var calcLeft = elements.target.left - elements.element.left +
71
-                elements.target.width/2;
72
-            $(".jitsipopover").css(
73
-                {top: position.top, left: position.left, display: "table"});
74
-            $(".jitsipopover > .arrow").css({left: calcLeft});
75
-            $(".jitsipopover > .jitsipopover__menu-padding").css(
76
-                {left: calcLeft - 50});
69
+            const { element, target } = elements;
70
+            const calcLeft = target.left - element.left + target.width / 2;
71
+            const paddingLeftPosition = calcLeft - 50;
72
+            const $jistiPopover = $('.jitsipopover');
73
+
74
+            $jistiPopover.css({
75
+                display: 'table',
76
+                left: element.left,
77
+                top: element.top
78
+            });
79
+            $jistiPopover.find('.arrow').css({ left: calcLeft });
80
+            $jistiPopover.find('.jitsipopover__menu-padding')
81
+                .css({ left: paddingLeftPosition });
82
+            $jistiPopover.find('.jitsipopover__menu-padding-top')
83
+                .css({ left: paddingLeftPosition });
77 84
         }
78 85
     }
79 86
 };
@@ -93,8 +100,6 @@ export default (function () {
93 100
      * Constructs new JitsiPopover and attaches it to the element
94 101
      * @param element jquery selector
95 102
      * @param options the options for the popover.
96
-     *  - {Function} onBeforePosition - function executed just before
97
-     *      positioning the popover. Useful for translation.
98 103
      * @constructor
99 104
      */
100 105
     function JitsiPopover(element, options)
@@ -132,6 +137,7 @@ export default (function () {
132 137
 
133 138
         return  (
134 139
             `<div class="jitsipopover ${skin} ${position}">
140
+                <div class="jitsipopover__menu-padding-top"></div>
135 141
                 ${arrow}
136 142
                 <div class="jitsipopover__content"></div>
137 143
                 <div class="jitsipopover__menu-padding"></div>
@@ -176,65 +182,41 @@ export default (function () {
176 182
      * Creates the popover html.
177 183
      */
178 184
     JitsiPopover.prototype.createPopover = function () {
179
-        $("body").append(this.template);
180
-        let popoverElem = $(".jitsipopover > .jitsipopover__content");
181
-
182
-        const { content } = this.options;
183
-
184
-        if (React.isValidElement(content)) {
185
-            /* jshint ignore:start */
186
-            ReactDOM.render(
187
-                <I18nextProvider i18n = { i18next }>
188
-                    { content }
189
-                </I18nextProvider>,
190
-                popoverElem.get(0),
191
-                () => {
192
-                    // FIXME There seems to be odd timing interaction when a
193
-                    // React Component is manually removed from the DOM and then
194
-                    // created again, as the ReactDOM callback will fire before
195
-                    // render is called on the React Component. Using a timeout
196
-                    // looks to bypass this behavior, maybe by creating
197
-                    // different execution context. JitsiPopover should be
198
-                    // rewritten into react soon anyway or at least rewritten
199
-                    // so the html isn't completely torn down with each update.
200
-                    setTimeout(() => this._popoverCreated());
201
-                });
202
-            /* jshint ignore:end */
203
-            return;
204
-        }
185
+        let $popover = $('.jitsipopover');
205 186
 
206
-        popoverElem.html(content);
207
-        this._popoverCreated();
208
-    };
187
+        if (!$popover.length) {
188
+            $('body').append(this.template);
209 189
 
210
-    /**
211
-     * Adds listeners and executes callbacks after the popover has been created
212
-     * and displayed.
213
-     *
214
-     * @private
215
-     * @returns {void}
216
-     */
217
-    JitsiPopover.prototype._popoverCreated = function () {
218
-        const { onBeforePosition } = this.options;
190
+            $popover = $('.jitsipopover');
219 191
 
220
-        if (typeof onBeforePosition === 'function') {
221
-            onBeforePosition($(".jitsipopover"));
192
+            $popover.on('mouseenter', () => {
193
+                this.popoverIsHovered = true;
194
+                if (typeof this.onHoverPopover === 'function') {
195
+                    this.onHoverPopover(this.popoverIsHovered);
196
+                }
197
+            });
198
+
199
+            $popover.on('mouseleave', () => {
200
+                this.popoverIsHovered = false;
201
+                this.hide();
202
+                if (typeof this.onHoverPopover === 'function') {
203
+                    this.onHoverPopover(this.popoverIsHovered);
204
+                }
205
+            });
222 206
         }
223 207
 
224
-        $('.jitsipopover').on('mouseenter', () => {
225
-            this.popoverIsHovered = true;
226
-            if (typeof this.onHoverPopover === 'function') {
227
-                this.onHoverPopover(this.popoverIsHovered);
228
-            }
229
-        }).on('mouseleave', () => {
230
-            this.popoverIsHovered = false;
231
-            this.hide();
232
-            if (typeof this.onHoverPopover === 'function') {
233
-                this.onHoverPopover(this.popoverIsHovered);
234
-            }
235
-        });
208
+        const $popoverContent = $popover.find('.jitsipopover__content');
236 209
 
237
-        this.refreshPosition();
210
+        /* jshint ignore:start */
211
+        ReactDOM.render(
212
+            <I18nextProvider i18n = { i18next }>
213
+                { this.options.content }
214
+            </I18nextProvider>,
215
+            $popoverContent.get(0),
216
+            () => {
217
+                this.refreshPosition();
218
+            });
219
+        /* jshint ignore:end */
238 220
     };
239 221
 
240 222
     /**
@@ -264,9 +246,9 @@ export default (function () {
264 246
      */
265 247
     JitsiPopover.prototype.updateContent = function (content) {
266 248
         this.options.content = content;
267
-        if(!this.popoverShown)
249
+        if (!this.popoverShown) {
268 250
             return;
269
-        this.remove();
251
+        }
270 252
         this.createPopover();
271 253
     };
272 254
 
@@ -279,11 +261,9 @@ export default (function () {
279 261
     JitsiPopover.prototype.remove = function () {
280 262
         const $popover = $('.jitsipopover');
281 263
         const $popoverContent = $popover.find('.jitsipopover__content');
282
-        const attachedComponent = $popoverContent.get(0);
283 264
 
284
-        if (attachedComponent) {
285
-            // ReactDOM will no-op if no React Component is found.
286
-            ReactDOM.unmountComponentAtNode(attachedComponent);
265
+        if ($popoverContent.length) {
266
+            ReactDOM.unmountComponentAtNode($popoverContent.get(0));
287 267
         }
288 268
 
289 269
         $popover.off();

+ 1
- 2
modules/UI/videolayout/ConnectionIndicator.js Datei anzeigen

@@ -1,4 +1,4 @@
1
-/* global $, APP, interfaceConfig, JitsiMeetJS */
1
+/* global $, interfaceConfig, JitsiMeetJS */
2 2
 /* jshint -W101 */
3 3
 
4 4
 /* eslint-disable no-unused-vars */
@@ -113,7 +113,6 @@ ConnectionIndicator.prototype.create = function () {
113 113
     this.popover = new JitsiPopover($(element), {
114 114
         content: popoverContent,
115 115
         skin: "black",
116
-        onBeforePosition: el => APP.translation.translateElement(el),
117 116
         position: interfaceConfig.VERTICAL_FILMSTRIP ? 'left' : 'top'
118 117
     });
119 118
 

+ 0
- 1
modules/UI/videolayout/RemoteVideo.js Datei anzeigen

@@ -115,7 +115,6 @@ RemoteVideo.prototype._initPopupMenu = function (popupMenuElement) {
115 115
         content: popupMenuElement.outerHTML,
116 116
         skin: "black",
117 117
         hasArrow: false,
118
-        onBeforePosition: el => APP.translation.translateElement(el),
119 118
         position: interfaceConfig.VERTICAL_FILMSTRIP ? 'left' : 'top'
120 119
     };
121 120
     let element = $("#" + this.videoSpanId + " .remotevideomenu");

Laden…
Abbrechen
Speichern