From 2e7bd2e3f03da46f171846d9d81466fb414f65d9 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Mon, 20 Apr 2020 23:05:50 +0300 Subject: [PATCH 01/37] Use flexboxes in AppTile/AppDrawer CSS for automatic resizing Using flexboxes removes the need for pixel constants, and the app content now resizes to fill the tiles. Signed-off-by: Pauli Virtanen --- res/css/views/rooms/_AppsDrawer.scss | 55 ++++++++++++++++-------- src/components/views/elements/AppTile.js | 12 +++--- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index 1b1bab67bc..61da3a360b 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -16,17 +16,14 @@ limitations under the License. */ /* -the tile title bar is 5 (top border) + 12 (title, buttons) + 5 (bottom padding) px = 22px -the body is assumed to be 300px (assumed by at least the sticker pickerm, perhaps elsewhere), -so the body height would be 300px - 22px (room for title bar) = 278px -BUT! the sticker picker also assumes it's a little less high than that because the iframe -for the sticker picker doesn't have any padding or margin on it's bottom. -so subtracking another 5px, which brings us at 273px. + Minimum size for usual AppTiles and fixed size for mini-tiles. */ -$AppsDrawerBodyHeight: 273px; +$AppTileMinHeight: 300px; +$MiniAppTileHeight: 114px; .mx_AppsDrawer { margin: 5px; + display: block; } .mx_AppsDrawer_hidden { @@ -36,7 +33,7 @@ $AppsDrawerBodyHeight: 273px; .mx_AppsContainer { display: flex; flex-direction: row; - align-items: center; + align-items: stretch; justify-content: center; } @@ -44,7 +41,7 @@ $AppsDrawerBodyHeight: 273px; order: 2; cursor: pointer; padding: 0; - margin: 5px auto 5px auto; + margin: 5px auto 5px 0px; color: $accent-color; font-size: $font-12px; } @@ -68,6 +65,9 @@ $AppsDrawerBodyHeight: 273px; margin-right: 5px; border: 5px solid $widget-menu-bar-bg-color; border-radius: 4px; + display: flex; + flex-direction: column; + min-height: $AppTileMinHeight; } .mx_AppTile:last-child { @@ -77,27 +77,40 @@ $AppsDrawerBodyHeight: 273px; .mx_AppTileFullWidth { max-width: 960px; width: 100%; - height: 100%; margin: 0; padding: 0; border: 5px solid $widget-menu-bar-bg-color; border-radius: 4px; + display: flex; + flex-direction: column; + min-height: $AppTileMinHeight; } .mx_AppTile_mini { max-width: 960px; width: 100%; - height: 100%; margin: 0; padding: 0; + display: flex; + flex-direction: column; + height: $MiniAppTileHeight; } -.mx_AppTile_persistedWrapper { - height: $AppsDrawerBodyHeight; +.mx_AppTile.mx_AppTile_minimised, +.mx_AppTileFullWidth.mx_AppTile_minimised, +.mx_AppTile_mini.mx_AppTile_minimised { + min-height: inherit; } +.mx_AppTile .mx_AppTile_persistedWrapper, +.mx_AppTileFullWidth .mx_AppTile_persistedWrapper, .mx_AppTile_mini .mx_AppTile_persistedWrapper { - height: 114px; + flex: 1; +} + +.mx_AppTile_persistedWrapper div { + width: 100%; + height: 100%; } .mx_AppTileMenuBar { @@ -109,6 +122,7 @@ $AppsDrawerBodyHeight: 273px; align-items: center; justify-content: space-between; cursor: pointer; + width: 100%; } .mx_AppTileMenuBar_expanded { @@ -171,7 +185,7 @@ $AppsDrawerBodyHeight: 273px; } .mx_AppTileBody { - height: $AppsDrawerBodyHeight; + height: 100%; width: 100%; overflow: hidden; } @@ -182,6 +196,13 @@ $AppsDrawerBodyHeight: 273px; overflow: hidden; } +.mx_AppTile .mx_AppTileBody, +.mx_AppTileFullWidth .mx_AppTileBody, +.mx_AppTile_mini .mx_AppTileBody_mini { + height: inherit; + flex: 1; +} + .mx_AppTileBody_mini iframe { border: none; width: 100%; @@ -190,7 +211,7 @@ $AppsDrawerBodyHeight: 273px; .mx_AppTileBody iframe { width: 100%; - height: $AppsDrawerBodyHeight; + height: 100%; overflow: hidden; border: none; padding: 0; @@ -330,7 +351,7 @@ form.mx_Custom_Widget_Form div { align-items: center; font-weight: bold; position: relative; - height: $AppsDrawerBodyHeight; + height: 100%; } .mx_AppLoading .mx_Spinner { diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 6a5dfc97e0..61d36723d3 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -775,14 +775,16 @@ export default class AppTile extends React.Component { const showMinimiseButton = this.props.showMinimise && this.props.show; const showMaximiseButton = this.props.showMinimise && !this.props.show; - let appTileClass; + let appTileClasses; if (this.props.miniMode) { - appTileClass = 'mx_AppTile_mini'; + appTileClasses = {mx_AppTile_mini: true}; } else if (this.props.fullWidth) { - appTileClass = 'mx_AppTileFullWidth'; + appTileClasses = {mx_AppTileFullWidth: true}; } else { - appTileClass = 'mx_AppTile'; + appTileClasses = {mx_AppTile: true}; } + appTileClasses.mx_AppTile_minimised = !this.props.show; + appTileClasses = classNames(appTileClasses); const menuBarClasses = classNames({ mx_AppTileMenuBar: true, @@ -814,7 +816,7 @@ export default class AppTile extends React.Component { } return -
+
{ this.props.showMenubar &&
From 11438aeee61cf3faabe170da49150003cd35ae52 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Tue, 21 Apr 2020 00:41:58 +0300 Subject: [PATCH 02/37] Fix resizer/sizer.js mouse event offset calculation The event coordinates are document coordinates, so the offset they are compared to should also be the document one. Signed-off-by: Pauli Virtanen --- src/resizer/sizer.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/resizer/sizer.js b/src/resizer/sizer.js index 50861d34d5..4ce9232457 100644 --- a/src/resizer/sizer.js +++ b/src/resizer/sizer.js @@ -56,6 +56,18 @@ export default class Sizer { return this.vertical ? this.container.offsetTop : this.container.offsetLeft; } + /** @return {number} container offset to document */ + _getPageOffset() { + let element = this.container; + let offset = 0; + while (element) { + const pos = this.vertical ? element.offsetTop : element.offsetLeft; + offset = offset + pos; + element = element.offsetParent; + } + return offset; + } + setItemSize(item, size) { if (this.vertical) { item.style.height = `${Math.round(size)}px`; @@ -80,9 +92,9 @@ export default class Sizer { offsetFromEvent(event) { const pos = this.vertical ? event.pageY : event.pageX; if (this.reverse) { - return (this._getOffset() + this.getTotalSize()) - pos; + return (this._getPageOffset() + this.getTotalSize()) - pos; } else { - return pos - this._getOffset(); + return pos - this._getPageOffset(); } } } From e897e97fd6e15b39aec89080d0096d570dcdfd63 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Thu, 23 Apr 2020 22:52:28 +0300 Subject: [PATCH 03/37] Make AppsDrawer resizable by dragging its bottom border Signed-off-by: Pauli Virtanen --- res/css/views/rooms/_AppsDrawer.scss | 38 ++++++++++++++++-- src/components/views/rooms/AppsDrawer.js | 51 +++++++++++++++++++++++- 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index 61da3a360b..3a33b73ec9 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -16,9 +16,10 @@ limitations under the License. */ /* - Minimum size for usual AppTiles and fixed size for mini-tiles. + Size settings */ -$AppTileMinHeight: 300px; +$AppsDrawerMinHeight: 50px; +$AppsDrawerDefaultHeight: 300px; $MiniAppTileHeight: 114px; .mx_AppsDrawer { @@ -35,6 +36,13 @@ $MiniAppTileHeight: 114px; flex-direction: row; align-items: stretch; justify-content: center; + min-height: $AppsDrawerMinHeight; + height: $AppsDrawerDefaultHeight; +} + +.mx_AppsDrawer_minimised .mx_AppsContainer { + min-height: inherit; + height: inherit; } .mx_AddWidget_button { @@ -67,7 +75,6 @@ $MiniAppTileHeight: 114px; border-radius: 4px; display: flex; flex-direction: column; - min-height: $AppTileMinHeight; } .mx_AppTile:last-child { @@ -83,7 +90,6 @@ $MiniAppTileHeight: 114px; border-radius: 4px; display: flex; flex-direction: column; - min-height: $AppTileMinHeight; } .mx_AppTile_mini { @@ -378,3 +384,27 @@ form.mx_Custom_Widget_Form div { .mx_AppLoading iframe { display: none; } + +/* Hidden resize handle (Apptile bottom serves as indicator) */ +.mx_AppsDrawer .mx_ResizeHandle > div { + background: inherit; +} + +.mx_AppsDrawer_fullWidth .mx_ResizeHandle { + max-width: 960px; + margin-left: auto; + margin-right: auto; +} + +.mx_AppsDrawer_minimised .mx_ResizeHandle { + display: none; +} + +/* Avoid apptile iframes capturing mouse event focus when resizing */ +.mx_AppsDrawer_resizing iframe { + pointer-events: none; +} + +.mx_AppsDrawer_resizing .mx_AppTile_persistedWrapper { + z-index: 1; +} diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index b64eb33435..842b93170f 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -30,6 +30,9 @@ import WidgetEchoStore from "../../../stores/WidgetEchoStore"; import AccessibleButton from '../elements/AccessibleButton'; import {IntegrationManagers} from "../../../integrations/IntegrationManagers"; import SettingsStore from "../../../settings/SettingsStore"; +import classNames from 'classnames'; +import ResizeHandle from '../elements/ResizeHandle'; +import {Resizer, FixedDistributor} from '../../../resizer'; // The maximum number of widgets that can be added in a room const MAX_WIDGETS = 2; @@ -60,6 +63,7 @@ export default createReactClass({ MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents); WidgetEchoStore.on('update', this._updateApps); this.dispatcherRef = dis.register(this.onAction); + this._createResizer(); }, componentWillUnmount: function() { @@ -69,6 +73,10 @@ export default createReactClass({ } WidgetEchoStore.removeListener('update', this._updateApps); if (this.dispatcherRef) dis.unregister(this.dispatcherRef); + if (this.resizer) { + this.resizer.detach(); + this.resizer = null; + } }, // TODO: [REACT-WARNING] Replace with appropriate lifecycle event @@ -154,6 +162,30 @@ export default createReactClass({ this._launchManageIntegrations(); }, + _createResizer: function() { + if (!this.resizeContainer) { + return; + } + + const classNames = { + handle: "mx_ResizeHandle", + vertical: "mx_ResizeHandle_vertical", + resizing: "mx_AppsDrawer_resizing", + }; + const resizer = new Resizer( + this.resizeContainer, + FixedDistributor, + {}, + ); + resizer.setClassNames(classNames); + resizer.attach(); + this.resizer = resizer; + }, + + _setResizeContainerRef: function(div) { + this.resizeContainer = div; + }, + render: function() { const apps = this.state.apps.map((app, index, arr) => { const capWhitelist = WidgetUtils.getCapWhitelistForAppTypeInRoomId(app.type, this.props.room.roomId); @@ -191,6 +223,13 @@ export default createReactClass({ ; } + const containerStyle = { + maxHeight: Math.max(this.props.maxHeight - 50, 300), + }; + if (!this.props.showApps && this.resizer) { + this.resizer.forHandleAt(0).item.clearSize(); + } + let spinner; if ( apps.length === 0 && WidgetEchoStore.roomHasPendingWidgets( @@ -202,12 +241,20 @@ export default createReactClass({ spinner = ; } + const classes = classNames({ + "mx_AppsDrawer": true, + "mx_AppsDrawer_hidden": this.props.hide, + "mx_AppsDrawer_fullWidth": apps.length < 2, + "mx_AppsDrawer_minimised": !this.props.showApps, + }); + return ( -
-
+
+
{ apps } { spinner }
+ { this._canUserModify() && addWidget }
); From 735826015daa75a7cd385a6f206dff283f4cbc0b Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Thu, 30 Apr 2020 20:29:08 +0300 Subject: [PATCH 04/37] Make AppsDrawer resize handle easier to grab --- res/css/views/rooms/_AppsDrawer.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index 3a33b73ec9..d6fb055bc0 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -386,6 +386,11 @@ form.mx_Custom_Widget_Form div { } /* Hidden resize handle (Apptile bottom serves as indicator) */ +.mx_AppsDrawer .mx_ResizeHandle { + position: relative; + z-index: 1; +} + .mx_AppsDrawer .mx_ResizeHandle > div { background: inherit; } From cca5ccd79d1e6ce6cdedf1d7f8ce49e2a59de87a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 21 Aug 2020 16:29:07 +0100 Subject: [PATCH 05/37] Switch widget resizing to re-resizable and add persistence --- res/css/views/rooms/_AppsDrawer.scss | 61 +++++++++----- res/css/views/voip/_CallContainer.scss | 4 + src/components/structures/RoomView.js | 4 +- .../views/elements/PersistedElement.js | 8 +- src/components/views/rooms/AppsDrawer.js | 79 ++++++++----------- src/hooks/useLocalStorage.ts | 37 +++++++++ 6 files changed, 124 insertions(+), 69 deletions(-) create mode 100644 src/hooks/useLocalStorage.ts diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index aae255f81a..348fb853d4 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -15,16 +15,43 @@ See the License for the specific language governing permissions and limitations under the License. */ -/* - Size settings -*/ -$AppsDrawerMinHeight: 50px; -$AppsDrawerDefaultHeight: 300px; $MiniAppTileHeight: 114px; .mx_AppsDrawer { - margin: 5px; - display: block; + margin: 5px 5px 5px 18px; + position: relative; + display: flex; + flex-direction: column; + overflow: hidden; + + .mx_RoomSublist_resizerHandles { + flex: 0 0 4px; + } + + .mx_RoomSublist_resizerHandle { + cursor: ns-resize; + border-radius: 3px; + + // Override styles from library + width: unset !important; + height: 4px !important; + + // This is positioned directly below frame + position: absolute; + bottom: -8px !important; // override from library + + // Together, these make the bar 64px wide + // These are also overridden from the library + left: calc(50% - 32px) !important; + right: calc(50% - 32px) !important; + } + + &:hover { + .mx_RoomSublist_resizerHandle { + opacity: 0.8; + background: $primary-fg-color; + } + } } .mx_AppsDrawer_hidden { @@ -36,13 +63,13 @@ $MiniAppTileHeight: 114px; flex-direction: row; align-items: stretch; justify-content: center; - min-height: $AppsDrawerMinHeight; - height: $AppsDrawerDefaultHeight; + height: 100%; } .mx_AppsDrawer_minimised .mx_AppsContainer { - min-height: inherit; - height: inherit; + // override the re-resizable inline styles + height: inherit !important; + min-height: inherit !important; } .mx_AddWidget_button { @@ -70,15 +97,14 @@ $MiniAppTileHeight: 114px; .mx_AppTile { max-width: 960px; width: 50%; - margin-right: 5px; border: 5px solid $widget-menu-bar-bg-color; border-radius: 4px; display: flex; flex-direction: column; -} -.mx_AppTile:last-child { - margin-right: 1px; + & + .mx_AppTile { + margin-left: 5px; + } } .mx_AppTileFullWidth { @@ -105,7 +131,7 @@ $MiniAppTileHeight: 114px; .mx_AppTile.mx_AppTile_minimised, .mx_AppTileFullWidth.mx_AppTile_minimised, .mx_AppTile_mini.mx_AppTile_minimised { - min-height: inherit; + height: 14px; } .mx_AppTile .mx_AppTile_persistedWrapper, @@ -117,7 +143,6 @@ $MiniAppTileHeight: 114px; .mx_AppTile_persistedWrapper div { width: 100%; height: 100%; - min-width: 300px; } .mx_AppTileMenuBar { @@ -402,7 +427,7 @@ form.mx_Custom_Widget_Form div { margin-right: auto; } -.mx_AppsDrawer_minimised .mx_ResizeHandle { +.mx_AppsDrawer_minimised .mx_RoomSublist_resizerHandle { display: none; } diff --git a/res/css/views/voip/_CallContainer.scss b/res/css/views/voip/_CallContainer.scss index 8d1b68dd99..4d26d8a312 100644 --- a/res/css/views/voip/_CallContainer.scss +++ b/res/css/views/voip/_CallContainer.scss @@ -36,6 +36,10 @@ limitations under the License. } } + .mx_AppTile_persistedWrapper div { + min-width: 300px; + } + .mx_IncomingCallBox { min-width: 250px; background-color: $primary-bg-color; diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index a79e5b0aa8..7605bdbfc0 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -1547,9 +1547,9 @@ export default createReactClass({ // header + footer + status + give us at least 120px of scrollback at all times. let auxPanelMaxHeight = window.innerHeight - - (83 + // height of RoomHeader + (54 + // height of RoomHeader 36 + // height of the status area - 72 + // minimum height of the message compmoser + 51 + // minimum height of the message compmoser 120); // amount of desired scrollback // XXX: this is a bit of a hack and might possibly cause the video to push out the page anyway diff --git a/src/components/views/elements/PersistedElement.js b/src/components/views/elements/PersistedElement.js index 7f9bfdebf4..9a64b7c7c4 100644 --- a/src/components/views/elements/PersistedElement.js +++ b/src/components/views/elements/PersistedElement.js @@ -17,7 +17,7 @@ limitations under the License. import React from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; - +import {throttle} from "lodash"; import ResizeObserver from 'resize-observer-polyfill'; import dis from '../../../dispatcher/dispatcher'; @@ -156,7 +156,7 @@ export default class PersistedElement extends React.Component { child.style.display = visible ? 'block' : 'none'; } - updateChildPosition(child, parent) { + updateChildPosition = throttle((child, parent) => { if (!child || !parent) return; const parentRect = parent.getBoundingClientRect(); @@ -167,9 +167,9 @@ export default class PersistedElement extends React.Component { width: parentRect.width + 'px', height: parentRect.height + 'px', }); - } + }, 100, {trailing: true, leading: true}); render() { - return
; + return
; } } diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index f173d76f24..502cbdc692 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -31,8 +31,8 @@ import AccessibleButton from '../elements/AccessibleButton'; import {IntegrationManagers} from "../../../integrations/IntegrationManagers"; import SettingsStore from "../../../settings/SettingsStore"; import classNames from 'classnames'; -import ResizeHandle from '../elements/ResizeHandle'; -import {Resizer, FixedDistributor} from '../../../resizer'; +import {Resizable} from "re-resizable"; +import {useLocalStorageState} from "../../../hooks/useLocalStorage"; // The maximum number of widgets that can be added in a room const MAX_WIDGETS = 2; @@ -63,7 +63,6 @@ export default createReactClass({ MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents); WidgetEchoStore.on('update', this._updateApps); this.dispatcherRef = dis.register(this.onAction); - this._createResizer(); }, componentWillUnmount: function() { @@ -73,10 +72,6 @@ export default createReactClass({ } WidgetEchoStore.removeListener('update', this._updateApps); if (this.dispatcherRef) dis.unregister(this.dispatcherRef); - if (this.resizer) { - this.resizer.detach(); - this.resizer = null; - } }, // TODO: [REACT-WARNING] Replace with appropriate lifecycle event @@ -162,30 +157,6 @@ export default createReactClass({ this._launchManageIntegrations(); }, - _createResizer: function() { - if (!this.resizeContainer) { - return; - } - - const classNames = { - handle: "mx_ResizeHandle", - vertical: "mx_ResizeHandle_vertical", - resizing: "mx_AppsDrawer_resizing", - }; - const resizer = new Resizer( - this.resizeContainer, - FixedDistributor, - {}, - ); - resizer.setClassNames(classNames); - resizer.attach(); - this.resizer = resizer; - }, - - _setResizeContainerRef: function(div) { - this.resizeContainer = div; - }, - render: function() { const apps = this.state.apps.map((app, index, arr) => { const capWhitelist = WidgetUtils.getCapWhitelistForAppTypeInRoomId(app.type, this.props.room.roomId); @@ -193,7 +164,7 @@ export default createReactClass({ return (); }); - if (apps.length == 0) { - return
; + if (apps.length === 0) { + return
; } let addWidget; @@ -223,13 +194,6 @@ export default createReactClass({ ; } - const containerStyle = { - maxHeight: Math.max(this.props.maxHeight - 50, 300), - }; - if (!this.props.showApps && this.resizer) { - this.resizer.forHandleAt(0).item.clearSize(); - } - let spinner; if ( apps.length === 0 && WidgetEchoStore.roomHasPendingWidgets( @@ -249,14 +213,39 @@ export default createReactClass({ }); return ( -
-
+
+ { apps } { spinner } -
- + { this._canUserModify() && addWidget }
); }, }); + +const PersistentVResizer = ({id, minHeight, maxHeight, className, handleWrapperClass, handleClass, children}) => { + const [height, setHeight] = useLocalStorageState("pvr_" + id, 100); + + return { + setHeight(height + d.height); + }} + handleWrapperClass={handleWrapperClass} + handleClasses={{bottom: handleClass}} + className={className} + enable={{bottom: true}} + > + { children } + ; +}; diff --git a/src/hooks/useLocalStorage.ts b/src/hooks/useLocalStorage.ts new file mode 100644 index 0000000000..2f110c58f2 --- /dev/null +++ b/src/hooks/useLocalStorage.ts @@ -0,0 +1,37 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +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. +*/ + +import {useEffect, useRef, useState} from "react"; + +// Hook behaving like useState but persisting the value to localStorage. Returns same as useState +export const useLocalStorageState = (key: string, initialValue: boolean) => { + const lsKey = useRef("useLocalStorageState_" + key).current; + + const [value, setValue] = useState(() => { + try { + const item = window.localStorage.getItem(lsKey); + return item ? JSON.parse(item) : initialValue; + } catch (error) { + return initialValue; + } + }); + + useEffect(() => { + window.localStorage.setItem(lsKey, JSON.stringify(value)); + }, [lsKey, value]); + + return [value, setValue]; +}; From 35cc1fb06d51290db8da256b773d4be77b101fb7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 21 Aug 2020 16:38:28 +0100 Subject: [PATCH 06/37] small tweaks --- res/css/views/rooms/_AppsDrawer.scss | 8 ++++---- src/components/views/rooms/AppsDrawer.js | 15 +++++++++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index 348fb853d4..1d62e0a9b4 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -24,11 +24,11 @@ $MiniAppTileHeight: 114px; flex-direction: column; overflow: hidden; - .mx_RoomSublist_resizerHandles { + .mx_AppsContainer_resizerHandles { flex: 0 0 4px; } - .mx_RoomSublist_resizerHandle { + .mx_AppsContainer_resizerHandle { cursor: ns-resize; border-radius: 3px; @@ -47,7 +47,7 @@ $MiniAppTileHeight: 114px; } &:hover { - .mx_RoomSublist_resizerHandle { + .mx_AppsContainer_resizerHandle { opacity: 0.8; background: $primary-fg-color; } @@ -427,7 +427,7 @@ form.mx_Custom_Widget_Form div { margin-right: auto; } -.mx_AppsDrawer_minimised .mx_RoomSublist_resizerHandle { +.mx_AppsDrawer_minimised .mx_AppsContainer_resizerHandle { display: none; } diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 502cbdc692..3a60f21f9c 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React, {useState} from 'react'; import PropTypes from 'prop-types'; import createReactClass from 'create-react-class'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; @@ -218,8 +218,8 @@ export default createReactClass({ id={"apps-drawer_" + this.props.room.roomId} minHeight={100} maxHeight={this.props.maxHeight - 50} - handleWrapperClass="mx_RoomSublist_resizerHandles" - handleClass="mx_RoomSublist_resizerHandle" + handleWrapperClass="mx_AppsContainer_resizerHandles" + handleClass="mx_AppsContainer_resizerHandle" className="mx_AppsContainer" > { apps } @@ -233,17 +233,24 @@ export default createReactClass({ const PersistentVResizer = ({id, minHeight, maxHeight, className, handleWrapperClass, handleClass, children}) => { const [height, setHeight] = useLocalStorageState("pvr_" + id, 100); + const [resizing, setResizing] = useState(false); return { + setResizing(true); + }} onResizeStop={(e, dir, ref, d) => { setHeight(height + d.height); + setResizing(false); }} handleWrapperClass={handleWrapperClass} handleClasses={{bottom: handleClass}} - className={className} + className={classNames(className, { + mx_AppsDrawer_resizing: resizing, + })} enable={{bottom: true}} > { children } From 97d8cec94e29e315b1e725f1d87b74d5edfacaef Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 21 Aug 2020 16:40:05 +0100 Subject: [PATCH 07/37] dedup --- src/components/views/rooms/AppsDrawer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 3a60f21f9c..9b37dd3f4f 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -240,11 +240,11 @@ const PersistentVResizer = ({id, minHeight, maxHeight, className, handleWrapperC minHeight={minHeight} maxHeight={maxHeight} onResizeStart={() => { - setResizing(true); + if (!resizing) setResizing(true); }} onResizeStop={(e, dir, ref, d) => { setHeight(height + d.height); - setResizing(false); + if (resizing) setResizing(false); }} handleWrapperClass={handleWrapperClass} handleClasses={{bottom: handleClass}} From dc08fee635641b1fa2eda45e08e221e8ff543d0a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 24 Aug 2020 14:38:58 +0100 Subject: [PATCH 08/37] Fix the resize handle being unreachable when you don't have permission to add widgets --- res/css/views/rooms/_AppsDrawer.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index 1d62e0a9b4..13130fc6f6 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -64,6 +64,7 @@ $MiniAppTileHeight: 114px; align-items: stretch; justify-content: center; height: 100%; + margin-bottom: 8px; } .mx_AppsDrawer_minimised .mx_AppsContainer { @@ -76,7 +77,7 @@ $MiniAppTileHeight: 114px; order: 2; cursor: pointer; padding: 0; - margin: 5px auto 5px 0; + margin: -3px auto 5px 0; color: $accent-color; font-size: $font-12px; } From 89ec90cf043d753baa4a15821dae0d758cabd3be Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 27 Aug 2020 10:27:27 +0100 Subject: [PATCH 09/37] Rewrite useLocalStorageState hook --- src/components/views/rooms/AppsDrawer.js | 2 +- src/hooks/useLocalStorage.ts | 37 -------------------- src/hooks/useLocalStorageState.ts | 44 ++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 38 deletions(-) delete mode 100644 src/hooks/useLocalStorage.ts create mode 100644 src/hooks/useLocalStorageState.ts diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 9b37dd3f4f..f58e0cc8e3 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -32,7 +32,7 @@ import {IntegrationManagers} from "../../../integrations/IntegrationManagers"; import SettingsStore from "../../../settings/SettingsStore"; import classNames from 'classnames'; import {Resizable} from "re-resizable"; -import {useLocalStorageState} from "../../../hooks/useLocalStorage"; +import {useLocalStorageState} from "../../../hooks/useLocalStorageState"; // The maximum number of widgets that can be added in a room const MAX_WIDGETS = 2; diff --git a/src/hooks/useLocalStorage.ts b/src/hooks/useLocalStorage.ts deleted file mode 100644 index 2f110c58f2..0000000000 --- a/src/hooks/useLocalStorage.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* -Copyright 2019 The Matrix.org Foundation C.I.C. - -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. -*/ - -import {useEffect, useRef, useState} from "react"; - -// Hook behaving like useState but persisting the value to localStorage. Returns same as useState -export const useLocalStorageState = (key: string, initialValue: boolean) => { - const lsKey = useRef("useLocalStorageState_" + key).current; - - const [value, setValue] = useState(() => { - try { - const item = window.localStorage.getItem(lsKey); - return item ? JSON.parse(item) : initialValue; - } catch (error) { - return initialValue; - } - }); - - useEffect(() => { - window.localStorage.setItem(lsKey, JSON.stringify(value)); - }, [lsKey, value]); - - return [value, setValue]; -}; diff --git a/src/hooks/useLocalStorageState.ts b/src/hooks/useLocalStorageState.ts new file mode 100644 index 0000000000..a75ab55402 --- /dev/null +++ b/src/hooks/useLocalStorageState.ts @@ -0,0 +1,44 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +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. +*/ + +import {Dispatch, SetStateAction, useCallback, useEffect, useState} from "react"; + +const getValue = (key: string, initialValue: T): T => { + try { + const item = window.localStorage.getItem(key); + return item ? JSON.parse(item) : initialValue; + } catch (error) { + return initialValue; + } +}; + +// Hook behaving like useState but persisting the value to localStorage. Returns same as useState +export const useLocalStorageState = (key: string, initialValue: T) => { + const lsKey = "mx_" + key; + + const [value, setValue] = useState(getValue(lsKey, initialValue)); + + useEffect(() => { + setValue(getValue(lsKey, initialValue)); + }, [lsKey, initialValue]); + + const _setValue: Dispatch> = useCallback((v: T) => { + window.localStorage.setItem(lsKey, JSON.stringify(v)); + setValue(v); + }, [lsKey]); + + return [value, _setValue]; +}; From bf3c49b8dfe6bdeeb4558579a489f63f3d534d7a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 27 Aug 2020 10:46:29 +0100 Subject: [PATCH 10/37] Remove redundant CSS rules --- res/css/views/rooms/_AppsDrawer.scss | 20 -------------------- src/components/views/rooms/AppsDrawer.js | 1 - 2 files changed, 21 deletions(-) diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index 13130fc6f6..fee3d61153 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -24,10 +24,6 @@ $MiniAppTileHeight: 114px; flex-direction: column; overflow: hidden; - .mx_AppsContainer_resizerHandles { - flex: 0 0 4px; - } - .mx_AppsContainer_resizerHandle { cursor: ns-resize; border-radius: 3px; @@ -412,22 +408,6 @@ form.mx_Custom_Widget_Form div { display: none; } -/* Hidden resize handle (Apptile bottom serves as indicator) */ -.mx_AppsDrawer .mx_ResizeHandle { - position: relative; - z-index: 1; -} - -.mx_AppsDrawer .mx_ResizeHandle > div { - background: inherit; -} - -.mx_AppsDrawer_fullWidth .mx_ResizeHandle { - max-width: 960px; - margin-left: auto; - margin-right: auto; -} - .mx_AppsDrawer_minimised .mx_AppsContainer_resizerHandle { display: none; } diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index f58e0cc8e3..1446cd505c 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -218,7 +218,6 @@ export default createReactClass({ id={"apps-drawer_" + this.props.room.roomId} minHeight={100} maxHeight={this.props.maxHeight - 50} - handleWrapperClass="mx_AppsContainer_resizerHandles" handleClass="mx_AppsContainer_resizerHandle" className="mx_AppsContainer" > From ccd08aa0bd042311604ce40d962a05adf2ca5348 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 27 Aug 2020 10:56:04 +0100 Subject: [PATCH 11/37] Update src/hooks/useLocalStorageState.ts Co-authored-by: J. Ryan Stinnett --- src/hooks/useLocalStorageState.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useLocalStorageState.ts b/src/hooks/useLocalStorageState.ts index a75ab55402..ce3b574f86 100644 --- a/src/hooks/useLocalStorageState.ts +++ b/src/hooks/useLocalStorageState.ts @@ -1,5 +1,5 @@ /* -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From bc9ea04118c4c414854f56cac44540e60d9e5bd8 Mon Sep 17 00:00:00 2001 From: dkanada Date: Sat, 29 Aug 2020 20:29:43 +0900 Subject: [PATCH 12/37] add lenny face command --- src/SlashCommands.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index d674634109..0aacfb4340 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -154,6 +154,19 @@ export const Commands = [ }, category: CommandCategories.messages, }), + new Command({ + command: 'lenny', + args: '', + description: _td('Prepends ( ͡° ͜ʖ ͡°) to a plain-text message'), + runFn: function(roomId, args) { + let message = '( ͡° ͜ʖ ͡°)'; + if (args) { + message = message + ' ' + args; + } + return success(MatrixClientPeg.get().sendTextMessage(roomId, message)); + }, + category: CommandCategories.messages, + }), new Command({ command: 'plain', args: '', From 8cae2fa0aafb22b44bdcb75d0e5757c30eab06da Mon Sep 17 00:00:00 2001 From: dkanada Date: Sun, 30 Aug 2020 15:17:21 +0900 Subject: [PATCH 13/37] update translations with new command string --- src/i18n/strings/en_EN.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 442f07499c..bf8118f2e5 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -149,6 +149,7 @@ "Command error": "Command error", "Usage": "Usage", "Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Prepends ¯\\_(ツ)_/¯ to a plain-text message", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message", "Sends a message as plain text, without interpreting it as markdown": "Sends a message as plain text, without interpreting it as markdown", "Sends a message as html, without interpreting it as markdown": "Sends a message as html, without interpreting it as markdown", "Searches DuckDuckGo for results": "Searches DuckDuckGo for results", From 6178b3c0e200fb66613135e71481cda2b15f6c89 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 2 Sep 2020 11:13:00 +0100 Subject: [PATCH 14/37] Wire up resizeNotifier --- src/components/structures/RoomView.js | 22 +++++++++++++--------- src/components/structures/ScrollPanel.js | 2 +- src/components/views/rooms/AppsDrawer.js | 20 ++++++++++++++++++-- src/components/views/rooms/AuxPanel.js | 1 + 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 7605bdbfc0..33df9b4961 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -1886,15 +1886,19 @@ export default createReactClass({ } const auxPanel = ( - + { aux } ); diff --git a/src/components/structures/ScrollPanel.js b/src/components/structures/ScrollPanel.js index 51113f4f56..5c3067c6eb 100644 --- a/src/components/structures/ScrollPanel.js +++ b/src/components/structures/ScrollPanel.js @@ -210,6 +210,7 @@ export default createReactClass({ }, onResize: function() { + debuglog("onResize"); this.checkScroll(); // update preventShrinkingState if present if (this.preventShrinkingState) { @@ -239,7 +240,6 @@ export default createReactClass({ // when scrolled all the way down. E.g. Chrome 72 on debian. // so check difference <= 1; return Math.abs(sn.scrollHeight - (sn.scrollTop + sn.clientHeight)) <= 1; - }, // returns the vertical height in the given direction that can be removed from diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 1446cd505c..7124d908de 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -33,6 +33,7 @@ import SettingsStore from "../../../settings/SettingsStore"; import classNames from 'classnames'; import {Resizable} from "re-resizable"; import {useLocalStorageState} from "../../../hooks/useLocalStorageState"; +import ResizeNotifier from "../../../utils/ResizeNotifier"; // The maximum number of widgets that can be added in a room const MAX_WIDGETS = 2; @@ -43,6 +44,7 @@ export default createReactClass({ propTypes: { userId: PropTypes.string.isRequired, room: PropTypes.object.isRequired, + resizeNotifier: PropTypes.instanceOf(ResizeNotifier).isRequired, showApps: PropTypes.bool, // Should apps be rendered hide: PropTypes.bool, // If rendered, should apps drawer be visible }, @@ -217,9 +219,10 @@ export default createReactClass({ { apps } { spinner } @@ -230,7 +233,16 @@ export default createReactClass({ }, }); -const PersistentVResizer = ({id, minHeight, maxHeight, className, handleWrapperClass, handleClass, children}) => { +const PersistentVResizer = ({ + id, + minHeight, + maxHeight, + className, + handleWrapperClass, + handleClass, + resizeNotifier, + children, +}) => { const [height, setHeight] = useLocalStorageState("pvr_" + id, 100); const [resizing, setResizing] = useState(false); @@ -241,9 +253,13 @@ const PersistentVResizer = ({id, minHeight, maxHeight, className, handleWrapperC onResizeStart={() => { if (!resizing) setResizing(true); }} + onResize={() => { + resizeNotifier.notifyTimelineHeightChanged(); + }} onResizeStop={(e, dir, ref, d) => { setHeight(height + d.height); if (resizing) setResizing(false); + resizeNotifier.notifyTimelineHeightChanged(); }} handleWrapperClass={handleWrapperClass} handleClasses={{bottom: handleClass}} diff --git a/src/components/views/rooms/AuxPanel.js b/src/components/views/rooms/AuxPanel.js index 521aeec406..b51e3fefa8 100644 --- a/src/components/views/rooms/AuxPanel.js +++ b/src/components/views/rooms/AuxPanel.js @@ -203,6 +203,7 @@ export default createReactClass({ maxHeight={this.props.maxHeight} showApps={this.props.showApps} hide={this.props.hideAppsDrawer} + resizeNotifier={this.props.resizeNotifier} />; let stateViews = null; From cffe90250464895e07cb067cb9b48eafbbd33d5b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 2 Sep 2020 12:00:35 +0100 Subject: [PATCH 15/37] Add feature to ResizeNotifier to maintain isResizing state and use it to skip onScroll handling --- src/components/structures/GroupView.js | 2 +- src/components/structures/LoggedInView.tsx | 9 ++++++++- src/components/structures/MainSplit.js | 17 ++++++++++++++--- src/components/structures/RoomView.js | 5 +---- src/components/structures/ScrollPanel.js | 5 +++-- src/components/structures/UserView.js | 4 +++- src/components/views/rooms/AppsDrawer.js | 3 ++- src/resizer/resizer.js | 6 ++++++ src/utils/ResizeNotifier.js | 13 +++++++++++++ 9 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 2e2fa25169..c6703b1b9a 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -1335,7 +1335,7 @@ export default createReactClass({
- + { this._getMembershipSection() } { this._getGroupSection() } diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index e427eb92cb..33e7c4a238 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -257,6 +257,12 @@ class LoggedInView extends React.Component { window.localStorage.setItem("mx_lhs_size", '' + size); this.props.resizeNotifier.notifyLeftHandleResized(); }, + onResizeStart: () => { + this.props.resizeNotifier.startResizing(); + }, + onResizeStop: () => { + this.props.resizeNotifier.stopResizing(); + }, }; const resizer = new Resizer( this._resizeContainer.current, @@ -650,12 +656,13 @@ class LoggedInView extends React.Component { break; case PageTypes.UserView: - pageElement = ; + pageElement = ; break; case PageTypes.GroupView: pageElement = ; break; } diff --git a/src/components/structures/MainSplit.js b/src/components/structures/MainSplit.js index 800ed76bb9..47dfe83ad6 100644 --- a/src/components/structures/MainSplit.js +++ b/src/components/structures/MainSplit.js @@ -19,9 +19,18 @@ import React from 'react'; import { Resizable } from 're-resizable'; export default class MainSplit extends React.Component { - _onResized = (event, direction, refToElement, delta) => { + _onResizeStart = () => { + this.props.resizeNotifier.startResizing(); + }; + + _onResize = () => { + this.props.resizeNotifier.notifyRightHandleResized(); + }; + + _onResizeStop = (event, direction, refToElement, delta) => { + this.props.resizeNotifier.stopResizing(); window.localStorage.setItem("mx_rhs_size", this._loadSidePanelSize().width + delta.width); - } + }; _loadSidePanelSize() { let rhsSize = parseInt(window.localStorage.getItem("mx_rhs_size"), 10); @@ -58,7 +67,9 @@ export default class MainSplit extends React.Component { bottomLeft: false, topLeft: false, }} - onResizeStop={this._onResized} + onResizeStart={this._onResizeStart} + onResize={this._onResize} + onResizeStop={this._onResizeStop} className="mx_RightPanel_ResizeWrapper" handleClasses={{left: "mx_RightPanel_ResizeHandle"}} > diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 33df9b4961..2e06be7b4c 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -2096,10 +2096,7 @@ export default createReactClass({ onLeaveClick={(myMembership === "join") ? this.onLeaveClick : null} e2eStatus={this.state.e2eStatus} /> - +
{auxPanel}
diff --git a/src/components/structures/ScrollPanel.js b/src/components/structures/ScrollPanel.js index 5c3067c6eb..1e34e7e2ff 100644 --- a/src/components/structures/ScrollPanel.js +++ b/src/components/structures/ScrollPanel.js @@ -166,7 +166,7 @@ export default createReactClass({ this._pendingFillRequests = {b: null, f: null}; if (this.props.resizeNotifier) { - this.props.resizeNotifier.on("middlePanelResized", this.onResize); + this.props.resizeNotifier.on("middlePanelResizedNoisy", this.onResize); } this.resetScrollState(); @@ -196,11 +196,12 @@ export default createReactClass({ this.unmounted = true; if (this.props.resizeNotifier) { - this.props.resizeNotifier.removeListener("middlePanelResized", this.onResize); + this.props.resizeNotifier.removeListener("middlePanelResizedNoisy", this.onResize); } }, onScroll: function(ev) { + if (this.props.resizeNotifier.isResizing) return; // skip scroll events caused by resizing debuglog("onScroll", this._getScrollNode().scrollTop); this._scrollTimeout.restart(); this._saveScrollState(); diff --git a/src/components/structures/UserView.js b/src/components/structures/UserView.js index 694592af88..8e21771bb9 100644 --- a/src/components/structures/UserView.js +++ b/src/components/structures/UserView.js @@ -80,7 +80,9 @@ export default class UserView extends React.Component { const RightPanel = sdk.getComponent('structures.RightPanel'); const MainSplit = sdk.getComponent('structures.MainSplit'); const panel = ; - return (); + return ( + + ); } else { return (
); } diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 7124d908de..feda2e8db1 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -252,6 +252,7 @@ const PersistentVResizer = ({ maxHeight={maxHeight} onResizeStart={() => { if (!resizing) setResizing(true); + resizeNotifier.startResizing(); }} onResize={() => { resizeNotifier.notifyTimelineHeightChanged(); @@ -259,7 +260,7 @@ const PersistentVResizer = ({ onResizeStop={(e, dir, ref, d) => { setHeight(height + d.height); if (resizing) setResizing(false); - resizeNotifier.notifyTimelineHeightChanged(); + resizeNotifier.stopResizing(); }} handleWrapperClass={handleWrapperClass} handleClasses={{bottom: handleClass}} diff --git a/src/resizer/resizer.js b/src/resizer/resizer.js index 2234fc5bdf..1e75bf3bdf 100644 --- a/src/resizer/resizer.js +++ b/src/resizer/resizer.js @@ -105,6 +105,9 @@ export default class Resizer { if (this.classNames.resizing) { this.container.classList.add(this.classNames.resizing); } + if (this.config.onResizeStart) { + this.config.onResizeStart(); + } const {sizer, distributor} = this._createSizerAndDistributor(resizeHandle); distributor.start(); @@ -119,6 +122,9 @@ export default class Resizer { if (this.classNames.resizing) { this.container.classList.remove(this.classNames.resizing); } + if (this.config.onResizeStop) { + this.config.onResizeStop(); + } distributor.finish(); body.removeEventListener("mouseup", finishResize, false); document.removeEventListener("mouseleave", finishResize, false); diff --git a/src/utils/ResizeNotifier.js b/src/utils/ResizeNotifier.js index 5467716576..512946828b 100644 --- a/src/utils/ResizeNotifier.js +++ b/src/utils/ResizeNotifier.js @@ -31,6 +31,19 @@ export default class ResizeNotifier extends EventEmitter { // with default options, will call fn once at first call, and then every x ms // if there was another call in that timespan this._throttledMiddlePanel = throttle(() => this.emit("middlePanelResized"), 200); + this._isResizing = false; + } + + get isResizing() { + return this._isResizing; + } + + startResizing() { + this._isResizing = true; + } + + stopResizing() { + this._isResizing = false; } _noisyMiddlePanel() { From 5d8a082eb198ac516b71cff974f390da977c8a52 Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Fri, 4 Sep 2020 10:03:30 +0300 Subject: [PATCH 16/37] Add Jitsi auth check Checks for auth needed by looking up a well-known file from the preferred Jitsi domain. No file existing will assume no auth. --- src/widgets/Jitsi.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/widgets/Jitsi.ts b/src/widgets/Jitsi.ts index a52f8182aa..1805913ad6 100644 --- a/src/widgets/Jitsi.ts +++ b/src/widgets/Jitsi.ts @@ -34,6 +34,30 @@ export class Jitsi { return this.domain || 'jitsi.riot.im'; } + /** + * Checks for auth needed by looking up a well-known file + * + * If the file does not exist, we assume no auth. + * + * See TODO add link + */ + public async getJitsiAuth(): Promise { + if (!this.preferredDomain) { + return null; + } + let data; + try { + const response = await fetch(`https://${this.preferredDomain}/.well-known/element/jitsi`); + data = await response.json(); + } catch (error) { + return null; + } + if (data.auth) { + return data.auth; + } + return null; + } + public start() { const cli = MatrixClientPeg.get(); cli.on("WellKnown.client", this.update); From db2e1a9cd0b34a5ca700d329256794fd51c9c80d Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Fri, 4 Sep 2020 10:04:18 +0300 Subject: [PATCH 17/37] Add rfc4648 (base64/32/16) encoder to dependencies --- package.json | 1 + yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/package.json b/package.json index fc5ed57a77..dd293fabf4 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "react-focus-lock": "^2.4.1", "react-transition-group": "^4.4.1", "resize-observer-polyfill": "^1.5.1", + "rfc4648": "^1.4.0", "sanitize-html": "^1.27.1", "tar-js": "^0.3.0", "text-encoding-utf-8": "^1.0.2", diff --git a/yarn.lock b/yarn.lock index 5bd2be1567..ec099bbf7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7557,6 +7557,11 @@ retry@^0.10.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= +rfc4648@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.4.0.tgz#c75b2856ad2e2d588b6ddb985d556f1f7f2a2abd" + integrity sha512-3qIzGhHlMHA6PoT6+cdPKZ+ZqtxkIvg8DZGKA5z6PQ33/uuhoJ+Ws/D/J9rXW6gXodgH8QYlz2UCl+sdUDmNIg== + rimraf@2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" From 680de2af95c4319f97e69e2c6b8d55e6df6fd342 Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Fri, 4 Sep 2020 10:05:47 +0300 Subject: [PATCH 18/37] Create Jitsi "openidtoken-jwt" auth compatible conference ID's If the Jitsi server we're using for a Jitsi conference call has auth of "openidtoken-jwt" then instead of a random human readable room ID, encode the room ID in base32 (without padding). This can then be decoded back to the room ID on the Jitsi end of things. --- src/CallHandler.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index 18f6aeb98a..9d1760bf43 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -1,7 +1,7 @@ /* Copyright 2015, 2016 OpenMarket Ltd Copyright 2017, 2018 New Vector Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -67,6 +67,7 @@ import {generateHumanReadableId} from "./utils/NamingUtils"; import {Jitsi} from "./widgets/Jitsi"; import {WidgetType} from "./widgets/WidgetType"; import {SettingLevel} from "./settings/SettingLevel"; +import {base32} from "rfc4648"; global.mxCalls = { //room_id: MatrixCall @@ -388,8 +389,21 @@ async function _startCallApp(roomId, type) { return; } - const confId = `JitsiConference${generateHumanReadableId()}`; const jitsiDomain = Jitsi.getInstance().preferredDomain; + const jitsiAuth = await Jitsi.getInstance().getJitsiAuth(); + let confId; + if (jitsiAuth === 'openidtoken-jwt') { + // Create conference ID from room ID + // For compatibility with Jitsi, use base32 without padding. + // If the room ID needs to be decoded from the conference ID, + // the receiver should first uppercase it if needed and then add padding. + // More details here: + // TODO add link + confId = base32.stringify(Buffer.from(roomId), { pad: false }); + } else { + // Create a random human readable conference ID + confId = `JitsiConference${generateHumanReadableId()}`; + } let widgetUrl = WidgetUtils.getLocalJitsiWrapperUrl(); From a511ad6633cd0ba9144146845bb9a607bb48624d Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Fri, 4 Sep 2020 10:20:58 +0300 Subject: [PATCH 19/37] Add (potential) Jitsi auth type to widget data So we don't have to fetch the auth type when joining the conference. --- src/CallHandler.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CallHandler.js b/src/CallHandler.js index 9d1760bf43..702613bb81 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -411,12 +411,14 @@ async function _startCallApp(roomId, type) { const parsedUrl = new URL(widgetUrl); parsedUrl.search = ''; // set to empty string to make the URL class use searchParams instead parsedUrl.searchParams.set('confId', confId); + parsedUrl.searchParams.set('auth', jitsiAuth); widgetUrl = parsedUrl.toString(); const widgetData = { conferenceId: confId, isAudioOnly: type === 'voice', domain: jitsiDomain, + auth: jitsiAuth, }; const widgetId = ( From baa6d8a29430348ace9a5e67d19468d6c6a2d51b Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Fri, 4 Sep 2020 12:42:46 +0300 Subject: [PATCH 20/37] Correctly template in Jitsi widget auth if such exists Also add roomId to the widget data and URL template. It's needed by the Element Web Jitsi code to produce auth for the Jitsi backend. --- src/CallHandler.js | 4 ++-- src/components/views/elements/AppTile.js | 11 +++++++++-- src/utils/WidgetUtils.js | 11 ++++++++--- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index 702613bb81..3c7e7fcc78 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -405,13 +405,12 @@ async function _startCallApp(roomId, type) { confId = `JitsiConference${generateHumanReadableId()}`; } - let widgetUrl = WidgetUtils.getLocalJitsiWrapperUrl(); + let widgetUrl = WidgetUtils.getLocalJitsiWrapperUrl({auth: jitsiAuth}); // TODO: Remove URL hacks when the mobile clients eventually support v2 widgets const parsedUrl = new URL(widgetUrl); parsedUrl.search = ''; // set to empty string to make the URL class use searchParams instead parsedUrl.searchParams.set('confId', confId); - parsedUrl.searchParams.set('auth', jitsiAuth); widgetUrl = parsedUrl.toString(); const widgetData = { @@ -419,6 +418,7 @@ async function _startCallApp(roomId, type) { isAudioOnly: type === 'voice', domain: jitsiDomain, auth: jitsiAuth, + roomId: roomId, }; const widgetId = ( diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 299025f949..9e0dd3c6c2 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -603,6 +603,7 @@ export default class AppTile extends React.Component { // TODO: Namespace themes through some standard 'theme': SettingsStore.getValue("theme"), }); + console.log("DEBUG TEMPLATEDURL APPTILE", vars); if (vars.conferenceId === undefined) { // we'll need to parse the conference ID out of the URL for v1 Jitsi widgets @@ -626,7 +627,10 @@ export default class AppTile extends React.Component { if (WidgetType.JITSI.matches(this.props.app.type)) { console.log("Replacing Jitsi widget URL with local wrapper"); - url = WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: true}); + url = WidgetUtils.getLocalJitsiWrapperUrl({ + forLocalRender: true, + auth: this.props.app.data ? this.props.app.data.auth : null, + }); url = this._addWurlParams(url); } else { url = this._getSafeUrl(this.state.widgetUrl); @@ -637,7 +641,10 @@ export default class AppTile extends React.Component { _getPopoutUrl() { if (WidgetType.JITSI.matches(this.props.app.type)) { return this._templatedUrl( - WidgetUtils.getLocalJitsiWrapperUrl({forLocalRender: false}), + WidgetUtils.getLocalJitsiWrapperUrl({ + forLocalRender: false, + auth: this.props.app.data ? this.props.app.data.auth : null, + }), this.props.app.type, ); } else { diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index 645953210d..c9666d90d5 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -448,16 +448,21 @@ export default class WidgetUtils { return encodeURIComponent(`${widgetLocation}::${widgetUrl}`); } - static getLocalJitsiWrapperUrl(opts: {forLocalRender?: boolean}={}) { + static getLocalJitsiWrapperUrl(opts: {forLocalRender?: boolean, auth?: string}={}) { // NB. we can't just encodeURIComponent all of these because the $ signs need to be there - const queryString = [ + const queryParts = [ 'conferenceDomain=$domain', 'conferenceId=$conferenceId', 'isAudioOnly=$isAudioOnly', 'displayName=$matrix_display_name', 'avatarUrl=$matrix_avatar_url', 'userId=$matrix_user_id', - ].join('&'); + 'roomId=$matrix_room_id', + ]; + if (opts.auth) { + queryParts.push(`auth=${opts.auth}`); + } + const queryString = queryParts.join('&'); let baseUrl = window.location; if (window.location.protocol !== "https:" && !opts.forLocalRender) { From 13dbfa6b8560ade438d488f98900e99f9438f847 Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Fri, 4 Sep 2020 12:44:43 +0300 Subject: [PATCH 21/37] Remove debug statement --- src/components/views/elements/AppTile.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 9e0dd3c6c2..dd1082e6c3 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -603,7 +603,6 @@ export default class AppTile extends React.Component { // TODO: Namespace themes through some standard 'theme': SettingsStore.getValue("theme"), }); - console.log("DEBUG TEMPLATEDURL APPTILE", vars); if (vars.conferenceId === undefined) { // we'll need to parse the conference ID out of the URL for v1 Jitsi widgets From 355d8f584361fef3e5a605a94cda584cc2d03d68 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 5 Sep 2020 15:06:31 -0600 Subject: [PATCH 22/37] Update openid_credentials Widget API action for MSC1960 updates We now need to send a `state` and `original_request_id` per MSC1960's recent adjustments --- src/WidgetMessaging.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/WidgetMessaging.js b/src/WidgetMessaging.js index 6aed08c39d..56ee96ab21 100644 --- a/src/WidgetMessaging.js +++ b/src/WidgetMessaging.js @@ -186,7 +186,14 @@ export default class WidgetMessaging { isUserWidget: this.isUserWidget, onFinished: async (confirm) => { - const responseBody = {success: confirm}; + const responseBody = { + // Legacy (early draft) fields + success: confirm, + + // New style MSC1961 fields + state: confirm ? "allowed" : "blocked", + original_request_id: ev.requestId, // eslint-disable-line camelcase + }; if (confirm) { const credentials = await MatrixClientPeg.get().getOpenIdToken(); Object.assign(responseBody, credentials); From db1eb895525f5c6c30d42cfba8c1c10a09cf4f9e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 5 Sep 2020 15:10:28 -0600 Subject: [PATCH 23/37] MSC numbers are hard to remember --- src/WidgetMessaging.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WidgetMessaging.js b/src/WidgetMessaging.js index 56ee96ab21..c68e926ac1 100644 --- a/src/WidgetMessaging.js +++ b/src/WidgetMessaging.js @@ -190,7 +190,7 @@ export default class WidgetMessaging { // Legacy (early draft) fields success: confirm, - // New style MSC1961 fields + // New style MSC1960 fields state: confirm ? "allowed" : "blocked", original_request_id: ev.requestId, // eslint-disable-line camelcase }; From ae83222d52749f473481fbb6e6807aad2e86d501 Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Mon, 7 Sep 2020 14:32:00 +0300 Subject: [PATCH 24/37] Add GetOpenIDCredentials constant to WidgetApi --- src/widgets/WidgetApi.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/widgets/WidgetApi.ts b/src/widgets/WidgetApi.ts index 15603e9437..74351720e2 100644 --- a/src/widgets/WidgetApi.ts +++ b/src/widgets/WidgetApi.ts @@ -34,6 +34,7 @@ export enum KnownWidgetActions { GetCapabilities = "capabilities", SendEvent = "send_event", UpdateVisibility = "visibility", + GetOpenIDCredentials = "get_openid", ReceiveOpenIDCredentials = "openid_credentials", SetAlwaysOnScreen = "set_always_on_screen", ClientReady = "im.vector.ready", From d2e9ea58fde083564cfa5529ec988f6f89768b8e Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Mon, 7 Sep 2020 19:22:40 +0300 Subject: [PATCH 25/37] Add links to prosody openidtoken-jwt auth docs --- src/CallHandler.js | 4 +--- src/widgets/Jitsi.ts | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index 3c7e7fcc78..1a1c71b55f 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -395,10 +395,8 @@ async function _startCallApp(roomId, type) { if (jitsiAuth === 'openidtoken-jwt') { // Create conference ID from room ID // For compatibility with Jitsi, use base32 without padding. - // If the room ID needs to be decoded from the conference ID, - // the receiver should first uppercase it if needed and then add padding. // More details here: - // TODO add link + // https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification confId = base32.stringify(Buffer.from(roomId), { pad: false }); } else { // Create a random human readable conference ID diff --git a/src/widgets/Jitsi.ts b/src/widgets/Jitsi.ts index 1805913ad6..ca8de4468a 100644 --- a/src/widgets/Jitsi.ts +++ b/src/widgets/Jitsi.ts @@ -39,7 +39,7 @@ export class Jitsi { * * If the file does not exist, we assume no auth. * - * See TODO add link + * See https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification */ public async getJitsiAuth(): Promise { if (!this.preferredDomain) { From 4b43e39d2aa0bf151384ed910b4a7df6c92bc4cb Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Tue, 8 Sep 2020 11:31:40 +0300 Subject: [PATCH 26/37] Code review related changes * drop room ID from jitsi widget data * reame queryParts variable --- src/CallHandler.js | 1 - src/utils/WidgetUtils.js | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index 1a1c71b55f..27e8e34e16 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -416,7 +416,6 @@ async function _startCallApp(roomId, type) { isAudioOnly: type === 'voice', domain: jitsiDomain, auth: jitsiAuth, - roomId: roomId, }; const widgetId = ( diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index c9666d90d5..d5f6981476 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -450,7 +450,7 @@ export default class WidgetUtils { static getLocalJitsiWrapperUrl(opts: {forLocalRender?: boolean, auth?: string}={}) { // NB. we can't just encodeURIComponent all of these because the $ signs need to be there - const queryParts = [ + const queryStringParts = [ 'conferenceDomain=$domain', 'conferenceId=$conferenceId', 'isAudioOnly=$isAudioOnly', @@ -460,9 +460,9 @@ export default class WidgetUtils { 'roomId=$matrix_room_id', ]; if (opts.auth) { - queryParts.push(`auth=${opts.auth}`); + queryStringParts.push(`auth=${opts.auth}`); } - const queryString = queryParts.join('&'); + const queryString = queryStringParts.join('&'); let baseUrl = window.location; if (window.location.protocol !== "https:" && !opts.forLocalRender) { From d0c716d61c07666deb5bc5455705120e20f4f547 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 10:26:37 +0100 Subject: [PATCH 27/37] Fix permalink local linkification to not strip via servers --- src/utils/permalinks/Permalinks.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils/permalinks/Permalinks.js b/src/utils/permalinks/Permalinks.js index 466d1ed57d..d97ba55c4d 100644 --- a/src/utils/permalinks/Permalinks.js +++ b/src/utils/permalinks/Permalinks.js @@ -332,6 +332,10 @@ export function tryTransformPermalinkToLocalHref(permalink: string): string { if (permalinkParts.roomIdOrAlias) { const eventIdPart = permalinkParts.eventId ? `/${permalinkParts.eventId}` : ''; permalink = `#/room/${permalinkParts.roomIdOrAlias}${eventIdPart}`; + if (permalinkParts.viaServers.length > 0) { + const riotPermalinkConstructor = new SpecPermalinkConstructor(); + permalink += riotPermalinkConstructor.encodeServerCandidates(permalinkParts.viaServers); + } } else if (permalinkParts.groupId) { permalink = `#/group/${permalinkParts.groupId}`; } else if (permalinkParts.userId) { From c19336591e4cb82355bd93be7c3c062c3de9b71a Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Tue, 8 Sep 2020 12:59:05 +0300 Subject: [PATCH 28/37] Add OpenID token request flow to WidgetApi As per MSC1960. --- src/widgets/WidgetApi.ts | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/widgets/WidgetApi.ts b/src/widgets/WidgetApi.ts index 74351720e2..4099793500 100644 --- a/src/widgets/WidgetApi.ts +++ b/src/widgets/WidgetApi.ts @@ -65,6 +65,13 @@ export interface FromWidgetRequest extends WidgetRequest { response: any; } +export interface OpenIDCredentials { + accessToken: string; + tokenType: string; + matrixServerName: string; + expiresIn: number; +} + /** * Handles Element <--> Widget interactions for embedded/standalone widgets. * @@ -78,6 +85,8 @@ export class WidgetApi extends EventEmitter { private inFlightRequests: { [requestId: string]: (reply: FromWidgetRequest) => void } = {}; private readyPromise: Promise; private readyPromiseResolve: () => void; + private openIDCredentialsCallback: () => void; + public openIDCredentials: OpenIDCredentials; /** * Set this to true if your widget is expecting a ready message from the client. False otherwise (default). @@ -121,6 +130,10 @@ export class WidgetApi extends EventEmitter { // Acknowledge that we're shut down now this.replyToRequest(payload, {}); }); + } else if (payload.action === KnownWidgetActions.ReceiveOpenIDCredentials) { + // Save OpenID credentials + this.setOpenIDCredentials(payload); + this.replyToRequest(payload, {}); } else { console.warn(`[WidgetAPI] Got unexpected action: ${payload.action}`); } @@ -135,6 +148,32 @@ export class WidgetApi extends EventEmitter { }); } + public setOpenIDCredentials(value: WidgetRequest) { + const data = value.data; + if (data.state === 'allowed') { + this.openIDCredentials = { + accessToken: data.access_token, + tokenType: data.token_type, + matrixServerName: data.matrix_server_name, + expiresIn: data.expires_in, + } + } else if (data.state === 'blocked') { + this.openIDCredentials = null; + } + if (['allowed', 'blocked'].includes(data.state) && this.openIDCredentialsCallback) { + this.openIDCredentialsCallback() + } + } + + public requestOpenIDCredentials(credentialsResponseCallback: () => void) { + this.openIDCredentialsCallback = credentialsResponseCallback; + this.callAction( + KnownWidgetActions.GetOpenIDCredentials, + {}, + this.setOpenIDCredentials, + ); + } + public waitReady(): Promise { return this.readyPromise; } From 3af0d33e3b775f22217982667913a166f19175e1 Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Tue, 8 Sep 2020 13:00:00 +0300 Subject: [PATCH 29/37] Make a few fields readonly As they're only set in the constructor. --- src/widgets/WidgetApi.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widgets/WidgetApi.ts b/src/widgets/WidgetApi.ts index 4099793500..672cbf2a56 100644 --- a/src/widgets/WidgetApi.ts +++ b/src/widgets/WidgetApi.ts @@ -81,9 +81,9 @@ export interface OpenIDCredentials { * the given promise resolves. */ export class WidgetApi extends EventEmitter { - private origin: string; + private readonly origin: string; private inFlightRequests: { [requestId: string]: (reply: FromWidgetRequest) => void } = {}; - private readyPromise: Promise; + private readonly readyPromise: Promise; private readyPromiseResolve: () => void; private openIDCredentialsCallback: () => void; public openIDCredentials: OpenIDCredentials; From 07f7ff6831f84ac23b94f2742581659ba3d918a9 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 11:01:36 +0100 Subject: [PATCH 30/37] Fix WatchManager global room watchers --- src/settings/WatchManager.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/settings/WatchManager.ts b/src/settings/WatchManager.ts index d51439459c..925a99f2dc 100644 --- a/src/settings/WatchManager.ts +++ b/src/settings/WatchManager.ts @@ -18,11 +18,10 @@ import { SettingLevel } from "./SettingLevel"; export type CallbackFn = (changedInRoomId: string, atLevel: SettingLevel, newValAtLevel: any) => void; -const IRRELEVANT_ROOM = Symbol("any room"); +const IRRELEVANT_ROOM = String(null); interface RoomWatcherMap { - // @ts-ignore - TS wants string-only keys but we know better - https://github.com/Microsoft/TypeScript/issues/1863 - [roomId: string | symbol]: CallbackFn[]; + [roomId: string]: CallbackFn[]; } /** @@ -69,7 +68,7 @@ export class WatchManager { if (!inRoomId) { // Fire updates to all the individual room watchers too, as they probably // care about the change higher up. - callbacks.push(...Object.values(roomWatchers).reduce((r, a) => [...r, ...a], [])); + callbacks.push(...Object.values(roomWatchers).flat(1)); } else if (roomWatchers[IRRELEVANT_ROOM]) { callbacks.push(...roomWatchers[IRRELEVANT_ROOM]); } From b75e2aa299469aa43244e02d72329209a2dce866 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Sep 2020 16:13:05 +0100 Subject: [PATCH 31/37] WidgetEchoStore improve update event to include roomId and widgetId --- src/stores/WidgetEchoStore.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/stores/WidgetEchoStore.js b/src/stores/WidgetEchoStore.js index a5e7b12da0..7dd093d45e 100644 --- a/src/stores/WidgetEchoStore.js +++ b/src/stores/WidgetEchoStore.js @@ -93,13 +93,13 @@ class WidgetEchoStore extends EventEmitter { if (this._roomWidgetEcho[roomId] === undefined) this._roomWidgetEcho[roomId] = {}; this._roomWidgetEcho[roomId][widgetId] = state; - this.emit('update'); + this.emit('update', roomId, widgetId); } removeRoomWidgetEcho(roomId, widgetId) { delete this._roomWidgetEcho[roomId][widgetId]; if (Object.keys(this._roomWidgetEcho[roomId]).length === 0) delete this._roomWidgetEcho[roomId]; - this.emit('update'); + this.emit('update', roomId, widgetId); } } From 48d9c94c88e135aa92fbf0a1ac20211bd1ad1160 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Sep 2020 16:17:25 +0100 Subject: [PATCH 32/37] Extract editWidget into WidgetUtils --- src/components/views/elements/AppTile.js | 15 +-------------- src/utils/WidgetUtils.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 01366091fd..3cdc1cab43 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -310,20 +310,7 @@ export default class AppTile extends React.Component { if (this.props.onEditClick) { this.props.onEditClick(); } else { - // TODO: Open the right manager for the widget - if (SettingsStore.getValue("feature_many_integration_managers")) { - IntegrationManagers.sharedInstance().openAll( - this.props.room, - 'type_' + this.props.app.type, - this.props.app.id, - ); - } else { - IntegrationManagers.sharedInstance().getPrimaryManager().open( - this.props.room, - 'type_' + this.props.app.type, - this.props.app.id, - ); - } + WidgetUtils.editWidget(this.props.room, this.props.app); } } diff --git a/src/utils/WidgetUtils.js b/src/utils/WidgetUtils.js index 645953210d..75ee7c7d88 100644 --- a/src/utils/WidgetUtils.js +++ b/src/utils/WidgetUtils.js @@ -405,6 +405,7 @@ export default class WidgetUtils { app.creatorUserId = senderUserId; app.id = appId; + app.roomId = roomId; app.eventId = eventId; app.name = app.name || app.type; @@ -471,4 +472,13 @@ export default class WidgetUtils { const url = new URL("jitsi.html#" + queryString, baseUrl); // this strips hash fragment from baseUrl return url.href; } + + static editWidget(room, app) { + // TODO: Open the right manager for the widget + if (SettingsStore.getValue("feature_many_integration_managers")) { + IntegrationManagers.sharedInstance().openAll(room, 'type_' + app.type, app.id); + } else { + IntegrationManagers.sharedInstance().getPrimaryManager().open(room, 'type_' + app.type, app.id); + } + } } From 99cd2dceecad7368e04818f6983f3f64fe94502e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 12:07:24 +0100 Subject: [PATCH 33/37] Fix create-react-class regression. Can't call setState in c'tor --- src/components/views/elements/PowerSelector.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/PowerSelector.js b/src/components/views/elements/PowerSelector.js index e5f217dd90..66922df0f8 100644 --- a/src/components/views/elements/PowerSelector.js +++ b/src/components/views/elements/PowerSelector.js @@ -57,11 +57,14 @@ export default class PowerSelector extends React.Component { customValue: this.props.value, selectValue: 0, }; - - this._initStateFromProps(this.props); } // TODO: [REACT-WARNING] Replace with appropriate lifecycle event + // eslint-disable-next-line camelcase + UNSAFE_componentWillMount() { + this._initStateFromProps(this.props); + } + // eslint-disable-next-line camelcase UNSAFE_componentWillReceiveProps(newProps) { this._initStateFromProps(newProps); From 5c3c8cfb068a569bd66cb643eb0b28ff6fb00b0d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 15:07:16 +0100 Subject: [PATCH 34/37] Make Travis an ounce happier --- src/settings/WatchManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/WatchManager.ts b/src/settings/WatchManager.ts index 925a99f2dc..ea2f158ef6 100644 --- a/src/settings/WatchManager.ts +++ b/src/settings/WatchManager.ts @@ -18,7 +18,7 @@ import { SettingLevel } from "./SettingLevel"; export type CallbackFn = (changedInRoomId: string, atLevel: SettingLevel, newValAtLevel: any) => void; -const IRRELEVANT_ROOM = String(null); +const IRRELEVANT_ROOM: string = null; interface RoomWatcherMap { [roomId: string]: CallbackFn[]; From 1b99c11b1189fdf529471ede9c0f1482f10921fd Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 15:09:45 +0100 Subject: [PATCH 35/37] tidy --- src/utils/permalinks/Permalinks.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/utils/permalinks/Permalinks.js b/src/utils/permalinks/Permalinks.js index d97ba55c4d..3e510ffee9 100644 --- a/src/utils/permalinks/Permalinks.js +++ b/src/utils/permalinks/Permalinks.js @@ -333,8 +333,7 @@ export function tryTransformPermalinkToLocalHref(permalink: string): string { const eventIdPart = permalinkParts.eventId ? `/${permalinkParts.eventId}` : ''; permalink = `#/room/${permalinkParts.roomIdOrAlias}${eventIdPart}`; if (permalinkParts.viaServers.length > 0) { - const riotPermalinkConstructor = new SpecPermalinkConstructor(); - permalink += riotPermalinkConstructor.encodeServerCandidates(permalinkParts.viaServers); + permalink += new SpecPermalinkConstructor().encodeServerCandidates(permalinkParts.viaServers); } } else if (permalinkParts.groupId) { permalink = `#/group/${permalinkParts.groupId}`; From 8d03799ffe60f248e289ba51b885a1e22d43cb7d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 16:51:55 +0100 Subject: [PATCH 36/37] Fix create-react-class regression. Can't call setState in c'tor --- src/components/structures/auth/Login.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/structures/auth/Login.js b/src/components/structures/auth/Login.js index 53769fb5a6..a20bf0dd0a 100644 --- a/src/components/structures/auth/Login.js +++ b/src/components/structures/auth/Login.js @@ -124,7 +124,11 @@ export default class LoginComponent extends React.Component { 'm.login.cas': () => this._renderSsoStep("cas"), 'm.login.sso': () => this._renderSsoStep("sso"), }; + } + // TODO: [REACT-WARNING] Replace with appropriate lifecycle event + // eslint-disable-next-line camelcase + UNSAFE_componentWillMount() { this._initLoginLogic(); } From 27a65fff2b194aed3773b48b9f89d9c2d8dca92d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Sep 2020 17:34:32 +0100 Subject: [PATCH 37/37] null-guard roomId in RightPanel and pass Room to UserView --- src/components/structures/RightPanel.js | 13 +++++++------ src/components/views/right_panel/UserInfo.js | 8 +++----- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js index 11416b29fb..4dd2a7f75e 100644 --- a/src/components/structures/RightPanel.js +++ b/src/components/structures/RightPanel.js @@ -225,11 +225,12 @@ export default class RightPanel extends React.Component { const GroupRoomInfo = sdk.getComponent('groups.GroupRoomInfo'); let panel =
; + const roomId = this.props.room ? this.props.room.roomId : undefined; switch (this.state.phase) { case RightPanelPhases.RoomMemberList: - if (this.props.room.roomId) { - panel = ; + if (roomId) { + panel = ; } break; case RightPanelPhases.GroupMemberList: @@ -244,8 +245,8 @@ export default class RightPanel extends React.Component { case RightPanelPhases.EncryptionPanel: panel = ; break; case RightPanelPhases.Room3pidMemberInfo: - panel = ; + panel = ; break; case RightPanelPhases.GroupMemberInfo: panel = ; break; case RightPanelPhases.FilePanel: - panel = ; + panel = ; break; } diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js index 518bb133ce..b98211e5c8 100644 --- a/src/components/views/right_panel/UserInfo.js +++ b/src/components/views/right_panel/UserInfo.js @@ -20,7 +20,7 @@ limitations under the License. import React, {useCallback, useMemo, useState, useEffect, useContext} from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import {Group, RoomMember, User} from 'matrix-js-sdk'; +import {Group, RoomMember, User, Room} from 'matrix-js-sdk'; import dis from '../../../dispatcher/dispatcher'; import Modal from '../../../Modal'; import * as sdk from '../../../index'; @@ -1471,11 +1471,9 @@ const UserInfoHeader = ({onClose, member, e2eStatus}) => { ; }; -const UserInfo = ({user, groupId, roomId, onClose, phase=RightPanelPhases.RoomMemberInfo, ...props}) => { +const UserInfo = ({user, groupId, room, onClose, phase=RightPanelPhases.RoomMemberInfo, ...props}) => { const cli = useContext(MatrixClientContext); - // Load room if we are given a room id and memoize it - this can be undefined for User Info/Group Member Info - const room = useMemo(() => roomId ? cli.getRoom(roomId) : null, [cli, roomId]); // fetch latest room member if we have a room, so we don't show historical information, falling back to user const member = useMemo(() => room ? (room.getMember(user.userId) || user) : user, [room, user]); @@ -1529,7 +1527,7 @@ UserInfo.propTypes = { ]).isRequired, group: PropTypes.instanceOf(Group), groupId: PropTypes.string, - roomId: PropTypes.string, + room: PropTypes.instanceOf(Room), onClose: PropTypes.func, };