Explorar el Código

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 hace 7 años
padre
commit
0a1bd5a0c7

+ 23
- 3
css/_jitsi_popover.scss Ver fichero

16
     white-space: normal;
16
     white-space: normal;
17
     margin-top: -$popoverMenuPadding;
17
     margin-top: -$popoverMenuPadding;
18
 
18
 
19
-    &__menu-padding {
20
-        height: $popoverMenuPadding;
21
-        width: 100px;
19
+
20
+    &__menu-padding,
21
+    &__menu-padding-top {
22
         position: absolute;
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
         bottom: -$popoverMenuPadding;
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
     &__showmore {
46
     &__showmore {

+ 53
- 73
modules/UI/util/JitsiPopover.js Ver fichero

37
 
37
 
38
             $('.jitsipopover').css({
38
             $('.jitsipopover').css({
39
                 display: 'table',
39
                 display: 'table',
40
-                left: position.left,
41
-                top: position.top
40
+                left: element.left,
41
+                top: element.top
42
             });
42
             });
43
 
43
 
44
             // Move additional padding to the right edge of the popover and
44
             // Move additional padding to the right edge of the popover and
45
             // allow css to take care of width. The padding is used to maintain
45
             // allow css to take care of width. The padding is used to maintain
46
             // a hover state between the target and the popover.
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
             // Find the distance from the top of the popover to the center of
50
             // Find the distance from the top of the popover to the center of
52
             // the target and use that value to position the arrow to point to
51
             // the target and use that value to position the arrow to point to
67
         at: "top",
66
         at: "top",
68
         collision: "fit",
67
         collision: "fit",
69
         using: function setPositionTop(position, elements) {
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
      * Constructs new JitsiPopover and attaches it to the element
100
      * Constructs new JitsiPopover and attaches it to the element
94
      * @param element jquery selector
101
      * @param element jquery selector
95
      * @param options the options for the popover.
102
      * @param options the options for the popover.
96
-     *  - {Function} onBeforePosition - function executed just before
97
-     *      positioning the popover. Useful for translation.
98
      * @constructor
103
      * @constructor
99
      */
104
      */
100
     function JitsiPopover(element, options)
105
     function JitsiPopover(element, options)
132
 
137
 
133
         return  (
138
         return  (
134
             `<div class="jitsipopover ${skin} ${position}">
139
             `<div class="jitsipopover ${skin} ${position}">
140
+                <div class="jitsipopover__menu-padding-top"></div>
135
                 ${arrow}
141
                 ${arrow}
136
                 <div class="jitsipopover__content"></div>
142
                 <div class="jitsipopover__content"></div>
137
                 <div class="jitsipopover__menu-padding"></div>
143
                 <div class="jitsipopover__menu-padding"></div>
176
      * Creates the popover html.
182
      * Creates the popover html.
177
      */
183
      */
178
     JitsiPopover.prototype.createPopover = function () {
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
      */
246
      */
265
     JitsiPopover.prototype.updateContent = function (content) {
247
     JitsiPopover.prototype.updateContent = function (content) {
266
         this.options.content = content;
248
         this.options.content = content;
267
-        if(!this.popoverShown)
249
+        if (!this.popoverShown) {
268
             return;
250
             return;
269
-        this.remove();
251
+        }
270
         this.createPopover();
252
         this.createPopover();
271
     };
253
     };
272
 
254
 
279
     JitsiPopover.prototype.remove = function () {
261
     JitsiPopover.prototype.remove = function () {
280
         const $popover = $('.jitsipopover');
262
         const $popover = $('.jitsipopover');
281
         const $popoverContent = $popover.find('.jitsipopover__content');
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
         $popover.off();
269
         $popover.off();

+ 1
- 2
modules/UI/videolayout/ConnectionIndicator.js Ver fichero

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

+ 0
- 1
modules/UI/videolayout/RemoteVideo.js Ver fichero

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

Loading…
Cancelar
Guardar