From 7d0f6532ec69efc0c1c123e7afe3fdcb06c1382f Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 9 May 2018 13:44:41 +0100 Subject: [PATCH 01/23] Prepare changelog for v0.12.4-rc.1 --- CHANGELOG.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 334461bffb..30099319a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,68 @@ +Changes in [0.12.4-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.12.4-rc.1) (2018-05-09) +=============================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.12.3...v0.12.4-rc.1) + + * Update from Weblate. + [\#1881](https://github.com/matrix-org/matrix-react-sdk/pull/1881) + * Pin lolex at 2.3.2 to avoid bug causing tests to fail + [\#1880](https://github.com/matrix-org/matrix-react-sdk/pull/1880) + * Replies: un-break click-to-mention on SenderProfile for reply&preview + [\#1878](https://github.com/matrix-org/matrix-react-sdk/pull/1878) + * Add tests for RoomList + [\#1877](https://github.com/matrix-org/matrix-react-sdk/pull/1877) + * Fix crash when browser doesn't report page change measurement + [\#1874](https://github.com/matrix-org/matrix-react-sdk/pull/1874) + * fix thinko when changing from ClientPeg to context in static method (DUH) + [\#1875](https://github.com/matrix-org/matrix-react-sdk/pull/1875) + * Fix Replies :D + [\#1873](https://github.com/matrix-org/matrix-react-sdk/pull/1873) + * Update eslint-plugin-react + [\#1871](https://github.com/matrix-org/matrix-react-sdk/pull/1871) + * relax lint for jsx-curly-spacing and arrow-parens + [\#1872](https://github.com/matrix-org/matrix-react-sdk/pull/1872) + * Use develop js-sdk in jenkins build + [\#1870](https://github.com/matrix-org/matrix-react-sdk/pull/1870) + * Replies + [\#1741](https://github.com/matrix-org/matrix-react-sdk/pull/1741) + * Use the right js-sdk branch when testing + [\#1869](https://github.com/matrix-org/matrix-react-sdk/pull/1869) + * Prevent error responses wedging group request concurrency limit + [\#1867](https://github.com/matrix-org/matrix-react-sdk/pull/1867) + * Refresh group rooms and members when selecting a tag + [\#1868](https://github.com/matrix-org/matrix-react-sdk/pull/1868) + * Refactor GroupStores into one global GroupStore + [\#1866](https://github.com/matrix-org/matrix-react-sdk/pull/1866) + * Switch back to using blob URLs for rendering e2e attachments + [\#1864](https://github.com/matrix-org/matrix-react-sdk/pull/1864) + * Hide inline encryption icons except when hovering over a message + [\#1845](https://github.com/matrix-org/matrix-react-sdk/pull/1845) + * UI fixes in SessionRestoreErrorDialog + [\#1860](https://github.com/matrix-org/matrix-react-sdk/pull/1860) + * Fix UX issues with bug report dialog + [\#1863](https://github.com/matrix-org/matrix-react-sdk/pull/1863) + * fix ugly img errors and correctly render SVG thumbnails + [\#1865](https://github.com/matrix-org/matrix-react-sdk/pull/1865) + * Fix error handling on session restore + [\#1859](https://github.com/matrix-org/matrix-react-sdk/pull/1859) + * Add tests for GroupView + [\#1862](https://github.com/matrix-org/matrix-react-sdk/pull/1862) + * Update version of hoek + [\#1861](https://github.com/matrix-org/matrix-react-sdk/pull/1861) + * Fix bug that caused crash when analytics HS/IS whitelists not specified + [\#1858](https://github.com/matrix-org/matrix-react-sdk/pull/1858) + * Fix Analytics to not import DEFAULTS, therefore avoiding NPE + [\#1857](https://github.com/matrix-org/matrix-react-sdk/pull/1857) + * Null check piwik config before using it + [\#1856](https://github.com/matrix-org/matrix-react-sdk/pull/1856) + * Track actual window location origin and hash + [\#1853](https://github.com/matrix-org/matrix-react-sdk/pull/1853) + * Replace document.origin with window.location.origin + [\#1855](https://github.com/matrix-org/matrix-react-sdk/pull/1855) + * Optionally hide widget popout button. + [\#1854](https://github.com/matrix-org/matrix-react-sdk/pull/1854) + * Add a button to 'pop out' widgets in to their own tab. + [\#1851](https://github.com/matrix-org/matrix-react-sdk/pull/1851) + Changes in [0.12.3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.12.3) (2018-04-30) ===================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.12.3-rc.3...v0.12.3) From ddf98705f2e4fcee3f8449a4613bae6f1c7fbccb Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 9 May 2018 13:44:42 +0100 Subject: [PATCH 02/23] v0.12.4-rc.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9c71c1399c..eb6c2e33b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "0.12.3", + "version": "0.12.4-rc.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From 97b9316ec760376a7ac402e40c2a968e334f2091 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 9 May 2018 15:48:53 +0100 Subject: [PATCH 03/23] Fix issue incorrect positioning with widget loading indicator by making sure to apply the correct CSS class to the parent --- src/components/views/elements/AppTile.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 007eb8126c..bf24da506b 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -534,7 +534,11 @@ export default class AppTile extends React.Component { ); if (this.state.initialising) { - appTileBody = loadingElement; + appTileBody = ( +
+ { loadingElement } +
+ ); } else if (this.state.hasPermissionToLoad == true) { if (this.isMixedContent()) { appTileBody = ( From 0a326dc6a02c664f58fbe65d21ab568a969a1fd5 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 9 May 2018 14:59:35 +0100 Subject: [PATCH 04/23] Take feature_sticker_messagse out of labs --- src/components/views/rooms/MessageComposer.js | 5 +---- src/settings/Settings.js | 6 ------ 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 24b07eb3bf..28a90b375a 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -338,10 +338,7 @@ export default class MessageComposer extends React.Component { } } - let stickerpickerButton; - if (SettingsStore.isFeatureEnabled('feature_sticker_messages')) { - stickerpickerButton = ; - } + const stickerpickerButton = ; controls.push( Date: Wed, 9 May 2018 16:41:45 +0100 Subject: [PATCH 05/23] Improve appearance of short-lived app loading spinner by hiding it for 500ms - thereby only showing it if the loading is taking a long time. --- res/css/views/rooms/_AppsDrawer.scss | 13 +++++++++++++ src/components/views/elements/AppTile.js | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index 0cf3e7b9cb..28d432686d 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -266,6 +266,19 @@ form.mx_Custom_Widget_Form div { right: 0; } +.mx_AppLoading_spinner_fadeIn { + animation-fill-mode: backwards; + animation-duration: 200ms; + animation-delay: 500ms; + animation-name: mx_AppLoading_spinner_fadeIn_animation; +} + +@keyframes mx_AppLoading_spinner_fadeIn_animation { + from { opacity: 0 } + to { opacity: 1 } +} + + .mx_AppLoading iframe { display: none; } diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index bf24da506b..7c4ab13c5a 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -529,7 +529,7 @@ export default class AppTile extends React.Component { if (this.props.show) { const loadingElement = ( -
+
); From 54fdb234d5e780ab94ab7e33af1f98e5832ec20e Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 9 May 2018 17:34:53 +0100 Subject: [PATCH 06/23] Prepare changelog for v0.12.4-rc.2 --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30099319a9..8f8e89250e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +Changes in [0.12.4-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.12.4-rc.2) (2018-05-09) +=============================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.12.4-rc.1...v0.12.4-rc.2) + + * Improve appearance of short-lived widget loading spinner + * Make sticker picker fully-fledged feature + * Fix incorrect positioning with widget loading indicator + Changes in [0.12.4-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.12.4-rc.1) (2018-05-09) =============================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.12.3...v0.12.4-rc.1) From 6a0bff6685acd3e68b5b7221310140ed3a9aef48 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 9 May 2018 17:34:54 +0100 Subject: [PATCH 07/23] v0.12.4-rc.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eb6c2e33b3..3958e1b3d0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "0.12.4-rc.1", + "version": "0.12.4-rc.2", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From d503c86576380c4a7bdf1504ea4b8cdb76d3c37c Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Thu, 10 May 2018 16:00:58 +0100 Subject: [PATCH 08/23] Factor out ContextualMenu component --- src/components/structures/ContextualMenu.js | 88 +++++++++++---------- 1 file changed, 47 insertions(+), 41 deletions(-) diff --git a/src/components/structures/ContextualMenu.js b/src/components/structures/ContextualMenu.js index 0e2df890f3..99d1f892b9 100644 --- a/src/components/structures/ContextualMenu.js +++ b/src/components/structures/ContextualMenu.js @@ -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; +} + +class ContextualMenu extends React.Component { propTypes: { top: PropTypes.number, bottom: PropTypes.number, @@ -45,39 +57,14 @@ 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(); - }; + } + render() { const position = {}; let chevronFace = null; + const props = this.props; + if (props.top) { position.top = props.top; } else { @@ -158,20 +145,39 @@ 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 = ( -
-
- { chevron } - -
-
- + return
+
+ { chevron } +
- ); +
+ +
; + } +} - ReactDOM.render(menu, this.getOrCreateContainer()); +module.exports = { + createMenu: function(ElementClass, props) { + const closeMenu = function(...args) { + ReactDOM.unmountComponentAtNode(getOrCreateContainer()); + + if (props && props.onFinished) { + props.onFinished.apply(null, args); + } + }; + + const menu = ; + + ReactDOM.render(menu, getOrCreateContainer()); return {close: closeMenu}; }, From 9ec2570eab2330e6e079fad1537af28f0f1996ee Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Thu, 10 May 2018 17:51:49 +0100 Subject: [PATCH 09/23] Export ContextualMenu component, with added `hasBackground` property , which is only enabled when `createMenu` is used. --- src/components/structures/ContextualMenu.js | 43 +++++++++++---------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/components/structures/ContextualMenu.js b/src/components/structures/ContextualMenu.js index 99d1f892b9..17f1a23e14 100644 --- a/src/components/structures/ContextualMenu.js +++ b/src/components/structures/ContextualMenu.js @@ -40,7 +40,7 @@ function getOrCreateContainer() { return container; } -class ContextualMenu extends React.Component { +export default class ContextualMenu extends React.Component { propTypes: { top: PropTypes.number, bottom: PropTypes.number, @@ -57,6 +57,10 @@ class ContextualMenu extends React.Component { menuPaddingRight: PropTypes.number, menuPaddingBottom: PropTypes.number, menuPaddingLeft: PropTypes.number, + + // If true, insert an invisible screen-sized element behind the + // menu that when clicked will close it. + hasBackground: PropTypes.bool, } render() { @@ -154,31 +158,30 @@ class ContextualMenu extends React.Component { { chevron }
-
+ { props.hasBackground &&
}
; } } -module.exports = { - createMenu: function(ElementClass, props) { - const closeMenu = function(...args) { - ReactDOM.unmountComponentAtNode(getOrCreateContainer()); +export function createMenu(ElementClass, props) { + const closeMenu = function(...args) { + ReactDOM.unmountComponentAtNode(getOrCreateContainer()); - if (props && props.onFinished) { - props.onFinished.apply(null, args); - } - }; + if (props && props.onFinished) { + props.onFinished.apply(null, args); + } + }; - const menu = ; + const menu = ; - ReactDOM.render(menu, getOrCreateContainer()); + ReactDOM.render(menu, getOrCreateContainer()); - return {close: closeMenu}; - }, -}; + return {close: closeMenu}; +} From 2d2b529f80f8805bdce7d39c27ac0f61397c388d Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 11 May 2018 14:47:57 +0100 Subject: [PATCH 10/23] Use new ContextualMenu component in Stickerpicker --- src/components/views/rooms/Stickerpicker.js | 93 +++++++++++++-------- 1 file changed, 56 insertions(+), 37 deletions(-) diff --git a/src/components/views/rooms/Stickerpicker.js b/src/components/views/rooms/Stickerpicker.js index c055c67cd3..a252c0c886 100644 --- a/src/components/views/rooms/Stickerpicker.js +++ b/src/components/views/rooms/Stickerpicker.js @@ -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,6 +35,7 @@ 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.popoverWidth = 300; @@ -44,13 +44,17 @@ export default class Stickerpicker extends React.Component { 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 +63,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 +72,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,9 +88,15 @@ 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); } @@ -102,9 +114,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}); } } @@ -137,14 +147,13 @@ 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; // 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 = (
@@ -186,12 +195,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; } @@ -201,29 +205,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}); } /** @@ -231,7 +223,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}); } /** @@ -250,20 +249,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 = ; + if (this.state.showStickers) { // Show hide-stickers button stickersButton = @@ -288,6 +304,9 @@ export default class Stickerpicker extends React.Component {
; } - return stickersButton; + return
+ {stickersButton} + {this.state.showStickers && stickerPicker} +
; } } From fee480289ce4ab31173f55f3a4eec30521e39e0b Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 11 May 2018 14:49:50 +0100 Subject: [PATCH 11/23] Use correct CSS selector in message composer to stop any last div from having its right-padding removed --- res/css/views/rooms/_MessageComposer.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index 2e8f07b7ef..0a708a8edc 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -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; } From 42c59b59239bacd4a6652986f2338a80d8bd9c5e Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 11 May 2018 15:07:51 +0100 Subject: [PATCH 12/23] Make AppTile in Stickerpicker persistent using PersistedElement --- .../views/elements/PersistedElement.js | 116 ++++++++++++++++++ src/components/views/rooms/Stickerpicker.js | 7 ++ 2 files changed, 123 insertions(+) create mode 100644 src/components/views/elements/PersistedElement.js diff --git a/src/components/views/elements/PersistedElement.js b/src/components/views/elements/PersistedElement.js new file mode 100644 index 0000000000..4d8cd4140e --- /dev/null +++ b/src/components/views/elements/PersistedElement.js @@ -0,0 +1,116 @@ +/* +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 classNames = require('classnames'); +const React = require('react'); +const ReactDOM = require('react-dom'); +import PropTypes from 'prop-types'; + +// 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 =
+ {this.props.children} +
; + + ReactDOM.render(content, getOrCreateContainer()); + + return
; + } +} + diff --git a/src/components/views/rooms/Stickerpicker.js b/src/components/views/rooms/Stickerpicker.js index a252c0c886..5c411aa56e 100644 --- a/src/components/views/rooms/Stickerpicker.js +++ b/src/components/views/rooms/Stickerpicker.js @@ -150,6 +150,11 @@ export default class Stickerpicker extends React.Component { 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 @@ -166,6 +171,7 @@ export default class Stickerpicker extends React.Component { width: this.popoverWidth, }} > + + ); From bd0301c666686e389b966f9038a494f8f061390e Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 11 May 2018 16:22:54 +0100 Subject: [PATCH 13/23] Add API to send visibiliy actions to widgets --- src/WidgetMessaging.js | 10 ++++++++++ src/components/views/elements/AppTile.js | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/src/WidgetMessaging.js b/src/WidgetMessaging.js index effd96dacf..86eaa0b59b 100644 --- a/src/WidgetMessaging.js +++ b/src/WidgetMessaging.js @@ -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); diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 7c4ab13c5a..e1be5b11ee 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -389,6 +389,10 @@ 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); }); + + // Allow parents to access widget messaging + if (this.props.collectWidgetMessaging) this.props.collectWidgetMessaging(this.widgetMessaging); + this.setState({loading: false}); } From 06919e22d633a7f0c12b1f91870d2cbe8a1b7126 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 11 May 2018 16:23:18 +0100 Subject: [PATCH 14/23] When stickerpicker made visible, send visibility over postMessage --- src/components/views/elements/AppTile.js | 19 ++++++++++++++++--- src/components/views/rooms/Stickerpicker.js | 19 ++++++++++++++++++- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index e1be5b11ee..5bcb418242 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -167,6 +167,19 @@ export default class AppTile extends React.Component { // Widget action listeners this.dispatcherRef = dis.register(this._onWidgetAction); + + } + + componentDidUpdate() { + // Allow parents to access widget messaging + if (this.props.collectWidgetMessaging) { + this.props.collectWidgetMessaging(new Promise((resolve) => { + if (this.widgetMessaging) resolve(this.widgetMessaging); + + // Expect this to be resolved later + this._exposeWidgetMessaging = resolve; + })); + } } componentWillUnmount() { @@ -352,6 +365,9 @@ export default class AppTile extends React.Component { if (!this.widgetMessaging) { this._onInitialLoad(); } + if (this._exposeWidgetMessaging) { + this._exposeWidgetMessaging(this.widgetMessaging); + } } /** @@ -390,9 +406,6 @@ export default class AppTile extends React.Component { console.log(`Failed to get capabilities for widget type ${this.props.type}`, this.props.id, err); }); - // Allow parents to access widget messaging - if (this.props.collectWidgetMessaging) this.props.collectWidgetMessaging(this.widgetMessaging); - this.setState({loading: false}); } diff --git a/src/components/views/rooms/Stickerpicker.js b/src/components/views/rooms/Stickerpicker.js index 5c411aa56e..0bdf5fe5df 100644 --- a/src/components/views/rooms/Stickerpicker.js +++ b/src/components/views/rooms/Stickerpicker.js @@ -38,6 +38,8 @@ export default class Stickerpicker extends React.Component { this._onResize = this._onResize.bind(this); this._onFinished = this._onFinished.bind(this); + this._collectWidgetMessaging = this._collectWidgetMessaging.bind(this); + this.popoverWidth = 300; this.popoverHeight = 300; @@ -102,6 +104,14 @@ export default class Stickerpicker extends React.Component { } } + componentDidUpdate(prevProps, prevState) { + if (this._appWidgetMessaging && + prevState.showStickers !== this.state.showStickers + ) { + this._appWidgetMessaging.sendVisibility(this.state.showStickers); + } + } + _imError(errorMsg, e) { console.error(errorMsg, e); this.setState({ @@ -137,6 +147,12 @@ export default class Stickerpicker extends React.Component { ); } + async _collectWidgetMessaging(prom) { + const widgetMessaging = await prom; + this._appWidgetMessaging = widgetMessaging; + this._appWidgetMessaging.sendVisibility(true); + } + _getStickerpickerContent() { // Handle Integration Manager errors if (this.state._imError) { @@ -173,6 +189,7 @@ export default class Stickerpicker extends React.Component { > From a4190560405f96e14913103ac258f0ac09a91e61 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 11 May 2018 17:28:12 +0100 Subject: [PATCH 15/23] Delinting --- src/components/structures/ContextualMenu.js | 5 +++-- src/components/views/elements/AppTile.js | 1 - src/components/views/elements/PersistedElement.js | 2 -- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/structures/ContextualMenu.js b/src/components/structures/ContextualMenu.js index 17f1a23e14..daac294d12 100644 --- a/src/components/structures/ContextualMenu.js +++ b/src/components/structures/ContextualMenu.js @@ -173,12 +173,13 @@ export function createMenu(ElementClass, props) { } }; + // We only reference closeMenu once per call to createMenu const menu = ; ReactDOM.render(menu, getOrCreateContainer()); diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 5bcb418242..b345adcc1d 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -167,7 +167,6 @@ export default class AppTile extends React.Component { // Widget action listeners this.dispatcherRef = dis.register(this._onWidgetAction); - } componentDidUpdate() { diff --git a/src/components/views/elements/PersistedElement.js b/src/components/views/elements/PersistedElement.js index 4d8cd4140e..c4bac27b4e 100644 --- a/src/components/views/elements/PersistedElement.js +++ b/src/components/views/elements/PersistedElement.js @@ -14,10 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -const classNames = require('classnames'); const React = require('react'); const ReactDOM = require('react-dom'); -import PropTypes from 'prop-types'; // Shamelessly ripped off Modal.js. There's probably a better way // of doing reusable widgets like dialog boxes & menus where we go and From 9c5c5e282b619a726af57b0f3207d80451b2dff8 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 11 May 2018 18:06:58 +0100 Subject: [PATCH 16/23] Send visibility only when changed, and messaging is available --- src/components/views/elements/AppTile.js | 7 +------ src/components/views/rooms/Stickerpicker.js | 21 +++++++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index b345adcc1d..cd0eb1ce68 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -172,12 +172,7 @@ export default class AppTile extends React.Component { componentDidUpdate() { // Allow parents to access widget messaging if (this.props.collectWidgetMessaging) { - this.props.collectWidgetMessaging(new Promise((resolve) => { - if (this.widgetMessaging) resolve(this.widgetMessaging); - - // Expect this to be resolved later - this._exposeWidgetMessaging = resolve; - })); + this.props.collectWidgetMessaging(this.widgetMessaging); } } diff --git a/src/components/views/rooms/Stickerpicker.js b/src/components/views/rooms/Stickerpicker.js index 0bdf5fe5df..40ce6e6b39 100644 --- a/src/components/views/rooms/Stickerpicker.js +++ b/src/components/views/rooms/Stickerpicker.js @@ -105,11 +105,7 @@ export default class Stickerpicker extends React.Component { } componentDidUpdate(prevProps, prevState) { - if (this._appWidgetMessaging && - prevState.showStickers !== this.state.showStickers - ) { - this._appWidgetMessaging.sendVisibility(this.state.showStickers); - } + this._sendVisibilityToWidget(this.state.showStickers); } _imError(errorMsg, e) { @@ -147,10 +143,19 @@ export default class Stickerpicker extends React.Component { ); } - async _collectWidgetMessaging(prom) { - const widgetMessaging = await prom; + _collectWidgetMessaging(widgetMessaging) { this._appWidgetMessaging = widgetMessaging; - this._appWidgetMessaging.sendVisibility(true); + + // 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() { From a0bde2939ed4480110b02a6d9e81ee3c2a6cb94a Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 11 May 2018 18:26:35 +0100 Subject: [PATCH 17/23] Prepare changelog for v0.12.4-rc.3 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f8e89250e..89f3741535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +Changes in [0.12.4-rc.3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.12.4-rc.3) (2018-05-11) +=============================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.12.4-rc.2...v0.12.4-rc.3) + + * Instant Sticker Picker :zap: + [\#1888](https://github.com/matrix-org/matrix-react-sdk/pull/1888) + Changes in [0.12.4-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.12.4-rc.2) (2018-05-09) =============================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.12.4-rc.1...v0.12.4-rc.2) From d90292c6f15ce8fc5b07d8ab3a2a3c0d0635f6ae Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 11 May 2018 18:26:35 +0100 Subject: [PATCH 18/23] v0.12.4-rc.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3958e1b3d0..1125dd3980 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "0.12.4-rc.2", + "version": "0.12.4-rc.3", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From 98c06315cf44121779125a98f7025436c72f11af Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 14 May 2018 11:14:49 +0100 Subject: [PATCH 19/23] Update widget state when account data changes --- src/components/views/rooms/Stickerpicker.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/components/views/rooms/Stickerpicker.js b/src/components/views/rooms/Stickerpicker.js index 40ce6e6b39..51d4816e59 100644 --- a/src/components/views/rooms/Stickerpicker.js +++ b/src/components/views/rooms/Stickerpicker.js @@ -34,6 +34,7 @@ export default class Stickerpicker extends React.Component { this._onHideStickersClick = this._onHideStickersClick.bind(this); this._launchManageIntegrations = this._launchManageIntegrations.bind(this); this._removeStickerpickerWidgets = this._removeStickerpickerWidgets.bind(this); + this._updateWidget = this._updateWidget.bind(this); this._onWidgetAction = this._onWidgetAction.bind(this); this._onResize = this._onResize.bind(this); this._onFinished = this._onFinished.bind(this); @@ -90,11 +91,12 @@ 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, - }); + + // Track updates to widget state in account data + MatrixClientPeg.get().on('accountData', this._updateWidget); + + // Initialise widget state from current account data + this._updateWidget(); } componentWillUnmount() { @@ -116,6 +118,14 @@ export default class Stickerpicker extends React.Component { }); } + _updateWidget() { + const stickerpickerWidget = Widgets.getStickerpickerWidgets()[0]; + this.setState({ + stickerpickerWidget, + widgetId: stickerpickerWidget ? stickerpickerWidget.id : null, + }); + } + _onWidgetAction(payload) { if (payload.action === "user_widget_updated") { this.forceUpdate(); From 6345e474f5fea44a5246a264c09a4c6322aabe56 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 14 May 2018 11:38:17 +0100 Subject: [PATCH 20/23] Remove unused conditional --- src/components/views/elements/AppTile.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index cd0eb1ce68..cdd77227dc 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -359,9 +359,6 @@ export default class AppTile extends React.Component { if (!this.widgetMessaging) { this._onInitialLoad(); } - if (this._exposeWidgetMessaging) { - this._exposeWidgetMessaging(this.widgetMessaging); - } } /** From cf8077e60577d766bd93a7f659a09c6d3ab091f9 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 14 May 2018 11:42:38 +0100 Subject: [PATCH 21/23] Set loading: false when iFrame finishes loading --- src/components/views/elements/AppTile.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index cdd77227dc..55e2b93920 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -359,6 +359,7 @@ export default class AppTile extends React.Component { if (!this.widgetMessaging) { this._onInitialLoad(); } + this.setState({loading: false}); } /** @@ -396,8 +397,6 @@ 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}); } _onWidgetAction(payload) { From b6e317647a847041c216bfaf3fc88b82c3b64442 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 14 May 2018 13:41:41 +0100 Subject: [PATCH 22/23] Fix stickers briefly being 2x the size fixupHeight was the only thing actually fixing the size of the sticker image to be the size we want it rather than the pixel size of the image, and this was only getting run after the image loaded, causing a flash of 2x image. --- src/components/views/messages/MStickerBody.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/components/views/messages/MStickerBody.js b/src/components/views/messages/MStickerBody.js index 501db5e22b..3a412fc2e2 100644 --- a/src/components/views/messages/MStickerBody.js +++ b/src/components/views/messages/MStickerBody.js @@ -107,6 +107,16 @@ export default class MStickerBody extends MImageBody { placeholderFixupHeight = content.info.h + 'px'; } + // The pixel size of sticker images is generally larger than their intended display + // size so they render at native reolution on HiDPI displays. We therefore need to + // explicity set the size so they render at the intended size. + // XXX: This will be clobberred when we run fixupHeight(), but we need to do it + // here otherwise the stickers are momentarily displayed at the pixel size. + const imageStyle = { + height: content.info.h, + // leave the browser the calculate the width automatically + }; + placeholderSize = placeholderSize + 'px'; // Body 'ref' required by MImageBody @@ -132,6 +142,7 @@ export default class MStickerBody extends MImageBody { {content.body} Date: Mon, 14 May 2018 14:23:26 +0100 Subject: [PATCH 23/23] Remove redundant logging (currently shown on every render when no stickerpicker is present). --- src/components/views/rooms/Stickerpicker.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/rooms/Stickerpicker.js b/src/components/views/rooms/Stickerpicker.js index 51d4816e59..2a523e5e82 100644 --- a/src/components/views/rooms/Stickerpicker.js +++ b/src/components/views/rooms/Stickerpicker.js @@ -232,7 +232,6 @@ export default class Stickerpicker extends React.Component { ); } else { // Default content to show if stickerpicker widget not added - console.warn("No available sticker picker widgets"); stickersContent = this._defaultStickerpickerContent(); } return stickersContent;