Browse Source

feat(context-menu) Show participants context menu overlaid in a portal

master
hmuresan 3 years ago
parent
commit
9f7a4f86d8

+ 22
- 25
css/_popover.scss View File

@@ -3,48 +3,45 @@
3 3
  * to allow mouse movement from the popover trigger to the popover itself
4 4
  * without triggering a mouseleave event.
5 5
  */
6
-.popover-mousemove-padding-bottom {
7
-    bottom: -15px;
8
-    height: 20px;
9
-    position: absolute;
10
-    right: 0;
11
-    width: 100%;
12
-}
13
-
14 6
 %vertical-popover-padding {
15 7
     height: 100%;
16 8
     position: absolute;
17 9
     top: 0;
18
-    width: 40px;
10
+    width: 20px;
11
+    padding: 20px 0;
12
+    top: -20px;
13
+}
14
+
15
+%horizontal-popover-padding {
16
+    height: 25px;
17
+    position: absolute;
18
+    right: 0;
19
+    width: 100%;
20
+    padding: 0 35px;
21
+    left: -35px;
19 22
 }
20 23
 
21 24
 .popover-mousemove-padding-left {
22 25
     @extend %vertical-popover-padding;
23
-    left: -20px;
26
+    left: -35px;
24 27
 }
25 28
 
26 29
 .popover-mousemove-padding-right {
27 30
     @extend %vertical-popover-padding;
28
-    right: -20px;
31
+    right: -35px;
29 32
 }
30 33
 
31
-/**
32
- * An invisible element is added to the top of the popover to ensure the mouse
33
- * stays over the popover when the popover's height is shrunk, which would then
34
- * normally leave the mouse outside of the popover itself and cause a mouseleave
35
- * event.
36
- */
37
-.popover-mouse-padding-top {
38
-    height: 30px;
39
-    position: absolute;
40
-    right: 0;
41
-    top: -25px;
42
-    width: 100%;
34
+.popover-mousemove-padding-bottom {
35
+    @extend %horizontal-popover-padding;
36
+    bottom: -40px;
37
+}
38
+
39
+.popover-mousemove-padding-top {
40
+    @extend %horizontal-popover-padding;
41
+    top: -40px;
43 42
 }
44 43
 
45 44
 .popover {
46
-    background-color: $popoverBg;
47
-    border-radius: 3px;
48 45
     margin: -16px -24px;
49 46
     padding: 16px 24px;
50 47
     z-index: $popoverZ;

+ 86
- 44
react/features/base/popover/components/Popover.web.js View File

@@ -1,35 +1,10 @@
1 1
 /* @flow */
2 2
 
3
-import InlineDialog from '@atlaskit/inline-dialog';
4 3
 import React, { Component } from 'react';
5 4
 
6
-import { Drawer, DrawerPortal } from '../../../toolbox/components/web';
7
-
8
-/**
9
- * A map of dialog positions, relative to trigger, to css classes used to
10
- * manipulate elements for handling mouse events.
11
- *
12
- * @private
13
- * @type {object}
14
- */
15
-const DIALOG_TO_PADDING_POSITION = {
16
-    'left': 'popover-mousemove-padding-right',
17
-    'right': 'popover-mousemove-padding-left',
18
-    'top': 'popover-mousemove-padding-bottom'
19
-};
20
-
21
-/**
22
- * Takes the position expected by {@code InlineDialog} and maps it to a CSS
23
- * class that can be used styling the elements used for preventing mouseleave
24
- * events when moving from the trigger to the dialog.
25
- *
26
- * @param {string} position - From which position the dialog will display.
27
- * @private
28
- * @returns {string}
29
- */
30
-function _mapPositionToPaddingClass(position = 'left') {
31
-    return DIALOG_TO_PADDING_POSITION[position.split('-')[0]];
32
-}
5
+import { Drawer, DrawerPortal, DialogPortal } from '../../../toolbox/components/web';
6
+import { isMobileBrowser } from '../../environment/utils';
7
+import { getContextMenuStyle } from '../functions.web';
33 8
 
34 9
 /**
35 10
  * The type of the React {@code Component} props of {@link Popover}.
@@ -90,6 +65,11 @@ type Props = {
90 65
  */
91 66
 type State = {
92 67
 
68
+    /**
69
+     * The style to apply to the context menu in order to position it correctly.
70
+     */
71
+     contextMenuStyle: Object,
72
+
93 73
     /**
94 74
      * Whether or not the {@code InlineDialog} should be displayed.
95 75
      */
@@ -118,6 +98,7 @@ class Popover extends Component<Props, State> {
118 98
      */
119 99
     _containerRef: Object;
120 100
 
101
+    _contextMenuRef: HTMLElement;
121 102
 
122 103
     /**
123 104
      * Initializes a new {@code Popover} instance.
@@ -129,7 +110,8 @@ class Popover extends Component<Props, State> {
129 110
         super(props);
130 111
 
131 112
         this.state = {
132
-            showDialog: false
113
+            showDialog: false,
114
+            contextMenuStyle: null
133 115
         };
134 116
 
135 117
         // Bind event handlers so they are only bound once for every instance.
@@ -140,6 +122,9 @@ class Popover extends Component<Props, State> {
140 122
         this._onEscKey = this._onEscKey.bind(this);
141 123
         this._onThumbClick = this._onThumbClick.bind(this);
142 124
         this._onTouchStart = this._onTouchStart.bind(this);
125
+        this._setContextMenuRef = this._setContextMenuRef.bind(this);
126
+        this._setContextMenuStyle = this._setContextMenuStyle.bind(this);
127
+        this._getCustomDialogStyle = this._getCustomDialogStyle.bind(this);
143 128
     }
144 129
 
145 130
     /**
@@ -179,7 +164,7 @@ class Popover extends Component<Props, State> {
179 164
      * @returns {ReactElement}
180 165
      */
181 166
     render() {
182
-        const { children, className, content, id, overflowDrawer, position } = this.props;
167
+        const { children, className, content, id, overflowDrawer } = this.props;
183 168
 
184 169
         if (overflowDrawer) {
185 170
             return (
@@ -208,16 +193,47 @@ class Popover extends Component<Props, State> {
208 193
                 onMouseEnter = { this._onShowDialog }
209 194
                 onMouseLeave = { this._onHideDialog }
210 195
                 ref = { this._containerRef }>
211
-                <InlineDialog
212
-                    content = { this._renderContent() }
213
-                    isOpen = { this.state.showDialog }
214
-                    placement = { position }>
215
-                    { children }
216
-                </InlineDialog>
196
+                { this.state.showDialog && (
197
+                    <DialogPortal
198
+                        getRef = { this._setContextMenuRef }
199
+                        setSize = { this._setContextMenuStyle }
200
+                        style = { this.state.contextMenuStyle }>
201
+                        {this._renderContent()}
202
+                    </DialogPortal>
203
+                )}
204
+                { children }
217 205
             </div>
218 206
         );
219 207
     }
220 208
 
209
+    _setContextMenuStyle: (size: Object) => void;
210
+
211
+    /**
212
+     * Sets the context menu dialog style for positioning it on screen.
213
+     *
214
+     * @param {DOMRectReadOnly} size -The size info of the current context menu.
215
+     *
216
+     * @returns {void}
217
+     */
218
+    _setContextMenuStyle(size) {
219
+        const style = this._getCustomDialogStyle(size);
220
+
221
+        this.setState({ contextMenuStyle: style });
222
+    }
223
+
224
+    _setContextMenuRef: (elem: HTMLElement) => void;
225
+
226
+    /**
227
+     * Sets the context menu's ref.
228
+     *
229
+     * @param {HTMLElement} elem -The html element of the context menu.
230
+     *
231
+     * @returns {void}
232
+     */
233
+    _setContextMenuRef(elem) {
234
+        this._contextMenuRef = elem;
235
+    }
236
+
221 237
     _onTouchStart: (event: TouchEvent) => void;
222 238
 
223 239
     /**
@@ -230,9 +246,9 @@ class Popover extends Component<Props, State> {
230 246
     _onTouchStart(event) {
231 247
         if (this.state.showDialog
232 248
             && !this.props.overflowDrawer
233
-            && this._containerRef
234
-            && this._containerRef.current
235
-            && !this._containerRef.current.contains(event.target)) {
249
+            && this._contextMenuRef
250
+            && this._contextMenuRef.contains
251
+            && !this._contextMenuRef.contains(event.target)) {
236 252
             this._onHideDialog();
237 253
         }
238 254
     }
@@ -246,7 +262,10 @@ class Popover extends Component<Props, State> {
246 262
      * @returns {void}
247 263
      */
248 264
     _onHideDialog() {
249
-        this.setState({ showDialog: false });
265
+        this.setState({
266
+            showDialog: false,
267
+            contextMenuStyle: null
268
+        });
250 269
 
251 270
         if (this.props.onPopoverClose) {
252 271
             this.props.onPopoverClose();
@@ -327,6 +346,24 @@ class Popover extends Component<Props, State> {
327 346
         }
328 347
     }
329 348
 
349
+    _getCustomDialogStyle: (DOMRectReadOnly) => void;
350
+
351
+    /**
352
+     * Gets style for positioning the context menu on screen in regards to the trigger's
353
+     * position.
354
+     *
355
+     * @param {DOMRectReadOnly} size -The current context menu's size info.
356
+     *
357
+     * @returns {Object} - The new style of the context menu.
358
+     */
359
+    _getCustomDialogStyle(size) {
360
+        if (this._containerRef && this._containerRef.current) {
361
+            const bounds = this._containerRef.current.getBoundingClientRect();
362
+
363
+            return getContextMenuStyle(bounds, size, this.props.position);
364
+        }
365
+    }
366
+
330 367
     /**
331 368
      * Renders the React Element to be displayed in the {@code InlineDialog}.
332 369
      * Also adds padding to support moving the mouse from the trigger to the
@@ -336,15 +373,20 @@ class Popover extends Component<Props, State> {
336 373
      * @returns {ReactElement}
337 374
      */
338 375
     _renderContent() {
339
-        const { content, position } = this.props;
376
+        const { content } = this.props;
340 377
 
341 378
         return (
342 379
             <div
343
-                className = 'popover'
380
+                className = 'popover popupmenu'
344 381
                 onKeyDown = { this._onEscKey }>
345 382
                 { content }
346
-                <div className = 'popover-mouse-padding-top' />
347
-                <div className = { _mapPositionToPaddingClass(position) } />
383
+                {!isMobileBrowser() && (
384
+                    <>
385
+                        <div className = 'popover-mousemove-padding-top' />
386
+                        <div className = 'popover-mousemove-padding-right' />
387
+                        <div className = 'popover-mousemove-padding-left' />
388
+                        <div className = 'popover-mousemove-padding-bottom' />
389
+                    </>)}
348 390
             </div>
349 391
         );
350 392
     }

+ 158
- 0
react/features/base/popover/functions.web.js View File

@@ -0,0 +1,158 @@
1
+// @flow
2
+
3
+const LEFT_RIGHT_OFFSET = 25;
4
+const TOP_BOTTOM_OFFSET = 20;
5
+
6
+const getLeftAlignedStyle = bounds => {
7
+    return {
8
+        position: 'fixed',
9
+        right: `${window.innerWidth - bounds.x + LEFT_RIGHT_OFFSET}px`
10
+    };
11
+};
12
+
13
+const getRightAlignedStyle = bounds => {
14
+    return {
15
+        position: 'fixed',
16
+        left: `${bounds.x + bounds.width + LEFT_RIGHT_OFFSET}px`
17
+    };
18
+};
19
+
20
+const getTopAlignedStyle = bounds => {
21
+    return {
22
+        position: 'fixed',
23
+        bottom: `${window.innerHeight - bounds.y + TOP_BOTTOM_OFFSET}px`
24
+    };
25
+};
26
+
27
+const getBottomAlignedStyle = bounds => {
28
+    return {
29
+        position: 'fixed',
30
+        top: `${bounds.y + bounds.height + TOP_BOTTOM_OFFSET}px`
31
+    };
32
+};
33
+
34
+const getLeftRightStartAlign = (bounds, size) => {
35
+    return {
36
+        top: `${Math.min(bounds.y + 15, window.innerHeight - size.height - 20)}px`
37
+    };
38
+};
39
+
40
+const getLeftRightMidAlign = (bounds, size) => {
41
+    return {
42
+        bottom: `${window.innerHeight - bounds.y - bounds.height - (size.height / 2)}px`
43
+    };
44
+};
45
+
46
+const getLeftRightEndAlign = (bounds, size) => {
47
+    return {
48
+        bottom: `${Math.min(window.innerHeight - bounds.y - bounds.height, window.innerHeight - size.height)}px`
49
+    };
50
+};
51
+
52
+const getTopBotStartAlign = bounds => {
53
+    return {
54
+        right: `${window.innerWidth - bounds.x + 10}px`
55
+    };
56
+};
57
+
58
+const getTopBotMidAlign = (bounds, size) => {
59
+    return {
60
+        right: `${window.innerWidth - bounds.x - (size.width / 2)}px`
61
+    };
62
+};
63
+
64
+const getTopBotEndAlign = bounds => {
65
+    return {
66
+        left: `${bounds.x + bounds.width + 10}px`
67
+    };
68
+};
69
+
70
+/**
71
+ * Gets the trigger element's and the context menu's bounds/size info and
72
+ * computes the style to apply to the context menu to positioning it correctly
73
+ * in regards to the given position info.
74
+ *
75
+ * @param {DOMRect} triggerBounds -The bounds info of the trigger html element.
76
+ * @param {DOMRectReadOnly} dialogSize - The size info of the context menu.
77
+ * @param {string} position - The position of the context menu in regards to the trigger element.
78
+ *
79
+ * @returns {Object} = The style to apply to context menu for positioning it correctly.
80
+ */
81
+export const getContextMenuStyle = (triggerBounds: DOMRect,
82
+        dialogSize: DOMRectReadOnly,
83
+        position: string) => {
84
+    const parsed = position.split('-');
85
+
86
+    switch (parsed[0]) {
87
+    case 'top': {
88
+        let alignmentStyle = {};
89
+
90
+        if (parsed[1]) {
91
+            alignmentStyle = parsed[1] === 'start'
92
+                ? getTopBotStartAlign(triggerBounds)
93
+                : getTopBotEndAlign(triggerBounds);
94
+        } else {
95
+            alignmentStyle = getTopBotMidAlign(triggerBounds, dialogSize);
96
+        }
97
+
98
+        return {
99
+            ...getTopAlignedStyle(triggerBounds),
100
+            ...alignmentStyle
101
+        };
102
+    }
103
+    case 'bottom': {
104
+        let alignmentStyle = {};
105
+
106
+        if (parsed[1]) {
107
+            alignmentStyle = parsed[1] === 'start'
108
+                ? getTopBotStartAlign(triggerBounds)
109
+                : getTopBotEndAlign(triggerBounds);
110
+        } else {
111
+            alignmentStyle = getTopBotMidAlign(triggerBounds, dialogSize);
112
+        }
113
+
114
+        return {
115
+            ...getBottomAlignedStyle(triggerBounds),
116
+            ...alignmentStyle
117
+        };
118
+    }
119
+    case 'left': {
120
+        let alignmentStyle = {};
121
+
122
+        if (parsed[1]) {
123
+            alignmentStyle = parsed[1] === 'start'
124
+                ? getLeftRightStartAlign(triggerBounds, dialogSize)
125
+                : getLeftRightEndAlign(triggerBounds, dialogSize);
126
+        } else {
127
+            alignmentStyle = getLeftRightMidAlign(triggerBounds, dialogSize);
128
+        }
129
+
130
+        return {
131
+            ...getLeftAlignedStyle(triggerBounds),
132
+            ...alignmentStyle
133
+        };
134
+    }
135
+    case 'right': {
136
+        let alignmentStyle = {};
137
+
138
+        if (parsed[1]) {
139
+            alignmentStyle = parsed[1] === 'start'
140
+                ? getLeftRightStartAlign(triggerBounds, dialogSize)
141
+                : getLeftRightEndAlign(triggerBounds, dialogSize);
142
+        } else {
143
+            alignmentStyle = getLeftRightMidAlign(triggerBounds, dialogSize);
144
+        }
145
+
146
+        return {
147
+            ...getRightAlignedStyle(triggerBounds),
148
+            ...alignmentStyle
149
+        };
150
+    }
151
+    default: {
152
+        return {
153
+            ...getLeftAlignedStyle(triggerBounds),
154
+            ...getLeftRightEndAlign(triggerBounds, dialogSize)
155
+        };
156
+    }
157
+    }
158
+};

+ 2
- 23
react/features/toolbox/actions.web.js View File

@@ -3,7 +3,6 @@
3 3
 import type { Dispatch } from 'redux';
4 4
 
5 5
 import { overwriteConfig } from '../base/config';
6
-import { isLayoutTileView } from '../video-layout';
7 6
 
8 7
 import {
9 8
     CLEAR_TOOLBOX_TIMEOUT,
@@ -134,13 +133,10 @@ export function showToolbox(timeout: number = 0): Object {
134 133
 
135 134
         const {
136 135
             enabled,
137
-            visible,
138
-            overflowDrawer
136
+            visible
139 137
         } = state['features/toolbox'];
140
-        const { contextMenuOpened } = state['features/base/responsive-ui'];
141
-        const contextMenuOpenedInTileview = isLayoutTileView(state) && contextMenuOpened && !overflowDrawer;
142 138
 
143
-        if (enabled && !visible && !contextMenuOpenedInTileview) {
139
+        if (enabled && !visible) {
144 140
             dispatch(setToolboxVisible(true));
145 141
 
146 142
             // If the Toolbox is always visible, there's no need for a timeout
@@ -178,23 +174,6 @@ export function setOverflowDrawer(displayAsDrawer: boolean) {
178 174
     };
179 175
 }
180 176
 
181
-
182
-/**
183
- * Disables and hides the toolbox on demand when in tile view.
184
- *
185
- * @returns {void}
186
- */
187
-export function hideToolboxOnTileView() {
188
-    return (dispatch: Dispatch<any>, getState: Function) => {
189
-        const state = getState();
190
-        const { overflowDrawer } = state['features/toolbox'];
191
-
192
-        if (!overflowDrawer && isLayoutTileView(state)) {
193
-            dispatch(hideToolbox(true));
194
-        }
195
-    };
196
-}
197
-
198 177
 /**
199 178
  * Signals that toolbox timeout should be cleared.
200 179
  *

+ 99
- 0
react/features/toolbox/components/web/DialogPortal.js View File

@@ -0,0 +1,99 @@
1
+// @flow
2
+
3
+import { useEffect, useState } from 'react';
4
+import ReactDOM from 'react-dom';
5
+
6
+type Props = {
7
+
8
+    /**
9
+     * The component(s) to be displayed within the drawer portal.
10
+     */
11
+    children: React$Node,
12
+
13
+    /**
14
+     * Custom class name to apply on the container div.
15
+     */
16
+    className?: string,
17
+
18
+    /**
19
+     * Function used to get the refferrence to the container div.
20
+     */
21
+    getRef?: Function,
22
+
23
+    /**
24
+     * Function used to get the updated size info of the container on it's resize.
25
+     */
26
+    setSize?: Function,
27
+
28
+    /**
29
+     * Custom style to apply to the container div.
30
+     */
31
+    style?: Object,
32
+};
33
+
34
+/**
35
+ * Component meant to render a drawer at the bottom of the screen,
36
+ * by creating a portal containing the component's children.
37
+ *
38
+ * @returns {ReactElement}
39
+ */
40
+function DialogPortal({ children, className, style, getRef, setSize }: Props) {
41
+    const [ portalTarget ] = useState(() => {
42
+        const portalDiv = document.createElement('div');
43
+
44
+        return portalDiv;
45
+    });
46
+
47
+    useEffect(() => {
48
+        if (style) {
49
+            for (const styleProp of Object.keys(style)) {
50
+                // https://github.com/facebook/flow/issues/3733
51
+                const objStyle: Object = portalTarget.style;
52
+
53
+                objStyle[styleProp] = style[styleProp];
54
+            }
55
+        }
56
+        if (className) {
57
+            portalTarget.className = className;
58
+        }
59
+    }, [ style, className ]);
60
+
61
+    useEffect(() => {
62
+        if (portalTarget && getRef) {
63
+            getRef(portalTarget);
64
+        }
65
+    }, [ portalTarget ]);
66
+
67
+    useEffect(() => {
68
+        const size = {
69
+            width: 1,
70
+            height: 1
71
+        };
72
+        const observer = new ResizeObserver(entries => {
73
+            const { contentRect } = entries[0];
74
+
75
+            if (contentRect.width !== size.width || contentRect.height !== size.height) {
76
+                setSize && setSize(contentRect);
77
+            }
78
+        });
79
+
80
+        if (document.body) {
81
+            document.body.appendChild(portalTarget);
82
+            observer.observe(portalTarget);
83
+        }
84
+
85
+        return () => {
86
+            observer.unobserve(portalTarget);
87
+            if (document.body) {
88
+                document.body.removeChild(portalTarget);
89
+            }
90
+        };
91
+    }, []);
92
+
93
+    return ReactDOM.createPortal(
94
+      children,
95
+      portalTarget
96
+    );
97
+}
98
+
99
+export default DialogPortal;

+ 6
- 25
react/features/toolbox/components/web/DrawerPortal.js View File

@@ -1,7 +1,7 @@
1 1
 // @flow
2
+import React from 'react';
2 3
 
3
-import { useEffect, useState } from 'react';
4
-import ReactDOM from 'react-dom';
4
+import DialogPortal from './DialogPortal';
5 5
 
6 6
 type Props = {
7 7
 
@@ -18,29 +18,10 @@ type Props = {
18 18
  * @returns {ReactElement}
19 19
  */
20 20
 function DrawerPortal({ children }: Props) {
21
-    const [ portalTarget ] = useState(() => {
22
-        const portalDiv = document.createElement('div');
23
-
24
-        portalDiv.className = 'drawer-portal';
25
-
26
-        return portalDiv;
27
-    });
28
-
29
-    useEffect(() => {
30
-        if (document.body) {
31
-            document.body.appendChild(portalTarget);
32
-        }
33
-
34
-        return () => {
35
-            if (document.body) {
36
-                document.body.removeChild(portalTarget);
37
-            }
38
-        };
39
-    }, []);
40
-
41
-    return ReactDOM.createPortal(
42
-      children,
43
-      portalTarget
21
+    return (
22
+        <DialogPortal className = 'drawer-portal'>
23
+            { children }
24
+        </DialogPortal>
44 25
     );
45 26
 }
46 27
 

+ 1
- 0
react/features/toolbox/components/web/index.js View File

@@ -4,3 +4,4 @@ export { default as ToolbarButton } from './ToolbarButton';
4 4
 export { default as Toolbox } from './Toolbox';
5 5
 export { default as Drawer } from './Drawer';
6 6
 export { default as DrawerPortal } from './DrawerPortal';
7
+export { default as DialogPortal } from './DialogPortal';

+ 0
- 2
react/features/video-menu/components/web/LocalVideoMenuTriggerButton.js View File

@@ -14,7 +14,6 @@ import { connect } from '../../../base/redux';
14 14
 import { setParticipantContextMenuOpen } from '../../../base/responsive-ui/actions';
15 15
 import { getLocalVideoTrack } from '../../../base/tracks';
16 16
 import ConnectionIndicatorContent from '../../../connection-indicator/components/web/ConnectionIndicatorContent';
17
-import { hideToolboxOnTileView } from '../../../toolbox/actions';
18 17
 import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
19 18
 import { renderConnectionStatus } from '../../actions.web';
20 19
 
@@ -196,7 +195,6 @@ class LocalVideoMenuTriggerButton extends Component<Props> {
196 195
      */
197 196
     _onPopoverOpen() {
198 197
         this.props.dispatch(setParticipantContextMenuOpen(true));
199
-        this.props.dispatch(hideToolboxOnTileView());
200 198
     }
201 199
 
202 200
     _onPopoverClose: () => void;

+ 0
- 2
react/features/video-menu/components/web/RemoteVideoMenuTriggerButton.js View File

@@ -13,7 +13,6 @@ import { Popover } from '../../../base/popover';
13 13
 import { connect } from '../../../base/redux';
14 14
 import { setParticipantContextMenuOpen } from '../../../base/responsive-ui/actions';
15 15
 import { requestRemoteControl, stopController } from '../../../remote-control';
16
-import { hideToolboxOnTileView } from '../../../toolbox/actions';
17 16
 import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
18 17
 import { renderConnectionStatus } from '../../actions.web';
19 18
 
@@ -234,7 +233,6 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
234 233
      */
235 234
     _onPopoverOpen() {
236 235
         this.props.dispatch(setParticipantContextMenuOpen(true));
237
-        this.props.dispatch(hideToolboxOnTileView());
238 236
     }
239 237
 
240 238
     _onPopoverClose: () => void;

Loading…
Cancel
Save