Merge remote-tracking branch 'origin/develop' into develop
						commit
						125082a76d
					
				|  | @ -34,7 +34,7 @@ limitations under the License. | |||
|     width: 100%; | ||||
| } | ||||
| 
 | ||||
| .mx_MessageComposer_row div:last-child{ | ||||
| .mx_MessageComposer_row > div:last-child{ | ||||
|     padding-right: 0; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -94,6 +94,16 @@ export default class WidgetMessaging { | |||
|             }); | ||||
|     } | ||||
| 
 | ||||
|     sendVisibility(visible) { | ||||
|         return this.messageToWidget({ | ||||
|             api: OUTBOUND_API_NAME, | ||||
|             action: "visibility", | ||||
|             visible, | ||||
|         }) | ||||
|         .catch((error) => { | ||||
|             console.error("Failed to send visibility: ", error); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     start() { | ||||
|         this.fromWidget.addEndpoint(this.widgetId, this.widgetUrl); | ||||
|  |  | |||
|  | @ -26,9 +26,21 @@ import PropTypes from 'prop-types'; | |||
| // of doing reusable widgets like dialog boxes & menus where we go and
 | ||||
| // pass in a custom control as the actual body.
 | ||||
| 
 | ||||
| module.exports = { | ||||
|     ContextualMenuContainerId: "mx_ContextualMenu_Container", | ||||
| const ContextualMenuContainerId = "mx_ContextualMenu_Container"; | ||||
| 
 | ||||
| function getOrCreateContainer() { | ||||
|     let container = document.getElementById(ContextualMenuContainerId); | ||||
| 
 | ||||
|     if (!container) { | ||||
|         container = document.createElement("div"); | ||||
|         container.id = ContextualMenuContainerId; | ||||
|         document.body.appendChild(container); | ||||
|     } | ||||
| 
 | ||||
|     return container; | ||||
| } | ||||
| 
 | ||||
| export default class ContextualMenu extends React.Component { | ||||
|     propTypes: { | ||||
|         top: PropTypes.number, | ||||
|         bottom: PropTypes.number, | ||||
|  | @ -45,39 +57,18 @@ module.exports = { | |||
|         menuPaddingRight: PropTypes.number, | ||||
|         menuPaddingBottom: PropTypes.number, | ||||
|         menuPaddingLeft: PropTypes.number, | ||||
|     }, | ||||
| 
 | ||||
|     getOrCreateContainer: function() { | ||||
|         let container = document.getElementById(this.ContextualMenuContainerId); | ||||
| 
 | ||||
|         if (!container) { | ||||
|             container = document.createElement("div"); | ||||
|             container.id = this.ContextualMenuContainerId; | ||||
|             document.body.appendChild(container); | ||||
|         } | ||||
| 
 | ||||
|         return container; | ||||
|     }, | ||||
| 
 | ||||
|     createMenu: function(Element, props) { | ||||
|         const self = this; | ||||
| 
 | ||||
|         const closeMenu = function(...args) { | ||||
|             ReactDOM.unmountComponentAtNode(self.getOrCreateContainer()); | ||||
| 
 | ||||
|             if (props && props.onFinished) { | ||||
|                 props.onFinished.apply(null, args); | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         // Close the menu on window resize
 | ||||
|         const windowResize = function() { | ||||
|             closeMenu(); | ||||
|         }; | ||||
|         // If true, insert an invisible screen-sized element behind the
 | ||||
|         // menu that when clicked will close it.
 | ||||
|         hasBackground: PropTypes.bool, | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|         const position = {}; | ||||
|         let chevronFace = null; | ||||
| 
 | ||||
|         const props = this.props; | ||||
| 
 | ||||
|         if (props.top) { | ||||
|             position.top = props.top; | ||||
|         } else { | ||||
|  | @ -158,21 +149,40 @@ module.exports = { | |||
|             menuStyle["paddingRight"] = props.menuPaddingRight; | ||||
|         } | ||||
| 
 | ||||
|         const ElementClass = props.elementClass; | ||||
| 
 | ||||
|         // FIXME: If a menu uses getDefaultProps it clobbers the onFinished
 | ||||
|         // property set here so you can't close the menu from a button click!
 | ||||
|         const menu = ( | ||||
|             <div className={className} style={position}> | ||||
|                 <div className={menuClasses} style={menuStyle}> | ||||
|                     { chevron } | ||||
|                     <Element {...props} onFinished={closeMenu} onResize={windowResize} /> | ||||
|                 </div> | ||||
|                 <div className="mx_ContextualMenu_background" onClick={closeMenu}></div> | ||||
|                 <style>{ chevronCSS }</style> | ||||
|         return <div className={className} style={position}> | ||||
|             <div className={menuClasses} style={menuStyle}> | ||||
|                 { chevron } | ||||
|                 <ElementClass {...props} onFinished={props.closeMenu} onResize={props.windowResize} /> | ||||
|             </div> | ||||
|         ); | ||||
|             { props.hasBackground && <div className="mx_ContextualMenu_background" onClick={props.closeMenu}></div> } | ||||
|             <style>{ chevronCSS }</style> | ||||
|         </div>; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|         ReactDOM.render(menu, this.getOrCreateContainer()); | ||||
| export function createMenu(ElementClass, props) { | ||||
|     const closeMenu = function(...args) { | ||||
|         ReactDOM.unmountComponentAtNode(getOrCreateContainer()); | ||||
| 
 | ||||
|         return {close: closeMenu}; | ||||
|     }, | ||||
| }; | ||||
|         if (props && props.onFinished) { | ||||
|             props.onFinished.apply(null, args); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // We only reference closeMenu once per call to createMenu
 | ||||
|     const menu = <ContextualMenu | ||||
|         {...props} | ||||
|         hasBackground={true} | ||||
|         elementClass={ElementClass} | ||||
|         closeMenu={closeMenu} // eslint-disable-line react/jsx-no-bind
 | ||||
|         windowResize={closeMenu} // eslint-disable-line react/jsx-no-bind
 | ||||
|     />; | ||||
| 
 | ||||
|     ReactDOM.render(menu, getOrCreateContainer()); | ||||
| 
 | ||||
|     return {close: closeMenu}; | ||||
| } | ||||
|  |  | |||
|  | @ -169,6 +169,13 @@ export default class AppTile extends React.Component { | |||
|         this.dispatcherRef = dis.register(this._onWidgetAction); | ||||
|     } | ||||
| 
 | ||||
|     componentDidUpdate() { | ||||
|         // Allow parents to access widget messaging
 | ||||
|         if (this.props.collectWidgetMessaging) { | ||||
|             this.props.collectWidgetMessaging(this.widgetMessaging); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     componentWillUnmount() { | ||||
|         // Widget action listeners
 | ||||
|         dis.unregister(this.dispatcherRef); | ||||
|  | @ -357,6 +364,9 @@ export default class AppTile extends React.Component { | |||
|         if (!this.widgetMessaging) { | ||||
|             this._onInitialLoad(); | ||||
|         } | ||||
|         if (this._exposeWidgetMessaging) { | ||||
|             this._exposeWidgetMessaging(this.widgetMessaging); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | @ -394,6 +404,7 @@ export default class AppTile extends React.Component { | |||
|         }).catch((err) => { | ||||
|             console.log(`Failed to get capabilities for widget type ${this.props.type}`, this.props.id, err); | ||||
|         }); | ||||
| 
 | ||||
|         this.setState({loading: false}); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,114 @@ | |||
| /* | ||||
| Copyright 2018 New Vector Ltd. | ||||
| 
 | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
| 
 | ||||
|     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| 
 | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| const React = require('react'); | ||||
| const ReactDOM = require('react-dom'); | ||||
| 
 | ||||
| // Shamelessly ripped off Modal.js.  There's probably a better way
 | ||||
| // of doing reusable widgets like dialog boxes & menus where we go and
 | ||||
| // pass in a custom control as the actual body.
 | ||||
| 
 | ||||
| const ContainerId = "mx_PersistedElement"; | ||||
| 
 | ||||
| function getOrCreateContainer() { | ||||
|     let container = document.getElementById(ContainerId); | ||||
| 
 | ||||
|     if (!container) { | ||||
|         container = document.createElement("div"); | ||||
|         container.id = ContainerId; | ||||
|         document.body.appendChild(container); | ||||
|     } | ||||
| 
 | ||||
|     return container; | ||||
| } | ||||
| 
 | ||||
| // Greater than that of the ContextualMenu
 | ||||
| const PE_Z_INDEX = 3000; | ||||
| 
 | ||||
| /* | ||||
|  * Class of component that renders its children in a separate ReactDOM virtual tree | ||||
|  * in a container element appended to document.body. | ||||
|  * | ||||
|  * This prevents the children from being unmounted when the parent of PersistedElement | ||||
|  * unmounts, allowing them to persist. | ||||
|  * | ||||
|  * When PE is unmounted, it hides the children using CSS. When mounted or updated, the | ||||
|  * children are made visible and are positioned into a div that is given the same | ||||
|  * bounding rect as the parent of PE. | ||||
|  */ | ||||
| export default class PersistedElement extends React.Component { | ||||
|     constructor() { | ||||
|         super(); | ||||
|         this.collectChildContainer = this.collectChildContainer.bind(this); | ||||
|         this.collectChild = this.collectChild.bind(this); | ||||
|     } | ||||
| 
 | ||||
|     collectChildContainer(ref) { | ||||
|         this.childContainer = ref; | ||||
|     } | ||||
| 
 | ||||
|     collectChild(ref) { | ||||
|         this.child = ref; | ||||
|         this.updateChild(); | ||||
|     } | ||||
| 
 | ||||
|     componentDidMount() { | ||||
|         this.updateChild(); | ||||
|     } | ||||
| 
 | ||||
|     componentDidUpdate() { | ||||
|         this.updateChild(); | ||||
|     } | ||||
| 
 | ||||
|     componentWillUnmount() { | ||||
|         this.updateChildVisibility(this.child, false); | ||||
|     } | ||||
| 
 | ||||
|     updateChild() { | ||||
|         this.updateChildPosition(this.child, this.childContainer); | ||||
|         this.updateChildVisibility(this.child, true); | ||||
|     } | ||||
| 
 | ||||
|     updateChildVisibility(child, visible) { | ||||
|         if (!child) return; | ||||
|         child.style.display = visible ? 'block' : 'none'; | ||||
|     } | ||||
| 
 | ||||
|     updateChildPosition(child, parent) { | ||||
|         if (!child || !parent) return; | ||||
| 
 | ||||
|         const parentRect = parent.getBoundingClientRect(); | ||||
|         Object.assign(child.style, { | ||||
|             position: 'absolute', | ||||
|             top: parentRect.top + 'px', | ||||
|             left: parentRect.left + 'px', | ||||
|             width: parentRect.width + 'px', | ||||
|             height: parentRect.height + 'px', | ||||
|             zIndex: PE_Z_INDEX, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|         const content = <div ref={this.collectChild}> | ||||
|             {this.props.children} | ||||
|         </div>; | ||||
| 
 | ||||
|         ReactDOM.render(content, getOrCreateContainer()); | ||||
| 
 | ||||
|         return <div ref={this.collectChildContainer}></div>; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -17,7 +17,6 @@ import React from 'react'; | |||
| import { _t } from '../../../languageHandler'; | ||||
| import Widgets from '../../../utils/widgets'; | ||||
| import AppTile from '../elements/AppTile'; | ||||
| import ContextualMenu from '../../structures/ContextualMenu'; | ||||
| import MatrixClientPeg from '../../../MatrixClientPeg'; | ||||
| import Modal from '../../../Modal'; | ||||
| import sdk from '../../../index'; | ||||
|  | @ -36,21 +35,28 @@ export default class Stickerpicker extends React.Component { | |||
|         this._launchManageIntegrations = this._launchManageIntegrations.bind(this); | ||||
|         this._removeStickerpickerWidgets = this._removeStickerpickerWidgets.bind(this); | ||||
|         this._onWidgetAction = this._onWidgetAction.bind(this); | ||||
|         this._onResize = this._onResize.bind(this); | ||||
|         this._onFinished = this._onFinished.bind(this); | ||||
| 
 | ||||
|         this._collectWidgetMessaging = this._collectWidgetMessaging.bind(this); | ||||
| 
 | ||||
|         this.popoverWidth = 300; | ||||
|         this.popoverHeight = 300; | ||||
| 
 | ||||
|         this.state = { | ||||
|             showStickers: false, | ||||
|             imError: null, | ||||
|             stickerpickerX: null, | ||||
|             stickerpickerY: null, | ||||
|             stickerpickerWidget: null, | ||||
|             widgetId: null, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     _removeStickerpickerWidgets() { | ||||
|         console.warn('Removing Stickerpicker widgets'); | ||||
|         if (this.widgetId) { | ||||
|             this.scalarClient.disableWidgetAssets(widgetType, this.widgetId).then(() => { | ||||
|         if (this.state.widgetId) { | ||||
|             this.scalarClient.disableWidgetAssets(widgetType, this.state.widgetId).then(() => { | ||||
|                 console.warn('Assets disabled'); | ||||
|             }).catch((err) => { | ||||
|                 console.error('Failed to disable assets'); | ||||
|  | @ -59,8 +65,7 @@ export default class Stickerpicker extends React.Component { | |||
|             console.warn('No widget ID specified, not disabling assets'); | ||||
|         } | ||||
| 
 | ||||
|         // Wrap this in a timeout in order to avoid the DOM node from being pulled from under its feet
 | ||||
|         setTimeout(() => this.stickersMenu.close()); | ||||
|         this.setState({showStickers: false}); | ||||
|         Widgets.removeStickerpickerWidgets().then(() => { | ||||
|             this.forceUpdate(); | ||||
|         }).catch((e) => { | ||||
|  | @ -69,6 +74,9 @@ export default class Stickerpicker extends React.Component { | |||
|     } | ||||
| 
 | ||||
|     componentDidMount() { | ||||
|         // Close the sticker picker when the window resizes
 | ||||
|         window.addEventListener('resize', this._onResize); | ||||
| 
 | ||||
|         this.scalarClient = null; | ||||
|         if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) { | ||||
|             this.scalarClient = new ScalarAuthClient(); | ||||
|  | @ -82,14 +90,24 @@ export default class Stickerpicker extends React.Component { | |||
|         if (!this.state.imError) { | ||||
|             this.dispatcherRef = dis.register(this._onWidgetAction); | ||||
|         } | ||||
|         const stickerpickerWidget = Widgets.getStickerpickerWidgets()[0]; | ||||
|         this.setState({ | ||||
|             stickerpickerWidget, | ||||
|             widgetId: stickerpickerWidget ? stickerpickerWidget.id : null, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     componentWillUnmount() { | ||||
|         window.removeEventListener('resize', this._onResize); | ||||
|         if (this.dispatcherRef) { | ||||
|             dis.unregister(this.dispatcherRef); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     componentDidUpdate(prevProps, prevState) { | ||||
|         this._sendVisibilityToWidget(this.state.showStickers); | ||||
|     } | ||||
| 
 | ||||
|     _imError(errorMsg, e) { | ||||
|         console.error(errorMsg, e); | ||||
|         this.setState({ | ||||
|  | @ -102,9 +120,7 @@ export default class Stickerpicker extends React.Component { | |||
|         if (payload.action === "user_widget_updated") { | ||||
|             this.forceUpdate(); | ||||
|         } else if (payload.action === "stickerpicker_close") { | ||||
|             // Wrap this in a timeout in order to avoid the DOM node from being
 | ||||
|             // pulled from under its feet
 | ||||
|             setTimeout(() => this.stickersMenu.close()); | ||||
|             this.setState({showStickers: false}); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -127,6 +143,21 @@ export default class Stickerpicker extends React.Component { | |||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     _collectWidgetMessaging(widgetMessaging) { | ||||
|         this._appWidgetMessaging = widgetMessaging; | ||||
| 
 | ||||
|         // Do this now instead of in componentDidMount because we might not have had the
 | ||||
|         // reference to widgetMessaging when mounting
 | ||||
|         this._sendVisibilityToWidget(true); | ||||
|     } | ||||
| 
 | ||||
|     _sendVisibilityToWidget(visible) { | ||||
|         if (this._appWidgetMessaging && visible !== this._prevSentVisibility) { | ||||
|             this._appWidgetMessaging.sendVisibility(visible); | ||||
|             this._prevSentVisibility = visible; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     _getStickerpickerContent() { | ||||
|         // Handle Integration Manager errors
 | ||||
|         if (this.state._imError) { | ||||
|  | @ -137,14 +168,18 @@ export default class Stickerpicker extends React.Component { | |||
|         // TODO - Add support for Stickerpickers from multiple app stores.
 | ||||
|         // Render content from multiple stickerpack sources, each within their
 | ||||
|         // own iframe, within the stickerpicker UI element.
 | ||||
|         const stickerpickerWidget = Widgets.getStickerpickerWidgets()[0]; | ||||
|         const stickerpickerWidget = this.state.stickerpickerWidget; | ||||
|         let stickersContent; | ||||
| 
 | ||||
|         // Use a separate ReactDOM tree to render the AppTile separately so that it persists and does
 | ||||
|         // not unmount when we (a) close the sticker picker (b) switch rooms. It's properties are still
 | ||||
|         // updated.
 | ||||
|         const PersistedElement = sdk.getComponent("elements.PersistedElement"); | ||||
| 
 | ||||
|         // Load stickerpack content
 | ||||
|         if (stickerpickerWidget && stickerpickerWidget.content && stickerpickerWidget.content.url) { | ||||
|             // Set default name
 | ||||
|             stickerpickerWidget.content.name = stickerpickerWidget.name || _t("Stickerpack"); | ||||
|             this.widgetId = stickerpickerWidget.id; | ||||
| 
 | ||||
|             stickersContent = ( | ||||
|                 <div className='mx_Stickers_content_container'> | ||||
|  | @ -157,7 +192,9 @@ export default class Stickerpicker extends React.Component { | |||
|                             width: this.popoverWidth, | ||||
|                         }} | ||||
|                     > | ||||
|                     <PersistedElement> | ||||
|                         <AppTile | ||||
|                             collectWidgetMessaging={this._collectWidgetMessaging} | ||||
|                             id={stickerpickerWidget.id} | ||||
|                             url={stickerpickerWidget.content.url} | ||||
|                             name={stickerpickerWidget.content.name} | ||||
|  | @ -177,9 +214,10 @@ export default class Stickerpicker extends React.Component { | |||
|                             showPopout={false} | ||||
|                             onMinimiseClick={this._onHideStickersClick} | ||||
|                             handleMinimisePointerEvents={true} | ||||
|                             whitelistCapabilities={['m.sticker']} | ||||
|                             whitelistCapabilities={['m.sticker', 'visibility']} | ||||
|                             userWidget={true} | ||||
|                         /> | ||||
|                     </PersistedElement> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             ); | ||||
|  | @ -187,12 +225,7 @@ export default class Stickerpicker extends React.Component { | |||
|             // Default content to show if stickerpicker widget not added
 | ||||
|             console.warn("No available sticker picker widgets"); | ||||
|             stickersContent = this._defaultStickerpickerContent(); | ||||
|             this.widgetId = null; | ||||
|             this.forceUpdate(); | ||||
|         } | ||||
|         this.setState({ | ||||
|             showStickers: false, | ||||
|         }); | ||||
|         return stickersContent; | ||||
|     } | ||||
| 
 | ||||
|  | @ -202,29 +235,17 @@ export default class Stickerpicker extends React.Component { | |||
|      * @param  {Event} e Event that triggered the function | ||||
|      */ | ||||
|     _onShowStickersClick(e) { | ||||
|         const GenericElementContextMenu = sdk.getComponent('context_menus.GenericElementContextMenu'); | ||||
|         const buttonRect = e.target.getBoundingClientRect(); | ||||
| 
 | ||||
|         // The window X and Y offsets are to adjust position when zoomed in to page
 | ||||
|         const x = buttonRect.right + window.pageXOffset - 42; | ||||
|         const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19; | ||||
|         // const self = this;
 | ||||
|         this.stickersMenu = ContextualMenu.createMenu(GenericElementContextMenu, { | ||||
|             chevronOffset: 10, | ||||
|             chevronFace: 'bottom', | ||||
|             left: x, | ||||
|             top: y, | ||||
|             menuWidth: this.popoverWidth, | ||||
|             menuHeight: this.popoverHeight, | ||||
|             element: this._getStickerpickerContent(), | ||||
|             onFinished: this._onFinished, | ||||
|             menuPaddingTop: 0, | ||||
|             menuPaddingLeft: 0, | ||||
|             menuPaddingRight: 0, | ||||
| 
 | ||||
|         this.setState({ | ||||
|             showStickers: true, | ||||
|             stickerPickerX: x, | ||||
|             stickerPickerY: y, | ||||
|         }); | ||||
| 
 | ||||
| 
 | ||||
|         this.setState({showStickers: true}); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | @ -232,7 +253,14 @@ export default class Stickerpicker extends React.Component { | |||
|      * @param  {Event} ev Event that triggered the function call | ||||
|      */ | ||||
|     _onHideStickersClick(ev) { | ||||
|         setTimeout(() => this.stickersMenu.close()); | ||||
|         this.setState({showStickers: false}); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Called when the window is resized | ||||
|      */ | ||||
|     _onResize() { | ||||
|         this.setState({showStickers: false}); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | @ -251,20 +279,37 @@ export default class Stickerpicker extends React.Component { | |||
|                 this.scalarClient.getScalarInterfaceUrlForRoom( | ||||
|                     this.props.room, | ||||
|                     'type_' + widgetType, | ||||
|                     this.widgetId, | ||||
|                     this.state.widgetId, | ||||
|                 ) : | ||||
|                 null; | ||||
|         Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, { | ||||
|             src: src, | ||||
|         }, "mx_IntegrationsManager"); | ||||
| 
 | ||||
|         // Wrap this in a timeout in order to avoid the DOM node from being pulled from under its feet
 | ||||
|         setTimeout(() => this.stickersMenu.close()); | ||||
|         this.setState({showStickers: false}); | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
|         const TintableSvg = sdk.getComponent("elements.TintableSvg"); | ||||
|         const ContextualMenu = sdk.getComponent('structures.ContextualMenu'); | ||||
|         const GenericElementContextMenu = sdk.getComponent('context_menus.GenericElementContextMenu'); | ||||
|         let stickersButton; | ||||
| 
 | ||||
|         const stickerPicker = <ContextualMenu | ||||
|             elementClass={GenericElementContextMenu} | ||||
|             chevronOffset={10} | ||||
|             chevronFace={'bottom'} | ||||
|             left={this.state.stickerPickerX} | ||||
|             top={this.state.stickerPickerY} | ||||
|             menuWidth={this.popoverWidth} | ||||
|             menuHeight={this.popoverHeight} | ||||
|             element={this._getStickerpickerContent()} | ||||
|             onFinished={this._onFinished} | ||||
|             menuPaddingTop={0} | ||||
|             menuPaddingLeft={0} | ||||
|             menuPaddingRight={0} | ||||
|         />; | ||||
| 
 | ||||
|         if (this.state.showStickers) { | ||||
|             // Show hide-stickers button
 | ||||
|             stickersButton = | ||||
|  | @ -289,6 +334,9 @@ export default class Stickerpicker extends React.Component { | |||
|                     <TintableSvg src="img/icons-show-stickers.svg" width="35" height="35" /> | ||||
|                 </div>; | ||||
|         } | ||||
|         return stickersButton; | ||||
|         return <div> | ||||
|             {stickersButton} | ||||
|             {this.state.showStickers && stickerPicker} | ||||
|         </div>; | ||||
|     } | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Weblate
						Weblate