From b5320444a905f51d410429353e81e70ed7c08fde Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Tue, 8 May 2018 22:05:53 +0100 Subject: [PATCH 01/26] Fix current user and creator user ID properties. --- src/components/views/rooms/Stickerpicker.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/Stickerpicker.js b/src/components/views/rooms/Stickerpicker.js index c055c67cd3..031f93c919 100644 --- a/src/components/views/rooms/Stickerpicker.js +++ b/src/components/views/rooms/Stickerpicker.js @@ -164,8 +164,8 @@ export default class Stickerpicker extends React.Component { room={this.props.room} type={stickerpickerWidget.content.type} fullWidth={true} - userId={stickerpickerWidget.sender || MatrixClientPeg.get().credentials.userId} - creatorUserId={MatrixClientPeg.get().credentials.userId} + userId={MatrixClientPeg.get().credentials.userId} + creatorUserId={stickerpickerWidget.sender || MatrixClientPeg.get().credentials.userId} waitForIframeLoad={true} show={true} showMenubar={true} From 8e1ba6d139253d88a21d4c9efe7a4711734750f7 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Tue, 8 May 2018 22:44:49 +0100 Subject: [PATCH 02/26] Always allow users to edit their user widgets. --- src/components/views/elements/AppTile.js | 8 ++++++++ src/components/views/rooms/Stickerpicker.js | 1 + 2 files changed, 9 insertions(+) diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 007eb8126c..af754c7d4c 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -274,6 +274,11 @@ export default class AppTile extends React.Component { } _canUserModify() { + // User widgets should always be modifiable by their creator + if (this.props.userWidget && MatrixClientPeg.get().credentials.userId === this.props.creatorUserId) { + return true; + } + // Check if the current user can modify widgets in the current room return WidgetUtils.canUserModifyWidgets(this.props.room.roomId); } @@ -698,6 +703,8 @@ AppTile.propTypes = { // Optional function to be called on widget capability request // Called with an array of the requested capabilities onCapabilityRequest: PropTypes.func, + // Is this an instance of a user widget + userWidget: PropTypes.bool, }; AppTile.defaultProps = { @@ -710,4 +717,5 @@ AppTile.defaultProps = { showPopout: true, handleMinimisePointerEvents: false, whitelistCapabilities: [], + userWidget: false, }; diff --git a/src/components/views/rooms/Stickerpicker.js b/src/components/views/rooms/Stickerpicker.js index 031f93c919..9b489c3e38 100644 --- a/src/components/views/rooms/Stickerpicker.js +++ b/src/components/views/rooms/Stickerpicker.js @@ -178,6 +178,7 @@ export default class Stickerpicker extends React.Component { onMinimiseClick={this._onHideStickersClick} handleMinimisePointerEvents={true} whitelistCapabilities={['m.sticker']} + userWidget={true} /> From ee0e6c5913f2b0202bad6e81fd963e3dd59454ec Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 9 May 2018 11:53:37 +0100 Subject: [PATCH 03/26] Remove margins when in a ReplyThread to stop them taking so much space Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/elements/_ReplyThread.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/res/css/views/elements/_ReplyThread.scss b/res/css/views/elements/_ReplyThread.scss index a02f42751c..bf44a11728 100644 --- a/res/css/views/elements/_ReplyThread.scss +++ b/res/css/views/elements/_ReplyThread.scss @@ -14,8 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_ReplyThread { + margin-top: 0; +} + .mx_ReplyThread .mx_DateSeparator { font-size: 1em !important; + margin-top: 0; margin-bottom: 0; padding-bottom: 1px; bottom: -5px; From e10e65945771cf38591d9af6f29f23555da18e19 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 9 May 2018 14:59:35 +0100 Subject: [PATCH 05/26] 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 15:48:53 +0100 Subject: [PATCH 06/26] 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 0c1846630c1073bef1ce3ac94540a0b06efa3d14 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 9 May 2018 16:41:45 +0100 Subject: [PATCH 07/26] 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 1d04eb5196..63cf459987 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -534,7 +534,7 @@ export default class AppTile extends React.Component { if (this.props.show) { const loadingElement = ( -
+
); From 44ea11d386f2ac9be1e2b84d5cd887b2af81d18d Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Wed, 9 May 2018 16:55:49 +0100 Subject: [PATCH 08/26] Fix 'state_key' field name. --- src/ScalarMessaging.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js index 123d02159e..a163bf7bbd 100644 --- a/src/ScalarMessaging.js +++ b/src/ScalarMessaging.js @@ -349,7 +349,7 @@ function setWidget(event, roomId) { userWidgets[widgetId] = { content: content, sender: client.getUserId(), - stateKey: widgetId, + state_key: widgetId, type: 'm.widget', id: widgetId, }; From 904676913266b2b10cb3f8f20634115e603733ee Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Thu, 10 May 2018 16:00:58 +0100 Subject: [PATCH 09/26] 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 cbf52e00d29513791a2eb733c8abc00a1a2f179e Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Thu, 10 May 2018 16:43:10 +0100 Subject: [PATCH 10/26] Update widget 'widgetData' key to 'data' to match spec. --- src/FromWidgetPostMessageApi.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FromWidgetPostMessageApi.js b/src/FromWidgetPostMessageApi.js index ad1f1acbbd..c9c9d5c3de 100644 --- a/src/FromWidgetPostMessageApi.js +++ b/src/FromWidgetPostMessageApi.js @@ -137,12 +137,12 @@ export default class FromWidgetPostMessageApi { }); } else if (action === 'm.sticker') { // console.warn('Got sticker message from widget', widgetId); - dis.dispatch({action: 'm.sticker', data: event.data.widgetData, widgetId: event.data.widgetId}); + dis.dispatch({action: 'm.sticker', data: event.data.data, widgetId: event.data.widgetId}); } else if (action === 'integration_manager_open') { // Close the stickerpicker dis.dispatch({action: 'stickerpicker_close'}); // Open the integration manager - const data = event.data.widgetData; + const data = event.data.data; const integType = (data && data.integType) ? data.integType : null; const integId = (data && data.integId) ? data.integId : null; IntegrationManager.open(integType, integId); From 2c2eda1f006c1ddcde13e890ae8f5830d7ac9bf1 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Thu, 10 May 2018 17:51:49 +0100 Subject: [PATCH 11/26] 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 5cb892bf3cb23e1f49e413aeb81b70eecade6aa5 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Thu, 10 May 2018 18:06:00 +0100 Subject: [PATCH 12/26] Allow use of either 'data' or 'widgetData' fields for the time being. --- src/FromWidgetPostMessageApi.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/FromWidgetPostMessageApi.js b/src/FromWidgetPostMessageApi.js index c9c9d5c3de..13ffb4a74b 100644 --- a/src/FromWidgetPostMessageApi.js +++ b/src/FromWidgetPostMessageApi.js @@ -137,12 +137,15 @@ export default class FromWidgetPostMessageApi { }); } else if (action === 'm.sticker') { // console.warn('Got sticker message from widget', widgetId); - dis.dispatch({action: 'm.sticker', data: event.data.data, widgetId: event.data.widgetId}); + // NOTE -- The widgetData field is deprecated (in favour of the 'data' field) and will be removed eventually + const data = event.data.data || event.data.widgetData; + dis.dispatch({action: 'm.sticker', data: data, widgetId: event.data.widgetId}); } else if (action === 'integration_manager_open') { // Close the stickerpicker dis.dispatch({action: 'stickerpicker_close'}); // Open the integration manager - const data = event.data.data; + // NOTE -- The widgetData field is deprecated (in favour of the 'data' field) and will be removed eventually + const data = event.data.data || event.data.widgetData; const integType = (data && data.integType) ? data.integType : null; const integId = (data && data.integId) ? data.integId : null; IntegrationManager.open(integType, integId); From 2dc51c516c5328de992c8ccccd634bda6e33e1fb Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 11 May 2018 14:47:57 +0100 Subject: [PATCH 13/26] 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 9b489c3e38..8784599238 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 = (
@@ -187,12 +196,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 +206,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 +224,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 +250,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 = @@ -289,6 +305,9 @@ export default class Stickerpicker extends React.Component {
; } - return stickersButton; + return
+ {stickersButton} + {this.state.showStickers && stickerPicker} +
; } } From 6500797d2aa0b7b61e48bc8464605e5e9c4e4242 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 11 May 2018 14:49:50 +0100 Subject: [PATCH 14/26] 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 746eeee33cbc0f56061bc7c42d4dd081ae6f2324 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 11 May 2018 15:07:51 +0100 Subject: [PATCH 15/26] 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 8784599238..36b99b2422 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 4b6378a80d5f415c85eba76bce76c00b3d8050c4 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 11 May 2018 16:22:54 +0100 Subject: [PATCH 16/26] 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 63cf459987..0701117ab3 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -394,6 +394,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 8b049b2182b8425144ed16d3ed1485e2bd33f467 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 11 May 2018 16:23:18 +0100 Subject: [PATCH 17/26] 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 0701117ab3..b5d317ae59 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() { @@ -357,6 +370,9 @@ export default class AppTile extends React.Component { if (!this.widgetMessaging) { this._onInitialLoad(); } + if (this._exposeWidgetMessaging) { + this._exposeWidgetMessaging(this.widgetMessaging); + } } /** @@ -395,9 +411,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 36b99b2422..75d5e3acf4 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 1f2f3474ed6a92b8f1648af8a7dc132a2cbebc80 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 11 May 2018 17:28:12 +0100 Subject: [PATCH 18/26] 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 b5d317ae59..1b58778be4 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 06898394eb044c22422f246e3202f9d378ad5331 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Fri, 11 May 2018 18:06:58 +0100 Subject: [PATCH 19/26] 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 1b58778be4..38b6fc200b 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 75d5e3acf4..0584cd6b0a 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 dbbcabfed86d4a155696ebed805cf61000f3b4da Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sat, 12 May 2018 15:57:33 +0100 Subject: [PATCH 20/26] switch from asymmetrical fallback form to a cleaner one `mx-reply` Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/HtmlUtils.js | 1 - src/components/views/elements/ReplyThread.js | 26 ++++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js index 82b6830b78..58572cf144 100644 --- a/src/HtmlUtils.js +++ b/src/HtmlUtils.js @@ -186,7 +186,6 @@ const sanitizeHtmlParams = { ], allowedAttributes: { // custom ones first: - blockquote: ['data-mx-reply'], // used to allow explicit removal of a reply fallback blockquote, value ignored font: ['color', 'data-mx-bg-color', 'data-mx-color', 'style'], // custom to matrix span: ['data-mx-bg-color', 'data-mx-color', 'style'], // custom to matrix a: ['href', 'name', 'target', 'rel'], // remote target: custom to matrix diff --git a/src/components/views/elements/ReplyThread.js b/src/components/views/elements/ReplyThread.js index cd444fb090..6714de81a4 100644 --- a/src/components/views/elements/ReplyThread.js +++ b/src/components/views/elements/ReplyThread.js @@ -81,7 +81,7 @@ export default class ReplyThread extends React.Component { // Part of Replies fallback support static stripHTMLReply(html) { - return html.replace(/^
[\s\S]+?<\/blockquote>/, ''); + return html.replace(/^[\s\S]+?<\/mx-reply>/, ''); } // Part of Replies fallback support @@ -102,8 +102,8 @@ export default class ReplyThread extends React.Component { switch (ev.getContent().msgtype) { case 'm.text': case 'm.notice': { - html = `
In reply to ${mxid}` - + `
${html || body}
`; + html = `
In reply to ${mxid}` + + `
${html || body}
`; const lines = body.trim().split('\n'); if (lines.length > 0) { lines[0] = `<${mxid}> ${lines[0]}`; @@ -112,28 +112,28 @@ export default class ReplyThread extends React.Component { break; } case 'm.image': - html = `
In reply to ${mxid}` - + `
sent an image.
`; + html = `
In reply to ${mxid}` + + `
sent an image.
`; body = `> <${mxid}> sent an image.\n\n`; break; case 'm.video': - html = `
In reply to ${mxid}` - + `
sent a video.
`; + html = `
In reply to ${mxid}` + + `
sent a video.
`; body = `> <${mxid}> sent a video.\n\n`; break; case 'm.audio': - html = `
In reply to ${mxid}` - + `
sent an audio file.
`; + html = `
In reply to ${mxid}` + + `
sent an audio file.
`; body = `> <${mxid}> sent an audio file.\n\n`; break; case 'm.file': - html = `
In reply to ${mxid}` - + `
sent a file.
`; + html = `
In reply to ${mxid}` + + `
sent a file.
`; body = `> <${mxid}> sent a file.\n\n`; break; case 'm.emote': { - html = `
In reply to * ` - + `${mxid}
${html || body}
`; + html = `
In reply to * ` + + `${mxid}
${html || body}
`; const lines = body.trim().split('\n'); if (lines.length > 0) { lines[0] = `* <${mxid}> ${lines[0]}`; From 9e1172019182131ead40560015e06e30bf943072 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 12 May 2018 14:29:37 -0600 Subject: [PATCH 21/26] Add setting to enable widget screenshots (if widgets declare support) Fixes the remainder of https://github.com/vector-im/riot-web/issues/6708 Signed-off-by: Travis Ralston --- src/components/structures/UserSettings.js | 1 + src/components/views/elements/AppTile.js | 6 +++--- src/components/views/rooms/AppsDrawer.js | 3 +++ src/i18n/strings/en_EN.json | 1 + src/settings/Settings.js | 5 +++++ 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index 3bef4e41bb..35a55284fd 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -80,6 +80,7 @@ const SIMPLE_SETTINGS = [ { id: "TextualBody.disableBigEmoji" }, { id: "VideoView.flipVideoHorizontally" }, { id: "TagPanel.disableTagPanel" }, + { id: "enableWidgetScreenshots" }, ]; // These settings must be defined in SettingsStore diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 38b6fc200b..2923d6dceb 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -85,7 +85,7 @@ export default class AppTile extends React.Component { /** * Does the widget support a given capability - * @param {[type]} capability Capability to check for + * @param {string} capability Capability to check for * @return {Boolean} True if capability supported */ _hasCapability(capability) { @@ -607,7 +607,7 @@ export default class AppTile extends React.Component { } // Picture snapshot - only show button when apps are maximised. - const showPictureSnapshotButton = this._hasCapability('screenshot') && this.props.show; + const showPictureSnapshotButton = this._hasCapability('m.capability.screenshot') && this.props.show; const showPictureSnapshotIcon = 'img/camera_green.svg'; const popoutWidgetIcon = 'img/button-new-window.svg'; const windowStateIcon = (this.props.show ? 'img/minimize.svg' : 'img/maximize.svg'); @@ -711,7 +711,7 @@ AppTile.propTypes = { showDelete: PropTypes.bool, // Optionally hide the popout widget icon showPopout: PropTypes.bool, - // Widget apabilities to allow by default (without user confirmation) + // Widget capabilities to allow by default (without user confirmation) // NOTE -- Use with caution. This is intended to aid better integration / UX // basic widget capabilities, e.g. injecting sticker message events. whitelistCapabilities: PropTypes.array, diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 9f57ca51e9..8763ea3d7f 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -227,6 +227,8 @@ module.exports = React.createClass({ }, render: function() { + const enableScreenshots = SettingsStore.getValue("enableWidgetScreenshots", this.props.room.room_id); + const apps = this.state.apps.map( (app, index, arr) => { return (); }); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4adca0cc72..9b932ef2b6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -217,6 +217,7 @@ "Enable URL previews for this room (only affects you)": "Enable URL previews for this room (only affects you)", "Enable URL previews by default for participants in this room": "Enable URL previews by default for participants in this room", "Room Colour": "Room Colour", + "Enable widget screenshots on supported widgets": "Enable widget screenshots on supported widgets", "Collecting app version information": "Collecting app version information", "Collecting logs": "Collecting logs", "Uploading report": "Uploading report", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 89a12580d6..663318f990 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -265,4 +265,9 @@ export const SETTINGS = { default: true, controller: new AudioNotificationsEnabledController(), }, + "enableWidgetScreenshots": { + supportedLevels: LEVELS_ACCOUNT_SETTINGS, + displayName: _td('Enable widget screenshots on supported widgets'), + default: false, + }, }; From 0522ab8fcd2d5e91f91fd698a023d399189d6d3d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 12 May 2018 13:18:43 -0600 Subject: [PATCH 22/26] Expose the requestId fully in the toWidget postMessage API This field is flagged as required in the proposal. Addresses part of https://github.com/vector-im/riot-web/issues/6708 Signed-off-by: Travis Ralston --- src/ToWidgetPostMessageApi.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ToWidgetPostMessageApi.js b/src/ToWidgetPostMessageApi.js index ccaa0207c1..def4af56ae 100644 --- a/src/ToWidgetPostMessageApi.js +++ b/src/ToWidgetPostMessageApi.js @@ -51,11 +51,11 @@ export default class ToWidgetPostMessageApi { if (payload.response === undefined) { return; } - const promise = this._requestMap[payload._id]; + const promise = this._requestMap[payload.requestId]; if (!promise) { return; } - delete this._requestMap[payload._id]; + delete this._requestMap[payload.requestId]; promise.resolve(payload); } @@ -64,21 +64,21 @@ export default class ToWidgetPostMessageApi { targetWindow = targetWindow || window.parent; // default to parent window targetOrigin = targetOrigin || "*"; this._counter += 1; - action._id = Date.now() + "-" + Math.random().toString(36) + "-" + this._counter; + action.requestId = Date.now() + "-" + Math.random().toString(36) + "-" + this._counter; return new Promise((resolve, reject) => { - this._requestMap[action._id] = {resolve, reject}; + this._requestMap[action.requestId] = {resolve, reject}; targetWindow.postMessage(action, targetOrigin); if (this._timeoutMs > 0) { setTimeout(() => { - if (!this._requestMap[action._id]) { + if (!this._requestMap[action.requestId]) { return; } console.error("postMessage request timed out. Sent object: " + JSON.stringify(action), this._requestMap); - this._requestMap[action._id].reject(new Error("Timed out")); - delete this._requestMap[action._id]; + this._requestMap[action.requestId].reject(new Error("Timed out")); + delete this._requestMap[action.requestId]; }, this._timeoutMs); } }); From 1515ca11a8be49e9a000afcc1c94e778faae57b5 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 12 May 2018 13:21:27 -0600 Subject: [PATCH 23/26] Add a warning for widget developers when their postMessage is missing a requestId Signed-off-by: Travis Ralston --- src/FromWidgetPostMessageApi.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/FromWidgetPostMessageApi.js b/src/FromWidgetPostMessageApi.js index 13ffb4a74b..792fd73733 100644 --- a/src/FromWidgetPostMessageApi.js +++ b/src/FromWidgetPostMessageApi.js @@ -116,6 +116,12 @@ export default class FromWidgetPostMessageApi { return; // don't log this - debugging APIs like to spam postMessage which floods the log otherwise } + // Although the requestId is required, we don't use it. We'll be nice and process the message + // if the property is missing, but with a warning for widget developers. + if (!event.data.requestId) { + console.warn("fromWidget action '" + event.data.action + "' does not have a requestId"); + } + const action = event.data.action; const widgetId = event.data.widgetId; if (action === 'content_loaded') { From 98da8b35755e2bf00115f3b194de803808795746 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 12 May 2018 13:49:43 -0600 Subject: [PATCH 24/26] Send the widgetId as part of all toWidget requests Addresses part of https://github.com/vector-im/riot-web/issues/6708 Signed-off-by: Travis Ralston --- src/WidgetMessaging.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/WidgetMessaging.js b/src/WidgetMessaging.js index 86eaa0b59b..5b722df65f 100644 --- a/src/WidgetMessaging.js +++ b/src/WidgetMessaging.js @@ -44,6 +44,8 @@ export default class WidgetMessaging { } messageToWidget(action) { + action.widgetId = this.widgetId; // Required to be sent for all outbound requests + return this.toWidget.exec(action, this.target).then((data) => { // Check for errors and reject if found if (data.response === undefined) { // null is valid From 210fcf0d52219f767896f242783becaffab9f329 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sun, 13 May 2018 16:41:19 -0600 Subject: [PATCH 25/26] Correctly identify sticker picker widgets Widgets added to account data have the `type` of "m.widget", meaning we have to look at the `content.type` which will tell us what it is. This also fixes a bug where all user widgets become sticker picker widgets under the right conditions. Signed-off-by: Travis Ralston --- src/utils/widgets.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/utils/widgets.js b/src/utils/widgets.js index 0d7f5dbf3f..338df184e2 100644 --- a/src/utils/widgets.js +++ b/src/utils/widgets.js @@ -58,8 +58,7 @@ function getUserWidgetsArray() { */ function getStickerpickerWidgets() { const widgets = getUserWidgetsArray(); - const stickerpickerWidgets = widgets.filter((widget) => widget.type='m.stickerpicker'); - return stickerpickerWidgets; + return widgets.filter((widget) => widget.content && widget.content.type === "m.stickerpicker"); } /** @@ -73,7 +72,7 @@ function removeStickerpickerWidgets() { } const userWidgets = client.getAccountData('m.widgets').getContent() || {}; Object.entries(userWidgets).forEach(([key, widget]) => { - if (widget.type === 'm.stickerpicker') { + if (widget.content && widget.content.type === 'm.stickerpicker') { delete userWidgets[key]; } }); From 986fb3237d7016d4a90ec6c5164203f77997ec93 Mon Sep 17 00:00:00 2001 From: Richard Lewis Date: Mon, 14 May 2018 13:56:39 +0100 Subject: [PATCH 26/26] 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 0584cd6b0a..1bb972f67f 100644 --- a/src/components/views/rooms/Stickerpicker.js +++ b/src/components/views/rooms/Stickerpicker.js @@ -223,7 +223,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;